2012-10-05 15 views
6

Ich mache eine Android-zu-Android-VoIP (Lautsprecher) -App mit seiner AudioRecord und AudioTrack-Klasse, zusammen mit Speex über NDK Echo-Aufhebung zu tun. Ich konnte erfolgreich Daten von Speex speex_echo_cancellation() - Funktion übergeben und abrufen, aber das Echo bleibt. HierSpeex Echokompensation Konfiguration

ist der entsprechende Android-Thread-Code, ist die Aufnahme/Senden und Empfangen/Wiedergabe von Audio:

//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 

} 

Hier ist der relevante NDK/JNI Code:

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

Diese Code läuft gut und Audiodaten werden definitiv gesendet/empfangen/verarbeitet/von android-to-android abgespielt. Bei einer Audio-Abtastrate von 8000 Hz und einer Paketgröße von 2000 Bytes/1000 shorts habe ich festgestellt, dass eine frameSize von 1000 benötigt wird, damit das abgespielte Audio flüssig ist. Der meiste Wert von filterLength (aka Tail Length gemäß Speex doc) wird ausgeführt, scheint aber keine Auswirkungen auf die Echoentfernung zu haben.

Versteht jemand genug AEC, um mir einige Hinweise zur Implementierung oder Konfiguration von Speex zu geben? Danke fürs Lesen.

+0

Ich habe auch ähnliches Problem. Hast du irgendeine Lösung für dein Problem? – aProgrammer

+0

Hi hast du eine Lösung für das Problem gefunden? Danke – SoH

Antwort

2

Ihr Code ist richtig, aber etwas in nativen Codes fehlt, ich modifizierte init-Methode und das hinzugefügt speex Vorprozess nach Echo Cancellation, dann der Code hat gut funktioniert (Ich habe versucht, in den Fenstern) Hier ist systemeigenen Code

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

Sie können nützliche Beispiele wie Kodierung, Dekodierung, Echounterdrückung in speex Bibliotheksquelle finden (http://www.speex.org/downloads/)

+0

Fehler: EchoCanceller_jniHeader.h: Keine solche Datei oder Verzeichnis – EvilThinker

2

Sind Sie richtig ausgerichtet das Fernende-Signal (was Sie recv nennen) und nahe Ende-Signal (was Sie nennen, Rekord)? Es gibt immer eine gewisse Wiedergabe-/Aufzeichnungslatenz, die berücksichtigt werden muss. Dies erfordert im Allgemeinen eine Pufferung des Signals des fernen Endes in einem Ringpuffer für eine bestimmte Zeitperiode. Auf PCs sind dies normalerweise 50 - 120 ms. Auf Android vermute ich, dass es viel höher ist. Wahrscheinlich im Bereich von 150 - 400ms. Ich würde empfehlen, eine 100ms taillength mit speex zu verwenden und die Größe Ihres far-end-Puffers anzupassen, bis die AEC konvergiert. Diese Änderungen sollten ermöglichen, dass die AEC konvergieren kann, unabhängig von der Einbeziehung des Präprozessors, die hier nicht benötigt wird.