【问题标题】:Android Speex echo cancellation problemsAndroid Speex 回声消除问题
【发布时间】:2012-04-22 06:35:29
【问题描述】:

我有一个基本的audiorecord-audiotrack,两个android设备之间的udp数据包语音聊天。它有效,但我的回声很差。我正在尝试使用由 JNI 移植到 android 的 Speex 来消除回声。我导入的 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;
}

问题是,当我启动记录器/流式传输线程时,它显示它发送了一个包,然后应用程序崩溃而没有任何消息。您有什么建议或建议吗?请帮助我,因为我需要尽快完成这个项目,我已经努力工作并记录了自己,但它仍然不想很好地工作。谢谢!

编辑:我刚刚发现

//  Echo cancelling
buffer = Capture(buffer);

正在触发崩溃。

【问题讨论】:

  • 根据您对崩溃的更新为我的答案添加了更新。
  • 嗨,Emil Pana,您成功消除了回声吗?
  • 你解决过这个问题吗?
  • 我找不到你的电子邮件地址 如果你能把你的电子邮件地址发给我我的电子邮件地址 farzad.salimijazi@gmail.com ,@Emil Pana,我将不胜感激
  • 为什么你在调用 byte ShortToByteArray() 的时候有 short ByteToShortArray() ??

标签: android echo speex cancellation


【解决方案1】:

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

关于回声消除器 - 您当前使用的是内部缓冲区,大约 2 帧,这对您来说可能还不够。 Android 的上下文切换不像在 PC 上,你可能会得到 4 个连续播放帧 -> 4 个连续录制帧 -> 等等。所以你会这样丢帧。

此外,Android 音频流的延迟比 PC 更高,因此 2 帧缓冲区是不够的。您需要实现自己的缓冲并使用延迟的帧数进行播放,因此回声消除器会在其过滤器长度的边界内看到播放后的回声。

除此之外,分析和调试回声消除器的最佳方法是: 1. 实际上了解它在高层次上是如何工作的以及有什么要求 - 不是火箭科学,你可以在官方 Speex 文档上找到所有内容。 2. 使用提供的 echo_diagnostic 工具,并在 Audacity 等工具中检查流,看看哪里出了问题。

正确使用 Speex AEC 运行良好,我已经使用它完成了 3 个不同的项目,因此只需更正您的实施即可。

【讨论】:

  • 我在我的 android 应用程序上有类似的任务要做。我已经通过 JNI 调用成功实现了 Speex 编码。但是,如果我为 FLOATING_POINT 编译库,它就不起作用。如何在 FIXED_POINT 编译中提供 echo_cancellation ?
  • @SirKnigget 您是否在 Android 中为 AEC 使用了 opensles 和 speex?
  • 我仍然在努力使用 libspeexdsp 1.2rc1 来实现 AEC,请您在这里看看我的问题:stackoverflow.com/questions/66627272/… 吗?抄送@SirKnigget
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-31
  • 2020-10-10
相关资源
最近更新 更多