Передача аудио через TCP-сокет на Android

Я передаю микрофонный вход с сервера C через сокет. Я знаю, что поток работает, потому что он работает с клиентом C, и я получаю правильные значения на своем Android-клиенте.

Я передаю 1024 floatarray. Один поплавок - 4 байта. Поэтому я получил входящий поток с 4096 байтами на кадр. Я получаю поплавки из этих байтов, и я знаю, что эти поплавки - это те, которые я отправил, чтобы эта часть работала.

Теперь я хочу получить этот поток непосредственно на динамики телефонов, используя AudioTrack. Я попытался ввести байты, которые я получил напрямую: просто шум. Я попытался вернуть его в массив байтов, все тот же. Я попытался сделать это плавающим на короткое (потому что AudioTrack берет байты или короткие). Я мог бы получить что-то, что могло бы быть моим микрофонным входом (стуком), но очень колючим и очень лаконичным. Я бы понял, было ли отставание между кадрами, но я не могу даже получить один ясный звук. Тем не менее, я могу выпустить звук греха, который я создаю локально и помещаю в этот shortarray. Теперь я задаюсь вопросом, есть ли у меня некоторые проблемы в моем коде, которые вы видите, потому что я их не вижу.

Что я делаю: я помещаю 4 байта в массив байтов. Я получаю поплавок. Как только я получаю один фрейм в моем массиве с плавающей точкой (я контролирую это с помощью bool, не очень хорошо, но он должен работать) Я положил его в свой shortarray и позволил audiotrack воспроизводить его. Это двойное кастинг может быть медленным, но я делаю это, потому что его ближайший я получил, чтобы играть фактический ввод.

Edit: Я проверил endianess, сравнив поплавки, у них есть правильные значения между -1 и 1 и те же, что и я. Поскольку я не изменяю endianess при кастинге для float, я не понимаю, почему пересылка массива 4096 байт в AudioTrack напрямую не работает. В многопоточности может быть что-то не так, но я не понимаю, что это может быть.

Изменить 2:. Я обнаружил небольшую проблему - я reset j в 1023. Но это отсутствие плавающей точки не должно было быть проблемой. То, что я сделал, кроме этого, заключалось в том, чтобы поместить метод, который взял поток из сокета в другой поток, вместо того, чтобы называть его в async-задаче. Это заставило его работать, теперь я могу понять звуки микрофона. Тем не менее качество очень плохое - может быть причина для этого в коде? Также я получил задержку около 10 секунд. Только около половины секунды вызвано WLAN, поэтому я задаюсь вопросом, может ли это быть ошибкой кода. Любые дальнейшие мысли оцениваются.

Редактировать 3: Я играл с кодом и реализовал в комментариях несколько идей greenapps. С новой структурой потоков я столкнулся с проблемой не получения звука. Вроде бы. Я не понимаю, как это возможно, поэтому я переключился обратно. Другие вещи, которые я пытался сделать, чтобы потоки были более легкими, не имели никакого эффекта. У меня задержка, и у меня очень плохое качество (я могу определить удары, но я не могу понять голоса). Я понял, что что-то может быть неправильно с моими конвертированиями, поэтому я помещаю байты, которые я получаю из сокета прямо в AudioTrack - ничего, кроме уродливого пульсирующего статического шума. Теперь я еще более смущен, так как этот точный поток по-прежнему работает с клиентом C. Я верну отчет, если найду решение, но все равно приветствуем любую помощь.

Изменить 4. Я должен добавить, что я могу воспроизводить микрофонные входы из другого приложения для Android, где я отправляю этот вход напрямую в виде байтов (я бы исключил материал для создания плавающих элементов и поместил байты, которые я получаю напрямую audioTrack в моем коде игрока).
Также мне показалось, что это может быть проблемой, что упомянутый floatarray, который передается сервером C, поступает с 64-битной машины, а телефон 32 бит. Может быть, это проблема, так или иначе, хотя я просто потоки с плавающей запятой, как 4 байта? Или, еще одна моя мысль: базовый формат чисел байтов, которые я получаю, является float. Какой формат ожидает AudioTrack? Даже если положить только байты - мне нужно было бы сместить этот float в int и отбросить его обратно в байты или что-то в этом роде?

новый код:

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


}

старый код:

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


}

Ответы

Ответ 1

Sooo... Я просто решил это только через несколько часов после того, как я отчаянно положил на него щедрость, но это того стоит.

Я решил начать все сначала. Для дизайна с потоками и т.д. Я немного помог этому удивительному проекту, это очень помогло мне. Теперь я использую только один поток. Похоже, что основное внимание уделялось кастингу, но я не слишком уверен, возможно, это и многопоточность. Я не знаю, какие байты байта [] конструктора AudioTracker ожидает, но, конечно, нет байтов с плавающей запятой. Поэтому я знал, что мне нужно использовать короткий конструктор []. То, что я сделал, было -путь байтов в байте []
-изложите 4 из них и отбросите их в поплавок в петле
-принять каждый поплавок и бросить их в шорты

Поскольку я уже делал это раньше, я не уверен, в чем проблема. Но теперь это работает. Надеюсь, это поможет кому-то, кто чувствует такую ​​же боль, как и я. Большое спасибо всем вам, кто участвовал и комментировал.

Изменить: Я просто подумал об изменениях и подумал, что я использую CHANNEL_CONFIGURATION_STEREO вместо MONO ранее, внесли большой вклад в заикание. Поэтому сначала вы можете попробовать это, если столкнулись с этой проблемой. Тем не менее для меня это была лишь часть решения, меняя только то, что не помогло.

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

Ответ 2

Избавьтесь от всех, всех, тестов available(). Просто позвольте вашему блоку кода в следующих read() операторах. В любом случае вам нечего делать, и вы просто сжигаете потенциально ценные циклы процессора, даже пытаясь избежать блокировки.

EDIT. Конкретно:

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

Плохая практика, чтобы поймать это исключение и разрешить следующий код продолжать, как будто этого не произошло. Исключение должно быть разрешено для распространения вызывающему.

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

То же.

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

Это все бессмысленно. Замените все на short asInt = dis.readFShort();.

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

Учитывая, что первоначальное преобразование в short было через floatValue * Short.MAX_VALUE, это преобразование должно быть asFloat = (float)asInt/Short.MAX_VALUE.

            if(i==4)

Если i было 3 до 4 теперь, поэтому этот тест также бессмыслен.

                music = new byte[4]; 

Вам не нужно перераспределять music. Удалить.

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

См. выше. Бессмысленно. Исключение должно быть разрешено для распространения вызывающему.

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

Все это должно быть в блоке finally.

    }
};

        while(true){
        while(!doStop)

Вам не нужны обе эти петли.

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

См. выше. Бессмысленно. Исключение должно в этом случае завершать цикл, поскольку любая запись IOException в сокет является фатальной для соединения.      if (buffer.length < samples.length)         buffer = new short [samples.length];

Почему не buffer уже правильный размер? Альтернативно, что, если buffer.length > samples.length?