Created
December 20, 2020 15:31
-
-
Save adaskar/a3e0d891e5dbe76d57d04952d72bfb4d to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// | |
// main.swift | |
// AggregateAudioVolumeControl | |
// | |
// Created by Gurhan Polat on 20.12.2020. | |
// | |
import Foundation | |
import Cocoa | |
import AVFoundation | |
// @mode = 0 get | |
// @mode = 1 set n | |
var mode:Int = -1 | |
var volumeToBeSet:Float = 0.0 | |
// get current volume | |
if (CommandLine.arguments.count == 2 && | |
CommandLine.arguments[1] == "get") { | |
mode = 0 | |
} | |
// set current volume | |
else if (CommandLine.arguments.count == 3 && | |
CommandLine.arguments[1] == "set" && | |
Int32(CommandLine.arguments[2]) != nil && | |
Int32(CommandLine.arguments[2])! >= 0 && Int32(CommandLine.arguments[2])! <= 100) { | |
mode = 1 | |
volumeToBeSet = Float(Int32(CommandLine.arguments[2])!) / Float(100) | |
} | |
else { | |
print("usage: AggregateAudioVolumeControl <get | set n>") | |
exit(-1) | |
} | |
// get default audio output device | |
var address:AudioObjectPropertyAddress = AudioObjectPropertyAddress( | |
mSelector:AudioObjectPropertySelector(kAudioHardwarePropertyDefaultOutputDevice), | |
mScope:AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal), | |
mElement:AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)) | |
var audioDeviceID:AudioDeviceID = 0 | |
var propsize:UInt32 = UInt32(MemoryLayout<AudioDeviceID>.size) | |
var result = AudioObjectGetPropertyData(AudioDeviceID(kAudioObjectSystemObject), &address, 0, nil, &propsize, &audioDeviceID) | |
if (result != 0) { | |
print("kAudioHardwarePropertyDefaultOutputDevice") | |
exit(-1) | |
} | |
// get default audio output device uid | |
address = AudioObjectPropertyAddress( | |
mSelector:AudioObjectPropertySelector(kAudioDevicePropertyDeviceUID), | |
mScope:AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal), | |
mElement:AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)) | |
var audioDeviceUID:CFString? = nil | |
propsize = UInt32(MemoryLayout<CFString?>.size) | |
result = AudioObjectGetPropertyData(audioDeviceID, &address, 0, nil, &propsize, &audioDeviceUID) | |
if (result != 0) { | |
print("kAudioDevicePropertyDeviceUID") | |
exit(-1) | |
} | |
// get default audio output device transport type | |
address = AudioObjectPropertyAddress( | |
mSelector:AudioObjectPropertySelector(kAudioDevicePropertyTransportType), | |
mScope:AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal), | |
mElement:AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)) | |
var transportType:UInt32 = 0 | |
propsize = UInt32(MemoryLayout<UInt32>.size) | |
result = AudioObjectGetPropertyData(audioDeviceID, &address, 0, nil, &propsize, &transportType) | |
if (result != 0) { | |
print("kAudioDevicePropertyTransportType") | |
exit(-1) | |
} | |
// if transportType is not Aggregate then exit the tool | |
if (transportType != kAudioDeviceTransportTypeAggregate) { | |
print("audioDeviceID: \(audioDeviceID) uid: \(audioDeviceUID as String? ?? "") transportType: \(transportType == kAudioDeviceTransportTypeAggregate)") | |
print("this tool only works with a kAudioDeviceTransportTypeAggregate") | |
exit(1) | |
} | |
// get the sublist of the Aggregate Audio Device | |
address = AudioObjectPropertyAddress( | |
mSelector:AudioObjectPropertySelector(kAudioAggregateDevicePropertyActiveSubDeviceList), | |
mScope:AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal), | |
mElement:AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)) | |
var subDevicesID = [AudioDeviceID]() | |
for _ in 0..<32 { | |
subDevicesID.append(AudioDeviceID()) | |
} | |
propsize = UInt32(MemoryLayout<AudioDeviceID>.size * 32) | |
result = AudioObjectGetPropertyData(audioDeviceID, &address, 0, nil, &propsize, &subDevicesID) | |
if (result != 0) { | |
print("kAudioAggregateDevicePropertyActiveSubDeviceList") | |
exit(-1) | |
} | |
var volAvg: Float = 0.0 | |
var subDeviceCount:Int = Int((propsize / UInt32(MemoryLayout<AudioDeviceID>.size))) | |
// for eah sub device | |
// we either set the volume or just get it according to mode argument | |
print("audioDeviceUID: \(audioDeviceUID as String? ?? "") subDeviceCount: \(subDeviceCount)") | |
for i in 0..<subDeviceCount { | |
let subDevice:AudioDeviceID = subDevicesID[i] | |
var volLeft:Float = 0.0 | |
var volRight:Float = 0.0 | |
address = AudioObjectPropertyAddress( | |
mSelector:AudioObjectPropertySelector(kAudioDevicePropertyDeviceUID), | |
mScope:AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal), | |
mElement:AudioObjectPropertyElement(kAudioObjectPropertyElementMaster)) | |
var subDeviceUID:CFString? = nil | |
propsize = UInt32(MemoryLayout<CFString?>.size) | |
result = AudioObjectGetPropertyData(subDevice, &address, 0, nil, &propsize, &subDeviceUID) | |
if (result != 0) { | |
print("kAudioDevicePropertyDeviceUID subDevice") | |
exit(-1) | |
} | |
address = AudioObjectPropertyAddress( | |
mSelector:AudioObjectPropertySelector(kAudioDevicePropertyVolumeScalar), | |
mScope:AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput), | |
mElement:1) | |
propsize = UInt32(MemoryLayout<Float>.size) | |
// if mode is get, get the volume of left channel otherwise set it to parameter n | |
if (mode == 0) { | |
result = AudioObjectGetPropertyData(subDevice, &address, 0, nil, &propsize, &volLeft) | |
} | |
else { | |
result = AudioObjectSetPropertyData(subDevice, &address, 0, nil, propsize, &volumeToBeSet) | |
volLeft = volumeToBeSet | |
} | |
if (result != 0) { | |
print("kAudioDevicePropertyVolumeScalar volLeft") | |
exit(-1) | |
} | |
address = AudioObjectPropertyAddress( | |
mSelector:AudioObjectPropertySelector(kAudioDevicePropertyVolumeScalar), | |
mScope:AudioObjectPropertyScope(kAudioDevicePropertyScopeOutput), | |
mElement:2) | |
propsize = UInt32(MemoryLayout<Float>.size) | |
// if mode is get, get the volume of right channel otherwise set it to parameter n | |
if (mode == 0) { | |
result = AudioObjectGetPropertyData(subDevice, &address, 0, nil, &propsize, &volRight) | |
} | |
else { | |
result = AudioObjectSetPropertyData(subDevice, &address, 0, nil, propsize, &volumeToBeSet) | |
volRight = volumeToBeSet | |
} | |
if (result != 0) { | |
print("kAudioDevicePropertyVolumeScalar volRight") | |
exit(-1) | |
} | |
volAvg += (volLeft + volRight) / 2 | |
print("subDevice: \(subDeviceUID as String? ?? "") volLeft: \(volLeft) volLeft: \(volRight)") | |
} | |
volAvg = volAvg / Float(subDeviceCount) | |
print("volAvg: \(volAvg)") | |
// we always return current volume of the sound if no error | |
// if any error occurs we then return (-1):255 | |
exit(Int32(volAvg * 100)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment