如何在macOS中通过非默认输出设备播放麦克风音频?
在macOS上使用非默认输出设备监听麦克风音频
问题背景
需要在不修改系统默认输出设备的前提下,通过指定的非默认输出设备(比如外接耳机,系统默认是MacBook Pro扬声器)监听麦克风音频。目前已实现默认输出设备的麦克风监听,但尝试修改输出设备ID的代码无效,音频仍从默认设备输出。
现有默认输出监听代码
private func resetAudioEngine() { if let audioEngine = audioEngine { audioEngine.stop() self.audioEngine = nil } let audioEngine = AVAudioEngine() audioEngine.connect(audioEngine.inputNode, to: audioEngine.mainMixerNode, format: nil) audioEngine.connect(audioEngine.mainMixerNode, to: outputNode, format: nil) self.audioEngine = audioEngine }
尝试过但无效的代码
var outputDeviceID = deviceid if let audioSetup = audioEngine.outputNode.audioUnit { AudioUnitSetProperty(audioSetup,kAudioOutputUnitProperty_CurrentDevice,kAudioUnitScope_Global,AudioUnitElement(0), &outputDeviceID, UInt32(MemoryLayout.size(ofValue: outputDeviceID))) }
解决方案
核心思路是直接创建绑定目标输出设备的AVAudioOutputNode,而非修改默认输出节点的属性——因为AVAudioEngine的默认outputNode与系统默认设备强绑定,修改其AudioUnit属性容易因初始化时机、权限等问题失效。
完整实现代码
import AVFoundation class AudioMonitorManager { private var audioEngine: AVAudioEngine? /// 启动麦克风监听并输出到指定设备 /// - Parameter targetDeviceID: 目标输出设备的ID func startMonitoringToTargetDevice(targetDeviceID: AudioDeviceID) throws { stopMonitoring() let audioEngine = AVAudioEngine() let inputNode = audioEngine.inputNode let mainMixer = audioEngine.mainMixerNode // 基于目标设备ID创建自定义输出节点 let outputConfig = AVAudioDeviceIOConfiguration( deviceID: targetDeviceID, channelCount: 2, sampleRate: inputNode.inputFormat(forBus: 0).sampleRate, isInput: false ) let targetOutputNode = try AVAudioOutputNode(configuration: outputConfig) // 连接音频链路:麦克风输入 → 混音器 → 目标输出节点 audioEngine.connect(inputNode, to: mainMixer, format: nil) audioEngine.connect(mainMixer, to: targetOutputNode, format: nil) // 将自定义输出节点添加到引擎并启动 audioEngine.attach(targetOutputNode) try audioEngine.start() self.audioEngine = audioEngine } /// 停止监听 func stopMonitoring() { audioEngine?.stop() audioEngine = nil } /// 获取所有可用输出设备列表 static func getAllOutputDevices() -> [(name: String, id: AudioDeviceID)] { var devices: [(name: String, id: AudioDeviceID)] = [] let systemAudioObject = AudioObjectID(kAudioObjectSystemObject) // 获取所有音频设备ID var address = AudioObjectPropertyAddress( mSelector: kAudioHardwarePropertyDevices, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster ) var propertySize: UInt32 = 0 guard AudioObjectGetPropertyDataSize(systemAudioObject, &address, 0, nil, &propertySize) == noErr else { return devices } let deviceCount = Int(propertySize) / MemoryLayout<AudioDeviceID>.stride var deviceIDs = Array(repeating: AudioDeviceID(), count: deviceCount) guard AudioObjectGetPropertyData(systemAudioObject, &address, 0, nil, &propertySize, &deviceIDs) == noErr else { return devices } // 筛选输出设备并获取名称 for deviceID in deviceIDs { // 判断是否为输出设备 var outputAddress = AudioObjectPropertyAddress( mSelector: kAudioDevicePropertyStreamConfiguration, mScope: kAudioDevicePropertyScopeOutput, mElement: kAudioObjectPropertyElementMaster ) var outputSize: UInt32 = 0 if AudioObjectGetPropertyDataSize(deviceID, &outputAddress, 0, nil, &outputSize) != noErr || outputSize == 0 { continue } // 获取设备名称 var nameAddress = AudioObjectPropertyAddress( mSelector: kAudioDevicePropertyDeviceNameCFString, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster ) var name: CFString = "" as CFString var nameSize = UInt32(MemoryLayout<CFString>.size) if AudioObjectGetPropertyData(deviceID, &nameAddress, 0, nil, &nameSize, &name) == noErr { devices.append((name: name as String, id: deviceID)) } } return devices } }
关键说明
- 自定义输出节点:通过
AVAudioOutputNode(configuration:)直接绑定目标设备ID,从根源上避免了与系统默认设备的关联问题 - 节点挂载:必须调用
audioEngine.attach(targetOutputNode)将自定义节点添加到引擎,否则引擎无法识别并调度该节点 - 设备枚举:
getAllOutputDevices()方法可获取所有可用输出设备的名称和ID,用于用户选择目标设备
内容的提问来源于stack exchange,提问作者shahed
相关产品推荐
相关产品推荐

