Created
September 22, 2017 10:50
-
-
Save mash/b119e0992eb2749c73633f64f7054440 to your computer and use it in GitHub Desktop.
audio stuff on macOS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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? | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you very much!