在iOS Audio Calling APP中使用循环缓冲区的原因是什么?

我的问题是非常自我解释。 对不起,如果它似乎太愚蠢。

我正在写一个iOS VoIP拨号程序,并检查了一些开源代码(iOSaudio呼叫应用程序)。 几乎所有的人都使用循环缓冲存储logging和接收的PCMaudio数据。 所以我想知道为什么我们需要在这种情况下使用循环缓冲区。 使用这种audio缓冲器的确切原因是什么?

提前致谢。

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

在iOS中,如果您使用callback(audio单元)录制和播放audio(实际上,如果您想创build实时audio传输应用程序,则需要使用它),那么您将获得一定数量的数据logging器callback的时间(比如20毫秒)。 在iOS中,永远不会得到固定的数据长度(如果你设置的callback间隔为20ms,那么你将得到370或372字节的数据,你永远不会知道什么时候你会得到370字节或372字节。如果我错了)。 然后,要通过UDP数据包传输audio,您需要使用编解码器进行数据编码和解码(G729通常用于VoIP应用程序)。 但g729以8的乘数取数据。假设你每20ms编码368(8 * 46)个字节。 那么你打算怎么处理其余的数据呢? 您需要按顺序存储它以处理下一个块。

所以这就是原因。 还有一些其他的细节,但我为了更好的理解而简化了它。 如果您有任何问题,请在下面留言。

使用循环缓冲区可以让您从源代码asynchronous处理input和输出数据。 audio渲染过程发生在高优先级的线程上。 它要求从你的应用(回放)的audio样本,并提供audio(logging/处理)在callbackforms的定时器。

典型的情况是audiocallback每0.023秒触发一次,询问(和/或提供)1024个audio样本。 这个线程与系统硬件同步,所以你的callback在0.023秒之前返回是必要的。 如果你不这样做,硬件不会等待你,它只会跳过这个循环,你会听到stream行或沉默,或者想念你想录制的audio。

循环缓冲区的地方是在线程之间传递数据。 在一个audio应用程序中,这个应用程序将asynchronous地移动audio线程到audio线程。 一个线程产生样本到缓冲区的“头部”,另一个线程从“尾部”消耗它们。

以下是一个例子,从麦克风中获取audio样本并将其写入磁盘。 您的应用程序订阅了一个每0.023秒触发一次的callback,提供1024个样本进行logging。 简单的做法是简单地将audio从callback中写入磁盘。

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

这将工作! 大多数时候…

问题在于,不能保证写入磁盘将在0.023秒之前完成,所以你的录音有时会popup,因为SampleSaver只是简单的花了很长时间,硬件只是跳过下一个callback。

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

 //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" } 

这个操作非常快速,并且不会对这个敏感audio线程产生额外的压力。 然后,您创build自己的计时器一个单独的线程将样本写入磁盘。

 //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 } 

在那里,你不仅可以避免audio故障,而且如果你初始化一个足够大的缓冲区,你可以设置你的写入磁盘callback,每隔一秒钟或两次触发(在循环缓冲区已经build立好了一点audio),这比在0.023秒内写入磁盘要容易得多!

使用缓冲区的主要原因是样本可以asynchronous处理。 它们是在线程之间传递消息而不locking的好方法。 这里有一篇很好的文章解释了一个循环缓冲区实现的简单内存技巧。