Android的Speex语音回声消除问题回声、语音、问题、Android

2023-09-14 00:04:19 作者:小祸害

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

I have a basic audiorecord-audiotrack, udp packets voice chat between two android devices. It works, but I have a bad echo. I'm trying to remove the echo using Speex ported to android by JNI. The speex I imported works, but the echo cancellation doesn't. Native C code is:

#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 code:

And some of important java code:

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或建议?请帮助我,因为我需要尽快做这个项目,我已经努力过,记录自己,但它仍然不希望正常工作。谢谢!

The problem is that when I start the recorder/streaming thread, it shows me that it sends one package and then the app crashes with no message. Do you have any sugestions or advices? Please help me because I need to do this project asap and I've worked hard and documented myself but still it doesn't want to work well. Thank you!

编辑:我刚刚发现,

//  Echo cancelling
buffer = Capture(buffer);

被触发崩溃。

is triggering the crash.

推荐答案

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

UPDATE: Regarding crash - This is due to mis-use of JNI. You can't treat jshortarray as a C pointer. You need to call the relevant JNI functions to convert it to a C pointer (GetShortArrayElements and so on).

对于回波消除器 - 您当前使用的内部缓冲区,大约是2帧,这可能还不够你。 Android的上下文切换是不喜欢在PC上,你可能会得到4个连续回放的帧 - > 4个连续记录的画面 - >等。所以,你输帧的方式。

Regarding echo canceler - you currently use the internal buffer, which is about 2 frames, and that might not be enough for you. Android's context-switching is not like on a PC, and you might get 4 consecutive playback frames -> 4 consecutive recorded frames -> and so on. So you lose frames that way.

此外,机器人的音频流具有较高的等待时间比PC,因此,2帧缓冲器是不够的。您需要实现自己的缓冲,并与你推迟帧的播放量,因此回波消除器看到的回声滤波器长度的边界内播放后。

Also, Android audio streaming has higher latency than a PC, so the 2-frame buffer isn't enough. You need to implement your own buffering and play with the amount of frames by which you delay, so the echo canceler sees the echo after the playback inside the boundaries of its filter length.

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

Other than that, the best way to analyze and debug the echo canceler is: 1. Actually understand how it works on a high level and what are the requirements - not a rocket science, you can find everything on the official Speex docs. 2. Use the provided echo_diagnostic tool, and examine the streams in a tool such as Audacity to see where it went wrong.

的Speex的AEC工作pretty的同时正确使用时,我已经做了3个不同的项目中使用它,所以它的纠正你的实现只是一个问题。

The Speex AEC works pretty well when used correctly, and I've already done 3 different projects using it, so it's just a matter of correcting your implementation.