将实时 Android 音频流式传输到服务器

Posted

技术标签:

【中文标题】将实时 Android 音频流式传输到服务器【英文标题】:Stream Live Android Audio to Server 【发布时间】:2013-02-27 07:15:42 【问题描述】:

我目前正在尝试将实时麦克风音频从 android 设备流式传输到 Java 程序。我开始在两个安卓设备之间发送实时音频,以确认我的方法是正确的。在接收设备上几乎没有任何延迟,可以完美地听到音频。接下来,我将相同的音频流发送到一个小型 Java 程序,并且我验证了数据也被正确发送到这里。现在我想做的是对这些数据进行编码,并以某种方式在运行 Java 程序的服务器上播放它。我宁愿在使用 html5 或 javascript 的网络浏览器中播放它,但我对 VLC 等替代方法持开放态度。

这是发送实时麦克风音频的 Android 应用的代码

public class MainActivity extends Activity 


private Button startButton,stopButton;

public byte[] buffer;
public static DatagramSocket socket;
    AudioRecord recorder;

private int sampleRate = 44100;   
private int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;    
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;       
int minBufSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
    private boolean status = true;

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

     startButton = (Button) findViewById (R.id.start_button);
     stopButton = (Button) findViewById (R.id.stop_button);

     startButton.setOnClickListener(startListener);
     stopButton.setOnClickListener(stopListener);

     minBufSize += 2048;


@Override
public boolean onCreateOptionsMenu(Menu menu) 
    getMenuInflater().inflate(R.menu.main, menu);
    return true;


private final OnClickListener stopListener = new OnClickListener() 

    @Override
    public void onClick(View arg0) 
                status = false;
                recorder.release();
                Log.d("VS","Recorder released");
    
;

private final OnClickListener startListener = new OnClickListener() 

    @Override
    public void onClick(View arg0) 
                status = true;
                startStreaming();           
    
;



public void startStreaming()

    Thread streamThread = new Thread(new Runnable()
        @Override
        public void run()
        
            try

                DatagramSocket socket = new DatagramSocket();
                Log.d("VS", "Socket Created");

                byte[] buffer = new byte[minBufSize];

                Log.d("VS","Buffer created of size " + minBufSize);


                Log.d("VS", "Address retrieved");
                recorder = new AudioRecord(MediaRecorder.Audiosource.MIC,sampleRate,channelConfig,audioFormat,minBufSize);
                Log.d("VS", "Recorder initialized");


                recorder.startRecording();


                InetAddress IPAddress = InetAddress.getByName("192.168.1.5");
                byte[] sendData = new byte[1024];
                byte[] receiveData = new byte[1024];


                while (status == true)
                
                    DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, 50005);
                    socket.send(sendPacket);
                

             catch(UnknownHostException e) 
                Log.e("VS", "UnknownHostException");
             catch (IOException e) 
                Log.e("VS", "IOException");
                e.printStackTrace();
             


        

    );
    streamThread.start();


这是Java程序读取数据的代码..

class Server

   public static void main(String args[]) throws Exception
      
         DatagramSocket serverSocket = new DatagramSocket(50005);
            byte[] receiveData = new byte[1024];
            byte[] sendData = new byte[1024];
            while(true)
               
                  DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);



              serverSocket.receive(receivePacket);
              String sentence = new String( receivePacket.getData().toString());

              System.out.println("RECEIVED: " + sentence);
           
  

我知道在将音频发送到 Java 程序之前,我应该在应用端对音频进行编码,但我不确定在使用 AudioRecorder 时如何进行编码。我宁愿不使用 NDK,因为我没有使用它的经验,也没有真正有时间学习如何使用它......但是 :)

【问题讨论】:

你用什么流音频?我只想要一种方式。 我有兴趣编写一个 android 应用程序,将实时麦克风音频从 Android 设备流式传输到桌面应用程序。您能否提供一些有用的指针,指向与您发布的内容相关的一些资源?那将非常有帮助!谢谢! :) 你是如何在两台安卓设备之间实现实时音频的? 嘿@chuckliddell0,你能提供我在Android中发送者和接收者之间发送音频的源代码吗?这会很有帮助,因为我被困在这里。 【参考方案1】:

所以我解决了我的问题。问题主要出在接收方。接收器接收音频流并将其推送到 PC 的扬声器。由此产生的声音仍然很迟钝和破碎,但它仍然有效。调整缓冲区大小可能会改善这一点。

编辑:您使用线程来读取音频以避免延迟。此外,最好使用 16 000 的采样大小,因为它可以用于语音。

安卓代码:

package com.example.mictest2;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class Send extends Activity 
private Button startButton,stopButton;

public byte[] buffer;
public static DatagramSocket socket;
private int port=50005;

AudioRecord recorder;

private int sampleRate = 16000 ; // 44100 for music
private int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;    
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;       
int minBufSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
private boolean status = true;


@Override
public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    startButton = (Button) findViewById (R.id.start_button);
    stopButton = (Button) findViewById (R.id.stop_button);

    startButton.setOnClickListener (startListener);
    stopButton.setOnClickListener (stopListener);



private final OnClickListener stopListener = new OnClickListener() 

    @Override
    public void onClick(View arg0) 
                status = false;
                recorder.release();
                Log.d("VS","Recorder released");
    

;

private final OnClickListener startListener = new OnClickListener() 

    @Override
    public void onClick(View arg0) 
                status = true;
                startStreaming();           
    

;

public void startStreaming() 


    Thread streamThread = new Thread(new Runnable() 

        @Override
        public void run() 
            try 

                DatagramSocket socket = new DatagramSocket();
                Log.d("VS", "Socket Created");

                byte[] buffer = new byte[minBufSize];

                Log.d("VS","Buffer created of size " + minBufSize);
                DatagramPacket packet;

                final InetAddress destination = InetAddress.getByName("192.168.1.5");
                Log.d("VS", "Address retrieved");


                recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,sampleRate,channelConfig,audioFormat,minBufSize*10);
                Log.d("VS", "Recorder initialized");

                recorder.startRecording();


                while(status == true) 


                    //reading data from MIC into buffer
                    minBufSize = recorder.read(buffer, 0, buffer.length);

                    //putting buffer in the packet
                    packet = new DatagramPacket (buffer,buffer.length,destination,port);

                    socket.send(packet);
                    System.out.println("MinBufferSize: " +minBufSize);


                



             catch(UnknownHostException e) 
                Log.e("VS", "UnknownHostException");
             catch (IOException e) 
                e.printStackTrace();
                Log.e("VS", "IOException");
             
        

    );
    streamThread.start();
 
 

Android XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_
android:layout_
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >

<TextView
    android:id="@+id/textView1"
    android:layout_
    android:layout_
    android:text="@string/hello_world" />

<Button
    android:id="@+id/start_button"
    android:layout_
    android:layout_
    android:layout_below="@+id/textView1"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="130dp"
    android:text="Start" />

<Button
    android:id="@+id/stop_button"
    android:layout_
    android:layout_
    android:layout_alignLeft="@+id/button1"
    android:layout_below="@+id/button1"
    android:layout_marginTop="64dp"
    android:text="Stop" />

</RelativeLayout>

服务器代码:

package com.datagram;

import java.io.ByteArrayInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.SourceDataLine;

class Server 

AudioInputStream audioInputStream;
static AudioInputStream ais;
static AudioFormat format;
static boolean status = true;
static int port = 50005;
static int sampleRate = 44100;

public static void main(String args[]) throws Exception 


    DatagramSocket serverSocket = new DatagramSocket(50005);


    byte[] receiveData = new byte[1280]; 
    // ( 1280 for 16 000Hz and 3584 for 44 100Hz (use AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) to get the correct size)

    format = new AudioFormat(sampleRate, 16, 1, true, false);

    while (status == true) 
        DatagramPacket receivePacket = new DatagramPacket(receiveData,
                receiveData.length);

        serverSocket.receive(receivePacket);

        ByteArrayInputStream baiss = new ByteArrayInputStream(
                receivePacket.getData());

        ais = new AudioInputStream(baiss, format, receivePacket.getLength());

        // A thread solve the problem of chunky audio 
        new Thread(new Runnable() 
            @Override
            public void run() 
                toSpeaker(receivePacket.getData());
            
        ).start();
    


public static void toSpeaker(byte soundbytes[]) 
    try 

        DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, format);
        SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);

        sourceDataLine.open(format);

        FloatControl volumeControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN);
        volumeControl.setValue(100.0f);

        sourceDataLine.start();
        sourceDataLine.open(format);

        sourceDataLine.start();

        System.out.println("format? :" + sourceDataLine.getFormat());

        sourceDataLine.write(soundbytes, 0, soundbytes.length);
        System.out.println(soundbytes.toString());
        sourceDataLine.drain();
        sourceDataLine.close();
     catch (Exception e) 
        System.out.println("Not working in speakers...");
        e.printStackTrace();
    


我希望这可以帮助人们减少几个小时的痛苦:)

【讨论】:

这会产生持续的抖动并且声音不清晰。可以做什么? 让你通过服务器将你的语音流从一台安卓设备发送到多台安卓设备,就像群组对话一样。我正在努力学习它,但找不到任何东西。你能帮我吗? ? 我可以联系你吗?? 我发现在使用流式直播麦克风音频时,44.1KHz 对于采样率来说太高了。据我所见,8000 或 16000 的采样率效果更好。这与 android 文档中建议的 44100 采样率背道而驰,但这是我从经验中发现的。此外,请参阅en.wikipedia.org/wiki/Sampling_%28signal_processing%29,了解哪种采样率最适合您的应用。 @chuckliddell0 我正在开发同一个应用程序,我使用了你的代码,但问题是我的声音中有太多的回声,我不会流式传输通话音频,你能给我一些帮助吗?如果可能的话,你能提供你的联系方式吗?【参考方案2】:

我的 2 美分给你的代码以提高效率。不错的尝试

package com.datagram;

import java.io.ByteArrayInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.SourceDataLine;

class Server 

AudioInputStream audioInputStream;
static AudioInputStream ais;
static AudioFormat format;
static boolean status = true;
static int port = 50005;
static int sampleRate = 44100;

static DataLine.Info dataLineInfo;
static SourceDataLine sourceDataLine;

public static void main(String args[]) throws Exception 

    DatagramSocket serverSocket = new DatagramSocket(port);

    /**
     * Formula for lag = (byte_size/sample_rate)*2
     * Byte size 9728 will produce ~ 0.45 seconds of lag. Voice slightly broken.
     * Byte size 1400 will produce ~ 0.06 seconds of lag. Voice extremely broken.
     * Byte size 4000 will produce ~ 0.18 seconds of lag. Voice slightly more broken then 9728.
     */

    byte[] receiveData = new byte[4096];

    format = new AudioFormat(sampleRate, 16, 1, true, false);
    dataLineInfo = new DataLine.Info(SourceDataLine.class, format);
    sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
    sourceDataLine.open(format);
    sourceDataLine.start();

    FloatControl volumeControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN);
    volumeControl.setValue(1.00f);

    DatagramPacket receivePacket = new DatagramPacket(receiveData,
            receiveData.length);
    ByteArrayInputStream baiss = new ByteArrayInputStream(
            receivePacket.getData());
    while (status == true) 
        serverSocket.receive(receivePacket);
        ais = new AudioInputStream(baiss, format, receivePacket.getLength());
        toSpeaker(receivePacket.getData());
    
    sourceDataLine.drain();
    sourceDataLine.close();


    public static void toSpeaker(byte soundbytes[]) 
        try 
            sourceDataLine.write(soundbytes, 0, soundbytes.length);
         catch (Exception e) 
            System.out.println("Not working in speakers...");
            e.printStackTrace();
        
    

【讨论】:

这真的有助于减少噪音吗? 这甚至在 android 中都行不通。 ***.com/questions/16803343/… 变量aisbaiss在代码中究竟做了什么?我可以看到它们都使用接收到的数据进行了初始化,但在 toSpeaker 中使用了原始字节...... 如果此解决方案不适用于最新的 Android,请关注此帖子。 ***.com/questions/20193645/… 该帖子仅基于此方法,但作者修改了一些效果很好的更改。感谢两位作者 user1729564 和 kittu88【参考方案3】:

由于您的 android 代码中的以下行导致声音中断:

minBufSize += 2048;

您只是在添加空字节。另外,请使用CHANNEL_IN_MONO 而不是CHANNEL_CONFIGURATION_MONO

【讨论】:

以上是关于将实时 Android 音频流式传输到服务器的主要内容,如果未能解决你的问题,请参考以下文章

如何实时流式传输音频文件

将音频和视频从 Android 手机流式传输到 RTMP 服务器的最佳方式

如何将音频数据从 Android 流式传输到 WebSocket 服务器?

将麦克风从客户端浏览器流式传输到远程服务器,并将音频实时传递到 ffmpeg 以与第二个视频源结合

android将音频流式传输到服务器

将录制的音频从浏览器流式传输到服务器