1

I am trying to recieve an streaming audio from my app.

below is my code for recieving audio stream:

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

In Received text logger i can see data as coming as

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~������������/��=�����%�������

How can store this data into a wav file ?

Feroz Siddiqui
  • 3,840
  • 6
  • 34
  • 69
  • Console tries to show characters out of your bytes, that is ok. You don't know how to store bytes into file? Or there is another issue? – eleven Aug 29 '19 at 07:58
  • if i am writing audio in the file its not storing as audio. if i use AudioTrack i am unable to set file location ins Audio track. so i dont know how to store it – Feroz Siddiqui Aug 29 '19 at 08:01
  • i am kind of struggling with PCM encoding here i guess but not sure – Feroz Siddiqui Aug 29 '19 at 08:02
  • So then it might depend on how you're sending the file. You can at least try to compare file which you're sending (which is wav and works as expected) and file which you receive. You can compare files in different ways: md5, hex viewers. If they are not equal, then you're encoding it before sending. – eleven Aug 29 '19 at 08:08
  • i cannot see that thing its coming from other servers they are saying "just launch an UDP socket on any port and set this port number to recieve audio streaming." – Feroz Siddiqui Aug 29 '19 at 08:10
  • can you pass some piece of code for comparing the byte array – Feroz Siddiqui Aug 29 '19 at 08:15

1 Answers1

1
  1. What you see is the string representation of single udp packet after it was received and the received block has just being released. It is a very small fraction of the sound you want to convert to wave. Soon the while loop will continue and you will receive another packet and many more.. You need to collect all the packets in a buffer and then when you think it is ok - convert them to wave file.

  2. Remember Wave is not just the sound bytes you get from udp but also 44 bytes of prefix you need to add to this file in order to be recognized by players.

  3. Also if the udp is from another encoding format such as G711 - you must encode these bytes to PCM – if not you will hear heavy noise in the Sound of the wave or the stream you play.

  4. The buffer must be accurate. if it will be too big (many empty bytes in the end of the array) you will hear a sound of helicopter. if you know exactly what is the size of each packet then you can just write it to AudioTrack in order to play stream, or accumulate it and convert it to wave file when you will see fit. But If you are not sure about the size you can use this answer to get a buffer and then write the buffer to AudioTrack: Android AudioRecord to Server over UDP Playback Issues. they use Javax because it is very old answer but you just need to use AudioTrack instead in order to stream. It is not in this scope so I will just present the AudioTrack streaming replacements instead of Javax SourceDataLine:

         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();
         //.. then after receive UDP packets and the buffer is full:
          if(track != null && packet != null){
            track.write(audioStreamBuffer, 0, audioStreamBuffer.length);
          }
    
  5. You must not do this in the UI thread (I assume you know that).

  6. In the code I will show you - I am getting udp of audio logs from PTT radio. It is encoded in G711 Ulaw . each packet is of 172 bytes exactly. First 12 bytes are for RTP and I need to offset (remove) them in order to eliminate small noises. rest 160 bytes are 20MS of sound.

  7. I must decode the G711 Ulaw bytes to PCM shorts array. Then to take the short array and to make a wave file out of it. I am taking it after I see there was no packet receiving for more than one second (so I know the speech ended and the new block release is because of a new speech so I can take the old speech and make a wave file out of it). You can decide of a different buffer depends on what you are doing.
  8. It works fine. After the decoding the sound of the wave is very good. If you have UDP with PCM so you don’t need to decode G711 - just skip this part.

  9. Finally I want to mention I saw many old answers with code parts using javax.sound.sampled that seems great because it can convert easily an audio file or stream to wave format with AudioFileFormat And also convert G711 to pcm with AudioFormat manipulations. But unfortunately it is not part of current java for android. We must count on android AudioTrack instead (and AudioRecord if we want to get the sound from the mic) but AudioTrack play only PCM and do not support G711 format – so when streaming G711 with AudioTrack the noise is terrible. We must decode it in our code before writing it to the track. Also we cannot convert to wave file using audioInputStream – I tried to do this easily with javax.sound.sampled jar file I added to my app but android keep giving me errors such as format not supported for wave, and mixer errors when try to stream – so I understood latest android cannot work with javax.sound.sampled and I went to look for law level decoding of G711 and law level creation of wave file out of the buffer of byte array received from the UDP packets .

A. in manifest add:

         <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. in the worker thread:

       @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 pttSession[] = 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 && pttSession != null){
                       convertBytesToFile(pttSession);
                       pttSession = 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 == pttSession){
                        pttSession = slice;
                    }else{
                        pttSession = concat(pttSession, slice);
                        Log.d(TAG, "pttSession:" + Arrays.toString(pttSession));
                    }
                }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(pttSession != null){
                convertBytesToFile(pttSession);
                pttSession = 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. encoding/decoding G711 U-Law class: taken from: 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. Converting to wave file: Taken from here: 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;
 }
}
Udi Reshef
  • 1,083
  • 1
  • 11
  • 14