Android:AudioRecord 类问题:从不调用回调

Posted

技术标签:

【中文标题】Android:AudioRecord 类问题:从不调用回调【英文标题】:Android: AudioRecord Class Problem: Callback is never called 【发布时间】:2011-01-16 23:52:11 【问题描述】:

我的 android Java 应用程序需要将音频数据记录到 RAM 中并进行处理。 这就是我使用“AudioRecord”类而不是“MediaRecorder”(仅记录到文件)的原因。

到目前为止,我使用“read()”的繁忙循环轮询音频数据。到目前为止,这一直有效,但它过多地与 CPU 挂钩。 在两次轮询之间,我将线程置于睡眠状态以避免 100% 的 CPU 使用率。 然而,这并不是一个真正干净的解决方案,因为睡眠时间是 不能保证,您必须减去安全时间以免丢失音频 sn-ps。这不是 CPU 最优的。我需要尽可能多的空闲 CPU 周期 一个并行运行的线程。

现在我使用“OnRecordPositionUpdateListener”实现了录制。 根据 SDK Docs,这看起来很有希望,也是正确的方法。 一切似乎都正常(打开音频设备,读取()数据等) 但永远不会调用 Listner。

有人知道为什么吗?

信息: 我正在使用真正的设备,而不是在模拟器下。使用忙碌循环的录音基本上可以工作(但不能令人满意)。只有回调监听器永远不会被调用。

这是我的源代码中的一个 sn-p:

public class myApplication extends Activity 

  /* audio recording */
  private static final int AUDIO_SAMPLE_FREQ = 16000;
  private static final int AUDIO_BUFFER_BYTESIZE = AUDIO_SAMPLE_FREQ * 2 * 3; // = 3000ms
  private static final int AUDIO_BUFFER_SAMPLEREAD_SIZE = AUDIO_SAMPLE_FREQ  / 10 * 2; // = 200ms

  private short[] mAudioBuffer = null; // audio buffer
  private int mSamplesRead; // how many samples are recently read
  private AudioRecord mAudioRecorder; // Audio Recorder

  ...

  private OnRecordPositionUpdateListener mRecordListener = new OnRecordPositionUpdateListener() 

    public void onPeriodicNotification(AudioRecord recorder) 
      mSamplesRead = recorder.read(mAudioBuffer, 0, AUDIO_BUFFER_SAMPLEREAD_SIZE);
      if (mSamplesRead > 0) 

        // do something here...

      
    

    public void onMarkerReached(AudioRecord recorder) 
      Error("What? Hu!? Where am I?");
    
  ;

  ...

  public void onCreate(Bundle savedInstanceState) 

  try 
      mAudioRecorder = new AudioRecord(
          android.media.MediaRecorder.Audiosource.MIC, 
          AUDIO_SAMPLE_FREQ,
          AudioFormat.CHANNEL_CONFIGURATION_MONO,
          AudioFormat.ENCODING_PCM_16BIT,  
          AUDIO_BUFFER_BYTESIZE);
     catch (Exception e) 
      Error("Unable to init audio recording!");
    

    mAudioBuffer = new short[AUDIO_BUFFER_SAMPLEREAD_SIZE];
    mAudioRecorder.setPositionNotificationPeriod(AUDIO_BUFFER_SAMPLEREAD_SIZE);
    mAudioRecorder.setRecordPositionUpdateListener(mRecordListener);
    mAudioRecorder.startRecording();

    /* test if I can read anything at all... (and yes, this here works!) */
    mSamplesRead = mAudioRecorder.read(mAudioBuffer, 0, AUDIO_BUFFER_SAMPLEREAD_SIZE);

  

【问题讨论】:

【参考方案1】:

我认为问题在于您仍然需要执行读取循环。如果您设置回调,它们将在您读取为回调指定的帧数时触发。但是您仍然需要进行读取。我已经尝试过了,回调被调用得很好。当自记录开始以来读取了该数量的帧时,设置标记会导致回调。换句话说,你可以将标记设置在很远的未来,在你多次阅读之后,它就会触发。您可以将周期设置为更大的帧数,并且每次读取该帧数时都会触发回调。我认为他们这样做是为了你可以在一个紧密的循环中对原始数据进行低级处理,然后你的回调可以经常进行摘要级处理。您可以使用标记更轻松地决定何时停止记录(而不是在读取循环中计数)。

【讨论】:

谢谢 - 你解释得很好。 您的回答很有帮助。如果您能告诉我如何正确读取麦克风的实时输入流,我将不胜感激。一个恒定的while循环似乎不合适,因为它会阻塞整个过程,对吗?我认为回调是一种解决方案,但从你的回答来看,它们不是。谢谢。 读取音频数据的常量while循环必须在单独的线程中。您当然不能使用主线程来读取音频。当收集到适当数量的音频数据时,回调将触发。您可以将处理逻辑放入读取循环中,或者您可以使用回调来触发对刚刚读取的缓冲区的处理。读取阻塞但仅在该线程内,所以您应该没问题。 看来根本不需要setRecordPositionUpdateListener。它增加了更多的混乱而不是好处。【参考方案2】:

这是我用来查找平均噪声的代码。请注意,它基于侦听器通知,因此可以节省设备电池。这绝对是基于上面的例子。这些例子为我节省了很多时间,谢谢。

private AudioRecord recorder;
private boolean recorderStarted;
private Thread recordingThread;
private int bufferSize = 800;
private short[][] buffers = new short[256][bufferSize];
private int[] averages = new int[256];
private int lastBuffer = 0;

protected void startListenToMicrophone() 
    if (!recorderStarted) 

        recordingThread = new Thread() 
            @Override
            public void run() 
                int minBufferSize = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                        AudioFormat.ENCODING_PCM_16BIT);
                recorder = new AudioRecord(AudioSource.MIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                        AudioFormat.ENCODING_PCM_16BIT, minBufferSize * 10);
                recorder.setPositionNotificationPeriod(bufferSize);
                recorder.setRecordPositionUpdateListener(new OnRecordPositionUpdateListener() 
                    @Override
                    public void onPeriodicNotification(AudioRecord recorder) 
                        short[] buffer = buffers[++lastBuffer % buffers.length];
                        recorder.read(buffer, 0, bufferSize);
                        long sum = 0;
                        for (int i = 0; i < bufferSize; ++i) 
                            sum += Math.abs(buffer[i]);
                        
                        averages[lastBuffer % buffers.length] = (int) (sum / bufferSize);
                        lastBuffer = lastBuffer % buffers.length;
                    

                    @Override
                    public void onMarkerReached(AudioRecord recorder) 
                    
                );
                recorder.startRecording();
                short[] buffer = buffers[lastBuffer % buffers.length];
                recorder.read(buffer, 0, bufferSize);
                while (true) 
                    if (isInterrupted()) 
                        recorder.stop();
                        recorder.release();
                        break;
                    
                
            
        ;
        recordingThread.start();

        recorderStarted = true;
    


private void stopListenToMicrophone() 
    if (recorderStarted) 
        if (recordingThread != null && recordingThread.isAlive() && !recordingThread.isInterrupted()) 
            recordingThread.interrupt();
        
        recorderStarted = false;
    

【讨论】:

这个例子似乎在录制过程中旋转 CPU。 while(true) 循环会被重复执行... 嗨@soul,你为什么将setPositionNotificationPeriod 的 periodInFrames 设置为与缓冲区大小相同。【参考方案3】:

现在我使用 “OnRecordPositionUpdateListener”。这看起来很有希望,而且 根据 SDK Docs 的正确方法。一切似乎都有效 (打开音频设备,读取()数据等)但Listner是 从来没有打电话。

有人知道为什么吗?

我发现 OnRecordPositionUpdateListener 会被忽略,直到您执行第一个 .read()

换句话说,我发现如果我按照文档设置所有内容,我的监听器永远不会被调用。但是,如果我在执行初始.start() 之后首先调用.read(),那么监听器就会被调用——前提是我每次调用监听器时都调用.read()

换句话说,Listener 事件似乎只对每个 .read() 或类似事件有效一次。

我还发现,如果我请求读取任何小于 buffSize/2 的样本,则不会调用 Listener。所以似乎只有在缓冲区大小至少一半的.read() 之后才调用侦听器。要继续使用侦听器回调,必须在每次运行侦听器时调用 read。 (换句话说,将调用 read 放在 Listener 代码中。)

但是,监听器似乎是在数据还没有准备好的时候被调用的,导致阻塞。

此外,如果您的通知周期或时间大于缓冲区大小的一半,它们似乎永远不会被调用。

更新:

随着我继续深入挖掘,我发现回调似乎只有在 .read() 完成时才会被调用......!

我不知道这是错误还是功能。我最初的想法是,在阅读的时候我想要一个回调。但也许 android 开发人员有相反的想法,你只需将 while(1)xxxx.read(...) 单独放在一个线程中,并且为了让你不必跟踪每次 read() 完成的时间,回调基本上可以读取完成后告诉您。

哦。也许我刚刚才恍然大悟。而且我认为其他人已经在我之前指出了这一点,但它并没有陷入:位置或周期回调必须对已经读取的字节进行操作......

我想也许我一直在使用线程。

在这种风格中,一旦返回,就会有一个线程不断调用 read(),因为 read 是阻塞的并且耐心地等待足够的数据返回。

然后,回调将独立地每隔 x 个样本调用您指定的函数。

【讨论】:

感谢您的研究。它有助于!关于这个问题的更多信息:永远不会在 API 16、17、18 上调用 onPeriodicNotification 回调,除非在开始后调用 read()。但是,似乎问题已在 API >=19 上得到解决。 DmitryO,非常感谢您提供更多信息!我迫不及待想尝试一下。【参考方案4】:

对于通过 Intent 服务录制音频的用户,他们也可能会遇到此回调问题。我最近一直在研究这个问题,我想出了一个简单的解决方案,您可以在单独的线程中调用您的录制方法。这应该在录制时调用 onPeriodicNotification 方法没有任何问题。

类似这样的:

public class TalkService extends IntentService 
...
@Override
protected void onHandleIntent(Intent intent) 
Context context = getApplicationContext();
tRecord = new Thread(new recordAudio());
tRecord.start();
...
while (tRecord.isAlive()) 
    if (getIsDone()) 
        if (aRecorder.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) 
            socketConnection(context, host, port);
        
    
 
...
class recordAudio implements Runnable 

        public void run() 
            try 
                OutputStream osFile = new FileOutputStream(file);
                BufferedOutputStream bosFile = new BufferedOutputStream(osFile);
                DataOutputStream dosFile = new DataOutputStream(bosFile);

                aRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
                        sampleRate, channelInMode, encodingMode, bufferSize);

                data = new short[bufferSize];

                aRecorder.setPositionNotificationPeriod(sampleRate);
                aRecorder
                        .setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() 
                            int count = 1;

                            @Override
                            public void onPeriodicNotification(
                                    AudioRecord recorder) 
                                Log.e(WiFiDirect.TAG, "Period notf: " + count++);

                                if (getRecording() == false) 
                                    aRecorder.stop();
                                    aRecorder.release();
                                    setIsDone(true);
                                    Log.d(WiFiDirect.TAG,
                                            "Recorder stopped and released prematurely");
                                
                            

                            @Override
                            public void onMarkerReached(AudioRecord recorder) 
                                // TODO Auto-generated method stub

                            
                        );

                aRecorder.startRecording();
                Log.d(WiFiDirect.TAG, "start Recording");
                aRecorder.read(data, 0, bufferSize);
                for (int i = 0; i < data.length; i++) 
                    dosFile.writeShort(data[i]);
                

                if (aRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) 
                    aRecorder.stop();
                    aRecorder.release();
                    setIsDone(true);
                    Log.d(WiFiDirect.TAG, "Recorder stopped and released");
                

             catch (Exception e) 
                // TODO: handle exception
            
        
    

【讨论】:

以上是关于Android:AudioRecord 类问题:从不调用回调的主要内容,如果未能解决你的问题,请参考以下文章

Android:使用 audiorecord 类录制音频播放快进

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

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

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

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

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