Android-dev AudioRecord没有阻塞或线程

Jes*_*don 7 android-audiomanager android-audiorecord

我希望录制麦克风音频流,以便我可以在其上进行实时DSP.

我希望这样做而不必使用线程,并且.read()在等待新的音频数据时没有阻塞.

更新/答案:这是Android中的一个错误.4.2.2仍有问题,但5.01已经固定!我不确定分歧在哪里,但这就是故事.

注意:请不要说"只使用线程".线程很好,但这不是关于它们的,Android开发人员打算让AudioRecord完全可用,而不必指定线程,也不必处理阻塞read().谢谢!

这是我发现的:

初始化AudioRecord对象时,它会创建自己的内部环型缓冲区.当.start()被调用时,它开始记录到所述环形缓冲区(或者它实际上是什么类型).

.read()被调用时,它读取缓冲区大小的任一半或指定的字节数(取较小),然后返回.

如果内部缓冲区中有足够多的音频样本,则read()会立即返回数据.如果还不够,则read()等待直到有,然后返回数据.

.setRecordPositionUpdateListener()可用于设定的监听器,并且.setPositionNotificationPeriod().setNotificationMarkerPosition()可用于设置通知周期和位置,分别.

但是,除非满足某些要求,否则似乎永远不会调用Listener:

1:Period或Position必须等于bufferSize/2或(bufferSize/2)-1.

2:.read()必须在Period或Position计时器开始计数之前调用A - 换句话说,在调用.start()之后也调用.read(),并且每次调用Listener时,.read()再次调用.

3:.read()每次必须至少读取一半bufferSize.

因此,使用这些规则,我能够使回调/监听器工作,但由于某种原因,读取仍然是阻塞的,我无法弄清楚如何只有在有完整的读取值时才调用监听器.

如果我设置一个按钮视图点击阅读,那么我可以点击它,如果快速点击,读取块.但是,如果我等待音频缓冲区填充,那么第一次点击是即时的(读取立即返回),但是由于read()必须等待,我猜测是否会暂停快速点击.

非常感谢有关如何使Listener按预期工作的任何见解 - 以这样一种方式,当有足够的read()数据立即返回时,我的监听器被调用.

下面是我的代码的相关部分.

我的代码中有一些日志语句,它们向logcat发送字符串,这使我可以看到每个命令的执行时间,这就是我知道read()是阻塞的.(而且我的简单测试应用程序中的按钮在重复读取时响应非常慢,但CPU没有挂钩.)

谢谢,〜杰西

在我的OnCreate()中:

    bufferSize=AudioRecord.getMinBufferSize(samplerate,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT)*4;
        recorder = new AudioRecord (AudioSource.MIC,samplerate,AudioFormat.CHANNEL_CONFIGURATION_MONO,AudioFormat.ENCODING_PCM_16BIT,bufferSize);
        recorder.setRecordPositionUpdateListener(mRecordListener);
        recorder.setPositionNotificationPeriod(bufferSize/2);
        //recorder.setNotificationMarkerPosition(bufferSize/2);
        audioData = new short [bufferSize];

    recorder.startRecording();
            samplesread=recorder.read(audioData,0,bufferSize);//This triggers it to start doing the callback.
Run Code Online (Sandbox Code Playgroud)

那么这是我的倾听者:

public OnRecordPositionUpdateListener mRecordListener = new OnRecordPositionUpdateListener() 
{
    public void onPeriodicNotification(AudioRecord recorder) //This one gets called every period. 
    {
        Log.d("TimeTrack", "AAA");
        samplesread=recorder.read(audioData,0,bufferSize);
        Log.d("TimeTrack", "BBB");
        //player.write(audioData, 0, samplesread);
        //Log.d("TimeTrack", "CCC");
        reads++;
    }
    @Override
    public void onMarkerReached(AudioRecord recorder) //This one gets called only once -- when the marker is reached.
    {
        Log.d("TimeTrack", "AAA");
        samplesread=recorder.read(audioData,0,bufferSize);
        Log.d("TimeTrack", "BBB");
        //player.write(audioData, 0, samplesread);
        //Log.d("TimeTrack", "CCC");
    }
};
Run Code Online (Sandbox Code Playgroud)

更新:我在Android 2.2.3,2.3.4和现在4.0.3上尝试了这一点,并且所有行为都相同.另外:code.google上有一个关于它的公开错误 - 一个条目从2012年开始由其他人开始,然后一个从2013年开始由我开始(我不知道第一个):

更新2016:Ahhhh经过多年的想知道,无论是我还是机器人,我终于有了答案!我在4.2.2和同样的问题上尝试了我的上述代码.我在5.01上尝试了上面的代码,而且它的工作原理!并且不再需要初始的.read()调用.现在,一旦.setPositionNotificationPeriod()和.StartRecording()被称为,mRecordListener()神奇地开始被称为每次有数据可用的时间,现在,使其不再块,因为回调不叫,直到经过足够的数据已经被记录.我没有听过数据知道它是否正确记录,但是回调正在发生,就像它应该的那样,并且它不会阻止活动,就像过去一样!

http://code.google.com/p/android/issues/detail?id=53996

http://code.google.com/p/android/issues/detail?id=25138

如果关心这个错误的人登录并投票和/或评论该错误,谷歌可能会尽快解决.

小智 0

我不确定为什么你要避免生成单独的线程,但如果是因为你不想正确处理它们的编码,你可以在每次 .read 之后在 Timer 对象上使用 .schedule ,其中时间间隔是设置为填充缓冲区所需的时间(缓冲区中的样本数/sampleRate)。是的,我知道这是使用单独的线程,但是给出这个建议是假设您避免使用线程的原因是为了避免必须正确编码它们。

这样,它可能阻塞线程的最长时间应该可以忽略不计。但我不知道你为什么要这么做。

如果上述原因不是您避免使用单独线程的原因,我可以问为什么吗?

另外,实时到底是什么意思?您打算使用 AudioTrack 来播放受影响的音频吗?因为大多数 Android 设备上的延迟都非常糟糕。