macoscore-audioaudiotoolboxaudioqueueservices

CoreAudio AudioQueue stop issue


I'm making a CoreAudio based FLAC player, and ran into a naughty issue with AudioQueues. I'm initializing my stuff like this (variables beginning with an underscore are instance variables):

    _flacDecoder = FLAC__stream_decoder_new();

    AudioStreamBasicDescription asbd = {
        .mFormatID = kAudioFormatLinearPCM,
        .mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked,
        .mSampleRate = 44100,
        .mChannelsPerFrame = 2,
        .mBitsPerChannel = 16,
        .mBytesPerPacket = 4,
        .mFramesPerPacket = 1,
        .mBytesPerFrame = 4,
        .mReserved = 0
    };

    AudioQueueNewOutput(&asbd, HandleOutputBuffer, (__bridge void *)(self), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &_audioQueue);

    for (int i = 0; i < kNumberBuffers; ++i) {
        AudioQueueAllocateBuffer(_audioQueue, 0x10000, &_audioQueueBuffers[i]);
    }

    AudioQueueSetParameter(_audioQueue, kAudioQueueParam_Volume, 1.0);

16 bit stereo PCM at 44.1 kHz, pretty basic setup. kNumberBuffers is 3, and each buffer is 0x10000 bytes. I populate the buffers with these callbacks:

static void HandleOutputBuffer(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer){

    FLACPlayer * self = (__bridge FLACPlayer*)inUserData;

    UInt32 largestBlockSizeInBytes = self->_currentStreamInfo.max_blocksize * self->_currentStreamInfo.channels * self->_currentStreamInfo.bits_per_sample/8;

    inBuffer->mAudioDataByteSize = 0;
    self->_buffer = inBuffer;

    while(inBuffer->mAudioDataByteSize <= inBuffer->mAudioDataBytesCapacity - largestBlockSizeInBytes){
        FLAC__bool result = FLAC__stream_decoder_process_single(self->_flacDecoder);
        assert(result);

        if(FLAC__stream_decoder_get_state(self->_flacDecoder) == FLAC__STREAM_DECODER_END_OF_STREAM){
            AudioQueueStop(self->_audioQueue, false);
            break;
        }
    }

    AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}

static FLAC__StreamDecoderWriteStatus flacDecoderWriteCallback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data){

    FLACPlayer * self = (__bridge FLACPlayer *)client_data;

    assert(frame->header.bits_per_sample == 16); // TODO

    int16_t * bufferWritePosition = (int16_t*)((uint8_t*)self->_buffer->mAudioData + self->_buffer->mAudioDataByteSize);
    for(int i = 0; i < frame->header.blocksize; i++){
        for(int j = 0; j < frame->header.channels; j++){
            *bufferWritePosition = (int16_t)buffer[j][i];
            bufferWritePosition++;
        }
    }

    int totalFramePayloadInBytes = frame->header.channels * frame->header.blocksize * frame->header.bits_per_sample/8;
    self->_buffer->mAudioDataByteSize += totalFramePayloadInBytes;

    return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

static void flacDecoderMetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data){

    FLACPlayer * self = (__bridge FLACPlayer*) client_data;

    if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO){
        self->_currentStreamInfo = metadata->data.stream_info;
    }
}

Basically when the queue requests a new buffer, I fill the buffer from the FLAC__stream_decoder, then I enqueue it. Just like everyone else would do. When libFLAC tells me that I've reached the end of my file, I tell the AudioQueue to stop asynchronously, until it had consumed all the buffers' contents. However, instead of playing through the end, the playback stops a tiny bit before it should. If I remove this line:

AudioQueueStop(self->_audioQueue, false);

everything works fine; the audio plays end-to-end, although my queue keeps running till the end of time. If I change that line to this:

AudioQueueStop(self->_audioQueue, true);

then the playback stops immediately/synchronously, as you'd expect from Apple's documentation:

If you pass true, stopping occurs immediately (that is, synchronously). If you pass false, the function returns immediately, but the audio queue does not stop until its queued buffers are played or recorded (that is, the stop occurs asynchronously). Audio queue callbacks are invoked as necessary until the queue actually stops.

My questions are: - am I doing anything wrong? - how can I play my audio until the end, and shut down the queue appropriately?


Solution

  • Of course, after struggling with this stuff for hours, I've found the solution minutes after posting this question... The problem was that the AudioQueue doesn't care about buffers enqueued after calling AudioQueueStop(..., false). So now I'm feeding the queue like this, and everything works like charm:

    static void HandleOutputBuffer(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer){
    
        FLACPlayer * self = (__bridge FLACPlayer*)inUserData;
    
        UInt32 largestBlockSizeInBytes = self->_currentStreamInfo.max_blocksize * self->_currentStreamInfo.channels * self->_currentStreamInfo.bits_per_sample/8;
    
        inBuffer->mAudioDataByteSize = 0;
        self->_buffer = inBuffer;
    
        bool shouldStop = false;
    
        while(inBuffer->mAudioDataByteSize <= inBuffer->mAudioDataBytesCapacity - largestBlockSizeInBytes){
            FLAC__bool result = FLAC__stream_decoder_process_single(self->_flacDecoder);
            assert(result);
    
            if(FLAC__stream_decoder_get_state(self->_flacDecoder) == FLAC__STREAM_DECODER_END_OF_STREAM){
                shouldStop = true;
                break;
            }
        }
    
        AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
        if(shouldStop){
            AudioQueueStop(self->_audioQueue, false);
        }
    }