Skip to content

Instantly share code, notes, and snippets.

@mash
Created September 22, 2017 10:50
Show Gist options
  • Save mash/b119e0992eb2749c73633f64f7054440 to your computer and use it in GitHub Desktop.
Save mash/b119e0992eb2749c73633f64f7054440 to your computer and use it in GitHub Desktop.
audio stuff on macOS
//
// File.swift
// AudioSwitcher
//
// Created by Masakazu Ohtsuka on 2017/09/22.
// Copyright © 2017 maaash.jp. All rights reserved.
//
import Foundation
import Cocoa
import CoreAudio
import AVFoundation
import AudioUnit
enum InOut {
case In, Out
}
// CustomStringConvertible is defined elsewhere
extension AudioObjectPropertySelector {
var description :String {
get {
switch self {
case kAudioObjectPropertyName:
return "name"
case kAudioObjectPropertyManufacturer:
return "manufacturer"
case kAudioObjectPropertyModelName:
return "modelName"
case kAudioObjectPropertyClass:
return "class"
case kAudioObjectPropertyOwner:
return "owner"
case kAudioObjectPropertyCreator:
return "creator"
case kAudioObjectPropertyElementName:
return "elementName"
case kAudioObjectPropertyElementCategoryName:
return "elementCategoryName"
case kAudioObjectPropertyElementNumberName:
return "elementNumberName"
case kAudioObjectPropertySerialNumber:
return "serialNumber"
case kAudioObjectPropertyFirmwareVersion:
return "firmwareVersion"
default:
return "unknown"
}
}
}
}
enum Property :String {
case name, manufacturer, modelName, klass, owner, creator, elementName, elementCategoryName, elementNumberName, serialNumber, firmwareVersion
func audioObjectPropertyName() -> AudioObjectPropertySelector {
switch self {
case .name:
return kAudioObjectPropertyName
case .manufacturer:
return kAudioObjectPropertyManufacturer
case .modelName:
return kAudioObjectPropertyModelName
case .klass:
return kAudioObjectPropertyClass
case .owner:
return kAudioObjectPropertyOwner
case .creator:
return kAudioObjectPropertyCreator
case .elementName:
return kAudioObjectPropertyElementName
case .elementNumberName:
return kAudioObjectPropertyElementNumberName
case .elementCategoryName:
return kAudioObjectPropertyElementCategoryName
case .serialNumber:
return kAudioObjectPropertySerialNumber
case .firmwareVersion:
return kAudioObjectPropertyFirmwareVersion
}
}
static func all() -> [Property] {
return [.name, .manufacturer, .modelName, .klass, .owner, .creator, .elementName, .elementCategoryName, .elementNumberName, .serialNumber, .firmwareVersion]
}
}
struct AudioDevice {
let id :AudioObjectID
let inOut :InOut
let selected :Bool
let properties :[String:String]
init(id :AudioObjectID, inOut :InOut, selected :Bool) {
self.id = id
self.inOut = inOut
self.selected = selected
var p = [String:String]()
// other properties fails to access
[Property]([.name, .manufacturer]).forEach { property in
if let val = getProperty(of: id, selector: property.audioObjectPropertyName()) {
p[ property.rawValue ] = val
}
}
self.properties = p
}
}
func selectedAudioObject(inOut :InOut) -> AudioObjectID? {
var deviceID:AudioObjectID = AudioObjectID(0)
var size:UInt32 = UInt32(MemoryLayout<AudioObjectID>.size)
var address:AudioObjectPropertyAddress = AudioObjectPropertyAddress()
if inOut == .In {
address.mSelector = kAudioHardwarePropertyDefaultInputDevice
}
else {
address.mSelector = kAudioHardwarePropertyDefaultOutputDevice
}
address.mScope = kAudioObjectPropertyScopeGlobal
address.mElement = kAudioObjectPropertyElementMaster
let err1 = AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &address, 0, nil, &size, &deviceID)
if err1 != 0 {
print("error: \(err1)")
return nil
}
return deviceID
}
func currentAudioDevices() -> [AudioDevice] {
let ins = currentAudioObjects(inOut: .In)
let outs = currentAudioObjects(inOut: .Out)
let selectedIn = selectedAudioObject(inOut: .In)
let selectedOut = selectedAudioObject(inOut: .Out)
let inDevices = ins.flatMap { (id) -> AudioDevice? in
return AudioDevice(id: id, inOut: .In, selected: id == selectedIn)
}
let outDevices = outs.flatMap { (id) -> AudioDevice? in
return AudioDevice(id: id, inOut: .Out, selected: id == selectedOut)
}
return inDevices + outDevices
}
func currentAudioObjects(inOut :InOut) -> [AudioObjectID] {
var deviceIDs:[AudioObjectID]
var size:UInt32 = UInt32(MemoryLayout<[AudioObjectID]>.size)
var address:AudioObjectPropertyAddress = AudioObjectPropertyAddress()
address.mSelector = kAudioHardwarePropertyDevices
if inOut == .In {
address.mScope = kAudioObjectPropertyScopeInput
}
else {
address.mScope = kAudioObjectPropertyScopeOutput
}
address.mElement = kAudioObjectPropertyElementMaster
let err1 = AudioObjectGetPropertyDataSize(AudioObjectID(kAudioObjectSystemObject), &address, 0, nil, &size)
if err1 != 0 {
print("error: \(err1)")
return []
}
deviceIDs = [AudioObjectID](repeating: 0, count: Int(size/UInt32(MemoryLayout<AudioObjectID>.size)))
let err2 = AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &address, 0, nil, &size, &deviceIDs)
if err2 != 0 {
print("error: \(err2)")
return []
}
return deviceIDs
}
func getProperty(of id :AudioObjectID, selector :AudioObjectPropertySelector) -> String? {
var address:AudioObjectPropertyAddress = AudioObjectPropertyAddress()
address.mSelector = selector
address.mScope = kAudioObjectPropertyScopeGlobal
address.mElement = kAudioObjectPropertyElementMaster
var deviceName :CFString? = nil
var size = UInt32(MemoryLayout<String>.size)
let err1 = AudioObjectGetPropertyData(id, &address, 0, nil, &size, &deviceName)
if err1 != 0 {
print("failed to get property: \(selector.description), error: \(err1)")
return nil
}
return deviceName as String?
}
@petershaw
Copy link

Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment