Android如何将接收音频的UDP转换为wav文件

问题描述 投票:0回答:1

我正在尝试从我的应用程序接收流音频。

下面是我接收音频流的代码:

public class ClientListen implements Runnable {
private Context context;

    public ClientListen(Context context) {
        this.context = context;
    }

    @Override
    public void run() {
        boolean run = true;
        try {
            DatagramSocket udpSocket = new DatagramSocket(8765);
            InetAddress serverAddr = null;
            try {
                serverAddr = InetAddress.getByName("127.0.0.1");
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }

            while (run) {
                try {
                    byte[] message = new byte[8000];
                    DatagramPacket packet = new DatagramPacket(message,message.length);
                    Log.i("UDP client: ", "about to wait to receive");
                    udpSocket.setSoTimeout(10000);
                    udpSocket.receive(packet);

                    String text = new String(packet.getData(), 0, packet.getLength());
                    Log.d("Received text", text);
                } catch (IOException e) {
                    Log.e(" UDP clien", "error: ", e);
                    run = false;
                    udpSocket.close();
                }
            }
        } catch (SocketException e) {
            Log.e("Socket Open:", "Error:", e);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在接收到的文本记录器中,我可以看到数据来自

D/Received text: �������n�����������q�9�$�0�/�G�{�������s�����JiH&������d�����Z���������d�����E������C�+
    ��l��y�����������v���9����������u��f�j�������$�����K���������F��~R�2�����T��������������L�����!��G��8������s�;�"�,�R�����(��{�����*_��Z�������5������������\������x���j~������������/��=�����%�������

如何将这些数据存储到WAV文件中?

android udp audio-streaming
1个回答
0
投票
  1. 您看到的是接收到udp数据包,并且接收到的块刚刚释放后。这只是要转换为wav的声音的一小部分。不久,while循环将继续,您将收到另一个数据包,以及更多。您需要将所有数据包收集在缓冲区中,然后在您认为可以的情况下-将它们转换为wave文件。

  2. 记住Wave不仅是您从udp获得的声音字节,而且是您需要添加到此文件以便被播放器识别的44字节前缀。

  3. 另外,如果udp来自其他编码格式,例如G711,则必须将这些字节编码为PCM;否则,您会听到您播放的波浪声或流声。

  4. 缓冲区必须准确。如果太大(数组末尾有许多空字节),您会听到直升飞机的声音。如果您确切知道每个数据包的大小,则可以将其写入AudioTrack以播放流,或者将其累加并在合适时将其转换为wave文件。但是如果不确定大小,则可以可以使用以下答案获取缓冲区,然后将缓冲区写入AudioTrack:Android AudioRecord to Server over UDP Playback Issues。他们使用Javax是因为它的答案很老,但是您只需使用AudioTrack即可进行流式传输。

         final int SAMPLE_RATE = 8000; // Hertz
         final int STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
         int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
         int encodingFormat = AudioFormat.ENCODING_PCM_16BIT;
    
         AudioTrack track = new AudioTrack(STREAM_TYPE, SAMPLE_RATE, channelConfig,
               encodingFormat, BUF_SIZE, AudioTrack.MODE_STREAM);
          ...
         track.play();
         ...
          if(track != null && packet != null){
            track.write(audioStreamBuffer, 0, audioStreamBuffer.length);
          }
    
  5. 您必须在其他线程中执行此操作(我想您知道)。

  6. 在代码中,我将向您展示-我从广播中获取音频日志的udp。它用G711 Ulaw编码。每个数据包正好为172个字节。前12个字节用于RTP,我需要对它们进行偏移(删除)以消除小噪声。其余的160个字节是20MS声音。

  7. 我必须将G711 Ulaw字节解码为PCM短裤数组。然后取出短数组,并从中制作一个波形文件。我看到没有数据包接收的时间超过一秒钟后才开始使用它(所以我知道语音已结束并且新的块释放是由于新语音造成的,所以我可以采用旧语音并从中生成波形文件)。您可以根据正在执行的操作来决定使用其他缓冲区。
  8. 效果很好。解码后的声音非常好。如果您具有带PCM的UDP,则无需解码G711-只需跳过此部分。

  9. 最后我想提一提,我看到了许多使用javax.sound.sampled的代码部分的老答案,这似乎很棒,因为它可以使用AudioFileFormat轻松地将音频文件或流转换为波形格式还可以通过AudioFormat操作将G711转换为pcm。但不幸的是,它不是当前用于Android的Java的一部分。我们必须依靠android AudioTrack(如果要从麦克风获取声音,则必须使用AudioRecord),但是AudioTrack仅播放PCM并且不支持G711格式-因此,当通过AudioTrack流式传输G711时,噪音非常糟糕。在将其写入轨道之前,我们必须先在代码中对其进行解码。另外,我们无法使用audioInputStream转换为wave文件–我尝试使用添加到我的应用程序中的javax.sound.sampled jar文件轻松地做到这一点,但是android不断给我一些错误,例如wave不支持的格式,以及在尝试流式传输时出现混音器错误–因此,我了解到最新的android无法与javax.sound.sampled一起使用,因此我从从UDP数据包接收的字节数组缓冲区中寻找G711的法律级别解码和wave文件的法律级别创建。

A。在清单中添加:

         <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
         <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
         <uses-permission android:name="android.permission.INTERNET"/>

B。在工作线程中:

       @Override
      public void run(){
        Log.i(TAG, "ClientListen thread started. Thread id: " + Thread.currentThread().getId());
        try{
                udpSocket = new DatagramSocket(port);
        }catch(SocketException e){
              e.printStackTrace();
        }
        byte[] messageBuf = new byte[BUF_SIZE];
        Log.i(TAG, "waiting to receive packet in port: " + port);
        if(udpSocket != null){
            // here you can create new AudioTrack and play.track
            byte speakerSession[] = null;
            while (running){
                packet = new DatagramPacket(messageBuf, 0, messageBuf.length);
                Log.d(TAG, "inside while running loop");
                try{
                    Log.d(TAG, "receive block: waiting for user to press on 
                    speaker(listening now inside udpSocket for DatagramPacket..)");

                    //get inside receive block until packet will arrive through this socket
                     long timeBeforeBlock = System.currentTimeMillis();
                    udpSocket.receive(packet);
                    Log.d(TAG, "client received a packet, receive block stopped)");
                    //this is for sending msg handler to the UI tread (you may skip this)
                    sendState("getting UDP packets..."); 

          /* if previous block release happened more than one second ago - so this 
              packet release is for a new speech. so let’s copy the previous speech 
              to a wave file and empty the speech */
                  if(System.currentTimeMillis() - timeBeforeBlock > 1000 && speakerSession != null){
                       convertBytesToFile(speakerSession);
                       speakerSession = null;
                    }
                  /* let’s take the packet that was released and start new speech or add it to the ongoing speech. */
                     byte[] slice = Arrays.copyOfRange(packet.getData(), 12, packet.getLength());
                    if(null == speakerSession){
                        speakerSession = slice;
                    }else{
                        speakerSession = concat(speakerSession, slice);
                        Log.d(TAG, "speakerSession:" + Arrays.toString(speakerSession));
                    }
                }catch(IOException e){
                    Log.e(TAG, "UDP client IOException - error: ", e);
                    running = false;
                }
            }
            // let’s take the latest speech and make a last wave file out of it.
            if(speakerSession != null){
                convertBytesToFile(speakerSession);
                speakerSession = null;
            }
             // if running == false then stop listen.
            udpSocket.close();
            handler.sendEmptyMessage(MainActivity.UdpClientHandler.UPDATE_END);
         }else{
            sendState("cannot bind datagram socket to the specified port:" + port);
         }
      }


        private void convertBytesToFile(byte[] byteArray){

             //decode the bytes from G711U to PCM (outcome is a short array)
             G711UCodec decoder = new G711UCodec();
             int size = byteArray.length;
             short[] shortArray = new short[size];
             decoder.decode(shortArray, byteArray, size, 0);
             String newFileName = "speech_" + System.currentTimeMillis() + ".wav";
             //convert short array to wav (add 44 prefix shorts) and save it as a .wav file
             Wave wave = new Wave(SAMPLE_RATE, (short) 1, shortArray, 0, shortArray.length - 1);
             if(wave.writeToFile(Environment.getExternalStoragePublicDirectory
     (Environment.DIRECTORY_DOWNLOADS),newFileName)){ 
                   Log.d(TAG, "wave.writeToFile successful!");
                   sendState("create file: "+ newFileName);
             }else{
                   Log.w(TAG, "wave.writeToFile failed");
             }
         }

C。编码G711类:摘自:https://github.com/thinktube-kobe/airtube/blob/master/JavaLibrary/src/com/thinktube/audio/G711UCodec.java

 /**
 * G.711 codec. This class provides u-law conversion.
 */
 public class G711UCodec {
 // s00000001wxyz...s000wxyz
 // s0000001wxyza...s001wxyz
 // s000001wxyzab...s010wxyz
 // s00001wxyzabc...s011wxyz
 // s0001wxyzabcd...s100wxyz
 // s001wxyzabcde...s101wxyz
 // s01wxyzabcdef...s110wxyz
 // s1wxyzabcdefg...s111wxyz

private static byte[] table13to8 = new byte[8192];
private static short[] table8to16 = new short[256];

static {
    // b13 --> b8
    for (int p = 1, q = 0; p <= 0x80; p <<= 1, q += 0x10) {
        for (int i = 0, j = (p << 4) - 0x10; i < 16; i++, j += p) {
            int v = (i + q) ^ 0x7F;
            byte value1 = (byte) v;
            byte value2 = (byte) (v + 128);
            for (int m = j, e = j + p; m < e; m++) {
                table13to8[m] = value1;
                table13to8[8191 - m] = value2;
            }
        }
    }

    // b8 --> b16
    for (int q = 0; q <= 7; q++) {
        for (int i = 0, m = (q << 4); i < 16; i++, m++) {
            int v = (((i + 0x10) << q) - 0x10) << 3;
            table8to16[m ^ 0x7F] = (short) v;
            table8to16[(m ^ 0x7F) + 128] = (short) (65536 - v);
        }
    }
}

public int decode(short[] b16, byte[] b8, int count, int offset) {
    for (int i = 0, j = offset; i < count; i++, j++) {
        b16[i] = table8to16[b8[j] & 0xFF];
    }
    return count;
}

public int encode(short[] b16, int count, byte[] b8, int offset) {

    for (int i = 0, j = offset; i < count; i++, j++) {
        b8[j] = table13to8[(b16[i] >> 4) & 0x1FFF];
    }
    return count;
}

 public int getSampleCount(int frameSize) {
    return frameSize;
 }
}

D。转换为波形文件:从这里拍摄:https://github.com/google/oboe/issues/320

 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;


 public class Wave
 {
        private final int LONGINT = 4;
        private final int SMALLINT = 2;
        private final int INTEGER = 4;
        private final int ID_STRING_SIZE = 4;
        private final int WAV_RIFF_SIZE = LONGINT+ID_STRING_SIZE;
        private final int WAV_FMT_SIZE = (4*SMALLINT)+(INTEGER*2)+LONGINT+ID_STRING_SIZE;
        private final int WAV_DATA_SIZE = ID_STRING_SIZE+LONGINT;
        private final int WAV_HDR_SIZE = WAV_RIFF_SIZE+ID_STRING_SIZE+WAV_FMT_SIZE+WAV_DATA_SIZE;
        private final short PCM = 1;
        private final int SAMPLE_SIZE = 2;
        int cursor, nSamples;
        byte[] output;


public Wave(int sampleRate, short nChannels, short[] data, int start, int end)
{
    nSamples=end-start+1;
    cursor=0;
    output=new byte[nSamples*SMALLINT+WAV_HDR_SIZE];
    buildHeader(sampleRate,nChannels);
    writeData(data,start,end);
}

/*
 by Udi for using byteArray directly
*/
public Wave(int sampleRate, short nChannels, byte[] data, int start, int end)
{
    int size = data.length;
    short[] shortArray = new short[size];
    for (int index = 0; index < size; index++){
        shortArray[index] = (short) data[index];
    }
    nSamples=end-start+1;
    cursor=0;
    output=new byte[nSamples*SMALLINT+WAV_HDR_SIZE];
    buildHeader(sampleRate,nChannels);
    writeData(shortArray,start,end);
}



// ------------------------------------------------------------
private void buildHeader(int sampleRate, short nChannels)
{
    write("RIFF");
    write(output.length);
    write("WAVE");
    writeFormat(sampleRate, nChannels);
}
// ------------------------------------------------------------
public void writeFormat(int sampleRate, short nChannels)
{
    write("fmt ");
    write(WAV_FMT_SIZE-WAV_DATA_SIZE);
    write(PCM);
    write(nChannels);
    write(sampleRate);
    write(nChannels * sampleRate * SAMPLE_SIZE);
    write((short)(nChannels * SAMPLE_SIZE));
    write((short)16);
}
// ------------------------------------------------------------
public void writeData(short[] data, int start, int end)
{
    write("data");
    write(nSamples*SMALLINT);
    for(int i=start; i<=end; write(data[i++]));
}
// ------------------------------------------------------------
private void write(byte b)
{
    output[cursor++]=b;
}
// ------------------------------------------------------------
private void write(String id)
{
    if(id.length()!=ID_STRING_SIZE){

    }
    else {
        for(int i=0; i<ID_STRING_SIZE; ++i) write((byte)id.charAt(i));
    }
}
// ------------------------------------------------------------
private void write(int i)
{
    write((byte) (i&0xFF)); i>>=8;
    write((byte) (i&0xFF)); i>>=8;
    write((byte) (i&0xFF)); i>>=8;
    write((byte) (i&0xFF));
}
// ------------------------------------------------------------
private void write(short i)
{
    write((byte) (i&0xFF)); i>>=8;
    write((byte) (i&0xFF));
}
// ------------------------------------------------------------
public boolean writeToFile(File fileParent , String filename)
{
    boolean ok=false;

    try {
       File path=new File(fileParent, filename);
       FileOutputStream outFile = new FileOutputStream(path);
      outFile.write(output);
      outFile.close();
        ok=true;
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        ok=false;
    } catch (IOException e) {
        ok=false;
        e.printStackTrace();
    }
    return ok;
}


/**
 * by Udi for test: write file with temp name so if you write many packets each packet will be written to a new file instead of deleting
 * the previous file. (this is mainly for debug)
 * @param fileParent
 * @param filename
 * @return
 */
public boolean writeToTmpFile(File fileParent , String filename)
{
    boolean ok=false;

    try {
        File outputFile = File.createTempFile(filename, ".wav",fileParent);
        FileOutputStream fileoutputstream = new FileOutputStream(outputFile);
        fileoutputstream.write(output);
        fileoutputstream.close();
        ok=true;
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        ok=false;
    } catch (IOException e) {
        ok=false;
        e.printStackTrace();
    }
    return ok;
 }
}
© www.soinside.com 2019 - 2024. All rights reserved.