Передача аудио через 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
?