You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AudioKit在Mac上无法显示全部可用输入输出设备的问题求助

AudioKit跨平台音频设备选择与实时监听问题解决

先给你明确结论

完全可以通过AudioKit实现Mac Catalyst、iPad、iOS平台的独立输入输出设备选择+实时麦克风监听,你遇到的Mac端只返回当前设备的问题,是因为Mac Catalyst的音频架构兼容逻辑和纯Mac/iOS不一样,针对性处理就能解决。

为啥会出现这种差异?

不同平台的AudioKit底层音频管理逻辑有区别:

  • iOS/iPadOS:AudioKit依赖系统的AVAudioSession,Settings.session.availableInputs/availableOutputs能直接拿到所有可用设备,因为iOS的音频设备枚举是系统统一管控的,只要设备接入就会被检测到。
  • Mac Catalyst:它本质是iOS app跑在Mac上的兼容层,AudioKit默认沿用iOS的会话逻辑,但Mac本身的Core Audio架构和iOS不同。默认情况下,Catalyst app会直接继承Mac系统当前选中的音频上下文,导致AudioEngine.inputDevices这类API只返回当前在用的设备,而非全部可用的。
  • 纯Mac平台:AudioKit直接调用Core Audio的原生API枚举设备,所以能返回全量设备,这就是你看到纯Mac目标下方法不同的原因——底层依赖的框架不一样。

解决方法

针对Mac Catalyst的特殊情况,我们可以通过条件编译,在Catalyst下直接调用Core Audio的API枚举设备,iOS/iPadOS继续用AudioKit的原有逻辑:

1. 封装跨平台的设备枚举工具

import AudioKit
import CoreAudio

// 封装设备枚举逻辑
enum AudioDeviceHelper {
    static func getAllInputs() -> [Device] {
        #if targetEnvironment(macCatalyst)
            var devices = [Device]()
            // 用Core Audio获取所有系统音频设备
            let systemAudioObject = AudioObjectID(kAudioObjectSystemObject)
            var address = AudioObjectPropertyAddress(
                mSelector: kAudioHardwarePropertyDevices,
                mScope: kAudioObjectPropertyScopeGlobal,
                mElement: kAudioObjectPropertyElementMaster
            )
            
            var propertySize: UInt32 = 0
            guard AudioObjectGetPropertyDataSize(systemAudioObject, &address, 0, nil, &propertySize) == noErr else {
                return []
            }
            
            let deviceCount = Int(propertySize) / MemoryLayout<AudioObjectID>.stride
            var deviceIDs = [AudioObjectID](repeating: 0, count: deviceCount)
            _ = AudioObjectGetPropertyData(systemAudioObject, &address, 0, nil, &propertySize, &deviceIDs)
            
            // 筛选出输入设备
            for id in deviceIDs {
                var inputConfigAddress = AudioObjectPropertyAddress(
                    mSelector: kAudioDevicePropertyStreamConfiguration,
                    mScope: kAudioObjectPropertyScopeInput,
                    mElement: kAudioObjectPropertyElementMaster
                )
                var inputSize: UInt32 = 0
                if AudioObjectGetPropertyDataSize(id, &inputConfigAddress, 0, nil, &inputSize) == noErr && inputSize > 0 {
                    // 获取设备名称
                    var nameAddress = AudioObjectPropertyAddress(
                        mSelector: kAudioDevicePropertyDeviceNameCFString,
                        mScope: kAudioObjectPropertyScopeGlobal,
                        mElement: kAudioObjectPropertyElementMaster
                    )
                    var name: CFString?
                    var nameSize = UInt32(MemoryLayout<UnsafeMutablePointer<CFString?>>.stride)
                    _ = AudioObjectGetPropertyData(id, &nameAddress, 0, nil, &nameSize, &name)
                    
                    if let deviceName = name {
                        devices.append(Device(name: deviceName as String, id: id))
                    }
                }
            }
            return devices
        #else
            return AudioEngine.inputDevices
        #endif
    }
    
    static func getAllOutputs() -> [Device] {
        #if targetEnvironment(macCatalyst)
            var devices = [Device]()
            let systemAudioObject = AudioObjectID(kAudioObjectSystemObject)
            var address = AudioObjectPropertyAddress(
                mSelector: kAudioHardwarePropertyDevices,
                mScope: kAudioObjectPropertyScopeGlobal,
                mElement: kAudioObjectPropertyElementMaster
            )
            
            var propertySize: UInt32 = 0
            guard AudioObjectGetPropertyDataSize(systemAudioObject, &address, 0, nil, &propertySize) == noErr else {
                return []
            }
            
            let deviceCount = Int(propertySize) / MemoryLayout<AudioObjectID>.stride
            var deviceIDs = [AudioObjectID](repeating: 0, count: deviceCount)
            _ = AudioObjectGetPropertyData(systemAudioObject, &address, 0, nil, &propertySize, &deviceIDs)
            
            // 筛选出输出设备
            for id in deviceIDs {
                var outputConfigAddress = AudioObjectPropertyAddress(
                    mSelector: kAudioDevicePropertyStreamConfiguration,
                    mScope: kAudioObjectPropertyScopeOutput,
                    mElement: kAudioObjectPropertyElementMaster
                )
                var outputSize: UInt32 = 0
                if AudioObjectGetPropertyDataSize(id, &outputConfigAddress, 0, nil, &outputSize) == noErr && outputSize > 0 {
                    var nameAddress = AudioObjectPropertyAddress(
                        mSelector: kAudioDevicePropertyDeviceNameCFString,
                        mScope: kAudioObjectPropertyScopeGlobal,
                        mElement: kAudioObjectPropertyElementMaster
                    )
                    var name: CFString?
                    var nameSize = UInt32(MemoryLayout<UnsafeMutablePointer<CFString?>>.stride)
                    _ = AudioObjectGetPropertyData(id, &nameAddress, 0, nil, &nameSize, &name)
                    
                    if let deviceName = name {
                        devices.append(Device(name: deviceName as String, id: id))
                    }
                }
            }
            return devices
        #else
            return AudioEngine.outputDevices
        #endif
    }
}

2. 选中设备并启动实时监听

拿到设备列表后,给AudioEngine指定设备,再连接监听节点即可:

// 示例:选中第一个输入和输出设备
guard let inputDevice = AudioDeviceHelper.getAllInputs().first,
      let outputDevice = AudioDeviceHelper.getAllOutputs().first else {
    print("未检测到可用音频设备")
    return
}

do {
    // 设置AudioEngine的输入输出设备
    try AudioEngine.setInputDevice(inputDevice)
    try AudioEngine.setOutputDevice(outputDevice)
    
    // 创建输入节点和监听 mixer
    let inputNode = AudioEngine.inputNode
    let monitorMixer = Mixer()
    
    // 连接节点:输入 → 监听 mixer → 输出
    inputNode.connect(to: monitorMixer)
    monitorMixer.connect(to: AudioEngine.outputNode)
    
    // 启动音频引擎
    try AudioEngine.start()
    print("实时监听已启动")
} catch {
    print("音频引擎启动失败:\(error.localizedDescription)")
}

额外注意点

  • 权限配置:不管哪个平台,都要在Info.plist里加NSMicrophoneUsageDescription描述麦克风权限;Mac Catalyst还要在项目签名权限中开启音频输入输出权限。
  • 设备变化监听:可以用AudioObjectAddPropertyListener监听设备插拔事件,实时更新设备列表,让用户体验更流畅。

内容的提问来源于stack exchange,提问作者fhamm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 23:08:11