67

我想从麦克风录制音频并访问它以进行近乎实时的播放。我不确定如何使用 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”是否在其他地方正确排序打开和关闭?看起来这段代码要么读得太频繁,要么读得太少,这取决于读写需要多长时间。

该站点文档还说 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;
        }

    }

到目前为止,它在我试用过的六部 Android 手机上运行良好。

于 2011-01-28T11:58:33.280 回答
22

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

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

于 2011-01-29T02:01:43.810 回答
12

这是您需要使用 OnRecordPositionUpdateListener 和 Notification Period 的代码。

我注意到在实践中它不会在同一确切时间一致地发送通知,我想要,但它已经足够接近了。

关于detectAfterEvery

detectEvery 的大小需要足够大以容纳所需的数据量。所以对于这个例子,我们的采样率为 44100 Hz,这意味着我们需要每秒 44100 个样本。通过将 设置setPositionNotificationPeriod为 44100,代码告诉 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 回答