使用 NSData 解决生产者-消费者问题(用于音频流)

Posted

技术标签:

【中文标题】使用 NSData 解决生产者-消费者问题(用于音频流)【英文标题】:Solving a producer-consumer problem with NSData (for audio streaming) 【发布时间】:2011-01-10 11:43:18 【问题描述】:

我正在使用 AVAssetReader 将 PCM 数据从 iPod 轨道复制到缓冲区,然后使用 RemoteIO 音频单元播放。我正在尝试创建一个单独的线程来加载声音数据,以便在加载数据时可以访问和播放缓冲区中的数据。

我目前有一个大型 NSMutableData 对象,它最终保存了整首歌曲的数据。目前,我使用 NSOperation 在单独的线程中加载音频数据,如下所示:

    AVAssetReaderOutput 一次最多复制 8192 个字节到 CMBlockBuffer 将这些字节复制到 NSData 对象中 将此 NSData 对象附加到更大的 NSMutableData 对象(最终保存整首歌曲) 完成后,通过访问 NSMutableData 对象中的每个数据包来播放歌曲

我正在尝试在复制这些字节的同时播放歌曲。我不确定同时写入和读取文件的好方法是什么。

我有一个简短的想法:

    创建并填充 3 个 NSData 对象,每个对象的长度为 8192 字节,作为缓冲区。 开始播放。当我播放完第一个缓冲区后,将新数据加载到第一个缓冲区中。 播放完第二个缓冲区后,将新数据加载到第二个缓冲区中。第三个也一样 再次从第一个缓冲区开始播放,填充第三个缓冲区。等等。

或者,创建一个包含 3 * 8192 个 PCM 单元的 NSData 对象,并以某种方式使用两个不同的线程同时对其进行写入和读取。

我的代码现在在两个不同的线程上工作。我将数据附加到数组,直到我按下播放,此时它停止(可能是因为线程被阻塞,但我现在不知道)并播放直到它到达我加载的任何内容的末尾并导致 EXC_BAD_ACCESS 异常。

简而言之,我想在 PCM 数据被复制时找到正确的播放方式,比如一次复制 8192 个字节。我可能不得不使用另一个线程来执行此操作(我现在正在使用 NSOperation),但不清楚如何同时写入和读取缓冲区,最好使用一些更高级别的 Objective-C 方法。

【问题讨论】:

【参考方案1】:

我正在做这件事。您肯定需要在不同的线程上播放音频(我正在使用 RemoteIO 执行此操作)。您还需要使用循环缓冲区。如果您不熟悉此数据结构,您可能需要查找它,因为您将在此类操作中大量使用它。我的一般设置如下:

Lo​​adTrackThread 启动并开始从 AVAssetReader 加载数据并将其作为 PCM 存储在文件中。 一旦有足够的数据加载到我的 PCM 文件中,LoadPCMThread 就会启动,并且本质上将该文件加载到本地内存中以供我的 RemoteIO 线程按需使用。每当我的 RemoteIO 线程接近用完样本时,它就会将此数据输入循环缓冲区。 RemoteIO 播放回调线程消耗循环缓冲区帧并将它们提供给 RemoteIO 接口。它还通知 LoadPCMThread 在需要开始加载更多样本时唤醒。

就线程而言,这应该是您所需要的一切。您将需要在两个线程之间使用某种互斥锁或信号量,以确保您在同时写入文件时不会尝试读取文件(这是错误的形式,会导致您崩溃)。我只是让我的两个线程都设置一个布尔值并休眠一段时间,直到它被取消设置。可能有一种更复杂的方法可以做到这一点,但它适用于我的目的。

希望有帮助!

【讨论】:

以上是关于使用 NSData 解决生产者-消费者问题(用于音频流)的主要内容,如果未能解决你的问题,请参考以下文章

译使用阻塞队列解决生产者-消费者问题

用于图像处理的基于生产者-消费者的多线程

Java中的生产者消费者问题

如何在两个线程中使用队列——一个用于消费者,一个用于生产者

设计 Kafka 消费者和生产者以实现可扩展性

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。