使用 AudioQueue 播放断断续续的音频

Posted

技术标签:

【中文标题】使用 AudioQueue 播放断断续续的音频【英文标题】:Choppy audio playback with AudioQueue 【发布时间】:2015-01-08 00:02:46 【问题描述】:

我有以下代码打开一个 AudioQueue 以播放 16 位 pcm @ 44,100hz。它有一个非常奇怪的怪癖,一旦初始缓冲区被填满,它就会很快播放,然后在等待更多字节通过网络时变得“断断续续”。

所以要么我以某种方式弄乱了将数据子范围复制到缓冲区的代码,要么我告诉 AudioQueue 播放速度比通过网络传输的数据快。

有人有什么想法吗?我已经卡了几天了。

//
// Created by Benjamin St Pierre on 15-01-02.
// Copyright (c) 2015 Lightning Strike Solutions. All rights reserved.
//

#import <MacTypes.h>
#import "MediaPlayer.h"


@implementation MediaPlayer


@synthesize sampleQueue;


void OutputBufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) 
    //Cast userData to MediaPlayer Objective-C class instance
    MediaPlayer *mediaPlayer = (__bridge MediaPlayer *) inUserData;
    // Fill buffer.
    [mediaPlayer fillAudioBuffer:inBuffer];
    // Re-enqueue buffer.
    OSStatus err = AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
    if (err != noErr)
        NSLog(@"AudioQueueEnqueueBuffer() error %d", (int) err);


- (void)fillAudioBuffer:(AudioQueueBufferRef)inBuffer 
    if (self.currentAudioPiece == nil || self.currentAudioPiece.duration >= self.currentAudioPieceIndex) 
        //grab latest sample from sample queue
        self.currentAudioPiece = sampleQueue.dequeue;
        self.currentAudioPieceIndex = 0;
    

    //Check for empty sample queue
    if (self.currentAudioPiece == nil) 
        NSLog(@"Empty sample queue");
        memset(inBuffer->mAudioData, 0, kBufferByteSize);
        return;
    

    UInt32 bytesToRead = inBuffer->mAudioDataBytesCapacity;

    while (bytesToRead > 0) 
        UInt32 maxBytesFromCurrentPiece = self.currentAudioPiece.audioData.length - self.currentAudioPieceIndex;
        //Take the min of what the current piece can provide OR what is needed to be read
        UInt32 bytesToReadNow = MIN(maxBytesFromCurrentPiece, bytesToRead);

        NSData *subRange = [self.currentAudioPiece.audioData subdataWithRange:NSMakeRange(self.currentAudioPieceIndex, bytesToReadNow)];
        //Copy what you can before continuing loop
        memcpy(inBuffer->mAudioData, subRange.bytes, subRange.length);
        bytesToRead -= bytesToReadNow;

        if (bytesToReadNow == maxBytesFromCurrentPiece) 
            @synchronized (sampleQueue) 
                self.currentAudioPiece = self.sampleQueue.dequeue;
                self.currentAudioPieceIndex = 0;
            
         else 
            self.currentAudioPieceIndex += bytesToReadNow;
        
    
    inBuffer->mAudioDataByteSize = kBufferByteSize;


- (void)startMediaPlayer 
    AudiostreamBasicDescription streamFormat;
    streamFormat.mFormatID = kAudioFormatLinearPCM;
    streamFormat.mSampleRate = 44100.0;
    streamFormat.mChannelsPerFrame = 2;
    streamFormat.mBytesPerFrame = 4;
    streamFormat.mFramesPerPacket = 1;
    streamFormat.mBytesPerPacket = 4;
    streamFormat.mBitsPerChannel = 16;
    streamFormat.mReserved = 0;
    streamFormat.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;

    // New input queue
    OSStatus err = AudioQueueNewOutput(&streamFormat, OutputBufferCallback, (__bridge void *) self, nil, nil, 0, &outputQueue);
    if (err != noErr) 
        NSLog(@"AudioQueueNewOutput() error: %d", (int) err);
    

    int i;
    // Enqueue buffers
    AudioQueueBufferRef buffer;
    for (i = 0; i < kNumberBuffers; i++) 
        err = AudioQueueAllocateBuffer(outputQueue, kBufferByteSize, &buffer);
        memset(buffer->mAudioData, 0, kBufferByteSize);
        buffer->mAudioDataByteSize = kBufferByteSize;
        if (err == noErr) 
            err = AudioQueueEnqueueBuffer(outputQueue, buffer, 0, nil);
            if (err != noErr) NSLog(@"AudioQueueEnqueueBuffer() error: %d", (int) err);
         else 
            NSLog(@"AudioQueueAllocateBuffer() error: %d", (int) err);
            return;
        
    

    // Start queue
    err = AudioQueueStart(outputQueue, nil);
    if (err != noErr) NSLog(@"AudioQueueStart() error: %d", (int) err);


@end

【问题讨论】:

您在渲染回调中调用了 Objective-C 代码,这通常不是实时安全的,可能会导致过载。在-fillAudioBuffer: 中,使用互斥锁绝对不是实时安全的。 在我第一次尝试音频队列时,由于以下两个原因,我有声音伪影:1)我没有在回调中正确设置缓冲区->mAudioDataByteSize,2)我使用的缓冲区太少;将缓冲区从 3 增加到 5 解决了我最后剩下的音频卡顿问题。 【参考方案1】:

我将在这里采取一个赃物,并说您正在获得断断续续的播放,因为您没有为您的数据推进写入指针。我不太了解 Objective-C,无法告诉您此语法是否正确,但我认为您需要补充以下内容:

while (bytesToRead > 0) 
    ....
    memcpy(inBuffer->mAudioData, subRange.bytes, subRange.length);
    bytesToRead -= bytesToReadNow;
    inBuffer->mAudioData += bytesReadNow; // move the write pointer
    ...

【讨论】:

以上是关于使用 AudioQueue 播放断断续续的音频的主要内容,如果未能解决你的问题,请参考以下文章

使用 AudioQueue 和 Monotouch 静态声音录制

CoreAudio AudioQueue PCM 速度快且不稳定

ios音频:使用audioqueue改变播放进度

iOS音频播放之AudioQueue:播放本地音乐

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

在 iPhone 上使用 AudioQueue 录制/播放