设置Hog模式后无法播放声音
Can't play sound after setting Hog mode
我尝试用 AVAudioEngine
和 AVAudioPlayerNode
将音频文件播放到 USB 设备 (DAC)。当我将设备设置为 Hog Mode 时,没有播放声音,但在没有 Hog Mode 的情况下效果很好。
我也试过 AVAudioPlayer
,但也不行。
有谁知道为什么以及如何解决这个问题?
如果我将设备 ADI-2
设置为 Hog 模式,系统会将默认设备设置为另一台设备 Meridian
并在其上播放声音。我找不到在设备上以 hog 模式播放声音的方法 ADI-2
(见下图)。
no hog mode
hog mode
import Cocoa
import AVFoundation
class ViewController: NSViewController {
let audioEngine = AVAudioEngine()
let playerNode = AVAudioPlayerNode()
override func viewDidLoad() {
super.viewDidLoad()
do {
let outputDevice = AudioDevice.defaultOutputDevice()
guard let url = Bundle.main.url(forResource: "file", withExtension: "mp3") else { return }
let audioFile = try AVAudioFile(forReading: url)
outputDevice.setHogMode() // without this line, audio file is played.
audioEngine.attach(playerNode)
audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: audioFile.processingFormat)
try audioEngine.start()
playerNode.scheduleFile(audioFile, at: nil, completionHandler: nil)
playerNode.play()
} catch let error {
print(error.localizedDescription)
}
}
}
class AudioDevice {
var id: AudioDeviceID
init(deviceID: AudioDeviceID) {
self.id = deviceID
}
func setHogMode() -> Bool {
guard hogModePID() != pid_t(ProcessInfo.processInfo.processIdentifier) else { return false }
return toggleHogMode()
}
func unsetHogMode() -> Bool {
guard hogModePID() == pid_t(ProcessInfo.processInfo.processIdentifier) else { return false }
return toggleHogMode()
}
private func toggleHogMode() -> Bool {
let address = AudioObject.address(selector: kAudioDevicePropertyHogMode)
var newValue: UInt32 = 1
let status = AudioObject.setPropertyData(id, address: address, andValue: &newValue)
return noErr == status
}
private func hogModePID() -> pid_t? {
let address = AudioObject.address(selector: kAudioDevicePropertyHogMode)
var pid = pid_t()
let status = AudioObject.getPropertyData(id, address: address, andValue: &pid)
return noErr == status ? pid : nil
}
}
extension AudioDevice {
class func defaultOutputDevice() -> AudioDevice {
let address = AudioObject.address(selector: kAudioHardwarePropertyDefaultSystemOutputDevice)
var deviceId = AudioDeviceID()
let status = AudioObject.getPropertyData(AudioObjectID(kAudioObjectSystemObject), address: address, andValue: &deviceId)
if status != noErr {
print("OSStatus error \(status) (defaultOutputDevice)")
}
return AudioDevice(deviceID: deviceId)
}
}
class AudioObject {
class func address(selector: AudioObjectPropertySelector, scope: AudioObjectPropertyScope = kAudioObjectPropertyScopeGlobal, element: AudioObjectPropertyElement = kAudioObjectPropertyElementMaster) -> AudioObjectPropertyAddress {
return AudioObjectPropertyAddress(mSelector: selector, mScope: scope, mElement: element)
}
class func getPropertyData<T>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andValue value: inout T) -> OSStatus {
var theAddress = address
var size = UInt32(MemoryLayout<T>.size)
let status = AudioObjectGetPropertyData(objectID, &theAddress, UInt32(0), nil, &size, &value)
return status
}
class func getPropertyDataSize<Q>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout [Q], andSize size: inout UInt32) -> (OSStatus) {
var theAddress = address
return AudioObjectGetPropertyDataSize(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size)
}
class func getPropertyDataSize<Q>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout Q, andSize size: inout UInt32) -> (OSStatus) {
var theAddress = address
return AudioObjectGetPropertyDataSize(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size)
}
class func getPropertyDataSize(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andSize size: inout UInt32) -> (OSStatus) {
var nilValue: ExpressibleByNilLiteral?
return getPropertyDataSize(objectID, address: address, qualifierDataSize: nil, qualifierData: &nilValue, andSize: &size)
}
class func getPropertyDataArray<T, Q>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout Q, value: inout [T], andDefaultValue defaultValue: T) -> OSStatus {
var size = UInt32(0)
let sizeStatus = getPropertyDataSize(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, andSize: &size)
if noErr == sizeStatus {
value = [T](repeating: defaultValue, count: Int(size) / MemoryLayout<T>.size)
} else {
return sizeStatus
}
var theAddress = address
let status = AudioObjectGetPropertyData(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size, &value)
return status
}
class func getPropertyDataArray<T, Q>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout [Q], value: inout [T], andDefaultValue defaultValue: T) -> OSStatus {
var size = UInt32(0)
let sizeStatus = getPropertyDataSize(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, andSize: &size)
if noErr == sizeStatus {
value = [T](repeating: defaultValue, count: Int(size) / MemoryLayout<T>.size)
} else {
return sizeStatus
}
var theAddress = address
let status = AudioObjectGetPropertyData(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size, &value)
return status
}
class func getPropertyDataArray<T>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, value: inout [T], andDefaultValue defaultValue: T) -> OSStatus {
var nilValue: ExpressibleByNilLiteral?
return getPropertyDataArray(objectID, address: address, qualifierDataSize: nil, qualifierData: &nilValue, value: &value, andDefaultValue: defaultValue)
}
class func setPropertyData<T>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andValue value: inout T) -> OSStatus {
var theAddress = address
let size = UInt32(MemoryLayout<T>.size)
let status = AudioObjectSetPropertyData(objectID, &theAddress, UInt32(0), nil, size, &value)
return status
}
}
所以我自己从来没有真正使用过 AVAudioEngine
,但我猜如果你想让它使用特定的设备,你需要对其进行配置(我没有在代码)
我相信您看到的行为是:
- 默认
AVAudioEngine
会使用系统默认设备
- 通过在恰好是当前系统默认设置的设备上采用 hog 模式,该设备将不再是默认设置 - 您是在间接更改默认设备。
- 由于没有明确指定输出设备,播放使用当前系统默认设备。
似乎有一个类似的问题可能会有所帮助(尽管答案是 Objective-C):
就 hog 模式而言,它可能被认为不是非常用户友好(并且可能主要用于需要使用 non-mixable 格式时)。
我尝试用 AVAudioEngine
和 AVAudioPlayerNode
将音频文件播放到 USB 设备 (DAC)。当我将设备设置为 Hog Mode 时,没有播放声音,但在没有 Hog Mode 的情况下效果很好。
我也试过 AVAudioPlayer
,但也不行。
有谁知道为什么以及如何解决这个问题?
如果我将设备 ADI-2
设置为 Hog 模式,系统会将默认设备设置为另一台设备 Meridian
并在其上播放声音。我找不到在设备上以 hog 模式播放声音的方法 ADI-2
(见下图)。
no hog mode
hog mode
import Cocoa
import AVFoundation
class ViewController: NSViewController {
let audioEngine = AVAudioEngine()
let playerNode = AVAudioPlayerNode()
override func viewDidLoad() {
super.viewDidLoad()
do {
let outputDevice = AudioDevice.defaultOutputDevice()
guard let url = Bundle.main.url(forResource: "file", withExtension: "mp3") else { return }
let audioFile = try AVAudioFile(forReading: url)
outputDevice.setHogMode() // without this line, audio file is played.
audioEngine.attach(playerNode)
audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: audioFile.processingFormat)
try audioEngine.start()
playerNode.scheduleFile(audioFile, at: nil, completionHandler: nil)
playerNode.play()
} catch let error {
print(error.localizedDescription)
}
}
}
class AudioDevice {
var id: AudioDeviceID
init(deviceID: AudioDeviceID) {
self.id = deviceID
}
func setHogMode() -> Bool {
guard hogModePID() != pid_t(ProcessInfo.processInfo.processIdentifier) else { return false }
return toggleHogMode()
}
func unsetHogMode() -> Bool {
guard hogModePID() == pid_t(ProcessInfo.processInfo.processIdentifier) else { return false }
return toggleHogMode()
}
private func toggleHogMode() -> Bool {
let address = AudioObject.address(selector: kAudioDevicePropertyHogMode)
var newValue: UInt32 = 1
let status = AudioObject.setPropertyData(id, address: address, andValue: &newValue)
return noErr == status
}
private func hogModePID() -> pid_t? {
let address = AudioObject.address(selector: kAudioDevicePropertyHogMode)
var pid = pid_t()
let status = AudioObject.getPropertyData(id, address: address, andValue: &pid)
return noErr == status ? pid : nil
}
}
extension AudioDevice {
class func defaultOutputDevice() -> AudioDevice {
let address = AudioObject.address(selector: kAudioHardwarePropertyDefaultSystemOutputDevice)
var deviceId = AudioDeviceID()
let status = AudioObject.getPropertyData(AudioObjectID(kAudioObjectSystemObject), address: address, andValue: &deviceId)
if status != noErr {
print("OSStatus error \(status) (defaultOutputDevice)")
}
return AudioDevice(deviceID: deviceId)
}
}
class AudioObject {
class func address(selector: AudioObjectPropertySelector, scope: AudioObjectPropertyScope = kAudioObjectPropertyScopeGlobal, element: AudioObjectPropertyElement = kAudioObjectPropertyElementMaster) -> AudioObjectPropertyAddress {
return AudioObjectPropertyAddress(mSelector: selector, mScope: scope, mElement: element)
}
class func getPropertyData<T>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andValue value: inout T) -> OSStatus {
var theAddress = address
var size = UInt32(MemoryLayout<T>.size)
let status = AudioObjectGetPropertyData(objectID, &theAddress, UInt32(0), nil, &size, &value)
return status
}
class func getPropertyDataSize<Q>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout [Q], andSize size: inout UInt32) -> (OSStatus) {
var theAddress = address
return AudioObjectGetPropertyDataSize(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size)
}
class func getPropertyDataSize<Q>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout Q, andSize size: inout UInt32) -> (OSStatus) {
var theAddress = address
return AudioObjectGetPropertyDataSize(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size)
}
class func getPropertyDataSize(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andSize size: inout UInt32) -> (OSStatus) {
var nilValue: ExpressibleByNilLiteral?
return getPropertyDataSize(objectID, address: address, qualifierDataSize: nil, qualifierData: &nilValue, andSize: &size)
}
class func getPropertyDataArray<T, Q>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout Q, value: inout [T], andDefaultValue defaultValue: T) -> OSStatus {
var size = UInt32(0)
let sizeStatus = getPropertyDataSize(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, andSize: &size)
if noErr == sizeStatus {
value = [T](repeating: defaultValue, count: Int(size) / MemoryLayout<T>.size)
} else {
return sizeStatus
}
var theAddress = address
let status = AudioObjectGetPropertyData(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size, &value)
return status
}
class func getPropertyDataArray<T, Q>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout [Q], value: inout [T], andDefaultValue defaultValue: T) -> OSStatus {
var size = UInt32(0)
let sizeStatus = getPropertyDataSize(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, andSize: &size)
if noErr == sizeStatus {
value = [T](repeating: defaultValue, count: Int(size) / MemoryLayout<T>.size)
} else {
return sizeStatus
}
var theAddress = address
let status = AudioObjectGetPropertyData(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size, &value)
return status
}
class func getPropertyDataArray<T>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, value: inout [T], andDefaultValue defaultValue: T) -> OSStatus {
var nilValue: ExpressibleByNilLiteral?
return getPropertyDataArray(objectID, address: address, qualifierDataSize: nil, qualifierData: &nilValue, value: &value, andDefaultValue: defaultValue)
}
class func setPropertyData<T>(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andValue value: inout T) -> OSStatus {
var theAddress = address
let size = UInt32(MemoryLayout<T>.size)
let status = AudioObjectSetPropertyData(objectID, &theAddress, UInt32(0), nil, size, &value)
return status
}
}
所以我自己从来没有真正使用过 AVAudioEngine
,但我猜如果你想让它使用特定的设备,你需要对其进行配置(我没有在代码)
我相信您看到的行为是:
- 默认
AVAudioEngine
会使用系统默认设备 - 通过在恰好是当前系统默认设置的设备上采用 hog 模式,该设备将不再是默认设置 - 您是在间接更改默认设备。
- 由于没有明确指定输出设备,播放使用当前系统默认设备。
似乎有一个类似的问题可能会有所帮助(尽管答案是 Objective-C):
就 hog 模式而言,它可能被认为不是非常用户友好(并且可能主要用于需要使用 non-mixable 格式时)。