import CoreAudio import Foundation // Reads or sets the physical sample format of one output device's first stream, by device name, and reports the // host's default and system output devices. It never changes a default device. func property(_ object: AudioObjectID, _ selector: AudioObjectPropertySelector, scope: AudioObjectPropertyScope = kAudioObjectPropertyScopeGlobal, initial: Value) -> Value { var address = AudioObjectPropertyAddress(mSelector: selector, mScope: scope, mElement: kAudioObjectPropertyElementMain) var value = initial var size = UInt32(MemoryLayout.size) AudioObjectGetPropertyData(object, &address, 0, nil, &size, &value) return value } func array(_ object: AudioObjectID, _ selector: AudioObjectPropertySelector, scope: AudioObjectPropertyScope, of _: Element.Type, zero: Element) -> [Element] { var address = AudioObjectPropertyAddress(mSelector: selector, mScope: scope, mElement: kAudioObjectPropertyElementMain) var size: UInt32 = 0 AudioObjectGetPropertyDataSize(object, &address, 0, nil, &size) var values = [Element](repeating: zero, count: Int(size) / MemoryLayout.size) AudioObjectGetPropertyData(object, &address, 0, nil, &size, &values) return values } func name(_ device: AudioObjectID) -> String { let value: Unmanaged? = property(device, kAudioObjectPropertyName, initial: nil) return value?.takeRetainedValue() as String? ?? "?" } func describe(_ format: AudioStreamBasicDescription) -> String { "\(Int(format.mSampleRate)) Hz \(format.mChannelsPerFrame) ch \(format.mBitsPerChannel) bit" } let system = AudioObjectID(kAudioObjectSystemObject) let defaultOutput: AudioObjectID = property(system, kAudioHardwarePropertyDefaultOutputDevice, initial: 0) let systemOutput: AudioObjectID = property(system, kAudioHardwarePropertyDefaultSystemOutputDevice, initial: 0) print("default output=\(name(defaultOutput)) system output=\(name(systemOutput))") let arguments = Array(CommandLine.arguments.dropFirst()) let devices = array(system, kAudioHardwarePropertyDevices, scope: kAudioObjectPropertyScopeGlobal, of: AudioObjectID.self, zero: 0) guard let device = devices.first(where: { name($0) == arguments[0] }) else { print("no device named \(arguments[0])") exit(1) } let streams = array(device, kAudioDevicePropertyStreams, scope: kAudioObjectPropertyScopeOutput, of: AudioObjectID.self, zero: 0) guard let stream = streams.first else { print("no output stream") exit(1) } let current: AudioStreamBasicDescription = property(stream, kAudioStreamPropertyPhysicalFormat, initial: AudioStreamBasicDescription()) print("physical format=\(describe(current))") if arguments.count > 1, let bits = UInt32(arguments[1]) { let available = array(stream, kAudioStreamPropertyAvailablePhysicalFormats, scope: kAudioObjectPropertyScopeGlobal, of: AudioStreamRangedDescription.self, zero: AudioStreamRangedDescription()) guard var chosen = available.map(\.mFormat).first(where: { $0.mBitsPerChannel == bits && $0.mSampleRate == 48000 }) else { print("no \(bits)-bit format among \(available.map { describe($0.mFormat) })") exit(1) } var address = AudioObjectPropertyAddress(mSelector: kAudioStreamPropertyPhysicalFormat, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain) let status = AudioObjectSetPropertyData(stream, &address, 0, nil, UInt32(MemoryLayout.size), &chosen) usleep(300_000) let applied: AudioStreamBasicDescription = property(stream, kAudioStreamPropertyPhysicalFormat, initial: AudioStreamBasicDescription()) print("set \(bits) bit: status \(status), physical format now=\(describe(applied))") }