67

マイクからオーディオを録音し、ほぼリアルタイムで再生できるようにアクセスしたいと考えています。Android AudioRecord クラスを使用してマイク音声を録音し、すばやくアクセスする方法がわかりません。

AudioRecord クラスについては、公式サイトによると、「アプリは AudioRecord オブジェクトを時間内にポーリングする」、「満たされたバッファのサイズによって、未読データがオーバーランするまでの録音の時間の長さが決まります」。後で、ポーリングの頻度が低い場合は、より大きなバッファを使用することをお勧めします。実際にコードで例を示すことはありません。

私が本で見た 1 つの例では、AudioRecord クラスを使用して、ライブ マイク オーディオが新たに入力されたバッファーを継続的に読み取り、アプリがこのデータを SD ファイルに書き込みます。疑似コードは次のようになります-

set up AudioRecord object with buffer size and recording format info
set up a file and an output stream
myAudioRecord.startRecording();
while(isRecording)
{
    // myBuffer is being filled with fresh audio
    read audio data into myBuffer
    send contents of myBuffer to SD file
}
myAudioRecord.stop();

このコードがどのように読み取りと録音速度を同期させるかは不明です。ブール値の「isRecording」は、他の場所で適切にオン/オフされていますか? 読み取りと書き込みにかかる時間に応じて、このコードの読み取り頻度が高すぎたり低すぎたりする可能性があるようです。

サイトのドキュメントには、 AudioRecord クラスには、インターフェイスとして定義された OnRecordPositionUpdateListener という名前のネストされたクラスがあるとも書かれています。この情報は、何らかの方法で、記録の進行状況を通知する期間とイベント ハンドラーの名前を指定すると、指定された頻度でイベント ハンドラーが自動的に呼び出されることを示唆しています。疑似コードでの構造は次のようになると思います-

set target of period update message = myListener
set period to be about every 250 ms
other code

myListener()
{
    if(record button was recently tapped)
        handle message that another 250 ms of fresh audio is available
        ie, read it and send it somewhere
)

約 500 ミリ秒未満の遅延でマイク オーディオをキャプチャして処理できる特定のコードを見つける必要があります。Android は MediaRecorder という別のクラスを提供していますが、これはストリーミングをサポートしていないため、ライブ マイク オーディオを Wi-Fi ネットワーク経由でほぼリアルタイムでストリーミングしたい場合があります。具体的な例はどこにありますか?

4

4 に答える 4

34

通知や他の多くのテクニックを試してみた後、私はこのコードに落ち着きました。

private class AudioIn extends Thread { 
     private boolean stopped    = false;

     private AudioIn() { 

             start();
          }

     @Override
     public void run() { 
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
            AudioRecord recorder = null;
            short[][]   buffers  = new short[256][160];
            int         ix       = 0;

            try { // ... initialise

                  int N = AudioRecord.getMinBufferSize(8000,AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT);

                   recorder = new AudioRecord(AudioSource.MIC,
                                              8000,
                                              AudioFormat.CHANNEL_IN_MONO,
                                              AudioFormat.ENCODING_PCM_16BIT,
                                              N*10);

                   recorder.startRecording();

                   // ... loop

                   while(!stopped) { 
                      short[] buffer = buffers[ix++ % buffers.length];

                      N = recorder.read(buffer,0,buffer.length);
                      //process is what you will do with the data...not defined here
                      process(buffer);
                  }
             } catch(Throwable x) { 
               Log.w(TAG,"Error reading voice audio",x);
             } finally { 
               close();
             }
         }

      private void close() { 
          stopped = true;
        }

    }

これまでのところ、私が試した6台のAndroidフォンでかなり堅牢に動作しています。

于 2011-01-28T11:58:33.280 に答える
22

これらの答えを次のように組み合わせていただけないでしょうか...

while ループの前に setPositionNotificationPeriod(160) を使用します。これにより、160 フレームが読み取られるたびにコールバックが呼び出されるようになります。読み取りループを実行しているスレッド内で process(buffer) を呼び出す代わりに、コールバックから process(buffer) を呼び出します。変数を使用して最後の読み取りバッファーを追跡し、正しいバッファーを処理します。現在のように、読み取りをブロックすると、処理中に読み取りが行われなくなります。この二つは分けたほうがいいと思います。

于 2011-01-29T02:01:43.810 に答える
12

OnRecordPositionUpdateListener と通知期間を使用するために必要なコードは次のとおりです。

実際には、同じ正確な時刻に一貫して通知を送信しないことに気付きましたが、十分に近いです。

detectAfterEvery:

detectEvery のサイズは、必要なデータ量だけを保持するのに十分な大きさである必要があります。したがって、この例では、44100 Hz のサンプル レートがあり、これは、1 秒あたり 44100 サンプルが必要であることを意味します。を 44100 に設定するsetPositionNotificationPeriodことで、コードは Android に 44100 サンプル (約 1 秒ごと) を記録した後にコールバックするように指示します。

完全なコードは次のとおりです。

        final int sampleRate = 44100;
        int bufferSize =
                AudioRecord.getMinBufferSize(sampleRate,
                        AudioFormat.CHANNEL_CONFIGURATION_MONO,
                        AudioFormat.ENCODING_PCM_16BIT);

//aim for 1 second
        int detectAfterEvery = (int)((float)sampleRate * 1.0f);

        if (detectAfterEvery > bufferSize)
        {
            Log.w(TAG, "Increasing buffer to hold enough samples " + detectAfterEvery + " was: " + bufferSize);
            bufferSize = detectAfterEvery;
        }

        recorder =
                new AudioRecord(AudioSource.MIC, sampleRate,
                        AudioFormat.CHANNEL_CONFIGURATION_MONO,
                        AudioFormat.ENCODING_PCM_16BIT, bufferSize);
        recorder.setPositionNotificationPeriod(detectAfterEvery);

        final short[] audioData = new short[bufferSize];
        final int finalBufferSize = bufferSize;

        OnRecordPositionUpdateListener positionUpdater = new OnRecordPositionUpdateListener()
        {
            @Override
            public void onPeriodicNotification(AudioRecord recorder)
            {
                Date d = new Date();
//it should be every 1 second, but it is actually, "about every 1 second"
//like 1073, 919, 1001, 1185, 1204 milliseconds of time.
                Log.d(TAG, "periodic notification " + d.toLocaleString() + " mili " + d.getTime());
                recorder.read(audioData, 0, finalBufferSize);

                //do something amazing with audio data
            }

            @Override
            public void onMarkerReached(AudioRecord recorder)
            {
                Log.d(TAG, "marker reached");
            }
        };
        recorder.setRecordPositionUpdateListener(positionUpdater);

        Log.d(TAG, "start recording, bufferSize: " + bufferSize);
        recorder.startRecording(); 

//remember to still have a read loop otherwise the listener won't trigger
while (continueRecording)
        {
            recorder.read(audioData, 0, bufferSize);
        }
于 2012-01-27T10:33:01.993 に答える
3
private int freq =8000;
private AudioRecord audioRecord = null;
private Thread Rthread = null;

private AudioManager audioManager=null;
private AudioTrack audioTrack=null;
byte[] buffer = new byte[freq];

//call this method at start button

protected void Start()

{

loopback();

}

protected void loopback() { 

    android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
    final int bufferSize = AudioRecord.getMinBufferSize(freq,
            AudioFormat.CHANNEL_CONFIGURATION_MONO,
            AudioFormat.ENCODING_PCM_16BIT);


    audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, freq,
            AudioFormat.CHANNEL_CONFIGURATION_MONO,
            MediaRecorder.AudioEncoder.AMR_NB, bufferSize);

    audioTrack = new AudioTrack(AudioManager.ROUTE_HEADSET, freq,
            AudioFormat.CHANNEL_CONFIGURATION_MONO,
            MediaRecorder.AudioEncoder.AMR_NB, bufferSize,
            AudioTrack.MODE_STREAM);



    audioTrack.setPlaybackRate(freq);
     final byte[] buffer = new byte[bufferSize];
    audioRecord.startRecording();
    Log.i(LOG_TAG, "Audio Recording started");
    audioTrack.play();
    Log.i(LOG_TAG, "Audio Playing started");
    Rthread = new Thread(new Runnable() {
        public void run() {
            while (true) {
                try {
                    audioRecord.read(buffer, 0, bufferSize);                                    
                    audioTrack.write(buffer, 0, buffer.length);

                } catch (Throwable t) {
                    Log.e("Error", "Read write failed");
                    t.printStackTrace();
                }
            }
        }
    });
    Rthread.start();

}

録音されたオーディオを 100 ミリ秒未満の遅延で再生します。

于 2012-07-26T11:51:52.460 に答える