web-dev-qa-db-ja.com

Android AudioRecordクラス-ライブマイクの音声をすばやく処理し、コールバック関数を設定します

マイクから音声を録音し、それにアクセスして、ほぼリアルタイムで再生できるようにします。 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ネットワークを介してライブマイクオーディオをほぼリアルタイムでストリーミングしたい場合があります。具体例?

64
kenj

通知や他の多くのテクニックを使って多くのことを試した後、このコードに決めました。

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;
        }

    }

これまでのところ、半ダースAndroid私が試してみた携帯電話でかなり堅牢に動作しています。

33
tonys

これらの答えを次のように組み合わせることができるのだろうか...

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

20
Dave MacLean

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

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

detectAfterEveryについて:

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

完全なコードは here :です。

        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);
        }
11
gregm
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ミリ秒未満の遅延で再生されます。