【问题标题】:The sound muted after playing audio with Audio Queue on iOS for a while在 iOS 上使用 Audio Queue 播放音频一段时间后静音
【发布时间】:2015-08-27 22:57:31
【问题描述】:

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

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

开始播放时,声音正常。但是 1 到 2 分钟后,声音变小了,而且 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);

【问题讨论】:

    标签: ios audioqueue


    【解决方案1】:

    问题已解决。

    关键是,你不能让音频队列缓冲区等待,你必须一直喂它,否则它可能会被静音。如果你没有足够的数据,用空白数据填充它。

    所以下面的代码应该改一下:

    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;
    }
    ...
    

    【讨论】:

    • @davin zhao,我也面临同样的问题,我的有时不工作,声音停止,有两个问题要问你,也许答案也能解决我的问题 1)kAudioQueueBufferLength 的价值是什么2)如果可能的话,你会分享这个代码的样本吗?帮助将非常感激
    【解决方案2】:

    如果你使用 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 的空音频数据的伪无限循环更复杂但更好。苹果对贪婪的应用程序非常严厉;)

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-22
      • 1970-01-01
      • 2012-09-20
      • 2023-03-27
      • 1970-01-01
      • 2014-12-27
      • 2019-01-23
      相关资源
      最近更新 更多