5

I'm using the Audio Queue Services API to play audio streamed from a server over a TCP socket connection on an iPhone. I can play the buffers that were filled from the socket connection, I just cannot seem to make my AudioQueue call my AudioQueueOutputCallback function, and I'm out of ideas.

High level design

  1. Data is passed to the player from the socket connection, and written immediately into circular buffers in memory.
  2. As AudioQueueBuffers become available, data is copied from the circular buffers into the available AudioQueueBuffer, which is immediately re-queued. (Or would be, if my callback happened)

What happens

The buffers are all filled and enqueued successfully, and I hear the audio stream clearly. For testing, I use a large number of buffers (15) and all of them play through seamlessly, but the AudioQueueOutputCallback is never called, so I never re-queue any of those buffers, despite the fact that everything seems to be working perfectly. If I don't wait for my callback, assuming it will never be called, and instead drive the enqueueing of buffers based on the data as it is written, I can play the audio stream indefinitely, reusing and re-enqueueing buffers as if they had been explicitly returned to me by the callback. It is that fact: that I can play the stream perfectly while reusing buffers as needed, that confuses me the most. Why isn't the callback being called?

Possibly Relevant Code

The format of the stream is 16 bit linear PCM, 8 kHz, Mono:

_streamDescription.mSampleRate = 8000.0f;
_streamDescription.mFormatID = kAudioFormatLinearPCM;
_streamDescription.mBytesPerPacket = 2;
_streamDescription.mFramesPerPacket = 1;
_streamDescription.mBytesPerFrame = sizeof(AudioSampleType);
_streamDescription.mChannelsPerFrame = 1;
_streamDescription.mBitsPerChannel = 8 * sizeof(AudioSampleType)
_streamDescription.mReserved = 0;
_streamDescription.mFormatFlags = (kLinearPCMFormatFlagIsBigEndian | 
                                   kLinearPCMFormatFlagIsPacked);

My prototype and implementation of the callback are as follows. Nothing fancy, and pretty much identical to every example I've seen so far:

// Prototype, declared above the class's @implementation
void AQBufferCallback(void* inUserData, AudioQueueRef inAudioQueue, AudioQueueBufferRef inAudioQueueBuffer);

// Definition at the bottom of the file.
void AQBufferCallback(void* inUserData, AudioQueueRef inAudioQueue, AudioQueueBufferRef inAudioQueueBuffer) {

    printf("callback\n");
    [(MyAudioPlayer *)inUserData audioQueue:inAudioQueue didAquireBufferForReuse:inAudioQueueBuffer];
}

I create the AudioQueue like this:

OSStatus status = 0;
status = AudioQueueNewOutput(&_streamDescription, 
                             AQBufferCallback, // <-- Doesn't work...
                             self, 
                             CFRunLoopGetCurrent(),
                             kCFRunLoopCommonModes, 
                             0,
                             &_audioQueue);
if (status) {

    // This is not called...
    NSLog(@"Error creating new audio output queue: %@", [MyAudioPlayer stringForOSStatus:status]);
    return;
}

And I enqueue buffers like this. At this point, it is known that the local buffer contains the correct amount of data for copying:

memcpy(aqBuffer->mAudioData, localBuffer, kAQBufferSize);
aqBuffer->mAudioDataByteSize = kAQBufferSize;

OSStatus status = AudioQueueEnqueueBuffer(_audioQueue, aqBuffer, 0, NULL);
if (status) {
    // This is also not called.
    NSLog(@"Error enqueueing buffer %@", [MyAudioPlayer stringForOSStatus:status]);
}

Please save me.

Matt Wilding
  • 20,115
  • 3
  • 67
  • 95
  • Do you call `AudioQueueStart`? have only used the low level CoreAudio API and there you have to call `AudioDeviceStart` and `AudioDeviceStop` – Mattias Wadman Sep 27 '11 at 21:26
  • If nothing works, maybe OpenAL is easier to get going – Mattias Wadman Sep 27 '11 at 21:27
  • @Mattias: Yeah, I call AudioQueueStart(). That's how I'm able to hear the audio playback. I've never looked at OpenAL, and I just can't give up on what feels like an 98% working solution just yet. We'll see how I feel in a few days... – Matt Wilding Sep 27 '11 at 21:35
  • I have the callback declared in the .h file, I don't know if that could matter... – Fran Sevillano Sep 27 '11 at 22:32
  • @frowing: I doubt that the function prototype could have any effect on the behavior. I tried it, for the sake of thoroughness. No difference. – Matt Wilding Sep 27 '11 at 22:54
  • Is this done on the main thread or some background thread? – Mattias Wadman Sep 27 '11 at 22:55
  • @Mattias: That's a good question, and it's what I've been poking for the last hour. All of this actually happens on the background thread of a GCD serial dispatch queue. I don't find any threading considerations in the documentation, however. – Matt Wilding Sep 27 '11 at 22:57
  • It's probably not good if CFRunLoopGetCurrent is run loop of a thread that disappers or is run loop that dont care about kCFRunLoopCommonModes. Maybe try CFRunLoopGetMain()? – Mattias Wadman Sep 27 '11 at 23:11
  • @Mattias: That was it. I dispatched the queue creation to the main thread and it worked, (because CFRunLoopGetCurrent() then returns the same thing as CFRunLoopGetMain()). Your comment also works. Make that an answer and you get the Green Check. – Matt Wilding Sep 28 '11 at 00:00
  • Nice! have reformatted my comments into a answer now. – Mattias Wadman Sep 28 '11 at 07:42

2 Answers2

7

Is this executed on the main thread or a background thread? probably not good if CFRunLoopGetCurrent() returns a run loop of a thread that could disappear (thread pool etc) or is a run loop that don't care about kCFRunLoopCommonModes.

Try to change CFRunLoopGetCurrent() to CFRunLoopGetMain() or make sure AudioQueueNewOutput() and CFRunLoopGetCurrent() is executed on the main thread or a thread that you have control over and has a proper run loop.

Mattias Wadman
  • 11,172
  • 2
  • 42
  • 57
-2

Try changing self for (void*)self. Like this:

status = AudioQueueNewOutput(&_streamDescription, AQBufferCallback, (void*)self, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &_audioQueue);

Fran Sevillano
  • 8,103
  • 4
  • 31
  • 45
  • No dice with casting self. I can set that inUserData argument to anything, and the results are the same: good playback, no callback. – Matt Wilding Sep 27 '11 at 22:23