Intereting Posts
Значок приложения Android в строке состояния Как EasyEditSpan используется в Android для частичного редактирования текста? Как получить доступ к файловой системе другого телефона с помощью bluetooth в Android? Резервное копирование Adb не работает TextView: shadowDx / Dy / Radius in dip? Android: java.lang.IllegalAccessException при попытке использовать пользовательский класс «Приложение» Как измерить наклон телефона в плоскости XY с помощью акселерометра в Android Getbluetoothservice (), вызываемый без bluetoothmanagercallback Изменение цвета текста TimePicker Приложение не настроено: это приложение все еще находится в режиме разработки Как сервис возвращает результат активности Самый безопасный способ использования SharedPreferences Android воспроизводит аудиофайл во время телефонного звонка Отключение навигационного ящика, включение кнопки «вверх» / «вверх-вниз» в фрагментах Что может вызвать ошибку socket () «Permission denied»?

Конфигурация эхоподавления Speex

Я делаю приложение Android-to-Android VoIP (громкоговоритель), используя его класс AudioRecord и AudioTrack, а также Speex через NDK для эхоподавления. Мне удалось успешно передать данные из функции Speex speex_echo_cancellation () и получить их, но эхо остается.

Вот соответствующий код потока андроида, который записывает / передает и получает / воспроизводит аудио:

//constructor public MyThread(DatagramSocket socket, int frameSize, int filterLength){ this.socket = socket; nativeMethod_initEchoState(frameSize, filterLength); } public void run(){ short[] audioShorts, recvShorts, recordedShorts, filteredShorts; byte[] audioBytes, recvBytes; int shortsRead; DatagramPacket packet; //initialize recorder and player int samplingRate = 8000; int managerBufferSize = 2000; AudioTrack player = new AudioTrack(AudioManager.STREAM_MUSIC, samplingRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, managerBufferSize, AudioTrack.MODE_STREAM); recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, managerBufferSize); recorder.startRecording(); player.play(); //record first packet audioShorts = new short[1000]; shortsRead = recorder.read(audioShorts, 0, audioShorts.length); //convert shorts to bytes to send audioBytes = new byte[shortsRead*2]; ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(audioShorts); //send bytes packet = new DatagramPacket(audioBytes, audioBytes.length); socket.send(packet); while (!this.isInterrupted()){ //recieve packet/bytes (received audio data should have echo cancelled already) recvBytes = new byte[2000]; packet = new DatagramPacket(recvBytes, recvBytes.length); socket.receive(packet); //convert bytes to shorts recvShorts = new short[packet.getLength()/2]; ByteBuffer.wrap(packet.getData(), 0, packet.getLength()).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(recvShorts); //play shorts player.write(recvShorts, 0, recvShorts.length); //record shorts recordedShorts = new short[1000]; shortsRead = recorder.read(recordedShorts, 0, recordedShorts.length); //send played and recorded shorts into speex, //returning audio data with the echo removed filteredShorts = nativeMethod_speexEchoCancel(recordedShorts, recvShorts); //convert filtered shorts to bytes audioBytes = new byte[shortsRead*2]; ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(filteredShorts); //send off bytes packet = new DatagramPacket(audioBytes, audioBytes.length); socket.send(packet); }//end of while loop } 

Вот соответствующий код NDK / JNI:

 void nativeMethod_initEchoState(JNIEnv *env, jobject jobj, jint frameSize, jint filterLength){ echo_state = speex_echo_state_init(frameSize, filterLength); } jshortArray nativeMethod_speexEchoCancel(JNIEnv *env, jobject jObj, jshortArray input_frame, jshortArray echo_frame){ //create native shorts from java shorts jshort *native_input_frame = (*env)->GetShortArrayElements(env, input_frame, NULL); jshort *native_echo_frame = (*env)->GetShortArrayElements(env, echo_frame, NULL); //allocate memory for output data jint length = (*env)->GetArrayLength(env, input_frame); jshortArray temp = (*env)->NewShortArray(env, length); jshort *native_output_frame = (*env)->GetShortArrayElements(env, temp, 0); //call echo cancellation speex_echo_cancellation(echo_state, native_input_frame, native_echo_frame, native_output_frame); //convert native output to java layer output jshortArray output_shorts = (*env)->NewShortArray(env, length); (*env)->SetShortArrayRegion(env, output_shorts, 0, length, native_output_frame); //cleanup and return (*env)->ReleaseShortArrayElements(env, input_frame, native_input_frame, 0); (*env)->ReleaseShortArrayElements(env, echo_frame, native_echo_frame, 0); (*env)->ReleaseShortArrayElements(env, temp, native_output_frame, 0); return output_shorts; } 

Этот код работает нормально, а аудиоданные определенно отправляются / принимаются / обрабатываются / воспроизводятся с android-and-android. Учитывая, что частота дискретизации 8000 Гц и размер пакета в 2000 байт / 1000 снимков, я обнаружил, что для того, чтобы воспроизводимое аудио было плавным, требуется размер кадра 1000. Большинство значений filterLength (или длина хвоста в соответствии с Speex doc) будут работать, но, похоже, не влияют на удаление эха.

Кто-нибудь понимает достаточно AEC, чтобы предоставить мне некоторые рекомендации по внедрению или настройке Speex? Спасибо за прочтение.

Solutions Collecting From Web of "Конфигурация эхоподавления Speex"

Ваш код прав, но что-то не хватает в собственных кодах, я модифицировал метод init и добавил препроцесс speex после отмены эха, тогда ваш код работал хорошо (я пытался в Windows). Вот собственный код

 #include <jni.h> #include "speex/speex_echo.h" #include "speex/speex_preprocess.h" #include "EchoCanceller_jniHeader.h" SpeexEchoState *st; SpeexPreprocessState *den; JNIEXPORT void JNICALL Java_speex_EchoCanceller_open (JNIEnv *env, jobject jObj, jint jSampleRate, jint jBufSize, jint jTotalSize) { //init int sampleRate=jSampleRate; st = speex_echo_state_init(jBufSize, jTotalSize); den = speex_preprocess_state_init(jBufSize, sampleRate); speex_echo_ctl(st, SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate); speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_ECHO_STATE, st); } JNIEXPORT jshortArray JNICALL Java_speex_EchoCanceller_process (JNIEnv * env, jobject jObj, jshortArray input_frame, jshortArray echo_frame) { //create native shorts from java shorts jshort *native_input_frame = (*env)->GetShortArrayElements(env, input_frame, NULL); jshort *native_echo_frame = (*env)->GetShortArrayElements(env, echo_frame, NULL); //allocate memory for output data jint length = (*env)->GetArrayLength(env, input_frame); jshortArray temp = (*env)->NewShortArray(env, length); jshort *native_output_frame = (*env)->GetShortArrayElements(env, temp, 0); //call echo cancellation speex_echo_cancellation(st, native_input_frame, native_echo_frame, native_output_frame); //preprocess output frame speex_preprocess_run(den, native_output_frame); //convert native output to java layer output jshortArray output_shorts = (*env)->NewShortArray(env, length); (*env)->SetShortArrayRegion(env, output_shorts, 0, length, native_output_frame); //cleanup and return (*env)->ReleaseShortArrayElements(env, input_frame, native_input_frame, 0); (*env)->ReleaseShortArrayElements(env, echo_frame, native_echo_frame, 0); (*env)->ReleaseShortArrayElements(env, temp, native_output_frame, 0); return output_shorts; } JNIEXPORT void JNICALL Java_speex_EchoCanceller_close (JNIEnv *env, jobject jObj) { //close speex_echo_state_destroy(st); speex_preprocess_state_destroy(den); } 

Вы можете найти полезные примеры, такие как кодирование, декодирование, эхоподавление в источнике библиотеки speex (http://www.speex.org/downloads/)

Правильно ли вы совмещаете сигнал дальнего конца (то, что вы называете recv) и сигнал ближнего конца (что вы называете записью)? Всегда есть некоторое время ожидания воспроизведения / записи, которое необходимо учитывать. Обычно это требует буферизации сигнала дальнего конца в кольцевом буфере в течение определенного периода времени. На ПК это обычно около 50 – 120 мс. На Android я подозреваю, что это намного выше. Вероятно, в диапазоне от 150 до 400 мс. Я бы рекомендовал использовать 100 мс taillength с speex и регулировать размер вашего дальнего буфера до тех пор, пока AEC не сходится. Эти изменения должны позволить AEC сходиться независимо от включения препроцессора, что здесь не требуется.