Android AudioRecord类 - 快速处理实时麦克风音频,设置回调功能

时间:2010-12-24 08:48:42

标签: java android audiorecord android-hardware

我想从麦克风录制音频并访问它,以便近乎实时地播放。我不确定如何使用Android AudioRecord类来录制一些麦克风音频并快速访问它。

对于AudioRecord类,官方网站称“应用程序及时轮询AudioRecord对象”,“正在填充的缓冲区大小决定了在超读未读数据之前记录的时间长度”。后来建议在轮询频率较低时应使用更大的缓冲区。他们实际上从未在代码中显示示例。

我在书中看到的一个例子是使用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”是否在其他地方正确地打开和关闭?看起来这段代码可能过于频繁或过于频繁地读取,具体取决于读取和写入所需的时间。

网站doc还说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 个答案:

答案 0 :(得分:33)

在尝试使用通知和其他一些技术后,我决定使用此代码:

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手机上运行得非常强劲。

答案 1 :(得分:20)

我想知道你是否可以通过以下方式结合这些答案......

在while循环之前使用setPositionNotificationPeriod(160)。这应该导致每次读取160帧时都会调用回调。而不是在执行读循环的线程内部调用进程(缓冲区),而是从回调中调用进程(缓冲区)。使用变量来跟踪最后一个读缓冲区,以便处理正确的缓冲区。就像现在一样,你阻止阅读,然后你在处理时没有阅读。我认为将这两者分开可能会更好。

答案 2 :(得分:11)

以下是使用OnRecordPositionUpdateListener和Notification Period所需的代码。

我注意到在实践中它并没有在我想要的同一时间一致地发送通知,但它足够接近。

关于detectAfterEvery

detectEvery的大小需要足够大才能容纳您想要的数据量。因此,对于此示例,我们的采样率为44100 Hz,这意味着我们需要每秒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);
        }

答案 3 :(得分:2)

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毫秒。