使用音频队列服务通过套接字连接播放 PCM 数据

Posted

技术标签:

【中文标题】使用音频队列服务通过套接字连接播放 PCM 数据【英文标题】:Using Audio Queue Services to play PCM data over a socket connection 【发布时间】:2010-04-07 20:58:03 【问题描述】:

我正在为 iPhone 编写远程桌面客户端,并且正在尝试实现音频重定向。 客户端通过套接字连接连接到服务器,服务器一次发送 32K 块 PCM 数据。

我正在尝试使用 AQS 播放数据,它会播放前两秒(1 个缓冲区)。但是,由于下一个数据块还没有通过套接字进入,所以下一个 AudioQueueBuffer 是空的。当数据进来时,我用数据填充下一个可用缓冲区,并使用 AudioQueueEnqueueBuffer 将其排入队列。但是,它从不播放这些缓冲区。

如果队列中没有缓冲区,队列是否会停止播放,即使您稍后添加了缓冲区?

这是代码的相关部分:

void
wave_out_write(STREAM s, uint16 tick, uint8 index)


    if(items_in_queue == NUM_BUFFERS)
        return;
    
    if(!playState.busy)
        OSStatus status;
        status = AudioQueueNewOutput(&playState.dataFormat, AudioOutputCallback, &playState, CFRunLoopGetCurrent(), NULL, 0, &playState.queue);

        if(status == 0)
            for(int i=0; i<NUM_BUFFERS; i++)
                AudioQueueAllocateBuffer(playState.queue, 40000, &playState.buffers[i]);

            
            AudioQueueAddPropertyListener(playState.queue, kAudioQueueProperty_IsRunning, MyAudioQueuePropertyListenerProc, &playState);

            status = AudioQueueStart(playState.queue, NULL);
            if(status ==0)
                playState.busy = True;
            
            else
                return;
            
        
        else
            return;
        
    
    playState.buffers[queue_hi]->mAudioDataByteSize = s->size;

    memcpy(playState.buffers[queue_hi]->mAudioData, s->data, s->size);

    AudioQueueEnqueueBuffer(playState.queue, playState.buffers[queue_hi], 0, 0);
    queue_hi++;
    queue_hi = queue_hi % NUM_BUFFERS;
    items_in_queue++;



void AudioOutputCallback(void* inUserData, AudioQueueRef outAQ, AudioQueueBufferRef outBuffer)

    PlayState *playState = (PlayState *)inUserData;
    items_in_queue--;

谢谢!

【问题讨论】:

【参考方案1】:

使用 CoreAudio 的音频排队服务可以大大简化,方法是确保您始终在每个缓冲区完成播放后立即重新排队,方法是在播放完成时触发的回调中这样做。音频数据从网络接收时应位于单独的循环缓冲区中,这样网络和音频代码不会直接耦合。

为确保您不会丢失音频,请将固定数量的缓冲区与数据排队;这充当抖动缓冲器。在所有缓冲区都排队之前不要开始播放。一旦每个缓冲区完成播放,立即将其与下一个数据包重新排队。如果没有数据可用,只需排队一个静音缓冲区;由于到达的音频数据包最终会赶上,这只会以额外的延迟为代价减少丢失的音频。

【讨论】:

【参考方案2】:

您可能会发现这个问题的答案很有用:AudioQueue ate my buffer (first 15 milliseconds of it)

【讨论】:

【参考方案3】:

通常,当使用循环音频缓冲区时,您应该防止缓冲区不足。如果您缺少必要的数据(例如由于网络拥塞),请尝试使用静音填充您的音频数据或暂停音频播放。

一旦您的缓冲链欠载,您可能需要重新开始播放。我从未真正使用过 AudioQueue 缓冲区,但我记得在 Win32 编程中就是这种情况,所以如果我错了,请随时纠正我。

【讨论】:

你不必用沉默来填充任何东西。如果在调用回调时没有任何数据要放回队列中,请不要将任何数据放在那里。如果您的队列用完数据,它将自行播放静音,直到您再次将数据排入队列。如果所有缓冲区都出队列,则不会再次调用您的回调,但您仍然可以从另一个不是回调的函数将数据排入队列(例如,一旦数据再次到达)。这不是海报的问题。他的问题是队列中只有一个缓冲区可以开始。您至少需要 2 个。 谢谢 Mecki,我也有类似的问题,虽然解决方案还没有写出来,但至少这指向了正确的方向......【参考方案4】:

如果我没有足够的分数,我发现我可以在这里发布答案而不是 cmets,这很愚蠢。我只是想补充以下答案:

“可能是一旦你的缓冲链欠载,你需要重新开始播放。我从来没有真正欠载过 AudioQueue 缓冲区,但我记得在 Win32 编程中就是这种情况,所以请随时纠正我如果我错了。”

我实际上已经在我最近制作的音频播放器中测试了这个场景。我从头开始制作了一个 FLAC 解码器,它目前仅支持 16 位歌曲。如果我偶然发现一首 24 位的歌曲,我会一直与我正在播放的歌曲失去同步——它根本不会播放——这可能需要任何时间,比如 30 秒来恢复。这使音频队列非常饥饿,当我终于开始再次向音频队列发送缓冲区时,需要 30 秒的静音赶上下一首歌曲才能再次播放。

这只是我的观察,我还没有过多考虑为什么要观察这种行为。也许它会丢弃样本以匹配样本计数 AudioQueue 认为它现在应该播放 - 它在饥饿期间丢失了?我的音频播放器似乎可以快速播放歌曲,直到达到它想要再次播放的程度。

编辑:只要您为每个回调发布一个新缓冲区,您就永远不需要重新开始播放或任何事情。在我的播放器中,如果在“回调”下一个缓冲区时我没有完成对缓冲区的处理,则该缓冲区的线程将被阻塞,直到第一个缓冲区完成填充。这是通过 NSLock 完成的。这是当我的播放器不理解 24 位 FLAC 时我失去同步时 AudioQueues 严重饥饿的主要原因。当 AudioQueue 为您提供更多要填充的缓冲区时,NSLock 还可以防止任何竞争条件。我使用 3 个低延迟的缓冲区。太低的延迟会导致完全静音,因此您需要为您的系统找到一个“合适的大小”。

【讨论】:

以上是关于使用音频队列服务通过套接字连接播放 PCM 数据的主要内容,如果未能解决你的问题,请参考以下文章

Objective-C - 将流数据传递到音频队列

通过 tcp 套接字流式传输 PCM 音频

使用 AudioQueue 播放 PCM 音频流音量低

ios 使用audioQueue 录音以及播放 (pcm)

VLC如何在普通PC上播放24位PCM音频?

在 iPhone 上录制音频并使用 NSOutputStream 通过网络发送