android中具有固定数据报包长度的datagramsocket中的音频流质量不好

Posted

技术标签:

【中文标题】android中具有固定数据报包长度的datagramsocket中的音频流质量不好【英文标题】:Audio streaming quality not good in datagramsocket with fixed datagram packets length in android 【发布时间】:2016-10-31 09:37:35 【问题描述】:

我使用数据报套接字通过数据报包传输音频以进行电话对话。我可以听到另一端的音频。但是另一端听不到我的声音。

这里的数据包固定为340byte。前 20 个字节用于连接建立、源地址、目的地址等。接下来的 320 个字节是音频数据。这里音频数据总是固定为 320 字节。

    private static final int SAMPLE_INTERVAL = 20;
    private static final int SAMPLE_SIZE = 2;
    private static final int BUF_SIZE = SAMPLE_INTERVAL * SAMPLE_INTERVAL * SAMPLE_SIZE * 2; //1600Bytes

    // the audio recording options
    private static final int RECORDING_RATE = 8000; // Hertz
    private static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO;//16
    private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT;//2


    AudioRecord  audioRecorder = new AudioRecord(MediaRecorder.Audiosource.MIC,
                RECORDING_RATE, CHANNEL, FORMAT, BUF_SIZE * 10);

//音频传输线程

        @Override
        public void run() 


            long seq=0;
            byte[] buffer = new byte[BUF_SIZE];

            WiphoneProp callProp=new WiphoneProp();
            callProp.setOpCode(WiPhonePacket.OpCodes.Call.getValue());//1 byte
            callProp.setOperand(WiPhonePacket.OperandCodes.Talk.getValue());//1 byte
            callProp.setSelfState(0);//1 byte
            callProp.setCallId(1);//1 byte

            callProp.setCheckSum(BitConverter.checkSum(deviceSignature.getSignatureData(),0,deviceSignature.getSignatureData().length));//4 byte

            try 

                int offset=0;
                audioRecorder.startRecording();
                while (mic) 
                    // Capture audio from the mic and transmit it

                    try 
                        callProp.setWiphoneId(ByteBuffer.wrap(getBroadcastQuadIp().getAddress()).order(ByteOrder.LITTLE_ENDIAN).getInt());//4 byte
                        callProp.setClientId(ByteBuffer.wrap(getLocalIp().getAddress()).order(ByteOrder.LITTLE_ENDIAN).getInt());//4 byte
                     catch (IOException e) 
                        e.printStackTrace();
                    

                    callProp.setSequence(seq);//4 byte

                    byte[] buff=new byte[320];

                    audioRecorder.read(buffer, offset, 320);

                    System.arraycopy(buffer,offset,buff,0,buff.length);
                    callProp.setVoice(buff);//320 byte

                    byte[] voice = callProp.ToBuffer();

                    DatagramPacket packet = new DatagramPacket(voice, voice.length, InetAddress.getByName(deviceSignature.getDeviceIP()), WIPHONEPORT);
                    commonSoc.send(packet);

                    offset+=320;
                    if(offset>=1600)
                        offset=0;
                    
                    seq++;
                
                // Stop recording and release resources
                audioRecorder.stop();
                audioRecorder.release();
                mic = false;
            
           /* catch(InterruptedException e) 

                Log.e(TAG, "InterruptedException: " + e.toString());
                mic = false;
            */
            catch(SocketException e) 

                Log.e(TAG, "SocketException: " + e.toString());
                mic = false;
            
            catch(UnknownHostException e) 

                Log.e(TAG, "UnknownHostException: " + e.toString());
                mic = false;
            
            catch(IOException e) 

                Log.e(TAG, "IOException: " + e.toString());
                mic = false;
            
        

现在我的音频缓冲区大小为 1600 字节。 另一端没有音频。

我推荐了this

这是我的 c# 代码,用于获取 320 字节的音频流

  CaptureDevicesCollection captureDeviceCollection = new CaptureDevicesCollection();

                DeviceInformation deviceInfo = captureDeviceCollection[0];

                capture = new Capture(deviceInfo.DriverGuid);

                short channels = 1; //Stereo.
                short bitsPerSample = 16; //16Bit, alternatively use 8Bits.
                int samplesPerSecond = 8000;//Default: 22050; //11KHz use 11025 , 22KHz use 22050, 44KHz use 44100 etc.

                //Set up the wave format to be captured.
                waveFormat = new WaveFormat();
                waveFormat.Channels = channels;
                waveFormat.FormatTag = WaveFormatTag.Pcm;
                waveFormat.SamplesPerSecond = samplesPerSecond;
                waveFormat.BitsPerSample = bitsPerSample;
                waveFormat.BlockAlign = (short)(channels * (bitsPerSample / (short)8));
                waveFormat.AverageBytesPerSecond = waveFormat.BlockAlign * samplesPerSecond;

                captureBufferDescription = new CaptureBufferDescription();
                captureBufferDescription.BufferBytes = waveFormat.AverageBytesPerSecond / 5; //Approx 200 milliseconds of PCM data.
                captureBufferDescription.Format = waveFormat;

                playbackBufferDescription = new BufferDescription();
                playbackBufferDescription.BufferBytes = waveFormat.AverageBytesPerSecond / 5;
                playbackBufferDescription.Format = waveFormat;
                playbackBuffer = new SecondaryBuffer(playbackBufferDescription, device);

                bufferSize = captureBufferDescription.BufferBytes;

//// 获取 320 字节音频数据的逻辑

    try
                
                    //The following lines get audio from microphone and then send them 
                    //across network.
                    captureBuffer = new CaptureBuffer(captureBufferDescription, capture);
                    CreateNotifyPositions();
                    int blockSize = bufferSize / 10;
                    int halfBuffer = bufferSize / 2;
                    captureBuffer.Start(true);
                    //bool readFirstBufferPart = true;
                    int offset = 0;
                    MemoryStream memStream = new MemoryStream(halfBuffer);
                    bStop = false;
                    uint seq = 0;
                    while (!bStop)
                    
                        autoResetEvent.WaitOne();
                        this.Invoke(new Action(() =>
                            
                                listBox1.Items.Add(DateTime.Now.ToString("HH:mm:ss.fff"));
                            ));
                        offset = (int)(((seq + 5) % 10) * blockSize);
                        halfBuffer = blockSize;
                        memStream.Seek(0, SeekOrigin.Begin);
                        captureBuffer.Read(offset, memStream, halfBuffer, LockFlag.None);
                        //readFirstBufferPart = !readFirstBufferPart;
                        //offset = readFirstBufferPart ? 0 : halfBuffer;
                        byte[] dataToWrite = memStream.GetBuffer();
                        var voicePacket = new WiPhonePacket
                        
                            OpCode = (byte)WiPhonePacket.OpCodes.Call,
                            Operand = (byte)WiPhonePacket.OperandCodes.Talk,
                            SelfState = 0,
                            CallId = 1,
                            WiPhoneId = broadcastIp,
                            ClientId = localIp,
                            CheckSum = Helpers.General.CalculateChecksum(Configurations.Signature.SignatureData, 0, Configurations.Signature.SignatureData.Length),
                            Sequence = seq++
                        ;
                        voicePacket.SetVoice(dataToWrite);
                        var voiceBuffer = voicePacket.ToBuffer();
                        IPEndPoint ep = new IPEndPoint(IPAddress.Parse(Configurations.Signature.DeviceIP), 2739);
                        clientSocket.SendTo(voiceBuffer, ep);
                        //udpClient.Send(dataToWrite, dataToWrite.Length, otherPartyIP.Address.ToString(), 2739);
                    
                
                catch (Exception ex)
                
                    MessageBox.Show(ex.Message, "VoiceChat-Send ()", MessageBoxButtons.OK, MessageBoxIcon.Error);
                
                finally
                
                    captureBuffer.Stop();
                    captureBuffer.Dispose();
                    captureBuffer = null;
                    autoResetEvent.Dispose();
                    autoResetEvent = null;
                    notify.Dispose();
                    notify = null;
                    //Increment flag by one.
                    nUdpClientFlag += 1;
                    //When flag is two then it means we have got out of loops in Send and Receive.
                    //while (nUdpClientFlag != 2)
                    // 
                    //Clear the flag.
                    nUdpClientFlag = 0;
                    //Close the socket.
                    //udpClient.Close();
                




private void CreateNotifyPositions()
        
            try
            
                autoResetEvent = new AutoResetEvent(false);

                notify = new Notify(captureBuffer);

                BufferPositionNotify[] nots = new BufferPositionNotify[10];
                for (int i = 0; i < 10; i++)
                
                    var bufferPositionNotify1 = new BufferPositionNotify();
                    bufferPositionNotify1.Offset = (i + 1) * (bufferSize / 10) - 1;
                    bufferPositionNotify1.EventNotifyHandle = autoResetEvent.SafeWaitHandle.DangerousGetHandle();

                    nots[i] = bufferPositionNotify1;
                

                notify.SetNotificationPositions(nots);

            
            catch (Exception ex)
            
                MessageBox.Show(ex.Message, "VoiceChat-CreateNotifyPositions ()", MessageBoxButtons.OK, MessageBoxIcon.Error);
            
        

【问题讨论】:

我看到您的评论“从麦克风捕获音频”,但我没有看到相应的代码,例如读()。我错过了什么吗? @HartmutPfitzinger 谢谢你的回复。它的复制粘贴错误。我更新了我的帖子,请检查一下。 【参考方案1】:

感谢您添加源代码的缺失部分。这非常有用,因为它包含最明显的错误:

    if(i==0)
        bytes_read   = audioRecorder.read(buffer, 0, 319);
        System.arraycopy(buffer,0,buff,0,buff.length);
        callProp.setVoice(buff);//320 byte
    else
        bytes_read   = audioRecorder.read(buffer, 320, 639);
        System.arraycopy(buffer,320,buff,0,buff.length);
        callProp.setVoice(buff);//320 byte
    

audioRecorder.read() 将要读取的字节数作为第三个参数(而不是要写入的数组的最后一个元素的索引)。所以你应该把代码改成:

    if(i==0)
        bytes_read   = audioRecorder.read(buffer, 0, 320);
        System.arraycopy(buffer,0,buff,0,buff.length);
        callProp.setVoice(buff);//320 byte
    else
        bytes_read   = audioRecorder.read(buffer, 320, 320);
        System.arraycopy(buffer,320,buff,0,buff.length);
        callProp.setVoice(buff);//320 byte
    

之后音质应该会好很多,但不要期望太高:您的采样率只有 8kHz,这意味着可能的最高频率低于 4kHz,这与旧电话质量差不多。

【讨论】:

谢谢。之前缓冲区大小为 640 字节,所以我分两部分发送音频数据,但现在我更改为 1600 字节(半缓冲区),就像在 C# 中一样。无论如何,我会试试这个,让你知道状态。 实际上缓冲区大小因设备而异,有些设备有 640,有些设备有 1024。所以我创建了 1600 字节的常量缓冲区并读取固定 320 字节的音频数据。我更新了我的代码。请检查一下 更好。那么现在的声音呢? 现在没有声音了 我没有看到 mic in 设置为 true 的行 while (mic) 【参考方案2】:

我解决了以下问题

private static final int SAMPLE_INTERVAL = 20; // Milliseconds
private static final int SAMPLE_SIZE = 2; // Bytes
private static final int BUF_SIZE = SAMPLE_INTERVAL * SAMPLE_INTERVAL * SAMPLE_SIZE * 2; 


private static final int RECORDING_RATE = 8000; // Hertz
private static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO;//16
private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT;//2


audioRecorder = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION,
            RECORDING_RATE, CHANNEL, FORMAT,BUF_SIZE*10);


@Override
            public void run() 
                // Create an instance of the AudioRecord class
                long seq=0;
                int offset=0;
                byte[] buffer = new byte[BUF_SIZE];

                try 
                    // Create a socket and start recording
                    Log.i(TAG, "Packet destination: " + deviceSignature.getDeviceIP()+ " "+BUF_SIZE);
                    audioRecorder.startRecording();
                    while (mic) 
                        // Capture audio from the mic and transmit it

                        WiphoneProp callProp=new WiphoneProp();
                        callProp.setOpCode(WiPhonePacket.OpCodes.Call.getValue());
                        callProp.setOperand(WiPhonePacket.OperandCodes.Talk.getValue());
                        callProp.setSelfState(0);
                        callProp.setCallId(1);

                        callProp.setCheckSum(BitConverter.checkSum(deviceSignature.getSignatureData(),0,deviceSignature.getSignatureData().length));
                        try 
                            callProp.setWiphoneId(ByteBuffer.wrap(getBroadcastQuadIp().getAddress()).order(ByteOrder.LITTLE_ENDIAN).getInt());//broadcast ip or wiphone ip
                            callProp.setClientId(ByteBuffer.wrap(getLocalIp().getAddress()).order(ByteOrder.LITTLE_ENDIAN).getInt());//device ip or local ip
                         catch (IOException e) 
                            e.printStackTrace();
                        
                        callProp.setSequence(seq);

                        byte[] buff=new byte[320];

                        audioRecorder.read(buffer, offset, buff.length);

                        System.arraycopy(buffer,offset,buff,0,buff.length);

                        callProp.setVoice(buff);//320 byte
                        byte[] voice = callProp.ToBuffer();


                        DatagramPacket packet = new DatagramPacket(voice, voice.length, InetAddress.getByName(deviceSignature.getDeviceIP()), WIPHONEPORT);
                        commonSoc.send(packet);

                        seq++;
                        offset+=320;
                        if(offset>=1600)
                            offset=0;
                        


                    
                    // Stop recording and release resources
                    audioRecorder.stop();
                    audioRecorder.release();
                    mic = false;
                
               /* catch(InterruptedException e) 

                    Log.e(TAG, "InterruptedException: " + e.toString());
                    mic = false;
                */
                catch(SocketException e) 

                    Log.e(TAG, "SocketException: " + e.toString());
                    mic = false;
                
                catch(UnknownHostException e) 

                    Log.e(TAG, "UnknownHostException: " + e.toString());
                    mic = false;
                
                catch(IOException e) 

                    Log.e(TAG, "IOException: " + e.toString());
                    mic = false;
                
            

【讨论】:

以上是关于android中具有固定数据报包长度的datagramsocket中的音频流质量不好的主要内容,如果未能解决你的问题,请参考以下文章

数据报包到字符串

具有固定长度数组作为参数的 C++ 构造函数[关闭]

SQL 数据库 数据类型 固定长度的字符串怎么定义

通过数据报包发送数组的最佳方式是啥?

java基础——UDP实现dos聊天室案例

通过 LAN 广播 UDP 数据报包