2012-10-05 27 views
6

Wykonuję aplikację Android-to-Android VoIP (głośnik) przy użyciu jej klasy AudioRecord i AudioTrack, a także Speex poprzez NDK do anulowania echa. Udało mi się skutecznie przekazać i pobrać dane z funkcji speex_echo_cancellation() Speexa, ale echo pozostaje.Konfiguracja eliminacji echa Speexa

Oto istotne android kod wątek, który nagrywa/wysyłanie i odbieranie/Odtwarzanie 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 

} 

Oto odpowiedni kod 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; 
} 

Są kod działa poprawnie, a dane audio są zdecydowanie wysyłane/odbierane/przetwarzane/odtwarzane z Androida na Androida. Biorąc pod uwagę częstotliwość próbkowania audio 8000 Hz i rozmiar pakietu 2000 bajtów/1000 krotności, odkryłem, że wymagany jest rozmiar ramki wynoszący 1000, aby odtwarzany dźwięk był płynny. Większość wartości filtra Długość (również długość ogona zgodnie z dokumentem Speex) będzie działać, ale wydaje się nie mieć wpływu na usuwanie echa.

Czy ktoś rozumie wystarczającą ilość AEC, aby dostarczyć mi wskazówek dotyczących implementacji lub konfiguracji Speex? Dziękuje za przeczytanie.

+0

Też mam podobny problem. Czy masz jakieś rozwiązanie swojego problemu? – aProgrammer

+0

Witam, czy znalazłeś rozwiązanie tego problemu? Dzięki – SoH

Odpowiedz

2

Twój kod ma rację, ale czegoś brakuje w kodach rodzimych, I zmodyfikowane metody init, i dodał SPEEX Preprocesuj po echa, a następnie kod działa dobrze (próbowałem w oknach) Oto natywnego kodu

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

Możesz znaleźć użyteczne próbki, takie jak kodowanie, dekodowanie, anulowanie echa w źródle biblioteki speex (http://www.speex.org/downloads/)

+0

błąd: EchoCanceller_jniHeader.h: Brak takiego pliku lub katalogu – EvilThinker

2

Czy prawidłowo wyrównywasz sygnał dalekiego końca (co nazywasz recv) i blisko sygnału końcowego (co nazywasz nagrywaniem)? Zawsze występuje opóźnienie odtwarzania/nagrywania, które należy uwzględnić. Zwykle wymaga to buforowania sygnału dalekiego końca w buforze pierścieniowym przez pewien określony czas. Na komputerach zazwyczaj wynosi około 50 - 120 ms. Na Androidzie podejrzewam, że jest znacznie wyższy. Prawdopodobnie w zakresie 150 - 400ms. Polecam używanie taśmy o długości 100ms z funkcją speex i dostosowywanie rozmiaru twojego odległego bufora do momentu, gdy zbierze się AEC. Zmiany te powinny umożliwiać konwergencję AEC, niezależnie od włączenia preprocesora, co nie jest tutaj wymagane.

Powiązane problemy