macOS 13+中AVAudioSession不可用,如何获取音频输出路由?
问题描述
AVAudioSession在macOS 13 Ventura中已被弃用,macOS 14 Sonoma中完全无法使用。原本可通过AVAudioSession.sharedInstance().currentRoute()轻松获取音频输出路由,但现在需要替代方案实现类似功能——列出音频输出路由及其对应的正在使用的应用,目标输出格式如下:
Route name: Built-in Speaker App name: Spotify Route name: Headphones App name: Teams Route name: Display Audio App name: Chrome
原失效Swift代码:
import AppKit import AVFoundation class ViewController: NSViewController { private var audioSession: AVAudioSession! override func viewDidLoad() { super.viewDidLoad() // Initialize the audio session. audioSession = AVAudioSession.sharedInstance() // Get a list of all audio routes on the device. let audioRoutes = audioSession.currentRoutes // For each audio route, print the route name and the app name. for audioRoute in audioRoutes { print("Route name: \(audioRoute.name)") print("App name: \(audioRoute.sourcePortName)") } } }
补充背景:正在开发一款可单独管理各应用音量的工具,当前需先实现“列出音频输出路由及对应应用”的功能,已尝试AppKit/AVFoundation框架未找到可行替代方案。
解决方案:使用CoreAudio API替代
由于AVAudioSession的路由相关接口被弃用,需直接调用CoreAudio框架的底层API获取音频设备、应用会话及两者的关联关系。以下是实现代码:
import AppKit import CoreAudio class ViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() listAudioRoutesWithApps() } private func listAudioRoutesWithApps() { var audioDevices: [AudioDeviceID] = [] var propertyAddress = AudioObjectPropertyAddress( mSelector: kAudioHardwarePropertyDevices, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster ) // 获取所有音频设备 var dataSize: UInt32 = 0 var status = AudioObjectGetPropertyDataSize(AudioObjectID(kAudioObjectSystemObject), &propertyAddress, 0, nil, &dataSize) guard status == noErr else { print("获取设备列表大小失败:\(status)") return } let deviceCount = Int(dataSize / UInt32(MemoryLayout<AudioDeviceID>.size)) audioDevices = Array(repeating: 0, count: deviceCount) status = AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &propertyAddress, 0, nil, &dataSize, &audioDevices) guard status == noErr else { print("获取设备列表失败:\(status)") return } // 遍历每个设备,获取关联的应用 for deviceID in audioDevices { // 获取设备名称 var deviceName = [UInt8](repeating: 0, count: 256) propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString dataSize = UInt32(deviceName.count) status = AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, nil, &dataSize, &deviceName) guard status == noErr else { continue } guard let routeName = String(cString: deviceName) as String? else { continue } // 获取设备上的所有音频会话(对应应用) propertyAddress.mSelector = kAudioDevicePropertyIOStreamClients dataSize = 0 status = AudioObjectGetPropertyDataSize(deviceID, &propertyAddress, 0, nil, &dataSize) guard status == noErr else { continue } let clientCount = Int(dataSize / UInt32(MemoryLayout<AudioStreamID>.size)) var streamIDs = Array(repeating: AudioStreamID(), count: clientCount) status = AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, nil, &dataSize, &streamIDs) guard status == noErr else { continue } // 从音频会话获取应用信息 for streamID in streamIDs { var clientPID: pid_t = 0 propertyAddress.mSelector = kAudioStreamPropertyClientPID dataSize = UInt32(MemoryLayout<pid_t>.size) status = AudioObjectGetPropertyData(streamID, &propertyAddress, 0, nil, &dataSize, &clientPID) guard status == noErr, clientPID != 0 else { continue } if let app = NSRunningApplication(processIdentifier: clientPID) { print("Route name: \(routeName)") print("App name: \(app.localizedName ?? app.bundleIdentifier ?? "Unknown")\n") } } } } }
代码说明
- 获取音频设备列表:通过
kAudioHardwarePropertyDevices获取系统所有音频设备ID。 - 获取设备名称:使用
kAudioDevicePropertyDeviceNameCFString读取设备显示名称(对应输出中的“Route name”)。 - 获取设备关联音频流:通过
kAudioDevicePropertyIOStreamClients获取当前设备上的所有活跃音频流,每个流对应一个应用的音频会话。 - 获取应用信息:从音频流的
kAudioStreamPropertyClientPID获取进程ID,再通过NSRunningApplication获取应用名称(对应输出中的“App name”)。
注意事项
- 需在项目中添加
CoreAudio框架依赖。 - 部分音频流属于系统进程,可根据需求过滤非用户应用条目。
- 若需监听路由/应用变化,可使用
AudioObjectAddPropertyListener注册属性监听回调。
内容的提问来源于stack exchange,提问作者pportella23
相关产品推荐
相关产品推荐

