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

Audio streaming quality not good in datagramsocket with fixed datagram packets length in android

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

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

    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);
            }
        }

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

    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,这与旧电话质量有关。

我修复了如下问题

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;
                }
            }