Проблемы с эхоподавлением Android Speex
У меня есть базовая аудиозапись-аудиотрек, пакеты голосового чата udp между двумя устройствами Android. Это работает, но у меня плохое эхо. Я пытаюсь удалить эхо, используя Speex, портированный на Android от JNI. Speex, который я импортировал, работает, но эхоподавление - нет. Родной C-код:
#include <jni.h>
#include <speex/speex_echo.h>
#define FRAME_SIZE 256
#define FILTER_LENGTH 800
SpeexEchoState *echoState;
// Initialization of echo cancelation
void Java_telefonie_voip_VoIPActivity_InitEcho(JNIEnv * env, jobject jobj)
{
echoState = speex_echo_state_init(FRAME_SIZE, FILTER_LENGTH);
}
// Queue the frame to soundcard for playing (receiving)
void Java_telefonie_voip_VoIPActivity_Playback(JNIEnv * env, jobject jobj, jshortArray inputFrame)
{
speex_echo_playback(echoState, inputFrame);
}
jshortArray Java_telefonie_voip_VoIPActivity_Capture(JNIEnv * env, jobject jobj, jshortArray inputFrame)
{
jshortArray outputFrame;
speex_echo_capture(echoState, inputFrame, *&outputFrame);
return outputFrame;
}
// Destroing echo cancelation
void Java_telefonie_voip_VoIPActivity_DestroyEcho(JNIEnv * env, jobject jobj)
{
speex_echo_state_destroy(echoState);
}
И некоторые из важных Java-кодов:
native void InitEcho();
native void DestroyEcho();
native void Playback(short[] inputData); // listener/receiving
native short[] Capture(short[] inputData); // recorder/sender
static {
System.loadLibrary("speex");
}
public void Initialize ()
{
minBufferSize = 4096;
try
{
InitEcho();
}
catch (Exception e){Log.e(TAG, "jni " + e.getMessage());}
}
public void startRecording ()
{
isStreaming = true;
streamingThread = new Thread(new Runnable(){
@Override
public void run ()
{
try
{
audioRecorder = new AudioRecord(
MediaRecorder.AudioSource.MIC,
sampleRate,
channelConfig,
audioFormat,
minBufferSize*10
);
buffer = new short[minBufferSize];
audioRecorder.startRecording();
while(isStreaming)
{
sendPackets++;
minBufferSize = audioRecorder.read(buffer, 0, buffer.length);
// Echo cancelling
buffer = Capture(buffer);
// Send
dataPacket = new DatagramPacket(ShortToByteArray(buffer), buffer.length*2, receiverAddressIA, serverPort1);
dataSocket.send(dataPacket);
}
}
catch(Exception e)
{
Log.e(TAG, "2" + e.getMessage());
}
}
});
streamingThread.start();
}
public void startListen ()
{
isListening = true;
receivingThread = new Thread(new Runnable(){
@Override
public void run ()
{
try
{
audioTrack = new AudioTrack(
AudioManager.STREAM_VOICE_CALL,
sampleRate,
channelConfig,
audioFormat,
minBufferSize*10,
AudioTrack.MODE_STREAM
);
audioTrack.play();
buffer2 = new short[minBufferSize];
while (isListening)
{
receivedPackets++;
dataPacket2 = new DatagramPacket(ShortToByteArray(buffer2), buffer2.length*2);
dataSocket2.receive(dataPacket2);
// Cancel echo
Playback(buffer2);
// Queue to soundcard
audioTrack.write(buffer2, 0, minBufferSize);
}
}
catch(Exception e)
{
Log.e(TAG, "3" + e.getMessage());
}
}
});
receivingThread.start();
}
public short[] ByteToShortArray(byte[] byteArray)
{
short[] shortArray = new short[byteArray.length/2];
ByteBuffer.wrap(byteArray).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(shortArray);
return shortArray;
}
Проблема в том, что когда я запускаю поток записи / потоковой передачи, он показывает, что отправляет один пакет, а затем приложение вылетает без сообщения. У вас есть предложения или советы? Пожалуйста, помогите мне, потому что мне нужно сделать этот проект как можно скорее, и я много работал и документировал сам, но все же он не хочет работать хорошо. Спасибо!
Изменить: я только что обнаружил, что
// Echo cancelling
buffer = Capture(buffer);
вызывает сбой.
1 ответ
ОБНОВЛЕНИЕ: Относительно сбоя - это из-за неправильного использования JNI. Вы не можете рассматривать jshortarray как указатель Си. Вам необходимо вызвать соответствующие функции JNI, чтобы преобразовать его в указатель C (GetShortArrayElements и т. Д.).
Что касается эхоподавления - вы в настоящее время используете внутренний буфер, который составляет около 2 кадров, и этого может быть недостаточно для вас. Переключение контекста в Android не такое, как на ПК, и вы можете получить 4 последовательных кадра воспроизведения -> 4 последовательных записанных кадра -> и так далее. Таким образом, вы теряете кадры таким образом.
Кроме того, потоковое аудио в Android имеет большую задержку, чем ПК, поэтому 2-кадрового буфера недостаточно. Вам необходимо реализовать свою собственную буферизацию и поиграть с количеством кадров, на которое вы задерживаетесь, так что эхоподавитель видит эхо после воспроизведения внутри границ его длины фильтра.
Кроме этого, лучший способ проанализировать и отладить эхоподавитель: 1. На самом деле понять, как он работает на высоком уровне и каковы требования - не ракетостроение, вы можете найти все в официальных документах Speex. 2. Используйте предоставленный инструмент echo_diagnostic и изучите потоки в таком инструменте, как Audacity, чтобы увидеть, где это пошло не так.
Speex AEC работает очень хорошо при правильном использовании, и я уже сделал 3 различных проекта, используя его, так что это просто вопрос исправления вашей реализации.