使用 CoreAudio 中的 AudioQueue 从网络播放原始 pcm
Posted
技术标签:
【中文标题】使用 CoreAudio 中的 AudioQueue 从网络播放原始 pcm【英文标题】:playback raw pcm from network using AudioQueue in CoreAudio 【发布时间】:2014-12-04 15:32:16 【问题描述】:我需要在 OS X 上使用 CoreAudio 播放原始 PCM 数据(16 位签名)。我使用 UDP 套接字从网络获取它(发送方的数据是从麦克风捕获的)。 问题是我现在听到的只是一些短暂的破裂声,然后只有沉默。 我正在尝试使用 AudioQueue 播放数据。我是这样设置的:
// Set up stream format fields
AudiostreamBasicDescription streamFormat;
streamFormat.mSampleRate = 44100;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsBigEndian | kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
streamFormat.mBitsPerChannel = 16;
streamFormat.mChannelsPerFrame = 1;
streamFormat.mBytesPerPacket = 2 * streamFormat.mChannelsPerFrame;
streamFormat.mBytesPerFrame = 2 * streamFormat.mChannelsPerFrame;
streamFormat.mFramesPerPacket = 1;
streamFormat.mReserved = 0;
OSStatus err = noErr;
// create the audio queue
err = AudioQueueNewOutput(&streamFormat, MyAudioQueueOutputCallback, myData, NULL, NULL, 0, &myData->audioQueue);
if (err)
PRINTERROR("AudioQueueNewOutput"); myData->failed = true; result = false;
// allocate audio queue buffers
for (unsigned int i = 0; i < kNumAQBufs; ++i)
err = AudioQueueAllocateBuffer(myData->audioQueue, kAQBufSize, &myData->audioQueueBuffer[i]);
if (err)
PRINTERROR("AudioQueueAllocateBuffer"); myData->failed = true; break; result = false;
// listen for kAudioQueueProperty_IsRunning
err = AudioQueueAddPropertyListener(myData->audioQueue, kAudioQueueProperty_IsRunning, MyAudioQueueIsRunningCallback, myData);
if (err)
PRINTERROR("AudioQueueAddPropertyListener"); myData->failed = true; result = false;
MyAudioQueueOutputCallback 是:
void MyAudioQueueOutputCallback(void* inClientData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer)
// this is called by the audio queue when it has finished decoding our data.
// The buffer is now free to be reused.
MyData* myData = (MyData*)inClientData;
unsigned int bufIndex = MyFindQueueBuffer(myData, inBuffer);
// signal waiting thread that the buffer is free.
pthread_mutex_lock(&myData->mutex);
myData->inuse[bufIndex] = false;
pthread_cond_signal(&myData->cond);
pthread_mutex_unlock(&myData->mutex);
MyAudioQueueIsRunningCallback 是:
void MyAudioQueueIsRunningCallback(void* inClientData,
AudioQueueRef inAQ,
AudioQueuePropertyID inID)
MyData* myData = (MyData*)inClientData;
UInt32 running;
UInt32 size;
OSStatus err = AudioQueueGetProperty(inAQ, kAudioQueueProperty_IsRunning, &running, &size);
if (err) PRINTERROR("get kAudioQueueProperty_IsRunning"); return;
if (!running)
pthread_mutex_lock(&myData->mutex);
pthread_cond_signal(&myData->done);
pthread_mutex_unlock(&myData->mutex);
而我的数据是:
struct MyData
AudioQueueRef audioQueue; // the audio queue
AudioQueueBufferRef audioQueueBuffer[kNumAQBufs]; // audio queue buffers
AudioStreamPacketDescription packetDescs[kAQMaxPacketDescs]; // packet descriptions for enqueuing audio
unsigned int fillBufferIndex; // the index of the audioQueueBuffer that is being filled
size_t bytesFilled; // how many bytes have been filled
size_t packetsFilled; // how many packets have been filled
bool inuse[kNumAQBufs]; // flags to indicate that a buffer is still in use
bool started; // flag to indicate that the queue has been started
bool failed; // flag to indicate an error occurred
bool finished; // flag to inidicate that termination is requested
pthread_mutex_t mutex; // a mutex to protect the inuse flags
pthread_mutex_t mutex2; // a mutex to protect the AudioQueue buffer
pthread_cond_t cond; // a condition varable for handling the inuse flags
pthread_cond_t done; // a condition varable for handling the inuse flags
;
如果我发布了太多代码,我很抱歉 - 希望它可以帮助任何人了解我到底在做什么。
我的代码主要基于 this 代码,它是 Mac 开发人员库中适用于 CBR 数据的 AudioFileStreamExample 版本。
我还查看了this 的帖子并尝试了那里描述的 AudioStreamBasicDescription。并尝试将我的标志更改为 Little Endian 或 Big Endian。它没有用。 例如,我在此处和其他资源中查看了一些其他帖子,同时发现了类似的问题,例如,我检查了 PCM 数据的顺序。我只能发布两个以上的链接。
请任何人帮助我理解我做错了什么!也许我应该放弃这种方式并立即使用音频单元?我只是CoreAudio的新手,希望CoreAudio的中级能帮助我解决这个问题。
附:对不起我的英语,我尽力了。
【问题讨论】:
MyFindQueueBuffer
长什么样子?
就这样:int MyFindQueueBuffer(MyData* myData, AudioQueueBufferRef inBuffer) for (unsigned int i = 0; i < kNumAQBufs; ++i) if (inBuffer == myData->audioQueueBuffer[i]) return i; return -1;
对不起,换行符 %)
您是否在用户级驱动程序中执行此操作?我有一个非常相似的驱动程序正在开发中,但是在核心音频驱动程序中遇到了网络沙盒问题。你遇到过这个吗?
@foobar 你是怎么解决的?
【参考方案1】:
我希望你已经自己解决了这个问题,但是为了其他遇到这个问题的人的利益,我会发布一个答案。
这个问题很可能是因为一旦启动了音频队列,即使您停止将缓冲区排队,时间也会继续前进。但是,当您将缓冲区排入队列时,它会使用位于先前入队缓冲区之后的时间戳进行排入队列。这意味着,如果您不领先于音频队列正在播放的位置,您最终将使用过去的时间戳将缓冲区排入队列,因此音频队列将变为静音并且 isRunning 属性仍然为 true。
要解决此问题,您有几个选择。理论上最简单的方法是永远不会落后于提交缓冲区。但由于您使用的是 UDP,因此无法保证您将始终有数据要提交。
另一种选择是,您可以跟踪您应该播放的样本,并在需要间隔时提交一个空的静音缓冲区。如果您的源数据具有可用于计算您需要多少静音的时间戳,则此选项非常有效。但理想情况下,您不需要这样做。
相反,您应该使用系统时间计算缓冲区的时间戳。您需要使用 AudioQueueEnqueueBufferWithParameters 而不是 AudioQueueEnqueueBuffer。您只需要确保时间戳在队列当前所在的位置之前。您还必须跟踪启动队列时的系统时间,以便为您提交的每个缓冲区计算正确的时间戳。如果您的源数据有时间戳值,您应该也可以使用它们来计算缓冲区时间戳。
【讨论】:
以上是关于使用 CoreAudio 中的 AudioQueue 从网络播放原始 pcm的主要内容,如果未能解决你的问题,请参考以下文章
使用 CoreAudio 获取正确的 FileLengthFrames
CoreAudio:AudioUnit 既不能停止也不能未初始化