使用 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 &lt; kNumAQBufs; ++i) if (inBuffer == myData-&gt;audioQueueBuffer[i]) return i; return -1; 对不起,换行符 %) 您是否在用户级驱动程序中执行此操作?我有一个非常相似的驱动程序正在开发中,但是在核心音频驱动程序中遇到了网络沙盒问题。你遇到过这个吗? @foobar 你是怎么解决的? 【参考方案1】:

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

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

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

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

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

【讨论】:

以上是关于使用 CoreAudio 中的 AudioQueue 从网络播放原始 pcm的主要内容,如果未能解决你的问题,请参考以下文章

CoreAudio:Ausampler 单元复音

将低延迟音频从一个 CoreAudio 设备路由到另一个

使用 CoreAudio 获取正确的 FileLengthFrames

CoreAudio:AudioUnit 既不能停止也不能未初始化

CoreAudio,iOS:无法通过 RemoteIO 使用单声道输入和立体声输出

iOS 5/6:首次使用 CoreAudio 后音量变小