android 用speex做回音消除

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

    }
}


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容