使用 iOS 上的音频队列播放音频一段时间后声音静音

The sound muted after playing audio with Audio Queue on iOS for a while

我正在 iOS 上编写一个实时音频播放程序。

接收对端的音频RTP包,放入音频队列播放。

开始播放时,声音正常。但是1、2分钟后,声音静音了,AudioQueueAPI也没有报错。回调函数继续正常调用,没有异常。

但它只是静音。

我的回调函数:

1:循环直到有足够的数据可以复制到音频队列缓冲区

do 
{
    read_bytes_enabled = g_audio_playback_buf.GetReadByteLen();
    if (read_bytes_enabled >= kAudioQueueBufferLength)
    {
        break;
    }
    usleep(10*1000);
}
while (true);

2:拷贝到AudioQueue Buffer,入队。这个回调函数保持运行正常,没有错误。

//copy to audio queue buffer
read_bytes = kAudioQueueBufferLength;

g_audio_playback_buf.Read((unsigned char *)inBuffer->mAudioData, read_bytes);

WriteLog(LOG_PHONE_DEBUG, "AudioQueueBuffer(Play): copy [%d] bytes to AudioQueue buffer! Total len = %d", read_bytes, read_bytes_enabled);

inBuffer->mAudioDataByteSize = read_bytes;

UInt32 nPackets = read_bytes / g_audio_periodsize; // mono

inBuffer->mPacketDescriptionCount = nPackets;

// re-enqueue this buffer
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);

问题已解决

重点是,不能让音频队列缓冲等待,必须一直喂,否则可能会静音。如果您没有足够的数据,请用空白数据填充。

因此应更改以下代码:

do 
{
    read_bytes_enabled = g_audio_playback_buf.GetReadByteLen();
    if (read_bytes_enabled >= kAudioQueueBufferLength)
{
    break;
}
    usleep(10*1000);
}
while (true);

改为:

read_bytes_enabled = g_audio_playback_buf.GetReadByteLen();
if (read_bytes_enabled < kAudioQueueBufferLength)
{
    memset(inBuffer->mAudioData, 0x00, kAudioQueueBufferLength);
}
else
{
    inBuffer->mAudioDataByteSize = kAudioQueueBufferLength;
}
...

如果你使用AudioQueuePause,你可以让AudioQueue等待。

在这个例子中,在 Swift 5 中,我使用了一个通用队列。当这个队列为空时,就像您所做的那样,我在回调中用空数据填充缓冲区并调用 AudioQueuePause。重要的是要注意,在调用 AudioQueuePause 之前,所有 AudioQueueBuffer 都使用 AudioQueueEnqueueBuffer 发送到 AudioQueueRef。

创建一个 userData class 以将您需要的所有内容发送到您的回调:

class UserData {
    let dataQueue = Queue<Data>()
    let semaphore = DispatchSemaphore(value: 1)
}
private var inQueue: AudioQueueRef!
private var userData = UserData()

在创建 AudioQueue 并启动它时提供此 class 的实例:

AudioQueueNewOutput(&inFormat, audioQueueOutputCallback, &userData, nil, nil, 0, &inQueue)
AudioQueueStart(inQueue, nil)

生成所有缓冲区并且不直接将它们入队:调用回调函数:

for _ in 0...2 {
    var bufferRef: AudioQueueBufferRef!
    AudioQueueAllocateBuffer(inQueue, 320, &bufferRef)
    audioQueueOutputCallback(&userData, inQueue, bufferRef)
}

当您收到音频数据时,您可以调用一个方法将您的数据排入队列并让它等待回调函数获取它:

func audioReceived(_ audio: Data) {
    let dataQueue = userData.dataQueue
    let semaphore = userData.semaphore

    semaphore.wait()
    dataQueue.enqueue(audio)
    semaphore.signal()
    // Start AudioQueue every time, if it's already started this call do nothing
    AudioQueueStart(inQueue, nil)
}

终于可以像这样实现回调函数了:

private let audioQueueOutputCallback: AudioQueueOutputCallback = { (inUserData, inAQ, inBuffer) in
    // Get data from UnsageMutableRawPointer
    let userData: UserData = (inUserData!.bindMemory(to: UserData.self, capacity: 1).pointee)
    let queue = userData.dataQueue
    let semaphore = userData.semaphore
    // bind UnsafeMutableRawPointer to UnsafeMutablePointer<UInt8> for data copy
    let audioBuffer = inBuffer.pointee.mAudioData.bindMemory(to: UInt8.self, capacity: 320)

    if queue.isEmpty {
        print("Queue is empty: pause")
        AudioQueuePause(inAQ)
        audioBuffer.assign(repeating: 0, count: 320)
        inBuffer.pointee.mAudioDataByteSize = 320
    } else {
        semaphore.wait()
        if let data = queue.dequeue() {
            data.copyBytes(to: audioBuffer, count: data.count)
            inBuffer.pointee.mAudioDataByteSize = data.count
        } else {
            print("Error: queue is empty")
            semaphore.signal()
            return
        }
        semaphore.signal()
    }

    AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nil)
}

在我的例子中,我使用 320 字节的缓冲区来存储 20 毫秒的 PCM 数据 16 位,8kHz,单声道。 此解决方案更复杂,但比 CPU 的带有空音频数据的伪无限循环要好。 Apple 对贪婪的应用程序非常惩罚 ;)

我希望这个解决方案能有所帮助。