在iOS音频呼叫APP中使用循环缓冲区的原因是什么?

时间:2015-06-07 08:50:59

标签: ios objective-c audio core-audio audiounit

我的问题几乎是自我解释的。抱歉,如果它看起来太愚蠢了。

我正在编写iOS VoIP拨号器并检查了一些开源代码(iOS音频呼叫应用程序)。几乎所有这些都使用循环缓冲区来存储录制和接收的PCM音频数据。所以我想知道为什么在这种情况下我们需要使用循环缓冲区。使用此类音频缓冲区的确切原因是什么。

提前致谢。

2 个答案:

答案 0 :(得分:15)

使用循环缓冲区可以从源代码中异步处理输入和输出数据。音频渲染过程发生在高优先级线程上。它会从您的应用程序(回放)中请求音频样本,并以回调的形式在计时器上提供音频(录制/处理)。

典型的情况是音频回调每隔0.023秒触发一次,要求(和/或提供)1024个音频样本。此线程与系统硬件同步,因此必须在0.023秒之前返回您的回调。如果你不这样做,硬件将不会等你,它只会跳过那个循环,你会听到弹出或静音,或者你想要录制的音频错过。

循环缓冲区的位置是在线程之间传递数据。在一个音频应用程序中,它将异步地将样本移入和移出音频线程。一个线程将样本生成到缓冲区的“头部”,另一个线程从“尾部”消耗它们。

这是一个示例,从麦克风中检索音频样本并将其写入磁盘。您的应用已订阅每0.023秒触发一次的回调,提供1024个样本进行记录。天真的方法是简单地从该回调中将音频写入磁盘。

void myCallback(float *samples,int sampleCount, SampleSaver *saver){
    SampleSaverSaveSamples(saver,samples,sampleCount);
}

这会起作用!!大多数时候......

问题在于无法保证写入磁盘的时间会在0.023秒之前完成,因此您的录音会不时出现,因为SampleSaver只是花了太长时间而硬件只是跳过了下一个回调。

正确的方法是使用循环缓冲区。我个人使用TPCircularBuffer,因为它太棒了。它的工作方式(外部)是你要求缓冲区指向一个线程上的(头部)写入数据的指针,然后在另一个线程上请求缓冲区读取指针(尾部)。以下是使用TPCircularBuffer完成的操作(跳过设置并使用简化的回调)。

//this is on the high priority thread that can't wait for anything like a slow write to disk
void myCallback(float *samples,int sampleCount, TPCircularBuffer *buffer){
    int32_t availableBytes = 0;
    float *head = TPCircularBufferHead(buffer, &availableBytes);
    memcpy(head,samples,sampleCount * sizeof(float));//copies samples to head
    TPCircularBufferProduce(buffer,sampleCount * sizeof(float)); //moves buffer head "forward in the circle"

}

此操作非常快,并且不会对该敏感音频线程施加额外压力。然后,您可以创建一个单独的线程,将样本写入磁盘。

//this is on some background thread that can take it's sweet time
void myLeisurelySavingCallback(TPCircularBuffer *buffer, SampleSaver *saver){
    int32_t available;
    float *tail = TPCircularBufferTail(buffer, &available);
    int samplesInBuffer = available / sizeof(float); //mono 
    SampleSaverSaveSamples(saver, tail, samplesInBuffer);
    TPCircularBufferConsume(buffer, samplesInBuffer * sizeof(float)); // moves tail forward
}

你有它,不仅可以避免音频故障,而且如果你初始化一个足够大的缓冲区,你可以设置你的写入磁盘回调只能每隔一两秒激活一次(在循环缓冲区建立一个之后)你的系统比在0.023秒写入磁盘要容易得多!

使用缓冲区的主要原因是可以异步处理样本。它们是在没有锁的情况下在线程之间传递消息的好方法。 Here是一篇很好的文章,解释了实现循环缓冲区的整洁记忆技巧。

答案 1 :(得分:2)

好问题。使用循环缓冲区还有另一个很好的理由。

在iOS中,如果你使用回调(音频单元)来录制和播放音频(实际上你需要使用它,如果你想创建一个实时音频传输应用程序)那么你将获得一大块数据记录器回调的特定时间量(比如说20毫秒)。在iOS中,您永远不会得到固定长度的数据(如果您将回调间隔设置为20ms,那么您将获得370或372字节的数据。而且您永远不会知道何时获得370字节或372字节。纠正我如果我错了)。然后,要通过UDP数据包传输音频,您需要使用编解码器进行数据编码和解码(G729通常用于VoIP应用)。但是g729乘以8的乘数。假设你每20ms编码368(8 * 46)个字节。那你打算用其他数据做什么呢?您需要将按序列存储到下一个要处理的块中。

这就是原因。还有一些其他的细节,但为了你更好的理解,我很简单。如果您有任何疑问,请在下方发表评论。