Search code examples
objective-ccallbackcore-audio

AudioQueue callback and inUserData pointer lifetime


I've been working on an application in Objective-C that uses AudioQueueNewOutput. I've got experience with other sound libraries so the callback mechanism is familiar. However, one issue I've had is with the inUserData pointer. When you call AudioQueueStop, even with immediate set true, the callback can run again. This makes it inappropriate to deallocate whatever is in the user pointer at the same time as calling AudioQueueStop.

Is this the purpose of kAudioQueueProperty_IsRunning? Does the callback of that listener guarantee that the queue callback will not be called again and that the user pointer is safe to be deallocated?

If that is not the correct mechanism, then it's hard for me to think of what could be. The callback could try to synchronize how many buffers it thinks are left after AudioQueueStop has been called, but that seems fragile. I would like to avoid hacks like deallocating after so many seconds or leaking memory.


Solution

  • It turns out that you can use property listeners to do this safely. In particular, kAudioQueueProperty_IsRunning. Your object can keep a reference of itself and then nil it when the queue stops running.

    As an example,

    @interface Foo : NSObject
    @end
    
    @implementation Foo {
      AudioQueueRef queue;
      Foo *inUse;
    }
    
    static void listener_callback(void *user_data, AudioQueueRef queue,
                                  AudioQueuePropertyID prop) {
      Foo *p = (__bridge id)user_data;
      UInt32 res;
      UInt32 resSize = sizeof(res);
      AudioQueueGetProperty(queue, kAudioQueueProperty_IsRunning, &res, &resSize);
      if (resSize == sizeof(UInt32) && res == 0) {
        p->inUse = nil;
      }
    }
    
    - (id)init {
      // ... set up self and audio queue
      // create some kind of playback callback
      inUse = self;
      AudioQueueAddPropertyListener(queue, kAudioQueueProperty_IsRunning,
                                    listener_callback,
                                    (__bridge void *_Nullable)self);
      AudioQueueStart(queue, NULL);
      return self;
    }
    @end
    

    The property listener callback is only called when the queue starts and stops. If AudioQueueGetProperty yields 0 for this property, then the callback will not be called again and you are safe to deallocate.