通过WiFi在Android手机之间进行流媒体语音传输

31

我正在尝试通过WiFi从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地址,目标地址也类似于物理地址。不确定这是否相关。

那么问题出在哪里呢?


1
你至少要处理三个问题:延迟(或丢失)数据,整体数据吞吐量以及略微不匹配的采样频率可能性。实用的IP电话必须有一种处理所有三个问题的方法。不匹配的时钟非常棘手-最初,您可以引入延迟以给予一些缓冲余地,但如果发送方较慢,则会使用缓冲区,接收方将无法获得数据;而如果发送方更快,则缓冲区最终会溢出未播放的数据。 - Chris Stratton
@chuckliddell0 你说的两部手机之间的通信是什么意思?是指你能听到一些声音但杂音很多吗?尝试调整采样率和缓冲区大小,找到最佳匹配。 - Alabhya
@Alabhya 感谢您的回复,我已经解决了我的问题,最终我将采样率从8K更改为44100k。感谢您的帮助。现在我有一个安卓手机将音频数据发送到我的笔记本电脑上的小型服务器,您知道我该如何在笔记本电脑上播放此流吗?也许我应该将其输出到mp3文件中? - chuckliddell0
1
@Alabhya,你成功减少了延迟吗?我使用44100 HZ和缓冲区大小为4096实现了你的代码,它运行良好,但是还是有一点延迟。 - Daniel
扬声器没有声音,即使数据正在写入其中...可能是什么问题? - Jasper
显示剩余4条评论
3个回答

8

3
你能指导一下,如何只传输音频流而不传输视频? - kAmol
如何仅流式传输音频? - Shankara Narayana

3

我建议将问题分成三个部分。

第一部分

确保Socket连接正常工作,通过注释与音频相关的所有内容。

第二部分

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

第三部分

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

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

我的经验

我曾经做过类似的项目,并进行了测试。测试方法是在录制后将录制的音频数据写入sdcard文件中。

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


好的,我搞定了。声音中断太多,而且有延迟。需要找出正确的采样率和缓冲区大小。如果有任何建议,那就太好了。无论如何,非常感谢。你说的话很有帮助。 - Alabhya
在您的接收器活动中,在startReceiving()方法中,不要使用256作为缓冲区大小,而是使用您在下一行获取的minBufSize。除此之外,可能需要尝试不同的采样率,但即使是8k也应该很好。 - Atul Goyal
1
好的,我搞定了。显然minBufSize太大了,导致了延迟和断裂。将minBufSize设置为256,并在初始化AudioRecord和AudioTrack对象时将缓冲区大小设置为minBufSize*10。尝试了不同的采样率和这个设置,现在得到了一个令人满意的结果。 非常感谢! - Alabhya
嗨,Alabhya,你在接收和流传输方法中都将minBufSize设置为256了吗? - onizukaek

2
您需要仔细考虑您使用UDP(DatagramSocket类)作为网络协议的方式。
UDP是一种轻量级协议,不能保证接收数据包的顺序。这可能是音频出现杂音的原因之一。接收到未按顺序排列的数据包将导致音频播放时段的错位,这些音频样本的不连贯可能会在未正确排列的数据包处产生“咔嗒”声。此外,UDP数据包的成功传送也无法保证。任何被丢弃的数据包都显然会给听觉带来额外的杂音或扭曲感。
TCP(Socket类)将是最佳音频质量的更好选择。TCP是一种更为稳健的协议,可以保持接收数据包的顺序,并具有内置的错误检查和可重新发送任何丢失的数据包的功能。但是,由于其额外的功能,TCP具有更高的网络开销。
我开始回答时说,您需要仔细考虑使用哪个协议。这是因为使用两个协议都有各自的优点。
如果您想实现超低延迟播放但愿意牺牲音频质量,则UDP可以胜任。但是,需要进行一些实验以找到最佳缓冲区和样本大小。
如果您想要尽可能完美的音频重现,并且愿意引入略微更多的延迟,则TCP是最好的选择。
我无法确定TCP会增加多少延迟。但是,有可能在不影响用户体验的情况下进行实现。找出答案的唯一方法是尝试并查看结果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接