Android的Speex语音回声消除问题(Android Speex echo cancellat

2019-06-23 11:37发布

我有一个基本的audiorecord-audiotrack,两款Android设备之间的UDP数据包语音聊天。 它的工作原理,但我有一个坏的回声。 我试图删除使用Speex语音端口通过JNI到Android的回声。 在我的Speex进口的作品,但回声消除没有。 本机C代码为:

#include <jni.h>
#include <speex/speex_echo.h> 

#define FRAME_SIZE 256
#define FILTER_LENGTH 800

SpeexEchoState *echoState;

// Initialization of echo cancelation
void Java_telefonie_voip_VoIPActivity_InitEcho(JNIEnv * env, jobject jobj)
{
    echoState = speex_echo_state_init(FRAME_SIZE, FILTER_LENGTH);
}

//  Queue the frame to soundcard for playing (receiving)
void Java_telefonie_voip_VoIPActivity_Playback(JNIEnv * env, jobject jobj, jshortArray     inputFrame)
{
    speex_echo_playback(echoState, inputFrame);
}

jshortArray Java_telefonie_voip_VoIPActivity_Capture(JNIEnv * env, jobject jobj,     jshortArray inputFrame)
{
    jshortArray outputFrame;

    speex_echo_capture(echoState, inputFrame, *&outputFrame);

    return outputFrame;
}

// Destroing echo cancelation
void Java_telefonie_voip_VoIPActivity_DestroyEcho(JNIEnv * env, jobject jobj)
{
speex_echo_state_destroy(echoState); 
}

而一些重要的Java代码:

native void InitEcho();
native void DestroyEcho();
native void Playback(short[] inputData);  // listener/receiving
native short[] Capture(short[] inputData);  // recorder/sender
static {
    System.loadLibrary("speex");
}

public void Initialize ()
{
    minBufferSize = 4096;
    try
    {
        InitEcho();
    }
    catch (Exception e){Log.e(TAG, "jni " + e.getMessage());}
}

public void startRecording ()
{
    isStreaming = true;
streamingThread = new Thread(new Runnable(){
        @Override
        public void run ()
        {
            try
            {
                audioRecorder = new AudioRecord(
                        MediaRecorder.AudioSource.MIC,
                        sampleRate,
                        channelConfig,
                        audioFormat,
                        minBufferSize*10
                );

                buffer = new short[minBufferSize];
                audioRecorder.startRecording();

                while(isStreaming)
                {
                    sendPackets++;
                    minBufferSize = audioRecorder.read(buffer, 0, buffer.length);     
                    //  Echo cancelling
                    buffer = Capture(buffer);

                    //  Send
                    dataPacket = new DatagramPacket(ShortToByteArray(buffer), buffer.length*2, receiverAddressIA, serverPort1);
                    dataSocket.send(dataPacket);
                }
            }
            catch(Exception e)
            {
                Log.e(TAG, "2" + e.getMessage());
            }
        }
    });
    streamingThread.start();
}

public void startListen ()
{
    isListening = true;
receivingThread = new Thread(new Runnable(){
        @Override
        public void run ()
        {
            try
            {
                audioTrack = new AudioTrack(
                        AudioManager.STREAM_VOICE_CALL,
                        sampleRate,
                        channelConfig,
                        audioFormat,
                        minBufferSize*10,
                        AudioTrack.MODE_STREAM
                );

                audioTrack.play();

                buffer2 = new short[minBufferSize];

                while (isListening)
                {
                    receivedPackets++;
                    dataPacket2 = new DatagramPacket(ShortToByteArray(buffer2), buffer2.length*2);
                    dataSocket2.receive(dataPacket2);

                    //  Cancel echo
                    Playback(buffer2);

                    //  Queue to soundcard
                    audioTrack.write(buffer2, 0, minBufferSize);
                }
            }
            catch(Exception e)
            {
                Log.e(TAG, "3" + e.getMessage());
            }
        }
    });
    receivingThread.start();
}

public short[] ByteToShortArray(byte[] byteArray)
{
    short[] shortArray = new short[byteArray.length/2];
ByteBuffer.wrap(byteArray).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(shortArray);
    return shortArray;
}

问题是,当我开始记录/流线,它让我看到它发送一个包,然后应用程序崩溃,没有消息。 你有任何sugestions或建议? 请帮助我,因为我需要尽快做这个项目,我一直努力并记录自己,但仍然不希望工作得很好。 谢谢!

编辑:我刚刚发现,

//  Echo cancelling
buffer = Capture(buffer);

被触发崩溃。

Answer 1:

更新:关于崩溃 - 这是由于错误使用JNI的。 你不能把jshortarray作为C指针。 你需要调用相关的JNI函数将其转换为C指针(GetShortArrayElements等)。

关于回波消除器 - 您目前使用的内部缓冲区,大约是2帧,可能不适合你足够。 Android的上下文切换并不像在PC上,你可能会得到4个连续回放帧 - > 4个连续的记录帧 - >等。 所以你失去帧的方式。

此外,机器人的音频流具有比PC更高的延迟,所以在2帧缓冲器是不够的。 您需要实现自己的缓冲和与你延迟帧的量打,所以其滤波器长度的边界内播放后回波消除器看到的回声。

除此之外,最好的方法来分析和调试回波消除器是:1。真正了解它是如何工作的高层次和有什么要求 - 不是火箭科学,你可以找到官方的Speex文档的一切。 2.在工具使用提供的echo_diagnostic工具,并检查流如大言不惭地看到哪里出了问题。

该Speex的AEC工作得很好,正常使用时,我已经用它已经完成了3个不同的项目,所以它只是一个纠正你的实现问题。



文章来源: Android Speex echo cancellation problems