首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在iOS音频调用应用程序中使用循环缓冲区的原因是什么?

在iOS音频调用应用程序中使用循环缓冲区的原因是什么?
EN

Stack Overflow用户
提问于 2015-06-07 08:50:59
回答 2查看 2.9K关注 0票数 5

我的问题几乎是不言而喻的。如果看上去太傻了,很抱歉。

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

提前谢谢。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-06-07 20:31:00

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

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

所以这就是原因。还有一些其他细节的事情,但我认为这很简单,以便你更好地理解。如果您有任何问题,请在下面评论。

票数 2
EN

Stack Overflow用户

发布于 2015-06-07 21:50:06

使用循环缓冲区,您可以从它的源异步处理输入和输出数据。音频呈现过程发生在一个高优先级线程上。它要求您的应用程序提供音频示例(回放),并以回调的形式在计时器上提供音频(录制/处理)。

一个典型的场景是,音频回调每0.023秒触发一次,请求(和/或提供) 1024个音频示例。此线程与系统硬件同步,因此必须在0.023秒结束之前返回回调。如果你不这样做,硬件就不会等你了,它会跳过那个周期,你会有一个可听到的流行音乐或沉默,或者错过你想要录制的音频。

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

下面是一个示例,从麦克风中检索音频示例并将它们写入磁盘。你的应用程序订阅了一个每0.023秒启动一次的回调,提供了1024个要记录的样本。天真的方法是简单地从回调内部将音频写入磁盘。

代码语言:javascript
运行
复制
void myCallback(float *samples,int sampleCount, SampleSaver *saver){
    SampleSaverSaveSamples(saver,samples,sampleCount);
}

这会成功的!!大多数时候..。

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

正确的方法是使用循环缓冲区。我个人使用TPCircularBuffer是因为它很棒。它的工作方式(外部)是请求缓冲区将数据写入(头)到一个线程上,然后在另一个线程上请求缓冲区从(尾)读取指针。下面是如何使用TPCircularBuffer (跳过安装程序和使用简化的回调)来完成的。

代码语言:javascript
运行
复制
//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"

}

这一操作是超级快,没有额外的压力,对敏感的音频线程。然后创建自己的计时器--一个单独的线程--将示例写入磁盘。

代码语言:javascript
运行
复制
//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秒写入磁盘要容易得多!

使用缓冲区的主要原因是可以异步处理示例。它们也是在没有锁的线程之间传递消息的好方法。这里是一篇很好的文章,解释了循环缓冲区实现的一个简洁的内存技巧。

票数 18
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30691684

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档