2012-10-05 83 views
6

我正在使用AudioRecord和AudioTrack類與Speex一起通過NDK製作Android到Android的VoIP(揚聲器)應用程序,以便進行回聲消除。我能夠成功傳入並從Speex的speex_echo_cancellation()函數中檢索數據,但回聲仍然存在。Speex迴音消除配置

這裏是記錄/發送和接收/播放音頻的有關Android的線程代碼:

//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-to-android發送/接收/處理/播放的。假設音頻採樣率爲8000Hz,數據包大小爲2000bytes/1000shorts,我發現需要frameSize爲1000,以使播放的音頻平滑。 filterLength的大部分值(即根據Speex doc的尾長)將運行,但似乎對回聲消除沒有影響。

是否有人瞭解足夠的AEC,爲我提供了一些關於實施或配置Speex的指導?謝謝閱讀。

+0

我也有類似的問題。你有問題的解決方案嗎? – aProgrammer

+0

你確實找到了解決問題的辦法嗎?謝謝 – SoH

回答

2

你的代碼是正確的,但缺少本地代碼什麼的,我修改了init方法和回聲消除後添加Speex語音預處理,然後你的代碼行之有效(我試着在窗) 下面是本機代碼

#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/)中找到有用的示例,例如Encoding,Decoding,Echo Cancellation

+0

錯誤:EchoCanceller_jniHeader.h:沒有這樣的文件或目錄 – EvilThinker

2

您是否正確對齊了遠端信號和近端信號(你稱之爲記錄)?總會有一些需要考慮的回放/記錄延遲。這通常需要在特定的一段時間內緩衝環形緩衝器中的遠端信號。在個人電腦上這通常是50 - 120毫秒。在Android上我懷疑它要高得多。可能在150 - 400毫秒的範圍內。我建議使用speex和100ms taillength並調整遠端緩衝區的大小,直到AEC收斂。這些改變應該允許AEC匯聚,而不受包含預處理器的影響,這在此不是必需的。