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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 07:37:37