Ios 与核心音频线程同步

Ios 与核心音频线程同步,ios,multithreading,performance,audio,core-audio,Ios,Multithreading,Performance,Audio,Core Audio,我使用ioUnit的render回调将音频数据存储到循环缓冲区中: OSStatus ioUnitRenderCallback( void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp,

我使用ioUnit的render回调将音频数据存储到循环缓冲区中:

OSStatus ioUnitRenderCallback(
                          void *inRefCon,
                          AudioUnitRenderActionFlags *ioActionFlags,
                          const AudioTimeStamp *inTimeStamp,
                          UInt32 inBusNumber,
                          UInt32 inNumberFrames,
                          AudioBufferList *ioData)
{
    OSStatus err = noErr;

    AMNAudioController *This = (__bridge AMNAudioController*)inRefCon;

    err = AudioUnitRender(This.encoderMixerNode->unit,
                          ioActionFlags,
                          inTimeStamp,
                          inBusNumber,
                          inNumberFrames,
                          ioData);

    // Copy the audio to the encoder buffer
    TPCircularBufferCopyAudioBufferList(&(This->encoderBuffer), ioData, inTimeStamp, kTPCircularBufferCopyAll, NULL);

    return err;
}
然后我想从循环缓冲区中读取字节,将它们馈送到libLame,然后馈送到libShout。 我尝试启动一个线程,并使用NSCondition使其等待数据可用,但由于在核心音频回调上使用锁,这会导致各种问题

推荐的方法是什么

提前谢谢

关于我如何实现Adam答案的更多细节 我最终采纳了亚当的建议,并像这样付诸实施

制作人

我在Core Audio Render回调中使用TPCircularBufferProduceBytes将字节添加到循环缓冲区。在我的例子中,我有非交错音频数据,所以我最终使用了两个循环缓冲区

消费者

我使用pthread_create生成一个新线程 在新线程中创建一个新的CFTimer并将其添加到当前线程中 CFRunLoop 0.005秒的间隔似乎工作正常 我告诉当前的CFRunLoop运行 在计时器回调中,我对音频进行编码,并将其发送到服务器,如果没有数据缓冲,则会快速返回 我还有一个5MB的缓冲区,它看起来工作得很好,2MB给了我超限。这似乎有点高:/
使用重复计时器NSTimer或CADisplayLink轮询无锁循环缓冲区或FIFO。如果缓冲区中没有足够的数据,则跳过工作,并返回到运行循环。这是因为您知道采样率具有很高的准确性,以及一次需要处理多少数据,因此为了安全起见,可以将轮询率设置得稍微快一点,但仍然非常接近使用条件锁的效率


不建议在实时音频线程回调中使用信号量、锁或任何其他延迟不可预测的东西。

您的思路是正确的,但不需要任何条件。你肯定不想阻止。您正在使用的循环缓冲区实现是无锁的,应该可以做到这一点。在音频渲染回调中,通过调用TPCircularBufferProduceBytes将数据放入缓冲区。然后,在读卡器上下文中,计时器回调是好的,正如hotpaw所建议的那样,调用TPCircularBufferTail来获取尾部指针读取地址和要读取的可用字节数,然后调用TPCircularBufferConsume来进行实际读取。现在你已经完成了转移,没有带任何锁。只要确保分配的缓冲区足够大,以处理最坏的情况,即读卡器线程因任何原因被操作系统阻止,否则您可能会遇到缓冲区溢出情况,并将丢失数据。

从上面的示例中,不清楚哪个线程正在阻止另一个线程。阻止Core Audio的线程绝对不是您应该做的事情-否则可能存在优先级反转。@marko,如果我使用NSCondition让线程等待Core Audio线程设置值,则Core Audio线程将锁定。从那以后,我尝试使用信号量,这似乎是一种方法。TPCircularBufferCopyAudioBufferList在做什么?循环缓冲区已满时是否会阻塞?这当然可以解释你所看到的僵局。让你的应用程序处于这种状态,在调试器中中断它,并在这里发布线程堆栈的回溯。谢谢@hotpaw2。所以我应该在我的新线程上实例化一个nsrunlop,然后使用大约每200ms运行一次的NSTimer?谢谢@Adam Bryant。一个26460字节的缓冲区应该能容纳30秒的音频,这当然足够了。但是很明显,如果在最坏的情况下缓冲区满了,我不能在一个包中发送这么多。所以,在我的计时器的每次迭代中发送一个固定的量是最好的,不是吗?在这种情况下,如果我的计时器每200毫秒17640字节200毫秒的音频关闭一次,我应该发送多少?如果没有足够的字节,我应该发送5秒钟的音频,让计时器快速返回?我不知道你正在做什么的所有细节,但你需要以与产生音频相同的速率消耗音频,否则最终你会得到一个缓冲区不足或溢出。您知道音频的生成速率-通常为每秒44100个采样。现在做一个数学运算,设置你的计时器,让你在你的消费者叫喊的块大小时醒来?首选项。一旦开始滚动,在大多数计时器唤醒期间,您只需消耗一个数据包。但有时当你醒来时,你会发现消费者线程由于任何原因被延迟,你需要使用多个数据包。您可以通过调用TPCircularBufferTail来确定这一点,它会告诉您有多少音频等待您在环形缓冲区“availableBytes”中使用。基本上,您希望在每次醒来时将缓冲区耗尽到最接近的消费者数据包大小。如果有多个数据包,只需坐在一个循环中发送到libShout,直到所有数据包都被消耗掉。在客户机/服务器音频系统中,通常接收端是累积的
它是一个音频库,如果网络暂停,音频暂时无法传输,它可以从中提取音频。因此,这意味着在播放之前,您需要在开始时为系统注入音频。这个存储库的容量取决于您预期的网络中断程度。不过,存储库越大,延迟就越长,因此通常您会尝试通过实验找到正确的平衡。