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

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

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

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

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

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

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

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

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

новый код:

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


}

person tritop    schedule 03.06.2014    source источник
comment
На первый взгляд это выглядит так, как будто оно должно работать (это не удивительно хорошо спроектировано, но в нем нет вопиющих функциональных проблем), если не считать неэффективного выделения нового массива байтов для каждого чтения выборки. Некоторые возможные вещи для проверки: использует ли сервер один и тот же порядок байтов (для каждого числа с плавающей точкой) и все ли числа с плавающей запятой находятся в диапазоне от -1 до 1?   -  person user253751    schedule 03.06.2014
comment
Да, они есть. Я проверил правильность порядка байтов и сравнил числа с плавающей запятой, которые я получаю после помещения их в floatarray, с теми, которые я отправляю, все между -1 и 1, как и должно быть. Я знаю о дизайне, потому что много экспериментировал с этим кодом. Конечно, он будет очищен, прежде чем я его использую.   -  person tritop    schedule 03.06.2014
comment
+1 просто за то, что вы знаете, что делаете, и сначала проверяете очевидные вещи. Я не знаю, в чем ваша проблема, не запуская ее, просто некоторые идеи об очевидных вещах. Однако эта многопоточность также выглядит подозрительно.   -  person user253751    schedule 03.06.2014
comment
Эта многопоточность была и моей идеей для источника ошибок, но двойное время должно работать согласно моим журналам. Он вызывается, когда один массив готов к использованию. Поскольку он вызывается немедленно, массив не должен быть перезаписан до того, как он будет отправлен в метод writeSamples.   -  person tritop    schedule 03.06.2014
comment
Интересный. Вы можете объявить doStop volatile, поскольку он используется в двух потоках. Возможно, некоторые другие вары также используются обоими.   -  person greenapps    schedule 03.06.2014
comment
Я должен был подумать об этом. Я не думаю, что это решит проблему, но все же очень хороший момент, спасибо.   -  person tritop    schedule 03.06.2014
comment
У вас есть audioTrack.play() до того, как вы что-то получили. Не могли бы вы начать это после получения первых или нескольких образцов? Также вы создаете и запускаете второй поток в первом. Я бы это убрал. Также создание плеера вне потока.   -  person greenapps    schedule 03.06.2014
comment
После того, как я начал играть, я вхожу в цикл while. Думаю, я мог бы просто использовать другое логическое значение, чтобы запустить проигрыватель после получения первого образца, но я не вижу преимущества?   -  person tritop    schedule 03.06.2014
comment
That multithreading also looks suspicious, though.как сказал иммибис. Второй поток полностью потребляет одно ядро ​​или иным образом сильно влияет на скорость. Вы можете просто скопировать в буфер, где вы установили doStop=false, а затем только запустить поток для копирования в плеер. Или нет темы и просто скопировать/написать туда же.   -  person greenapps    schedule 03.06.2014
comment
Я тоже не знаю преимущества, но, поскольку вы ищете 10-секундную задержку, нужно что-то попробовать.   -  person greenapps    schedule 03.06.2014
comment
Это хорошие идеи. Я почищу темы, взгляну на вторую тему и отчитаюсь, спасибо. Я не слишком уверен в том, что не позволю audioTrack.write() запускаться в собственном потоке, поскольку он заблокирован.   -  person tritop    schedule 03.06.2014
comment
Разве аудиотреку не нужны (короткие) целочисленные образцы, а не числа с плавающей запятой?   -  person Chris Stratton    schedule 04.06.2014
comment
@Chris Он хочет короткий или байтовый. Я не могу изменить отправляемый массив с плавающей запятой, но поскольку я знаю, что длина числа с плавающей запятой составляет 4 байта, я могу взять 4 байта и вывести из них одно число с плавающей запятой. Конечно, я должен вернуть их обратно, прежде чем поместить в AudioTrack. Странно то, что звук byte[] -> float[] -> short[] — единственный, который хотя бы отдаленно звучит как ввод. Взятие байтов, которые я получаю из сокета, и немедленное помещение их в AudioTrack не работает, равно как и прямое использование коротких замыканий из этих байтов.   -  person tritop    schedule 05.06.2014
comment
@tritop Разве вы не должны читать целочисленное представление с плавающей запятой в порядке Big-Endian / network? На вашем сервере float->int приводит к LE int, но при записи в поток он обычно находится в порядке BE (как в Java DataOutputStream).   -  person sergio91pt    schedule 05.06.2014
comment
Просто чтобы развеять любые сомнения: Android ожидает короткие строки от -амплитуды до +амплитуды (как в вашем примере кода). Байты такие же, но в порядке LE (для PCM 16 бит).   -  person sergio91pt    schedule 05.06.2014
comment
Похоже, это сработало без изменения endianess. Немного странно, но... так как это работает, я не буду жаловаться. Спасибо, парни!   -  person tritop    schedule 05.06.2014


Ответы (2)


Тааак... Я только что решил эту проблему всего через несколько часов после того, как отчаянно назначил за нее награду, но это того стоит.

Я решил начать заново. Что касается дизайна с потоками и т. д. Мне помог этот замечательный проект, он мне очень помог. Сейчас использую только одну нить. Кажется, что главное было в кастинге, но я не уверен, возможно, дело было и в многопоточности. Я не знаю, какие байты ожидает конструктор byte[] AudioTracker, но уж точно не плавающие байты. Итак, я знал, что мне нужно использовать конструктор short[]. Я сделал следующее:
-поместил байты в byte[]
-взял 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();
person tritop    schedule 05.06.2014

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

ИЗМЕНИТЬ Чтобы быть точным:

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

                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 запись в сокет фатальна для соединения. если (буфер.длина ‹ выборки.длина) буфер = новый короткий[выборки.длина];

Почему buffer уже не подходит? В качестве альтернативы, что если buffer.length > samples.length?

person user207421    schedule 12.06.2014