android-audiomanager - Android-dev AudioRecord 无阻塞或线程

标签 android-audiomanager android-audiorecord

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

我想这样做而不必使用线程也不需要 .read()在等待新的音频数据时阻塞。

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

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

这是我发现的:

初始化 AudioRecord 对象时,它会创建自己的内部环形缓冲区。
.start()被调用,它开始记录到所说的环形缓冲区(或任何类型的缓冲区。)

.read()被调用,它读取 bufferSize 的一半或指定的字节数(以较小者为准),然后返回。

如果内部缓冲区中有足够多的音频样本,则 read() 会立即返回数据。如果还不够,则 read() 等待直到有,然后返回数据。
.setRecordPositionUpdateListener()可用于设置监听器,以及.setPositionNotificationPeriod().setNotificationMarkerPosition()可用于分别设置通知周期和位置。

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

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

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

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

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

如果我设置了一个按钮 View 来点击阅读,那么我可以点击它,如果快速点击,阅读块。但是如果我等待音频缓冲区填满,那么第一次点击是即时的(读取立即返回)但随后的快速点击被阻止,因为 read() 必须等待,我猜。

非常感谢您对我如何让监听器按预期工作的任何见解 - 当有足够的数据供 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.

然后这是我的听众:
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");
    }
};

更新:我已经在 Android 2.2.3、2.3.4 和现在的 4.0.3 上尝试过这个,并且都一样。
另外:code.google 上有一个关于它的公开错误 - 一个条目由其他人于 2012 年开始,然后是我从 2013 年开始的一个条目(我不知道第一个条目):

2016 年更新:啊,经过多年的怀疑,我终于知道是我还是 android,我终于有了答案!我在 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

如果关心此错误的人登录并对该错误进行投票和/或评论,那么谷歌可能会更快地解决它。

最佳答案

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

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

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

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

关于android-audiomanager - Android-dev AudioRecord 无阻塞或线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15804903/

相关文章:

Android API 设置音量级别

Android - 是否可以找出哪个应用程序具有音频焦点?

android - setStreamMute 从不取消静音

android在pcm中获取设备整体音频输出

android - 如何在 Android 中强制使用外部麦克风和电话扬声器?

android - 中断车载 radio 安卓蓝牙

android - 如何在应用程序强制停止时获得回调

android - 增加录制音频的音量输出

Android AudioTrack 和 AudioRecord 奇怪的行为

java - Android: fragment *.post() 中的处理程序?