在 SpeakHere 示例中,播放是通过以下方式实现的AudioQueue
.
在设置中AudioQueue
,指定一个函数,当队列需要更多数据时将调用该函数。
你可以在这个方法中看到:
void AQPlayer::SetupNewQueue()
这是指定回调函数的行:
XThrowIfError(AudioQueueNewOutput(&mDataFormat, AQPlayer::AQBufferCallback, this,
CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &mQueue), "AudioQueueNew failed");
如果你看一下AQPlayer::AQBufferCallback
,您将看到它从哪里获取数据。在此示例中,数据已写出到磁盘上的文件中。如果您想节省内存,或者音频文件可能非常大,那么这是一个很好的解决方案。
无论如何,看着AQPlayer::AQBufferCallback
,你会看到一个函数的调用AudioFileReadPackets
。这就是从磁盘上的文件中读取音频数据包的方法。它将它们直接读入缓冲区AudioQueue
将使用:
OSStatus result = AudioFileReadPackets(THIS->GetAudioFileID(), false, &numBytes, inCompleteAQBuffer->mPacketDescriptions, THIS->GetCurrentPacket(), &nPackets,
inCompleteAQBuffer->mAudioData);
该缓冲区是inCompleteAQBuffer->mAudioData
.
最后,回调函数必须将缓冲区排入队列,如下所示:
if (nPackets > 0) {
inCompleteAQBuffer->mAudioDataByteSize = numBytes;
inCompleteAQBuffer->mPacketDescriptionCount = nPackets;
AudioQueueEnqueueBuffer(inAQ, inCompleteAQBuffer, 0, NULL);
THIS->mCurrentPacket = (THIS->GetCurrentPacket() + nPackets);
}
首先请注意,它必须检查我们是否有一些数据包要播放。它还必须指定缓冲区中有多少字节。
然后,这里有这一行:
THIS->mCurrentPacket = (THIS->GetCurrentPacket() + nPackets);
它记录了我们所在的位置overall在我们的音频缓冲区中。换句话说,随着从文件中复制更多数据,我们需要定位mCurrentPacket
下一个副本将数据放在正确的位置。