macoscore-audio

Switching output audio device with CoreAudio in macOS


I have been using CoreAudio to switch the output device for audio output, but this technique is no longer available in macOS 13 Ventura.

How can I switch the output device in Ventura as well?

correct available output devices are

private func availableDevices() -> Array<AudioDeviceID> {
    var property: AudioObjectPropertyAddress = AudioObjectPropertyAddress(mSelector: kAudioHardwarePropertyDevices, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster)
    var dataSize:UInt32 = 0
    let status: OSStatus = AudioObjectGetPropertyDataSize(AudioObjectID(kAudioObjectSystemObject), &property, 0, nil, &dataSize)
    var devices:Array<AudioDeviceID> = Array()
    if status != kAudioHardwareNoError {
        devices = Array()
    } else {
        let deviceCount: Int = Int(dataSize) / MemoryLayout<AudioDeviceID>.size
        var foundDevices: Array<AudioDeviceID> = Array<AudioDeviceID>(repeating: 0, count: deviceCount)
        foundDevices.withUnsafeMutableBufferPointer { ( item: inout UnsafeMutableBufferPointer<AudioDeviceID>) -> () in
            let error: OSStatus = AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &property, 0, nil, &dataSize, item.baseAddress!)
            if error != kAudioHardwareNoError { print("each device ID Get Error: \(error)") }
        }// end withUnsafeMutableBufferPointer
        devices = Array(foundDevices)
    }// end if

    return devices
}// end availableDevices

And assign output audio device to output node is

internal func assignAudioDeviceTo (engine audioEngine: AVAudioEngine, selector key: SpeechPreferenceKey) {
    let output: AVAudioOutputNode = audioEngine.outputNode
    if let audioUnit: AudioUnit = output.audioUnit {
        let audioDevices: AudioDevices = AudioDevices()
        var deviceID: AudioDeviceID = AudioDevices.currentOutputtDevice().deviceID
        if let deviceName: String = key.string {
            if let device = audioDevices.outputDevices[deviceName]?.deviceID { deviceID = device }
        }// end if output device name is defined
        let error: OSStatus = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Output, 0, &deviceID, UInt32(MemoryLayout<AudioDeviceID>.size))
        if error != noErr {
            print("output device can not assign user selected device: \(error)")
        }// end if
        audioEngine.connect(audioEngine.mainMixerNode, to: output, fromBus: 0, toBus: 0, format: nil)
    }// end if output node have audio unit

    do {
        audioEngine.prepare()
        try audioEngine.start()
    } catch let error {
        print("output device start error: \(error)")
    }// end do try - catch start output device engine
}// end func assignAudioDeviceTo

Solution

  • I'm not sure why your method isn't successful, but using

    audioEngine.outputNode.auAudioUnit.setDeviceID(deviceID)
    

    works in my testing.