如何在 macOS 版 Swift 中将音频路由到默认扬声器?

问题描述 投票:0回答:2

我有一个为 macOS swiftUI 应用程序播放音频的功能,但我希望它每次都通过默认内置扬声器播放声音。有谁知道有什么可靠的方法吗?

我研究了很多,但还没有找到适用于 Macos 的可靠方法。这是我尝试过的:

  1. AVRoutePickerView

这仅适用于 ios 和 Mac Catalyst,但不适用于 macOS

  1. 在AVAudioEngine中获取设备ID

我找到了这个代码片段,但它假设内置扬声器设备 ID 保持不变,但事实并非如此,所以这没有帮助。

    engine = AVAudioEngine()
    let output = engine.outputNode

    // get the low level input audio unit from the engine:
    let outputUnit = output.audioUnit!
    // use core audio low level call to set the input device:
    var outputDeviceID: AudioDeviceID = 51  // replace with actual, dynamic value
    AudioUnitSetProperty(outputUnit,
                         kAudioOutputUnitProperty_CurrentDevice,
                         kAudioUnitScope_Global,
                         0,
                         &outputDeviceID,
                         UInt32(MemoryLayout<AudioDeviceID>.size))
  1. 禁用蓝牙,以便音频仅通过主扬声器而不是蓝牙扬声器。这似乎不是最好的方法,所以我还没有测试过。

以下是我播放声音的代码:

func playTheSound() {
    let url = Bundle.main.url(forResource: "Blow", withExtension: "mp3")
    player = try! AVAudioPlayer(contentsOf: url!)
    player?.play()
    print("Sound was played")
    //

那么,关于如何将音频路由到 macOS 主扬声器有什么建议吗?

swift macos cocoa
2个回答
1
投票

“默认内置”我认为您实际上只是指“内置”。默认扬声器是音频已经路由到的扬声器。

最简单且“可能”始终有效的解决方案是路由到 UID“BuiltInSpeakerDevice”。例如,这可以满足您的要求: let player = AVPlayer() func playTheSound() { let url = URL(filePath: "/System/Library/Sounds/Blow.aiff") let item = AVPlayerItem(url: url) player.replaceCurrentItem(with: item) player.audioOutputDeviceUniqueID = "BuiltInSpeakerDevice" player.play() }

注意这里AVPlayer和
audioOutputDeviceUniqueID

的使用。我敢打赌这在大约 100% 的情况下都有效。如果没有内置扬声器,它甚至应该“工作”,因为如果 UID 不存在,它会默默地失败(不会崩溃)。

但是...叹息...我找不到任何记录此内容的地方或该字符串的任何系统常量。我真的很讨厌魔法、无证的字符串。所以,让我们做对吧。此外,如果我们做得正确,它也可以与 AVAudioEngine 一起使用。那么我们就到那里吧。

首先,您应该始终查看 Swift 4 中宝贵的

CoreAudio 输出设备有用方法

。我不知道是否有人把它变成了一个真正的框架,但这是一个例子的宝库。以下代码是其现代版本。 struct AudioDevice { let id: AudioDeviceID static func getAll() -> [AudioDevice] { var propertyAddress = AudioObjectPropertyAddress( mSelector: kAudioHardwarePropertyDevices, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain) // Get size of buffer for list var devicesBufferSize: UInt32 = 0 AudioObjectGetPropertyDataSize(AudioObjectID(kAudioObjectSystemObject), &propertyAddress, 0, nil, &devicesBufferSize) let devicesCount = Int(devicesBufferSize) / MemoryLayout<AudioDeviceID>.stride // Get list let devices = Array<AudioDeviceID>(unsafeUninitializedCapacity: devicesCount) { buffer, initializedCount in AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &propertyAddress, 0, nil, &devicesBufferSize, buffer.baseAddress!) initializedCount = devicesCount } return devices.map(Self.init) } var hasOutputStreams: Bool { var propertySize: UInt32 = 256 var propertyAddress = AudioObjectPropertyAddress( mSelector: kAudioDevicePropertyStreams, mScope: kAudioDevicePropertyScopeOutput, mElement: kAudioObjectPropertyElementMain) AudioObjectGetPropertyDataSize(id, &propertyAddress, 0, nil, &propertySize) return propertySize > 0 } var isBuiltIn: Bool { transportType == kAudioDeviceTransportTypeBuiltIn } var transportType: AudioDevicePropertyID { var deviceTransportType = AudioDevicePropertyID() var propertySize = UInt32(MemoryLayout<AudioDevicePropertyID>.size) var propertyAddress = AudioObjectPropertyAddress( mSelector: kAudioDevicePropertyTransportType, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain) AudioObjectGetPropertyData(id, &propertyAddress, 0, nil, &propertySize, &deviceTransportType) return deviceTransportType } var uid: String { var propertySize = UInt32(MemoryLayout<CFString>.size) var propertyAddress = AudioObjectPropertyAddress( mSelector: kAudioDevicePropertyDeviceUID, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain) var result: CFString = "" as CFString AudioObjectGetPropertyData(id, &propertyAddress, 0, nil, &propertySize, &result) return result as String } }

完成后,您可以获取第一个内置输出设备:

player.audioOutputDeviceUniqueID = AudioDevice.getAll() .first(where: {$0.hasOutputStreams && $0.isBuiltIn })? .uid

或者,如果您想要更多控制,您可以使用 AVAudioEngine 方法(请注意此处 
uid

id
之间的区别):
let player = AVAudioPlayerNode()
let engine = AVAudioEngine()

func playTheSound() {
    let output = engine.outputNode

    let outputUnit = output.audioUnit!
    var outputDeviceID = AudioDevice.getAll()
        .first(where: {$0.hasOutputStreams && $0.isBuiltIn })!
        .id

    AudioUnitSetProperty(outputUnit,
                         kAudioOutputUnitProperty_CurrentDevice,
                         kAudioUnitScope_Global,
                         0,
                         &outputDeviceID,
                         UInt32(MemoryLayout<AudioDeviceID>.size))

    engine.attach(player)
    engine.connect(player, to: engine.outputNode, format: nil)
    try! engine.start()

    let url = URL(filePath: "/System/Library/Sounds/Blow.aiff")
    let file = try! AVAudioFile(forReading: url)
    player.scheduleFile(file, at: nil)
    player.play()
}



0
投票

© www.soinside.com 2019 - 2024. All rights reserved.