2

In my app I need to be able to listen for system volume changes in OS X. Looking through the AudioToolbox library it seems like what I want is AudioHardwareServiceAddPropertyListener. I tried setting up the listener with the following code:

// Volume changed callback
static OSStatus onVolumeChange(AudioObjectID inObjectID,
                                  UInt32 inNumberAddresses,
                                  const AudioObjectPropertyAddress* inAddresses,
                                  void* inClientData)
{
    NSLog(@"Volume changed");
    return noErr;
}

- (void) registerVolumeListener
{

    // Get default output device id
    AudioObjectPropertyAddress defaultOutputDevicePropertyAddress = {
        .mScope = kAudioObjectPropertyScopeGlobal,
        .mElement = kAudioObjectPropertyElementMaster,
        .mSelector = kAudioHardwarePropertyDefaultOutputDevice
    };
    AudioDeviceID outputDeviceID = kAudioObjectUnknown;
    UInt32 propertySize = sizeof(AudioDeviceID);
    OSStatus status = AudioHardwareServiceGetPropertyData(kAudioObjectSystemObject,
                                                          &defaultOutputDevicePropertyAddress,
                                                          0,
                                                          NULL,
                                                          &propertySize,
                                                          &outputDeviceID);

    // Set up listener for master volume property on default device
    AudioObjectPropertyAddress virtualMasterVolumePropertyAddress = {
        .mScope = kAudioDevicePropertyScopeOutput,
        .mElement = kAudioObjectPropertyElementMaster,
        .mSelector = kAudioHardwareServiceDeviceProperty_VirtualMasterVolume
    };
    status = AudioHardwareServiceAddPropertyListener(outputDeviceID,
                                                     &virtualMasterVolumePropertyAddress,
                                                     onVolumeChange,
                                                     (__bridge void*)self);

    // Change default device volume to trigger callback
    Float32 volumeToSet = .2;
    propertySize = sizeof(Float32);
    status = AudioHardwareServiceSetPropertyData(outputDeviceID,
                                                 &virtualMasterVolumePropertyAddress,
                                                 0,
                                                 NULL,
                                                 propertySize,
                                                 &volumeToSet);
}

but when I call registerVolumeListener from elsewhere in the code it doesn't trigger the onVolumeChange callback. When I change the system volume normally it doesn't trigger the callback either. Anyone have an idea of what might be going wrong?

  • Are you checking the status returns? Are there any errors? – Ken Thomases Jul 09 '15 at 23:22
  • I mostly omitted that for brevity, but none of the statuses are in error when I run it. (each status == noErr) – Brian Sherman Jul 09 '15 at 23:48
  • You may need to add a listener for `kAudioHardwareServiceProperty_ServiceRestarted` and re-add your listener when that callback is called. Basically, the service forgets all other listeners when that happens. – Ken Thomases Jul 10 '15 at 00:26
  • That's not firing either hmm – Brian Sherman Jul 10 '15 at 16:37
  • Core Audio has its own threads, but just to cover the bases: on which thread are you registering these callbacks? Are you letting the program run is main run loop? By the way, does setting the master volume work (other than not invoking your listener)? Does the volume actually change? – Ken Thomases Jul 10 '15 at 20:11
  • I'm registering these callbacks on the main thread. All the program is doing at this point is initializing "VolumeListener" then calling registerVolumeListener. Should I be kicking off a new thread for this? Setting and getting of the volume properties works fine. – Brian Sherman Jul 10 '15 at 21:58
  • I thought maybe it was an issue because virtualMasterVolumePropertyAddress is a virtual property, but I tried the same thing with kAudioDevicePropertyMute and that isn't working either. – Brian Sherman Jul 10 '15 at 22:01
  • I meant: what is your program doing after calling `registerVolumeListener()`? Is it an app with a main event loop and are you returning to it and it's just idle there? Is it a command-line program and, if so, what is it doing after the call to `registerVolumeListener()`? For instance, blocking in `sleep()` or something might be bad. – Ken Thomases Jul 10 '15 at 22:44
  • I think I found the Issue. My program is a command line program that was being kept alive with a while(true).. If I use a runloop like here http://stackoverflow.com/a/15071731/1710690 the callbacks fire fine. It seems my main thread was blocking the listeners from firing. – Brian Sherman Jul 10 '15 at 23:19

0 Answers0