8

リアルタイム DSP を実行できるように、マイクのオーディオ ストリームを録音したいと考えています。

.read()スレッドを使用したり、新しいオーディオ データを待機している間にブロックしたりせずに、そうしたいと考えています。

更新/回答: Android のバグです。4.2.2 にはまだ問題がありますが、5.01 は修正されています! 違いがどこにあるかはわかりませんが、それが話です。

注: 「スレッドを使用してください」とは言わないでください。スレッドは問題ありませんが、これはスレッドに関するものではありません。Android 開発者は、スレッドを指定したり read() のブロックに対処したりすることなく、AudioRecord を完全に使用できるようにすることを意図していました。ありがとうございました!

これが私が見つけたものです:

AudioRecord オブジェクトが初期化されると、独自の内部リング タイプ バッファが作成されます。が.start()呼び出されると、上記のリング バッファ (または実際の種類は何でも) への記録を開始します。

.read()呼び出されると、bufferSize の半分または指定されたバイト数 (どちらか少ない方) を読み取ってから戻ります。

内部バッファに十分な数のオーディオ サンプルがある場合、read() はデータとともに即座に戻ります。まだ十分でない場合、read() は十分になるまで待機し、データを返します。

.setRecordPositionUpdateListener()を使用してリスナーを設定し、 を.setPositionNotificationPeriod()使用.setNotificationMarkerPosition()して通知期間と位置をそれぞれ設定できます。

ただし、特定の要件が満たされない限り、リスナーは呼び出されないようです。

1: 期間または位置は、bufferSize/2 または (bufferSize/2)-1 と等しくなければなりません。

2: .read()Period または Position タイマーがカウントを開始する前に、 a を呼び出す必要があります。つまり、呼び出した.start()後、 も呼び出し.read()、リスナーが呼び出されるたびに、.read()再度呼び出します。

3:.read()毎回少なくとも bufferSize の半分を読み取る必要があります。

したがって、これらのルールを使用して、コールバック/リスナーを機能させることができますが、何らかの理由で読み取りがまだブロックされており、完全な読み取りの価値がある場合にのみリスナーを呼び出す方法がわかりません。

クリックして読み取るようにボタン ビューを設定すると、それをタップして、すばやくタップするとブロックを読み取ることができます。しかし、オーディオ バッファがいっぱいになるのを待つと、最初のタップは瞬時に行われます (読み取りはすぐに返されます) が、read() が待機する必要があるため、後続の高速タップはブロックされると思います。

read() がすぐに返すのに十分なデータがあるときにリスナーが呼び出されるように、リスナーを意図したとおりに機能させる方法についての洞察をいただければ幸いです。

以下は私のコードの関連部分です。

私のコードには、logcat に文字列を送信するログ ステートメントがいくつかあります。これにより、各コマンドの所要時間を確認できます。これにより、read() がブロックされていることがわかります。(そして、私の単純なテスト アプリのボタンも、読み取りを繰り返しているときに応答が非常に遅くなりますが、CPU は固定されていません。)

ありがとう、〜ジェシー

私の OnCreate() で:

    bufferSize=AudioRecord.getMinBufferSize(samplerate,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT)*4;
        recorder = new AudioRecord (AudioSource.MIC,samplerate,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT,bufferSize);
        recorder.setRecordPositionUpdateListener(mRecordListener);
        recorder.setPositionNotificationPeriod(bufferSize/2);
        //recorder.setNotificationMarkerPosition(bufferSize/2);
        audioData = new short [bufferSize];

    recorder.startRecording();
            samplesread=recorder.read(audioData,0,bufferSize);//This triggers it to start doing the callback.

次に、ここに私のリスナーがあります:

public OnRecordPositionUpdateListener mRecordListener = new OnRecordPositionUpdateListener() 
{
    public void onPeriodicNotification(AudioRecord recorder) //This one gets called every period. 
    {
        Log.d("TimeTrack", "AAA");
        samplesread=recorder.read(audioData,0,bufferSize);
        Log.d("TimeTrack", "BBB");
        //player.write(audioData, 0, samplesread);
        //Log.d("TimeTrack", "CCC");
        reads++;
    }
    @Override
    public void onMarkerReached(AudioRecord recorder) //This one gets called only once -- when the marker is reached.
    {
        Log.d("TimeTrack", "AAA");
        samplesread=recorder.read(audioData,0,bufferSize);
        Log.d("TimeTrack", "BBB");
        //player.write(audioData, 0, samplesread);
        //Log.d("TimeTrack", "CCC");
    }
};

更新: Android 2.2.3、2.3.4、そして現在は 4.0.3 でこれを試しましたが、すべて同じように動作します。また、code.google には未解決のバグがあります。2012 年に他の誰かが開始したエントリと、2013 年に私が開始したエントリがあります (最初のエントリについては知りませんでした)。

更新 2016: ああ、それが私なのかアンドロイドなのか何年も疑問に思っていたのですが、ついに答えが出ました! 上記のコードを 4.2.2 で試しましたが、同じ問題がありました。上記のコードを 5.01 で試してみましたが、うまくいきました!!! また、最初の .read() 呼び出しも不要になりました。.setPositionNotificationPeriod() と .StartRecording() が呼び出されると、mRecordListener() は魔法のようにデータが利用可能になるたびに呼び出されるようになり、ブロックされなくなります。これは、十分なデータが記録されるまでコールバックが呼び出されないためです。 . 正しく記録されているかどうかを知るためにデータを聞いていませんが、コールバックは正常に発生しており、以前のようにアクティビティをブロックしていません!

http://code.google.com/p/android/issues/detail?id=53996

http://code.google.com/p/android/issues/detail?id=25138

このバグを気にかけている人がログインしてバグに投票したりコメントしたりすれば、おそらく Google によってより早く対処されるでしょう。

4

2 に答える 2

1

回答が遅くなりましたが、ジェシーがどこでミスをしたかはわかっていると思います。バッファ サイズと同じサイズの short を要求しているため、読み取り呼び出しがブロックされていますが、バッファ サイズはバイト単位で、short には 2 バイトが含まれています。短い配列をバッファと同じ長さにすると、2 倍のデータが読み込まれます。

解決策は次のようにすることですaudioData = new short[bufferSize/2]。バッファ サイズが 1000 バイトの場合、この方法で 1000 バイトの 500 ショートを要求します。

また、彼はに変更samplesread=recorder.read(audioData,0,bufferSize)する必要がありますsamplesread=recorder.read(audioData,0,audioData.length)

アップデート

わかりました、ジェシー。positionNotificationPeriod という別の間違いがどこにあるのかがわかります。この値は、リスナーをあまり頻繁に呼び出さないように十分大きくする必要があり、リスナーが呼び出されたときに、読み取るバイトを収集する準備ができていることを確認する必要があります。リスナーが呼び出されたときにバイトの準備ができていない場合、メイン スレッドは、要求されたバイトが AudioRecord によって収集されるまで、recorder.read(audioData, 0, audioData.length) 呼び出しによってブロックされます。設定した時間間隔 (リスナーを呼び出す頻度) に基づいて、バッファー サイズと shorts 配列の長さを計算する必要があります。位置通知期間、バッファ サイズ、および shorts 配列の長さはすべて正しく調整する必要があります。例を示しましょう。

int periodInFrames = sampleRate / 10;
int bufferSize = periodInFrames * 1 * 16 / 8;
audioData = new short [bufferSize / 2];

int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (bufferSize < minBufferSize) bufferSize = minBufferSize;

recorder = new AudioRecord(AudioSource.MIC, sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, buffersize);
recorder.setRecordPositionUpdateListener(mRecordListener);
recorder.setPositionNotificationPeriod(periodInFrames);
recorder.startRecording();

public OnRecordPositionUpdateListener mRecordListener = new OnRecordPositionUpdateListener() {
    public void onPeriodicNotification(AudioRecord recorder) {
        samplesread = recorder.read(audioData, 0, audioData.length);
        player.write(short2byte(audioData));
    }
};

private byte[] short2byte(short[] data) {
        int dataSize = data.length;
        byte[] bytes = new byte[dataSize * 2];

        for (int i = 0; i < dataSize; i++) {
            bytes[i * 2] = (byte) (data[i] & 0x00FF);
            bytes[(i * 2) + 1] = (byte) (data[i] >> 8);
            data[i] = 0;
        }
        return bytes;
    }

では、少し説明を。

まず、オーディオ データを収集するためにリスナーを呼び出す頻度を設定します (periodInFrames)。PositionNotificationPeriod はフレーム単位で表されます。サンプリング レートは 1 秒あたりのフレーム数で表されるため、44100 のサンプリング レートの場合、1 秒あたり 44100 フレームになります。これを 10 で割ったので、リスナーは 4410 フレームごとに呼び出されます = 100 ミリ秒 - これは妥当な時間間隔です。

ここで、periodInFrames に基づいてバッファー サイズを計算するので、収集する前にデータが上書きされることはありません。バッファサイズはバイト単位で表されます。時間間隔は 4410 フレームで、各フレームにはモノラルの場合は 1 バイト、ステレオの場合は 2 バイトが含まれるため、チャネル数 (この場合は 1) を掛けます。各チャネルには、ENCODING_8BIT の場合は 1 バイト、ENCODING_16BIT の場合は 2 バイトが含まれるため、サンプルあたりのビット数 (ENCODING_16BIT の場合は 16、ENCODING_8BIT の場合は 8) を掛けて、8 で割ります。

次に、audioData の長さを bufferSize の半分に設定して、リスナーが呼び出されたときに、読み取るバイトが既にそこにあり、収集されるのを待っていることを確認します。これは、short には 2 バイトが含まれ、bufferSize はバイト単位で表されるためです。

次に、bufferSize が AudioRecord オブジェクトを正常に初期化するのに十分な大きさかどうかを確認します。そうでない場合は、bufferSize を最小サイズに設定します。時間間隔や audioData の長さを変更する必要はありません。

リスナーでは、データを読み取り、短い配列に保存します。これが、バッファー サイズの代わりに audioData.length を使用する理由です。なぜなら、audioData.length だけがバッファーに含まれる short の数を教えてくれるからです。

しばらく前に動作していたので、動作するかどうか教えてください。

于 2015-06-10T16:29:45.507 に答える