Android AudioRecord 类 - 快速处理现场麦克风音频,设置回调函数

Posted

技术标签:

【中文标题】Android AudioRecord 类 - 快速处理现场麦克风音频,设置回调函数【英文标题】:Android AudioRecord class - process live mic audio quickly, set up callback function 【发布时间】:2011-05-30 08:09:18 【问题描述】:

我想从麦克风录制音频并访问它以进行近乎实时的播放。我不确定如何使用 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 网络近乎实时地流式传输实时麦克风音频。我在哪里可以找到一些具体的例子?

【问题讨论】:

你解决过这个问题吗?我正在考虑为您的问题添加赏金... 赏金已添加.. 到目前为止没有帮助 This question 让我了解了回调的用法。 【参考方案1】:

在对通知和一堆其他技术进行了大量试验后,我决定使用以下代码:

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 手机上运行良好。

【讨论】:

基本上是实验——我碰巧使用的是 GSM 编解码器,所以“块”大小很短 [160]。 AudioRecord.read(buffer,0,buffer.length) 缓冲区很短[160] 只要“块”准备好就返回。并且对内部 AudioRecord 缓冲区数量的试验表明,10 倍的最小缓冲区大小足以(大部分)避免缓冲区溢出(并且应用程序可以容忍偶尔丢失的缓冲区)。希望对您有所帮助:-)。 好吧,你不能为每个设备手动试验,所以你需要一些系统来动态更新缓冲区大小,对吧?我目前通过实现最大精度时间来做到这一点,该时间检查读取是否不会过早返回。如果它返回的时间早于 250 毫秒,它将增加缓冲区大小。这是有效的,因为读取是一种阻塞方法。不确定是否有更好的方法。 哦...刚刚意识到我误读了您的第一条评论。 process(buffer) 方法只是将读取缓冲区放入队列中,以供单独的线程处理。所以 read(...) 总是“准备好阅读”。我的通知不太成功 - 你可能会有更好的运气。 您可以使用getMinBufferSize 获取特定格式的最小缓冲区大小。 标准语音基带(在电话中)为 0.3KHz-3.4KHz - 这是模拟电话早期的遗留物,当时人们认为这足以进行可理解的对话。如果您有兴趣,请参考大量谷歌信息。【参考方案2】:

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

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

【讨论】:

为什么是 160 ?在我的情况下应该是什么...***.com/questions/9413998/…?【参考方案3】:

这是您需要使用 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);
        

【讨论】:

+1 不错的答案,假设我想将 audioData 写入 AudioTrack 对象,我应该在哪里调用它.. 在侦听器内部的读取调用之后或在读取调用之后循环?...请帮助!!! 同样的问题:你能解释一下你处理detectAfterEvery的方式吗? 我编辑了这个问题来讨论detectAfterEvery。我还提供了更广泛代码的链接 传递给 setPositionNotificationPeriod 的参数不应该是帧,而不是样本吗?【参考方案4】:
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 毫秒的延迟播放录制的音频。

【讨论】:

以上是关于Android AudioRecord 类 - 快速处理现场麦克风音频,设置回调函数的主要内容,如果未能解决你的问题,请参考以下文章

Android多媒体功能开发(11)——使用AudioRecord类录制音频

无法在 Android 中使用 AudioRecord 进行录制

Android AudioRecord 类 - 快速处理现场麦克风音频,设置回调函数

Android 音视频开发:使用AudioRecord采集音频PCM并保存

Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能

Android音频FFT使用audiorecord检索特定频率幅度