繁体   English   中英

在 iOS 上使用 Audio Queue 播放音频一段时间后声音静音

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

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

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

开始播放时,声音正常。 但是过了一两分钟,声音就静音了,AudioQueue API也没有报错。 回调函数继续正常调用,没有异常。

但它只是静音。

我的回调函数:

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 UserData {
    let dataQueue = Queue<Data>()
    let semaphore = DispatchSemaphore(value: 1)
}
private var inQueue: AudioQueueRef!
private var userData = UserData()

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

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 毫秒的 16 位、8kHz、单声道 PCM 数据。 此解决方案更复杂,但比 CPU 的空音频数据的伪无限循环更好。 苹果对贪婪的应用程序非常严厉;)

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM