通过 WiFi 在 Android 手机之间流式传输语音

Posted

技术标签:

【中文标题】通过 WiFi 在 Android 手机之间流式传输语音【英文标题】:Streaming voice between Android Phones over WiFi 【发布时间】:2012-03-03 13:08:45 【问题描述】:

我正在尝试通过 WiFi 将麦克风中的音频从 1 个 android 流式传输到另一个。 在查看了一些示例后,我制作了 2 个应用程序,每个应用程序都有一个 Activity,1 个用于捕获和发送音频,另一个用于接收。

我使用 Audiorecord 和 Audiotrack 类来捕捉和播放。但是,我只是听到了一些噼啪声(虽然我恢复了,但在我做了一些更改后它现在已经停止了)

发送语音的活动。

public class VoiceSenderActivity extends Activity 

private EditText target;
private TextView streamingLabel;
private Button startButton,stopButton;

public byte[] buffer;
public static DatagramSocket socket;
private int port=50005;         //which port??
AudioRecord recorder;

//Audio Configuration. 
private int sampleRate = 8000;      //How much will be ideal?
private int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;    
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;       

private boolean status = true;




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

    target = (EditText) findViewById (R.id.target_IP);
    streamingLabel = (TextView) findViewById(R.id.streaming_label);
    startButton = (Button) findViewById (R.id.start_button);
    stopButton = (Button) findViewById (R.id.stop_button);

    streamingLabel.setText("Press Start! to begin");

    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 


                int minBufSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
                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(target.getText().toString());
                Log.d("VS", "Address retrieved");


                recorder = new AudioRecord(MediaRecorder.Audiosource.MIC,sampleRate,channelConfig,audioFormat,minBufSize);
                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);


                



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


        

    );
    streamThread.start();
 
 

接收语音的活动

public class VoiceReceiverActivity extends Activity 


private Button receiveButton,stopButton;

public static DatagramSocket socket;
private AudioTrack speaker;

//Audio Configuration. 
private int sampleRate = 8000;      //How much will be ideal?
private int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;    
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;       

private boolean status = true;


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

    receiveButton = (Button) findViewById (R.id.receive_button);
    stopButton = (Button) findViewById (R.id.stop_button);
    findViewById(R.id.receive_label);

    receiveButton.setOnClickListener(receiveListener);
    stopButton.setOnClickListener(stopListener);




private final OnClickListener stopListener = new OnClickListener() 

    @Override
    public void onClick(View v) 
        status = false;
        speaker.release();
        Log.d("VR","Speaker released");

    

;


private final OnClickListener receiveListener = new OnClickListener() 

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

    

;

public void startReceiving() 

    Thread receiveThread = new Thread (new Runnable() 

        @Override
        public void run() 

            try 

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


                byte[] buffer = new byte[256];


                //minimum buffer size. need to be careful. might cause problems. try setting manually if any problems faced
                int minBufSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);

                speaker = new AudioTrack(AudioManager.STREAM_MUSIC,sampleRate,channelConfig,audioFormat,minBufSize,AudioTrack.MODE_STREAM);

                speaker.play();

                while(status == true) 
                    try 


                        DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
                        socket.receive(packet);
                        Log.d("VR", "Packet Received");

                        //reading content from packet
                        buffer=packet.getData();
                        Log.d("VR", "Packet data read into buffer");

                        //sending data to the Audiotrack obj i.e. speaker
                        speaker.write(buffer, 0, minBufSize);
                        Log.d("VR", "Writing buffer content to speaker");

                     catch(IOException e) 
                        Log.e("VR","IOException");
                    
                


             catch (SocketException e) 
                Log.e("VR", "SocketException");
            


        

    );
    receiveThread.start();



我使用wireshark检查数据包是否正在发送,我可以看到数据包。然而,源是发送设备的 MAC 地址,而目标也类似于物理地址。不过不确定这是否相关。

那么有什么问题呢?

【问题讨论】:

您至少要处理三个问题:延迟(或丢失)数据、整体数据吞吐量以及采样频率略有不匹配的可能性。实用的 IP 电话必须有处理这三者的方法。不匹配的时钟非常棘手 - 最初您可以引入延迟以提供一些缓冲余量,但如果发送方速度较慢,您将耗尽缓冲区并且接收方将缺乏数据;而如果发送者速度更快,缓冲区最终会溢出未播放的数据。 我确实设法让这个工作真正发挥作用。确实没有频率不匹配的问题。数据延迟,是的。有一种我自己的协议来匹配接收器/发送器时钟。最后它确实起作用了,但只是有一些延迟(随着与无线路由器的距离而增加) 嘿,我为您上面的代码实现了一个测试应用程序,对下面建议的所有必要更改进行了更改,但我仍然遇到问题。我在两部手机之间进行通信没问题,但我认为麦克风录制不正确,因为我在另一端听不到任何声音。您是否有指向我可以查看的示例解决方案的链接? @chuckliddell0 你在两部手机之间获得通讯是什么意思?你的意思是你能听到一些声音但它是乱码?尝试调整您的采样率和缓冲区大小并获得最佳拟合。 @Alabhya 您是否设法减少了延迟?我使用 44100 HZ 和 4096 的缓冲区大小实现了你的代码,它工作正常,但有点滞后。 【参考方案1】:

我会尝试将问题分为三个部分。

第 1 部分

确保 Socket 连接 工作正常 通过评论与音频相关的所有内容

第 2 部分

只需从发件人发送任意文本消息 [Hello WiFi],然后在接收方应用程序中接收并打印它。

第 3 部分

录音机是否真的在工作? 尝试在单独的项目中测试您的录制方式,看看它是否正常工作。

使用this 代码捕获麦克风并播放。

我的经历

我曾经做过一个类似的项目并进行测试,我所做的是在录制后将录制的音频数据作为文件写入 sdcard 上

(这将是原始音频,所以大多数音乐播放器将无法播放它......我猜 mPlayer 应该播放它)

【讨论】:

好的,我搞定了。声音中断太多,有滞后。需要为此找出正确的采样率和缓冲区大小。如果对此有任何意见,那就太好了。无论如何,非常感谢。你说的有帮助。 在您的接收器活动中,在 startReceiving() 方法中不要使用 256 作为缓冲区大小,而是使用您在下一行中获得的 minBufSize。除此之外,可能想玩一点不同的采样率,但即使是 8k 也应该不错。 好的,我搞定了。显然 minBufSize 太多了,因此滞后和中断。将 minBufSize 设置为 256,在初始化 AudioRecord 和 AudioTrack 对象时将缓冲区大小设置为 minBufSize*10。尝试了不同的采样率组合,现在得到了一个令人满意的组合。非常感谢! 嗨 Alabhya,您是否在接收和流式传输方法中都将 minBufSize 设置为 256?【参考方案2】:

嘿,有一个名为“Libstreaming”的开源库,用于使用 WIFI 通过网络传输语音/视频。看看吧:

https://github.com/fyhertz/libstreaming

也提供了一些例子,请看一下:

https://github.com/fyhertz/libstreaming-examples

我已经使用该库通过网络流式传输 RTSP 音频,希望它可能有用。

【讨论】:

你能指导一下,你是怎么做只有音频流而没有流视频的? 我们怎么做,只有音频【参考方案3】:

您需要仔细考虑使用 UDP(DatagramSocket 类)作为您的网络协议。

UDP 是一种轻量级协议,不能保证保持接收数据包的顺序。这可能是音频出现乱码的部分原因。乱序接收的数据包将导致乱序播放的音频数据包。在这些无序数据包的边界处,您将听到音频样本被有效损坏的咔嗒声/爆裂声。除此之外,不保证 UDP 数据包能够成功传送。任何丢弃的数据包显然会增加听到的任何乱码或失真。

TCP(Socket 类)将是获得最佳音频质量的更好选择。 TCP 是一种更健壮的协议,它将保持接收数据包的顺序。它还具有内置的错误检查功能,并将重新发送任何丢弃的数据包。但是,由于这种注意力功能,TCP 具有更高的网络开销。

我在开始回复时说您需要仔细考虑您使用的协议。这是因为根据对您来说重要的内容,可以使用其中任何一种。

如果您想要超低延迟播放但乐于牺牲音频质量,那么 UDP 将起作用。但是,需要进行一些实验才能找到最佳缓冲区和样本大小。

如果您想要实现零失真的最佳音频再现,但又乐于引入稍微多一点的延迟,那么 TCP 是您的理想之选。

我不能说 TCP 会增加多少延迟。但它有可能在不影响用户体验的情况下实施。找出答案的唯一方法就是尝试一下。

【讨论】:

以上是关于通过 WiFi 在 Android 手机之间流式传输语音的主要内容,如果未能解决你的问题,请参考以下文章

android 手机和树莓派之间通过 WiFi 进行通信,无需外部调制解调器/路由器?

在 Android 上使用 OpenSL ES 通过套接字通信流式传输 MP3 音频

通过 wifi 传输视频?

在iOS上通过WiFi和蓝牙流式传输视频

在外部蓝牙设备和 Android 手机之间传输音频

如何使用套接字编程在两个 android 手机之间创建实时音频流..?