1

I try to play an audio file with AVAudioEngine and AVAudioPlayerNode to an USB device (DAC). When I set the device in Hog Mode, the sound is not played but it works great without Hog Mode.

I also try with AVAudioPlayer, and it doesn't work either.

Does anyone know why and how to solve the problem?

If I set a device ADI-2 to Hog mode, the system set default device to another device Meridian and play sound on it. I can't find a way to play sound on device in hog mode ADI-2 (see images below).

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
    }
}
Carbonimax
  • 13
  • 1
  • 3
  • I don't know, just some food for thought - look at MythTV & Kodi source code - they're disabling auto hog just before setting the hog mode, there're also comments that some drivers don't support it or that they're buggy, they also do disable mixing after they set the hog mode, ... – zrzka Aug 06 '20 at 06:55
  • I managed to play sound with `audioOutIOProcID` and `AudioDeviceCreateIOProcID`. Do I have to use low-level APIs or is it possible to play an audio file with `AVAudioEngine` in hog mode? – Carbonimax Aug 07 '20 at 21:34
  • @Carbonimax Hi, do you have any code samples how you achived to set hod mode? your code works for me, but hod mode is not setting. – Jack Daniel Sep 28 '22 at 22:34

1 Answers1

0

So I've never actually used AVAudioEngine myself, but I would guess that if you want it to use a specific device, you need to configure it to do so (I don't see that in the code)

I believe the behavior you are seeing is that:

  • By default, AVAudioEngine will use the system default device
  • By taking hog mode on a device that happens to be the current system default, that device can no longer be the default by definition - you are indirectly changing the default device.
  • Since no output device was explicitly specified, playback uses the current system default device.

Looks like there's a similar question that may help (though the answers are Objective-C): Set AVAudioEngine Input and Output Devices

As far as hog mode goes, it may not be considered very user friendly (and may be intended primarily when you need to work with a non-mixable format).

user1325158
  • 194
  • 3
  • 9