3

基本的な audiorecord-audiotrack、udp パケットの音声チャットを 2 つの Android デバイス間で使用しています。動作しますが、エコーが悪いです。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;
}

問題は、レコーダー/ストリーミング スレッドを開始すると、1 つのパッケージが送信されたことが示され、アプリがメッセージなしでクラッシュすることです。提案やアドバイスはありますか?私はこのプロジェクトをできるだけ早く行う必要があり、一生懸命働いて自分自身を文書化しましたが、それでもうまくいきたくないので、助けてください。ありがとうございました!

編集:私はちょうどそれを発見しました

//  Echo cancelling
buffer = Capture(buffer);

クラッシュを引き起こしています。

4

1 に答える 1

2

更新: クラッシュについて - これは JNI の誤用によるものです。jshortarray を C ポインターとして扱うことはできません。関連する JNI 関数を呼び出して C ポインタに変換する必要があります (GetShortArrayElements など)。

エコー キャンセラーについて - 現在、約 2 フレームの内部バッファーを使用していますが、それでは十分ではない可能性があります。Android のコンテキスト切り替えは PC とは異なり、4 つの連続した再生フレーム -> 4 つの連続した記録されたフレーム -> というようになる場合があります。そのため、フレームが失われます。

また、Android のオーディオ ストリーミングは PC よりも遅延が大きいため、2 フレームのバッファーでは不十分です。独自のバッファリングを実装し、遅延するフレームの量で再生する必要があるため、エコー キャンセラは、再生後にフィルター長の境界内でエコーを認識します。

それ以外に、エコー キャンセラを分析してデバッグするための最良の方法は次のとおりです。2. 提供された echo_diagnostic ツールを使用し、Audacity などのツールでストリームを調べて、問題が発生した場所を確認します。

Speex AEC は、正しく使用すると非常にうまく機能します。私はそれを使用して既に 3 つの異なるプロジェクトを行ったので、実装を修正するだけです。

于 2012-04-10T12:33:51.300 に答える