This is fairly simple (I think). I\'m simply wanting to get a notification in my application whenever a user changes the default sound input or sound output device in System Pr
Set up an AudioObjectPropertyAddress
for the default output device:
AudioObjectPropertyAddress outputDeviceAddress = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
Then, use AudioObjectAddPropertyListener
to register a listener for changes in the default device:
AudioObjectAddPropertyListener(kAudioObjectSystemObject,
&outputDeviceAddress,
&callbackFunction, nil);
The callback function looks like this:
OSStatus callbackFunction(AudioObjectID inObjectID,
UInt32 inNumberAddresses,
const AudioObjectPropertyAddress inAddresses[],
void *inClientData)
As a side note, you should also use AudioObjectPropertyAddress
to tell the HAL to manage its own thread for notifications. You do this by setting the run loop selector to NULL. I actually perform this step before setting up the output device listener.
AudioObjectPropertyAddress runLoopAddress = {
kAudioHardwarePropertyRunLoop,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
CFRunLoopRef runLoop = NULL;
UInt32 size = sizeof(CFRunLoopRef);
AudioObjectSetPropertyData(kAudioObjectSystemObject,
&runLoopAddress, 0, NULL, size, &runLoop);
Here's how to do it in Swift:
Register for notifications by adding a listener block, for example when a View Controller loads its view. The addListenerBlock
function simplifies adding property listener blocks. Swift allows parameters to be variables, which is very convenient for the forPropertyAddress
parameter. When calling addListenerBlock
, the property address parameters are just plugged in.
import Cocoa
import CoreAudio
class ViewController: NSViewController {
// Utility function to simplify adding listener blocks:
func addListenerBlock( listenerBlock: AudioObjectPropertyListenerBlock, onAudioObjectID: AudioObjectID, var forPropertyAddress: AudioObjectPropertyAddress) {
if (kAudioHardwareNoError != AudioObjectAddPropertyListenerBlock(onAudioObjectID, &forPropertyAddress, nil, listenerBlock)) {
print("Error calling: AudioObjectAddPropertyListenerBlock") }
}
override func viewDidLoad() { super.viewDidLoad()
addListenerBlock(audioObjectPropertyListenerBlock,
onAudioObjectID: AudioObjectID(bitPattern: kAudioObjectSystemObject),
forPropertyAddress: AudioObjectPropertyAddress(
mSelector: kAudioHardwarePropertyDefaultOutputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster))
}
...
Provide a listener block function to receive the notifications. You're given an array that could potentially have more than one property address, so loop through and look for the one you want:
func audioObjectPropertyListenerBlock (numberAddresses: UInt32, addresses: UnsafePointer<AudioObjectPropertyAddress>) {
var index: UInt32 = 0
while index < numberAddresses {
let address: AudioObjectPropertyAddress = addresses[0]
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: sizeof(AudioDeviceID))
let systemObjectID = AudioObjectID(bitPattern: kAudioObjectSystemObject)
if (kAudioHardwareNoError != AudioObjectGetPropertyData(systemObjectID, &devicePropertyAddress, 0, nil, &dataSize, &deviceID)) { return 0 }
return deviceID
}
}
Of course, you can simply add more cases
to the switch
statement if you want to monitor other audio device properties. Call addListenerBlock
to add whatever devices and property addresses you're interested in.