当缓冲区大小很小时,音频队列播放速度过快

时间:2022-10-21 20:21:43

I am able to stream and play m4a files using Audio File Services + Audio Queue Services. Bitrate information of the file is not available to the Audio Queue because of file type.

我可以使用音频文件服务和音频队列服务流和播放m4a文件。由于文件类型的原因,音频队列无法获得文件的比特率信息。

After downloading the all of the audio packets I feed them to the player.

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

When I choose a buffer size around 32768 or 16384 since callbacks are called less often and and each buffer size is big, it seems it is playing almost at regular speed. Problem is sometimes I have to play small files as well but when I choose a small buffer size -512 or 1024 or 2048 up to 8192- audio plays really fast and with occasional glitches.

当我选择一个32768或16384左右的缓冲区大小时,因为调用回调的频率更低,而且每个缓冲区大小都很大,看起来它几乎以正常的速度运行。问题是有时我也必须播放小文件,但是当我选择一个小的缓冲区大小-512或1024或2048或高达8192-音频播放速度非常快,偶尔会有小故障。

I know calling objective-c function in c callback is not a great idea but for readability and easiness I do that. Regardless I think that is not the problem.

我知道在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]);

}

I also get audio packets from a mutablearray of dictionaries...

我还从一组字典中获取音频包……

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

                        }
                    }

                }

            }

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

EDIT:
For anyone who is visiting this in the future, turns out my exact problem was AudioStreamPacketDescription packetDescs[XMAQMaxPacketDescs]; so XMAQMaxPacketDescs here is 512 when I choose bigger buffer sizes I was enqueueing closer numbers to 512 packets for each buffer so it was playing at normal speed

编辑:对于任何将来访问这个网站的人来说,我的确切问题是audiostreampacketdescs [XMAQMaxPacketDescs];所以XMAQMaxPacketDescs在这里是512当我选择更大的缓冲区大小时,我为每个缓冲区分配了更接近512个数据包,所以它以正常的速度运行

However for small buffer sizes like 1024 this is only 2-3 packets total so rest of the 508 packets were 0, and player was trying to play all the packetdescriptions 512 of them an that's why it was too fast.

但是对于像1024这样的小缓冲区来说,这仅仅是2-3个包,所以508个包的其余部分都是0,而player试图播放所有的packetdescription 512个包,这就是为什么它太快的原因。

I solved the problem by counting the number of total number of packets that I put the buffers then I created a dynamic AudioStreamPacketDescription description array..

我通过计算我放入缓冲区的数据包的总数来解决这个问题,然后我创建了一个动态的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];
                                }

However I accepted and rewarded 100 points to DAVE answer's below, soon I realized my problem was different.....

然而,我接受并奖励了100分给了戴夫回答的下面,很快我意识到我的问题是不同的。

1 个解决方案

#1


1  

When you allocate your queue for variable bit rate, instead of using XMAQDefaultBufSize, for variable bit rate, you need to calculate the packet size. I pulled a method from this tutorial from this book that shows how it's done.

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

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

You would use it like this.

就像这样。

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

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

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

Using kAudioConverterPropertyMaximumOutputPacketSize, you calculate the smallest buffer size you can safely use for the unpredictable variable bit rate file. If your file is too small, you just need to identify which samples are padding for the codec.

使用kAudioConverterPropertyMaximumOutputPacketSize,您可以计算出用于不可预知的可变比特率文件的最小缓冲区大小。如果您的文件太小,您只需要确定哪些示例是为编解码器填充的。

#1


1  

When you allocate your queue for variable bit rate, instead of using XMAQDefaultBufSize, for variable bit rate, you need to calculate the packet size. I pulled a method from this tutorial from this book that shows how it's done.

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

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

You would use it like this.

就像这样。

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

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

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

Using kAudioConverterPropertyMaximumOutputPacketSize, you calculate the smallest buffer size you can safely use for the unpredictable variable bit rate file. If your file is too small, you just need to identify which samples are padding for the codec.

使用kAudioConverterPropertyMaximumOutputPacketSize,您可以计算出用于不可预知的可变比特率文件的最小缓冲区大小。如果您的文件太小,您只需要确定哪些示例是为编解码器填充的。