【问题标题】:Audio Queue is playing too fast when the buffer size is small缓冲区较小时,音频队列播放速度过快
【发布时间】:2015-05-29 15:51:09
【问题描述】:

我能够使用音频文件服务 + 音频队列服务流式传输和播放 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 分,很快我就意识到我的问题不一样.....

【问题讨论】:

    标签: ios objective-c buffer audioqueue audioqueueservices


    【解决方案1】:

    当您为可变比特率分配队列时,您需要计算数据包大小,而不是使用 XMAQDefaultBufSize。我从this 书中的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,您可以计算出可以安全用于不可预测的可变比特率文件的最小缓冲区大小。如果您的文件太小,您只需确定哪些样本正在为编解码器填充。

    【讨论】:

    • hmm 这个例子不是来自 Learning Core Audio Book 吗?实际上,我已经创建了查看那本书的音频队列,我记得这种方法对我不起作用……但让我再试一次。也许我之前做错了什么...github.com/abbood/Learning-Core-Audio-Book-Code-Sample/blob/…
    • @Spacedust_ 方法相同,示例不同。我已经在我的回答中链接到它。
    • 好的,我记得为什么这对我不起作用 // if frames per packet is zero, then the codec has no predictable packet == time // so we can't tailor this (we don't know how many Packets represent a time period // we'll just return a default buffer size 对于每个音频数据包 NSNumber *mVariableFramesInPacket = [myDict objectForKey:@"mVariableFramesInPacket"]; 这总是零
    • @Spacedust_ 正确,您的问题是较小的缓冲区大小失败。获取 kAudioConverterPropertyMaximumOutputPacketSize 将为您提供可以安全使用的最小 bufferSize。如果缓冲区大小大于文件的帧数,则读取的某些帧本质上将是编解码器的填充。然后,您必须修剪前导和尾随填充样本以获取音频样本
    • 你是对的,实际上当我在我的macs终端上使用afinfo命令时,我可以看到文件的packet size upper bound: 805,当我选择806的缓冲区大小时,音频播放正常该文件的速度,我认为您在那里诊断出问题......现在我的问题是因为我正在流式传输音频文件(我正在尝试创建一个渐进式下载播放器)AudioFileStreamGetProperty 返回错误,可能是因为这是一个 m4a 文件pastebin.com/AzYHL0Ht
    猜你喜欢
    • 2011-10-31
    • 1970-01-01
    • 2017-12-15
    • 2014-07-09
    • 1970-01-01
    • 1970-01-01
    • 2015-07-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多