android 用speex做迴音消除

語言: CN / TW / HK

Android迴音消除困擾了我將近一個星期終於解決了。

專案一邊是使用Java FX的PC端,一邊是Android裝置進行實時語言通話。

廢話不多說直接上程式碼。 speex_jni.cpp

#include <jni.h>
#include <string.h>
#include <unistd.h>

#include <speex/speex.h>
#include <speex/speex_preprocess.h>
#include <speex/speex_echo.h>
#include<Android/log.h>

SpeexEchoState *st;
SpeexPreprocessState *den;
extern "C"
JNIEXPORT void JNICALL Java_com_e_voice_audio_Speex_open
        (JNIEnv *env, jobject jObj, jint jSampleRate, jint jBufSize, jint jTotalSize)
{
    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);
    speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_DENOISE, st);
    speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_DEREVERB, st);


}
extern "C"
JNIEXPORT jshortArray JNICALL Java_com_e_voice_audio_Speex_process
        (JNIEnv * env, jobject jObj, jshortArray input_frame, jshortArray echo_frame)
{

    //create native shorts from java shorts
    jshort *native_input_frame = env->GetShortArrayElements(input_frame, 0);
    jshort *native_echo_frame = env->GetShortArrayElements(echo_frame, 0);

    //allocate memory for output data
    jint length = env->GetArrayLength(input_frame);
    jshortArray temp = env->NewShortArray(length);
    jshort *native_output_frame = env->GetShortArrayElements(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(length);
    env->SetShortArrayRegion(output_shorts, 0, length, native_output_frame);

    //cleanup and return
    env->ReleaseShortArrayElements(input_frame, native_input_frame, 0);
    env->ReleaseShortArrayElements(echo_frame, native_echo_frame, 0);
    env->ReleaseShortArrayElements(temp, native_output_frame, 0);

    return output_shorts;
}

extern "C"
JNIEXPORT void JNICALL Java_com_e_voice_audio_Speex_playback
        (JNIEnv *env, jobject jObj, jshortArray echo_frame)
{
    jshort *native_echo_frame = env->GetShortArrayElements(echo_frame, 0);
    speex_echo_playback(st, native_echo_frame);
    env->ReleaseShortArrayElements(echo_frame, native_echo_frame, 0);
}
extern "C"
JNIEXPORT jshortArray JNICALL Java_com_e_voice_audio_Speex_capture
        (JNIEnv *env, jobject jObj, jshortArray input_frame)
{
    env->MonitorEnter(jObj);
    jshort *native_input_frame = env->GetShortArrayElements(input_frame, 0);

    jint length = env->GetArrayLength(input_frame);
    jshortArray temp = env->NewShortArray(length);
    jshort *native_output_frame = env->GetShortArrayElements(temp, 0);

    speex_echo_capture(st, native_input_frame, native_output_frame);
    speex_preprocess_run(den, native_output_frame);

    jshortArray output_shorts = env->NewShortArray(length);
    env->SetShortArrayRegion(output_shorts, 0, length, native_output_frame);

    env->ReleaseShortArrayElements(input_frame, native_input_frame, 0);
    env->ReleaseShortArrayElements(temp, native_output_frame, 0);
    env->MonitorExit(jObj);
    return output_shorts;


}
extern "C"
JNIEXPORT void JNICALL Java_com_e_voice_audio_Speex_reset(JNIEnv *env, jobject jObj) {
    speex_echo_state_reset(st);
}
extern "C"
JNIEXPORT void JNICALL Java_com_e_voice_audio_Speex_close
        (JNIEnv *env, jobject jObj)
{
    speex_echo_state_destroy(st);
    speex_preprocess_state_destroy(den);
    st = 0;
    den = 0;
}

Speex.java



import com.e.voice.util.Constants;

/**
 * 迴音消除
 */

public class Speex {

    static {
        try {
            System.loadLibrary("speex");
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private static Speex speex = null;

    private Speex() {
        init();
    }

    public static Speex getInstance() {
        if (speex == null) {
            synchronized (Speex.class) {
                if (speex == null) {
                    speex = new Speex();
                }
            }
        }
        return speex;
    }


    public void init() {
       //8000,620,160*25
        open(8000,620,160*25);
    }

    public void closeS(){
        speex.reset();
        speex.close();
        speex = null;
    }

    public native void close();
    public native void open(int jSampleRate, int jBufSize, int jTotalSize );
    public native short[] process(short[] recordArray, short[] playArray);
    public native void reset();
    public native void  playback(short[] playArray);
    public native short[]  capture(short[] recordArray);


}

播放錄音




/**
 * AudioTrack音訊播放,錄音
 *
 * @author Robbie
 */
public class Tracker extends JobHandler {

    private static AudioTrack audioTrack;


    private AudioRecord audioRecord;
    // 音訊大小
    private int inAudioBufferSize;
    // 音訊大小
    private static int outAudioBufferSize;
    // 播放標誌
    private boolean isPlaying = true;

    public Tracker(Handler handler) {
        super(handler);
        outAudioBufferSize = 1280;
        inAudioBufferSize = 1280;
        this.init();
        initAudioTrace(AudioManager.AUDIO_SESSION_ID_GENERATE);
    }

    public static void initAudioTrace(int sessionId) {
        audioTrack = new AudioTrack((new AudioAttributes.Builder())
                .setLegacyStreamType(Constants.streamType)
                .build(),
                (new AudioFormat.Builder())
                        .setChannelMask(Constants.outputChannelConfig)
                        .setEncoding(Constants.audioFormat)
                        .setSampleRate(Constants.sampleRateInHz)
                        .build(),
                outAudioBufferSize,
                Constants.trackMode, sessionId);


        audioTrack.setVolume(1.0f);
    }

    public boolean isPlaying() {
        return isPlaying;
    }

    public void setPlaying(boolean playing) {
        isPlaying = playing;
    }

    public void init() {
        //inAudioBufferSize =AudioRecord.getMinBufferSize(
        //Constants.sampleRateInHz, Constants.inputChannelConfig, Constants.audioFormat)*2;
        // 初始化音訊錄製
        audioRecord = new AudioRecord(Constants.audioSource,
                Constants.sampleRateInHz, Constants.inputChannelConfig, Constants.audioFormat, inAudioBufferSize);


    }


    @Override
    public void run() {
        AudioData currentAudioData;

        while ((currentAudioData = MessageQueue.getInstance(MessageQueue.TRACKER_DATA_QUEUE).take()) != null) {

            if (!IntercomService.connecting) {
                break;
            }
            
            if (audioTrack == null) {
                continue;
            }
            try {
                if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
                    audioTrack.play();
                }
                short[] bytesPkg = currentAudioData.getRawData();
                if (bytesPkg != null) {
                    AudioDataUtil.playback(bytesPkg);
                    audioTrack.write(bytesPkg, 0, bytesPkg.length);
                }
                this.reacord(bytesPkg);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    private void reacord(short[] bytesPkg) {
        if(audioRecord == null){
            this.init();
        }
        if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) {
            audioRecord.startRecording();
        }

        AudioData audioData = new AudioData();
        // 例項化音訊資料緩衝
        short[] rawData = new short[inAudioBufferSize / 2];

        audioRecord.read(rawData, 0, inAudioBufferSize / 2);

        if (bytesPkg != null) {

            short[] recordData = AudioDataUtil.capture(rawData);

            audioData.setRawData(recordData);
        } else {
            audioData.setRawData(rawData);
        }

        MessageQueue.getInstance(MessageQueue.ENCODER_DATA_QUEUE).put(audioData);
    }

    @Override
    public void free() {
        if (audioTrack != null) {
            audioTrack.flush();
            audioTrack.stop();
            audioTrack.release();
            audioTrack = null;
        }


        if (audioRecord != null) {
            audioRecord.stop();
            audioRecord.release();
            AcousticEchoCancelerUtil.getInstance().release();
            audioRecord = null;
        }

    }
}