我的问题几乎是不言而喻的。如果看上去太傻了,很抱歉。
我正在编写一个iOS VoIP拨号程序,并检查了一些开源代码(iOS音频调用应用程序)。几乎所有这些都使用循环缓冲器存储记录和接收的PCM音频数据。所以我想知道为什么在这种情况下我们需要使用循环缓冲区。使用这种音频缓冲区的确切原因是什么。
提前谢谢。
发布于 2015-06-07 20:31:00
问得好。使用循环缓冲区还有另一个很好的理由。
在iOS中,如果您使用回调(音频单元)来录制和播放音频(实际上,如果您想创建一个实时音频传输应用程序,您需要使用它),那么您将从记录器回调中获得一段特定时间(例如20毫秒)的数据。在iOS中,永远不会得到固定长度的数据(如果将回调间隔设置为20 as,那么就会得到370或372字节的数据)。你永远不会知道什么时候你会得到370字节或372字节。如果我错了,请纠正我。然后,要通过UDP数据包传输音频,需要使用编解码器进行数据编码和解码(G729通常用于VoIP应用程序)。但是g729以8的乘数接收数据,假设每20毫秒编码368(8*46)字节。那么,你打算如何处理其余的数据呢?您需要按序列存储它,以便处理下一个块。
所以这就是原因。还有一些其他细节的事情,但我认为这很简单,以便你更好地理解。如果您有任何问题,请在下面评论。
发布于 2015-06-07 21:50:06
使用循环缓冲区,您可以从它的源异步处理输入和输出数据。音频呈现过程发生在一个高优先级线程上。它要求您的应用程序提供音频示例(回放),并以回调的形式在计时器上提供音频(录制/处理)。
一个典型的场景是,音频回调每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秒写入磁盘要容易得多!
使用缓冲区的主要原因是可以异步处理示例。它们也是在没有锁的线程之间传递消息的好方法。这里是一篇很好的文章,解释了循环缓冲区实现的一个简洁的内存技巧。
https://stackoverflow.com/questions/30691684
复制相似问题