Search code examples
swift3core-audio

AudioObjectAddPropertyListenerBlock not called in Swift 3


I want to observe Audio Device Parameter changes using Swift 3. I look up some info and came to this code. In my viewmodel, I setup a listener block, and the resulting status is 0 (succeeded):

    override func viewDidLoad() {
        super.viewDidLoad()



        addListenerBlock(listenerBlock: audioObjectPropertyListenerBlock,
                         onAudioObjectID: AudioObjectID(bitPattern: kAudioObjectSystemObject),
                         forPropertyAddress: AudioObjectPropertyAddress(
                            mSelector: kAudioDevicePropertyVolumeScalar,
                            mScope: kAudioObjectPropertyScopeGlobal,
                            mElement: kAudioObjectPropertyElementMaster))

    }

    // Utility function to simplify adding listener blocks:
    func addListenerBlock( listenerBlock: @escaping AudioObjectPropertyListenerBlock, onAudioObjectID: AudioObjectID, forPropertyAddress: AudioObjectPropertyAddress) {
        var forPropertyAddress = forPropertyAddress

let status = AudioObjectAddPropertyListenerBlock(onAudioObjectID, &forPropertyAddress, nil, listenerBlock)
        print(status)
    }


    func audioObjectPropertyListenerBlock (numberAddresses: UInt32, addresses: UnsafePointer<AudioObjectPropertyAddress>) {

        var index: UInt32 = 0
        while index < numberAddresses {
            let address: AudioObjectPropertyAddress = addresses[index]
            switch address.mSelector {
            case kAudioHardwarePropertyDefaultOutputDevice:

                let deviceID = getDefaultAudioOutputDevice()
                print("kAudioHardwarePropertyDefaultOutputDevice: \(deviceID)")

            default:

                print("We didn't expect this!")

            }
            index += 1
        }
    }
        // Utility function to get default audio output device:
        func getDefaultAudioOutputDevice () -> AudioObjectID {

            var devicePropertyAddress = AudioObjectPropertyAddress(mSelector: kAudioHardwarePropertyDefaultOutputDevice, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster)
            var deviceID: AudioObjectID = 0
            var dataSize = UInt32(truncatingBitPattern: MemoryLayout<AudioDeviceID>.size)
            let systemObjectID = AudioObjectID(bitPattern: kAudioObjectSystemObject)
            if (kAudioHardwareNoError != AudioObjectGetPropertyData(systemObjectID, &devicePropertyAddress, 0, nil, &dataSize, &deviceID)) { return 0 }
            return deviceID
    }

Yet even though the OSStatus is 0 and the listener is added, it is not called when I change a parameter (in this case, output volume). I set a breakpoint in my audioObjectPropertyListenerBlock but my application never gets there. What did I miss?


Solution

  • The problems lie here:

    addListenerBlock(listenerBlock: audioObjectPropertyListenerBlock,
                   onAudioObjectID: AudioObjectID(bitPattern: kAudioObjectSystemObject),
                    forPropertyAddress: AudioObjectPropertyAddress(
                             mSelector: kAudioDevicePropertyVolumeScalar,
                                mScope: kAudioObjectPropertyScopeGlobal,
                            mElement: kAudioObjectPropertyElementMaster))
    
    1. onAudioObjectID should be the default output device: getDefaultAudioOutputDevice() (these int-ly typed APIs are confusing)
    2. mScope should be kAudioObjectPropertyScopeOutput, and
    3. mElement should be 1 or 2. (at least my output device doesn't have a master element!)

    All together:

    addListenerBlock(listenerBlock: audioObjectPropertyListenerBlock,
                     onAudioObjectID: getDefaultAudioOutputDevice(),
                     forPropertyAddress: AudioObjectPropertyAddress(
                        mSelector: kAudioDevicePropertyVolumeScalar,
                        mScope: kAudioObjectPropertyScopeOutput,
                        mElement: 1))
    

    After these changes, your listener block is called when the left channel output volume is changed.

    I'm not sure what you're trying to do in the listener block, however, as mSelector is always going to be the kAudioDevicePropertyVolumeScalar you requested, and never kAudioObjectSystemObject.