我需要在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);
}
}
和MyData是:
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 Developer Library的AudioFileStreamExample版本,适合使用CBR数据。
此外,我查看了this帖子,并尝试了在那里描述的AudioStreamBasicDescription。并试图将我的旗帜改为Little或Big Endian。它没用。
我查看了这里和其他资源中的其他帖子,同时发现了类似的问题,我检查了我的PCM数据的顺序,例如。我只是不能发布两个以上的链接。
请有人帮我理解我做错了什么!也许我应该放弃这种方式并立即使用Audio Units?我只是CoreAudio中的新手,希望CoreAudio的中级水平能帮助我解决这个问题。
P.S。抱歉我的英语,我尽我所能。
答案 0 :(得分:2)
我希望你已经自己解决了这个问题,但是为了其他有这个问题的人的利益,我会发布一个答案。
问题很可能是因为一旦音频队列启动,即使你停止入队缓冲,时间也会继续前进。但是,当您将缓冲区排入队列时,它会在先前排队的缓冲区之后排列一个时间戳。这意味着如果您不在音频队列正在播放的位置之前,您将最终使用过去的时间戳排队缓冲区,因此音频队列将保持静默并且isRunning属性仍然为true。
要解决此问题,您有几个选择。最简单的理论是永远不会落后于提交缓冲区。但由于您使用的是UDP,因此无法保证您始终可以提交数据。
另一个选择是你可以跟踪你应该播放的样本,并在需要有空隙时提交一个空的静音缓冲区。如果源数据具有可用于计算所需静音的时间戳,则此选项很有效。但理想情况下,你不需要这样做。
相反,您应该使用系统时间计算缓冲区的时间戳。而不是AudioQueueEnqueueBuffer,您需要使用AudioQueueEnqueueBufferWithParameters。您只需要确保时间戳位于队列当前所在的位置。您还必须跟踪启动队列时的系统时间,以便为要提交的每个缓冲区计算正确的时间戳。如果源数据上有时间戳值,则应该能够使用它们来计算缓冲区时间戳。