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



我有一个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的主扬声器,有什么建议吗?

默认内置"我猜你的意思是"内置的"。默认扬声器是音频已经路由到的扬声器。

对于这个问题,最简单的解决方案(可能总是有效)是路由到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方法,如果你想要更多的控制(注意uidid之间的区别):

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()
}

最新更新