Search code examples
iphonecaudiocore-audio

AudioQueue ate my buffer (first 15 milliseconds of it)


I am generating audio programmatically. I hear gaps of silence between my buffers. When I hook my phone to a scope, I see that the first few samples of each buffer are missing, and in their place is silence. The length of this silence varies from almost nothing to as much as 20 ms.

My first thought is that my original callback function takes too much time. I replace it with the shortest one possible--it re-renqueues the same buffer over and over. I observe the same behavior.

AudioQueueRef aq;
AudioQueueBufferRef aq_buffer;
AudioStreamBasicDescription asbd;

void aq_callback (void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
    OSStatus s = AudioQueueEnqueueBuffer(aq, aq_buffer, 0, NULL);
}

void aq_init(void) {
    OSStatus s;

    asbd.mSampleRate = AUDIO_SAMPLES_PER_S;
    asbd.mFormatID = kAudioFormatLinearPCM;
    asbd.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; 
    asbd.mBytesPerPacket = 1;
    asbd.mFramesPerPacket = 1; 
    asbd.mBytesPerFrame = 1;
    asbd.mChannelsPerFrame = 1;
    asbd.mBitsPerChannel = 8;
    asbd.mReserved = 0;


    int PPM_PACKETS_PER_SECOND = 50;
    // one buffer is as long as one PPM frame
    int BUFFER_SIZE_BYTES = asbd.mSampleRate/PPM_PACKETS_PER_SECOND*asbd.mBytesPerFrame;

    s = AudioQueueNewOutput(&asbd, aq_callback, NULL, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &aq);
    s = AudioQueueAllocateBuffer(aq, BUFFER_SIZE_BYTES, &aq_buffer);

    // put samples in the buffer
    buffer_data(my_data, aq_buffer);

    s = AudioQueueStart(aq, NULL);
    s = AudioQueueEnqueueBuffer(aq, aq_buffer, 0, NULL);
}

Solution

  • I'm not familiar with the iPhone audio APIs but it appears to be similar to other ones where generally you would queue up more than one buffer, This way when the system is finished processing the first buffer, it can immediately start processing the next buffer (since it's already been queued up) while the completion callback on the 1st buffer is being executed.

    Something like:

    AudioQueueRef aq;
    AudioQueueBufferRef aq_buffer[2];
    AudioStreamBasicDescription asbd;
    
    void aq_callback (void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
        // note that the callback tells us which buffer has been completed, so all
        //  we have to do is queue it back up
        OSStatus s = AudioQueueEnqueueBuffer(aq, inBuffer, 0, NULL);
    }
    
    void aq_init(void) {
        OSStatus s;
    
        asbd.mSampleRate = AUDIO_SAMPLES_PER_S;
        asbd.mFormatID = kAudioFormatLinearPCM;
        asbd.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; 
        asbd.mBytesPerPacket = 1;
        asbd.mFramesPerPacket = 1; 
        asbd.mBytesPerFrame = 1;
        asbd.mChannelsPerFrame = 1;
        asbd.mBitsPerChannel = 8;
        asbd.mReserved = 0;
    
    
        int PPM_PACKETS_PER_SECOND = 50;
        // one buffer is as long as one PPM frame
        int BUFFER_SIZE_BYTES = asbd.mSampleRate/PPM_PACKETS_PER_SECOND*asbd.mBytesPerFrame;
    
        s = AudioQueueNewOutput(&asbd, aq_callback, NULL, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &aq);
        s = AudioQueueAllocateBuffer(aq, BUFFER_SIZE_BYTES, &aq_buffer[0]);
        s = AudioQueueAllocateBuffer(aq, BUFFER_SIZE_BYTES, &aq_buffer[1]);
    
        // put samples in the buffer - fill both buffers
        buffer_data(my_data, aq_buffer[0]);
        buffer_data(my_data, aq_buffer[1]);
    
        s = AudioQueueStart(aq, NULL);
        s = AudioQueueEnqueueBuffer(aq, aq_buffer[0], 0, NULL);
        s = AudioQueueEnqueueBuffer(aq, aq_buffer[1], 0, NULL);
    }