缓冲区较小时音频队列播放速度过快

Audio Queue is playing too fast when the buffer size is small

我可以使用音频文件服务 + 音频队列服务流式传输和播放 m4a 文件。由于文件类型,文件的比特率信息不可用于音频队列。

下载所有音频包后,我将它们提供给播放器。

当我选择大约 32768 或 16384 的缓冲区大小时,因为回调被调用的频率较低并且每个缓冲区大小都很大,所以它似乎几乎以正常速度播放。问题是有时我也必须播放小文件,但是当我选择较小的缓冲区大小 -512、1024 或 2048 到 8192 时,音频播放速度非常快,偶尔会出现故障。

我知道在 c 回调中调用 objective-c 函数不是一个好主意,但为了可读性和简便性,我这样做了。无论如何我认为这不是问题。

// allocate the buffers and prime the queue with some data before starting
AudioQueueBufferRef buffers[XMNumberPlaybackBuffers];

int i;
for (i = 0; i < XMNumberPlaybackBuffers; ++i)
{
    err=AudioQueueAllocateBuffer(queue, XMAQDefaultBufSize, &buffers[i]);
    if (err) {
        [self failWithErrorCode:err customError:AP_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED];
    }
    @synchronized(self)
    {
        state=AP_WAITING_FOR_QUEUE_TO_START;
    }


    // manually invoke callback to fill buffers with data
    MyAQOutputCallBack((__bridge void *)(self), queue, buffers[i]);

}

我还从字典的可变数组中获取音频数据包...

#define XMNumberPlaybackBuffers 4 
#define XMAQDefaultBufSize 8192 
#pragma mark playback callback function
static void MyAQOutputCallBack(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inCompleteAQBuffer)
{
    // this is called by the audio queue when it has finished decoding our data.
    // The buffer is now free to be reused.
    NSLog(@"MyAQOutputCallBack..");

    //printf("MyAQOutputCallBack...\n");
    XMAudioPlayer* player = (__bridge XMAudioPlayer *)inUserData;
    [player handleBufferCompleteForQueue:inAQ buffer:inCompleteAQBuffer];
    //printf("##################\n");

}

- (void)handleBufferCompleteForQueue:(AudioQueueRef)inAQ
                              buffer:(AudioQueueBufferRef)inBuffer
{
    //NSLog(@"######################\n");
    AudioTimeStamp queueTime;
    Boolean discontinuity;
    err = AudioQueueGetCurrentTime(queue, NULL, &queueTime, &discontinuity);
    printf("queueTime.mSampleTime %.2f\n",queueTime.mSampleTime/dataFormat.mSampleRate);

    AudioStreamPacketDescription packetDescs[XMAQMaxPacketDescs];   // packet descriptions for enqueuing audio

    BOOL isBufferFilled=NO;

    size_t bytesFilled=0;               // how many bytes have been filled
    size_t packetsFilled=0;         // how many packets have been filled
    size_t bufSpaceRemaining;

    while (isBufferFilled==NO && isEOF==NO) {
        if (currentlyReadingBufferIndex<[sharedCache.audioCache count]) {

            //loop thru untill buffer is enqued
            if (sharedCache.audioCache) {

                NSMutableDictionary *myDict= [[NSMutableDictionary alloc] init];
                myDict=[sharedCache.audioCache objectAtIndex:currentlyReadingBufferIndex];

                //why I cant use this info?
                //UInt32 inNumberBytes =[[myDict objectForKey:@"inNumberBytes"] intValue];
                UInt32 inNumberPackets =[[myDict objectForKey:@"inNumberPackets"] intValue];
                NSData *convert=[myDict objectForKey:@"inInputData"];
                const void *inInputData=(const char *)[convert bytes];

                //AudioStreamPacketDescription *inPacketDescriptions;
                AudioStreamPacketDescription *inPacketDescriptions= malloc(sizeof(AudioStreamPacketDescription));

                NSNumber *mStartOffset  = [myDict objectForKey:@"mStartOffset"];
                NSNumber *mDataByteSize   = [myDict objectForKey:@"mDataByteSize"];
                NSNumber *mVariableFramesInPacket   = [myDict objectForKey:@"mVariableFramesInPacket"];

                inPacketDescriptions->mVariableFramesInPacket=[mVariableFramesInPacket intValue];
                inPacketDescriptions->mStartOffset=[mStartOffset intValue];
                inPacketDescriptions->mDataByteSize=[mDataByteSize intValue];



                for (int i = 0; i < inNumberPackets; ++i)
                {
                    SInt64 packetOffset =  [mStartOffset intValue];
                    SInt64 packetSize   =   [mDataByteSize intValue];
                    //printf("packetOffset %lli\n",packetOffset);
                    //printf("packetSize %lli\n",packetSize);

                    currentlyReadingBufferIndex++;

                    if (packetSize > packetBufferSize)
                    {
                        //[self failWithErrorCode:AS_AUDIO_BUFFER_TOO_SMALL];
                    }

                    bufSpaceRemaining = packetBufferSize - bytesFilled;
                    //printf("bufSpaceRemaining %zu\n",bufSpaceRemaining);

                    // if the space remaining in the buffer is not enough for this packet, then enqueue the buffer.
                    if (bufSpaceRemaining < packetSize)
                    {


                        inBuffer->mAudioDataByteSize = (UInt32)bytesFilled;
                        err=AudioQueueEnqueueBuffer(inAQ,inBuffer,(UInt32)packetsFilled,packetDescs);
                        if (err) {
                            [self failWithErrorCode:err customError:AP_AUDIO_QUEUE_ENQUEUE_FAILED];
                        }
                        isBufferFilled=YES;
                        [self incrementBufferUsedCount];
                        return;

                    }
                    @synchronized(self)
                    {

                        //
                        // If there was some kind of issue with enqueueBuffer and we didn't
                        // make space for the new audio data then back out
                        //
                        if (bytesFilled + packetSize > packetBufferSize)
                        {
                            return;
                        }

                        // copy data to the audio queue buffer
                        //error -66686 refers to
                        //kAudioQueueErr_BufferEmpty          = -66686
                        //memcpy((char*)inBuffer->mAudioData + bytesFilled, (const char*)inInputData + packetOffset, packetSize);
                        memcpy(inBuffer->mAudioData + bytesFilled, (const char*)inInputData + packetOffset, packetSize);

                        // fill out packet description
                        packetDescs[packetsFilled] = inPacketDescriptions[0];
                        packetDescs[packetsFilled].mStartOffset = bytesFilled;
                        bytesFilled += packetSize;
                        packetsFilled += 1;
                        free(inPacketDescriptions);
                    }

                    // if that was the last free packet description, then enqueue the buffer.
//                    size_t packetsDescsRemaining = kAQMaxPacketDescs - packetsFilled;
//                    if (packetsDescsRemaining == 0) {
//                        
//                    }

                    if (sharedCache.numberOfToTalPackets>0)
                    {
                        if (currentlyReadingBufferIndex==[sharedCache.audioCache count]-1) {

                            if (loop==NO) {
                                inBuffer->mAudioDataByteSize = (UInt32)bytesFilled;
                                lastEnqueudBufferSize=bytesFilled;
                                lastbufferPacketCount=(int)packetsFilled;
                                err=AudioQueueEnqueueBuffer(inAQ,inBuffer,(UInt32)packetsFilled,packetDescs);
                                if (err) {
                                    [self failWithErrorCode:err customError:AP_AUDIO_QUEUE_ENQUEUE_FAILED];
                                }
                                printf("if that was the last free packet description, then enqueue the buffer\n");
                                //go to the next item on keepbuffer array
                                isBufferFilled=YES;

                                [self incrementBufferUsedCount];
                                return;
                            }
                            else
                            {
                                //if loop is yes return to first packet pointer and fill the rest of the buffer before enqueing it
                                //set the currently reading to zero
                                //check the space in buffer
                                //if space is avaialbele create a while loop till it is filled
                                //then enqueu the buffer
                                currentlyReadingBufferIndex=0;
                            }

                        }
                    }

                }

            }

        }
  }
}
#######################################

编辑:
对于将来访问此内容的任何人,事实证明我的确切问题是 AudioStreamPacketDescription packetDescs[XMAQMaxPacketDescs]; 所以 XMAQMaxPacketDescs 这里是 512 当我选择更大的缓冲区大小时我正在为每个缓冲区排队更接近 512 个数据包所以它是以正常速度播放

然而,对于像 1024 这样的小缓冲区大小,这总共只有 2-3 个数据包,因此其余 508 个数据包为 0,播放器试图播放其中的所有数据包描述 512,这就是它太快的原因。

我通过计算放入缓冲区的数据包总数解决了这个问题,然后我创建了一个动态 AudioStreamPacketDescription 描述数组..

  AudioStreamPacketDescription * tempDesc = (AudioStreamPacketDescription *)(malloc(packetsFilledDesc * sizeof(AudioStreamPacketDescription)));
                                memcpy(tempDesc,packetDescs, packetsFilledDesc*sizeof(AudioStreamPacketDescription));

                                err = AudioQueueEnqueueBuffer(inAQ,inBuffer,packetsFilledDesc,tempDesc);
                                if (err) {
                                    [self failWithErrorCode:err customError:AP_AUDIO_QUEUE_ENQUEUE_FAILED];
                                }

但是我接受了下面的 DAVE 回答并奖励了 100 分,很快我意识到我的问题不同了.....

当您为可变比特率分配队列而不是使用 XMAQDefaultBufSize 时,对于可变比特率,您需要计算数据包大小。我从 this tutorial from this 书中提取了一个方法来展示它是如何完成的。

void DeriveBufferSize (AudioQueueRef audioQueue, AudioStreamBasicDescription ASBDescription, Float64 seconds, UInt32 *outBufferSize)
{
    static const int maxBufferSize = 0x50000; // punting with 50k
    int maxPacketSize = ASBDescription.mBytesPerPacket; 
    if (maxPacketSize == 0) 
    {                           
        UInt32 maxVBRPacketSize = sizeof(maxPacketSize);
        AudioQueueGetProperty(audioQueue, kAudioConverterPropertyMaximumOutputPacketSize, &maxPacketSize, &maxVBRPacketSize);
    }

    Float64 numBytesForTime = ASBDescription.mSampleRate * maxPacketSize * seconds;
    *outBufferSize =  (UInt32)((numBytesForTime < maxBufferSize) ? numBytesForTime : maxBufferSize);
}

你会像这样使用它。

Float64 bufferDurSeconds = 0.54321;  
AudioStreamBasicDescription myAsbd = self.format; // or something

UInt32 bufferByteSize;   
DeriveBufferSize(recordState.queue, myAsbd, bufferDurSeconds, &bufferByteSize);

AudioQueueAllocateBuffer(queue, bufferByteSize, &buffers[i]);

使用 kAudioConverterPropertyMaximumOutputPacketSize,您可以计算可以安全地用于不可预测的可变比特率文件的最小缓冲区大小。如果您的文件太小,您只需要确定哪些样本正在为编解码器填充。