Android回音消除困扰了我将近一个星期终于解决了。
项目一边是使用Java FX的PC端,一边是Android设备进行实时语言通话。
废话不多说直接上代码。
speex_jni.cpp
#include
#include
#include
#include
#include
#include
#include
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;
}
}
}