使用CoreAudio中的AudioQueue从网络播放原始pcm

时间:2014-12-04 15:32:16

标签: core-audio pcm audioqueueservices


我需要在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。抱歉我的英语,我尽我所能。

1 个答案:

答案 0 :(得分:2)

我希望你已经自己解决了这个问题,但是为了其他有这个问题的人的利益,我会发布一个答案。

问题很可能是因为一旦音频队列启动,即使你停止入队缓冲,时间也会继续前进。但是,当您将缓冲区排入队列时,它会在先前排队的缓冲区之后排列一个时间戳。这意味着如果您不在音频队列正在播放的位置之前,您将最终使用过去的时间戳排队缓冲区,因此音频队列将保持静默并且isRunning属性仍然为true。

要解决此问题,您有几个选择。最简单的理论是永远不会落后于提交缓冲区。但由于您使用的是UDP,因此无法保证您始终可以提交数据。

另一个选择是你可以跟踪你应该播放的样本,并在需要有空隙时提交一个空的静音缓冲区。如果源数据具有可用于计算所需静音的时间戳,则此选项很有效。但理想情况下,你不需要这样做。

相反,您应该使用系统时间计算缓冲区的时间戳。而不是AudioQueueEnqueueBuffer,您需要使用AudioQueueEnqueueBufferWithParameters。您只需要确保时间戳位于队列当前所在的位置。您还必须跟踪启动队列时的系统时间,以便为要提交的每个缓冲区计算正确的时间戳。如果源数据上有时间戳值,则应该能够使用它们来计算缓冲区时间戳。