15

I am streaming mic input from a C Server via socket. I know the stream works because it does with a C client and I am getting the right values on my Android client.

I am streaming a 1024 floatarray. One float are 4 bytes. So I got a incoming stream with 4096 bytes per frame. I am getting the floats out of this bytes and I know this floats are the ones I sent, so that part should work.

Now I want to get that stream directly to the phones speakers by using AudioTrack. I tried to input the bytes I received directly: just noise. I tried to cast it back to a byte array, still the same. I tried to cast that float into short (because AudioTrack takes bytes or short). I could get something that could have been my mic input (knocking), but very scratchy and and extremely laggy. I would understand if there was a lag between the frames, but I can't even get one clear sound. I can, however, output a sin sound clearly that I produce locally and put into that shortarray. Now I wonder if I got some issues in my code anyone of you can see, because I don't see them.

What I am doing is: I put 4 bytes in a byte array. I get the float out of it. As soon as I got one Frame in my float array (I am controlling that with a bool, not nice, but it should work) I put it in my shortarray and let audiotrack play it. This double casting might be slow, but I do it because its the closest I got to playing the actual input.

Edit: I checked the endianess by comparing the floats, they have the proper values between -1 and 1 and are the same ones I send. Since I don't change the endianess when casting to float, I don't get why forwarding a 4096 byte array to AudioTrack directly doesn't work neither. There might be something wrong with the multithreading, but I don't see what it could be.

Edit 2: I discovered a minor problem - I reset j at 1023. But that missing float should not have been the problem. What I did other than that was to put the method that took the stream from the socket in another thread instead of calling it in a async task. That made it work, I now am able to understand the mic sounds. Still the quality is very poor - might there be a reason for that in the code? Also I got a delay of about 10 seconds. Only about half a second is caused by WLAN, so I wonder if it might be the codes fault. Any further thoughts are appreciated.

Edit 3: I played around with the code and implemented a few of greenapps ideas in the comments. With the new thread structure I was facing the problem of not getting any sound. Like at all. I don't get how that is even possible, so I switched back. Other things I tried to make the threads more lightweight didn't have any effect. I got a delay and I got a very poor quality (I can identify knocks, but I can't understand voices). I figured something might be wrong with my convertions, so I put the bytes I receive from the socket directly in AudioTrack - nothing but ugly pulsing static noise. Now I am even more confused, since this exact stream still works with the C client. I will report back if I find a solution, but still any help is welcome.

Edit 4 I should add, that I can play mic inputs from another android app where I send that input directly as bytes (I would exclude the float casting stuff and put the bytes I receive directly to audioTrack in my player code).
Also it occured to me, that it could be a problem, that the said floatarray that is streamed by the C Server comes from a 64bit machine while the phone is 32bit. Could that be a problem somehow, even though I am just streaming floats as 4 bytes? Or, another thought of mine: The underlying number format of the bytes I receive is float. What format does AudioTrack expect? Even if put in just bytes - would I need to cast that float to a int and cast that back to bytes or something?

new code:

public class PCMSocket {

AudioTrack audioTrack;
boolean doStop = false;
int musicLength = 4096;
byte[] music;
Socket socket;
short[] buffer = new short[4096];
float[] fmusic = new float[1024];
WriteToAudio writeThread;
ReadFromSocket readThread;


public PCMSocket()
{

}

public void start()
{
    doStop = false;
    readThread = new ReadFromSocket();
    readThread.start();
}

public class ReadFromSocket extends Thread
{       
    public void run()
    {
    doStop=true;

    InetSocketAddress address = new InetSocketAddress("xxx.xxx.xxx.x", 8000);

    socket = new Socket();
    int timeout = 6000;   
    try {
        socket.connect(address, timeout);
    } catch (IOException e2) {
        e2.printStackTrace();
    }

     musicLength = 1024;

    InputStream is = null;

    try {
        is = socket.getInputStream();
    } catch (IOException e) {
        e.printStackTrace();
    }

    BufferedInputStream bis = new BufferedInputStream(is);
    DataInputStream dis = new DataInputStream(bis);     

    try{

    int minSize =AudioTrack.getMinBufferSize( 44100, AudioFormat.CHANNEL_CONFIGURATION_STEREO, AudioFormat.ENCODING_PCM_16BIT ); 

    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
            AudioFormat.CHANNEL_OUT_STEREO, 
            AudioFormat.ENCODING_PCM_16BIT, minSize,
            AudioTrack.MODE_STREAM);
        audioTrack.play();

      } catch (Throwable t)
      {
          t.printStackTrace();
        doStop = true;
      }

    writeThread = new WriteToAudio();
    readThread.start();

    int i = 0;   
    int j=0;

    try {
        if(dis.available()>0)Log.d("PCMSocket", "receiving");
        music = new byte[4];
        while (dis.available() > 0)
        {
            music[i]=0;
            music[i] = dis.readByte(); 

            if(i==3)
            {
                int asInt = 0;
                asInt = ((music[0] & 0xFF) << 0) 
                        | ((music[1] & 0xFF) << 8) 
                        | ((music[2] & 0xFF) << 16) 
                        | ((music[3] & 0xFF) << 24);
                float asFloat = 0;
                asFloat = Float.intBitsToFloat(asInt);
                fmusic[j]=asFloat;
            }

            i++;
            j++;
            if(i==4)
            {
                music = new byte[4]; 
                i=0;
            }
            if(j==1024)
            {
                j=0;
                if(doStop)doStop=false;
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    try {
        dis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }  

    }
};


public class WriteToAudio extends Thread
{       
    public void run()
    {
        while(true){
        while(!doStop)
        {           
            try{
                writeSamples(fmusic);

            }catch(Exception e)
            {
                e.printStackTrace();
            }    
            doStop = true;
        }
        }
    }
};


public void writeSamples(float[] samples) 
{   
   fillBuffer( samples );
   audioTrack.write( buffer, 0, samples.length );
}

private void fillBuffer( float[] samples )
{ 
   if( buffer.length < samples.length )
      buffer = new short[samples.length];

   for( int i = 0; i < samples.length; i++ )
   {
      buffer[i] = (short)(samples[i] * Short.MAX_VALUE);
   }
}   


}

old code:

public class PCMSocket {
AudioTrack audioTrack;
WriteToAudio thread;
boolean doStop = false;
int musicLength = 4096;
byte[] music;
Socket socket;
short[] buffer = new short[4096];
float[] fmusic = new float[1024];


public PCMSocket()
{

}

public void start()
{
    doStop = false;
    new GetStream().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

private class GetStream extends AsyncTask<Void, Void, Void> {

    @Override
    protected Void doInBackground(Void... values) { 
        PCMSocket.this.getSocket();
        return null;

    }

    @Override
    protected void onPreExecute() {
    }



    @Override
    protected void onPostExecute(Void result)
    {
        return;
    }

    @Override
    protected void onProgressUpdate(Void... values) {
    }
}

private void getSocket()
{
    doStop=true;

    InetSocketAddress address = new InetSocketAddress("xxx.xxx.xxx.x", 8000);

    socket = new Socket();
    int timeout = 6000;   
    try {
        socket.connect(address, timeout);
    } catch (IOException e2) {
        e2.printStackTrace();
    }

     musicLength = 1024;

    InputStream is = null;

    try {
        is = socket.getInputStream();
    } catch (IOException e) {
        e.printStackTrace();
    }

    BufferedInputStream bis = new BufferedInputStream(is);
    DataInputStream dis = new DataInputStream(bis);     

    try{

    int minSize =AudioTrack.getMinBufferSize( 44100, AudioFormat.CHANNEL_CONFIGURATION_STEREO, AudioFormat.ENCODING_PCM_16BIT ); 

    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
            AudioFormat.CHANNEL_OUT_STEREO, 
            AudioFormat.ENCODING_PCM_16BIT, minSize,
            AudioTrack.MODE_STREAM);
        audioTrack.play();

      } catch (Throwable t)
      {
          t.printStackTrace();
        doStop = true;
      }

    thread = new WriteToAudio();
    thread.start();

    int i = 0;   
    int j=0;

    try {
        if(dis.available()>0)Log.d("PCMSocket", "receiving");
        music = new byte[4];
        while (dis.available() > 0)
        {
            music[i]=0;
            music[i] = dis.readByte(); 

            if(i==3)
            {
                int asInt = 0;
                asInt = ((music[0] & 0xFF) << 0) 
                        | ((music[1] & 0xFF) << 8) 
                        | ((music[2] & 0xFF) << 16) 
                        | ((music[3] & 0xFF) << 24);
                float asFloat = 0;
                asFloat = Float.intBitsToFloat(asInt);
                fmusic[j]=asFloat;
            }

            i++;
            j++;
            if(i==4)
            {
                music = new byte[4]; 
                i=0;
            }
            if(j==1023)
            {
                j=0;
                if(doStop)doStop=false;
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    try {
        dis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }  

}


public class WriteToAudio extends Thread
{       
    public void run()
    {
        while(true){
        while(!doStop)
        {           
            try{
                writeSamples(fmusic);

            }catch(Exception e)
            {
                e.printStackTrace();
            }    
            doStop = true;
        }
        }
    }
};


public void writeSamples(float[] samples) 
{   
   fillBuffer( samples );
   audioTrack.write( buffer, 0, samples.length );
}

private void fillBuffer( float[] samples )
{ 
   if( buffer.length < samples.length )
      buffer = new short[samples.length*4];

   for( int i = 0; i < samples.length; i++ )
   {
      buffer[i] = (short)(samples[i] * Short.MAX_VALUE);
   }
}   


}
CRABOLO
  • 8,605
  • 39
  • 41
  • 68
tritop
  • 1,655
  • 2
  • 18
  • 30
  • At first glance, this looks like it should work (it's not amazingly well designed, but there are no glaring functional problems), apart from the inefficiency in allocating a new byte array for each sample read. Some possible things to check: is the server using the same byte order (for each float), and are the floats all between -1 and 1? – user253751 Jun 03 '14 at 07:20
  • Yup, they are. I tested for the right endianess and I compared the floats I get after putting them in the floatarray with the ones I send, all between -1 and 1 as they should be. I know about the design thing, thats because I experimented a lot with that code. Of course it will get cleaned up before I use it. – tritop Jun 03 '14 at 07:23
  • +1 just for knowing what you're doing and checking obvious things first. I don't know what your problem is without running it, just some ideas of obvious things. That multithreading also looks suspicious, though. – user253751 Jun 03 '14 at 07:26
  • That multithreading was my idea for a errorsource too, but the double while should work according to my logs. It is called once one array is ready to be used. Since it is called immediatly, the array shouldn't be overwritten before it was sent to the writeSamples method. – tritop Jun 03 '14 at 07:31
  • Interesting. You could declare doStop volatile as it is used in two threads. Maybe some other vars also which are used by both. – greenapps Jun 03 '14 at 11:05
  • I should have thought of that. I don't think it will solve the problem, but still a very good point, thanks. – tritop Jun 03 '14 at 11:09
  • You have `audioTrack.play()` before you received anything. Couldn't you better start it after your first or some samples received? Also you create and start the second thread in the first one. I would take that out. Also the creating of the player outside a thread. – greenapps Jun 03 '14 at 11:14
  • After I started playing I go into that while loop. I guess I could just use another bool to start the player after the first sample has arrived, but I dont see the advantage? – tritop Jun 03 '14 at 11:22
  • `That multithreading also looks suspicious, though.`as immibis said. The second thread will consume one core totally or otherwise have a great impact on speed. You could just copy to buffer where you set `doStop=false` and then only start a thread to copy to the player. Or no thread and just copy/write there too. – greenapps Jun 03 '14 at 11:23
  • I don't know the advantage either but as you are looking into a 10 second delay something has to be tried. – greenapps Jun 03 '14 at 11:25
  • That are some nice ideas. I will clean up the threads, take a look at the second thread and report back, thanks. I'm not too sure about not letting audioTrack.write() run in a own thread though since its blocking. – tritop Jun 03 '14 at 11:35
  • Doesn't audiotrack want (short) integer samples, not floats? – Chris Stratton Jun 04 '14 at 18:18
  • @Chris It wants short or byte. I can't change the float array thats beeing sent, but since I know that a float is 4 bytes long I can take 4 bytes and cast one float out of them. Of course I have to cast them back before putting them into AudioTrack. The strange thing is that the sound of byte[] -> float[] -> short[] is the only one that at least remotly sound like the input. Taking the bytes I get from the socket and putting them immediatly to AudioTrack doesn't work, neither does casting shorts from that bytes directly. – tritop Jun 05 '14 at 07:14
  • @tritop Shouldn't you be reading the float integer representation in Big-Endian/network order? On your server, float->int results in a LE int but when written to a stream its normally in BE order (like in Java's DataOutputStream). – sergio91pt Jun 05 '14 at 10:29
  • Just to clear any doubts: Android expects the shorts from -amplitude to +amplitude (like in your code sample). The bytes are the same thing but in LE order (for PCM 16bits). – sergio91pt Jun 05 '14 at 10:35
  • It seems like it worked without changing the endianess. Kind of strange, but...since it works I wont complain. Thanks guys! – tritop Jun 05 '14 at 13:10

2 Answers2

10

Sooo...I just solved this only hours after I desperatly put bounty on it, but thats worth it.

I decided to start over. For the design thing with threads etc. I took some help from this awesome project, it helped me a lot. Now I use only one thread. It seems like the main point was the casting stuff, but I am not too sure, it also may have been the multithreading. I don't know what kind of bytes the byte[] constructor of AudioTracker expects, but certainly no float bytes. So I knew I need to use the short[] constructor. What I did was
-put the bytes in a byte[]
-take 4 of them and cast them to a float in a loop
-take each float and cast them to shorts

Since I already did that before, I am not too sure what the problem was. But now it works. I hope this can help someone who wents trough the same pain as me. Big thanks to all of you who participated and commented.

Edit: I just thought about the changes and figured that me using CHANNEL_CONFIGURATION_STEREO instead of MONO earlier has contributed a lot to the stuttering. So you might want to try that one first if you encounter this problem. Still for me it was only a part of the solution, changing just that didn't help.

    static final int frequency = 44100;
    static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
    static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
    boolean isPlaying;
    int playBufSize;
    Socket socket;
    AudioTrack audioTrack;

    playBufSize=AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, frequency, channelConfiguration, audioEncoding, playBufSize, AudioTrack.MODE_STREAM);

    new Thread() {
        byte[] buffer = new byte[4096];
        public void run() {
            try { 
                socket = new Socket(ip, port); 
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            audioTrack.play();
            isPlaying = true;
            while (isPlaying) {
                int readSize = 0;
                try { readSize = socket.getInputStream().read(buffer); }
                catch (Exception e) {
                    e.printStackTrace();
                }
                short[] sbuffer = new short[1024];
                for(int i = 0; i < buffer.length; i++)
                {

                    int asInt = 0;
                    asInt = ((buffer[i] & 0xFF) << 0) 
                            | ((buffer[i+1] & 0xFF) << 8) 
                            | ((buffer[i+2] & 0xFF) << 16) 
                            | ((buffer[i+3] & 0xFF) << 24);
                    float asFloat = 0;
                    asFloat = Float.intBitsToFloat(asInt);
                    int k=0;
                    try{k = i/4;}catch(Exception e){}
                    sbuffer[k] = (short)(asFloat * Short.MAX_VALUE);

                    i=i+3;
                }
                audioTrack.write(sbuffer, 0, sbuffer.length);
            }  
            audioTrack.stop();
            try { socket.close(); }
            catch (Exception e) { e.printStackTrace(); }
        }
    }.start();
tritop
  • 1,655
  • 2
  • 18
  • 30
1

Get rid of all, all, the available() tests. Just let your code block in the following read() statement(s). You don't have anything better to do anyway, and you're just burning potentially valuable CPU cycles by even trying to avoid the block.

EDIT To be specific:

    try {
        socket.connect(address, timeout);
    } catch (IOException e2) {
        e2.printStackTrace();
    }

Poor practice to catch this exception and allow the following code to continue as though it hadn't happened. The exception should be allowed to propagate to the caller.

    try {
        is = socket.getInputStream();
    } catch (IOException e) {
        e.printStackTrace();
    }

Ditto.

    try {
        if(dis.available()>0)Log.d("PCMSocket", "receiving");

Remove. You're receiving anyway.

        music = new byte[4];
        while (dis.available() > 0)

Pointless. Remove. The following reads will block.

        {
            music[i]=0;

Pointless. Remove.

            music[i] = dis.readByte(); 

            if(i==3)
            {
                int asInt = 0;
                asInt = ((music[0] & 0xFF) << 0) 
                        | ((music[1] & 0xFF) << 8) 
                        | ((music[2] & 0xFF) << 16) 
                        | ((music[3] & 0xFF) << 24);

This is all pointless. Replace it all with short asInt = dis.readInt();.

                float asFloat = 0;
                asFloat = Float.intBitsToFloat(asInt);

Given that the original conversion to short was via floatValue * Short.MAX_VALUE, this conversion should be asFloat = (float)asInt/Short.MAX_VALUE.

            if(i==4)

If i was 3 before it will be 4 now, so this test is also pointless.

                music = new byte[4]; 

You don't need to reallocate music. Remove.

    } catch (IOException e) {
        e.printStackTrace();
    }

See above. Pointless. The exception should be allowed to propagate to the caller.

    try {
        dis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }  

All this should be in a finally block.

    }
};

        while(true){
        while(!doStop)

You don't need both these loops.

            try{
                writeSamples(fmusic);
            }catch(Exception e)
            {
                e.printStackTrace();
            }

See above. Pointless. The exception should in this case terminate the loop, as any IOException writing to a socket is fatal to the connection. if( buffer.length < samples.length ) buffer = new short[samples.length];

Why isn't buffer already the right size? Alternatively, what if buffer.length > samples.length?

user207421
  • 305,947
  • 44
  • 307
  • 483