6

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.

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.

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.

// 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

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.

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 * 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.....

SpaceDust__
  • 4,844
  • 4
  • 43
  • 82

1 Answers1

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.

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.

dave234
  • 4,793
  • 1
  • 14
  • 29
  • hmm isn't this example from the Learning Core Audio Book ? Actually I have created the audio queue looking at that book, I remember this approach which didnt work for me... but let me give it another shot. Maybe I did something wrong previously... https://github.com/abbood/Learning-Core-Audio-Book-Code-Sample/blob/master/CH05_Player/CH05_Player/main.c – SpaceDust__ Jun 08 '15 at 14:51
  • @Spacedust_ Same approach, different example. I've already linked to it in my answer. – dave234 Jun 08 '15 at 14:59
  • ok I remembered why this didnt work for me `// 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` for every audio packet `NSNumber *mVariableFramesInPacket = [myDict objectForKey:@"mVariableFramesInPacket"];` this is always zero – SpaceDust__ Jun 08 '15 at 15:16
  • 1
    @Spacedust_ Correct, your issue is the smaller buffer size failing. Getting kAudioConverterPropertyMaximumOutputPacketSize will give you the smallest bufferSize you can safely use. If the buffer size is larger than the frame count of your file, some of the frames read will essentially be padding for the codec. You would then have to trim the leading and trailing padding samples to get at your audio samples – dave234 Jun 08 '15 at 15:26
  • you are right, actually when I use the `afinfo` command on my macs terminal I can see the `packet size upper bound: 805` for the file and when I choose a buffer size of `806` audio plays at normal speed for that file, I think you diagnosed the problem there...... now my problem is since I am streaming the audio file (I am trying to create a progressive download player) `AudioFileStreamGetProperty` returns with an error, maybe because it is an m4a file http://pastebin.com/AzYHL0Ht – SpaceDust__ Jun 08 '15 at 15:43
  • actually, I figured out why it was not working because in the pastebin link I send you `audioFileStream` was getting deallocated, so property is always returning `0` , if you go ahead and make your second comment the answer, I will accept it and reward your bounty points – SpaceDust__ Jun 08 '15 at 15:55
  • The comment just backs up the answer, so I'll just tack it on the bottom. – dave234 Jun 08 '15 at 15:59
  • Dave turns out my problem, was completely different, I realized that when I was doing more test, anyways, I have added my own answer to my question – SpaceDust__ Jun 11 '15 at 20:59