233 lines
8.3 KiB
Objective-C
233 lines
8.3 KiB
Objective-C
//
|
|
// MMAudioTelemetryProvider.m
|
|
// MacMonitor
|
|
//
|
|
// Telemetry provider enumerating CoreAudio input/output devices, sample rates, volume, and active status.
|
|
//
|
|
|
|
#import "MMAudioTelemetryProvider.h"
|
|
#import <CoreAudio/CoreAudio.h>
|
|
|
|
@implementation MMAudioDevice
|
|
|
|
- (NSDictionary<NSString *, id> *)toDictionary {
|
|
return @{
|
|
@"deviceID": @(self.deviceID),
|
|
@"name": self.name ?: @"Unknown Audio Device",
|
|
@"manufacturer": self.manufacturer ?: @"Unknown",
|
|
@"uid": self.uid ?: @"",
|
|
@"isInput": @(self.isInput),
|
|
@"isOutput": @(self.isOutput),
|
|
@"isDefaultInput": @(self.isDefaultInput),
|
|
@"isDefaultOutput": @(self.isDefaultOutput),
|
|
@"sampleRate": @(self.sampleRate),
|
|
@"channelCount": @(self.channelCount),
|
|
@"volume": @(roundf(self.volume * 100.0f) / 100.0f),
|
|
@"isMuted": @(self.isMuted)
|
|
};
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation MMAudioTelemetryProvider
|
|
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
return self;
|
|
}
|
|
|
|
- (MMTelemetryDomain)domain {
|
|
return MMTelemetryDomainAudio;
|
|
}
|
|
|
|
- (NSString *)providerIdentifier {
|
|
return @"com.i3omb.macmonitor.telemetry.audio";
|
|
}
|
|
|
|
- (BOOL)isAvailable {
|
|
return YES;
|
|
}
|
|
|
|
+ (AudioDeviceID)defaultDeviceForProperty:(AudioObjectPropertySelector)selector {
|
|
AudioObjectPropertyAddress address = {
|
|
.mSelector = selector,
|
|
.mScope = kAudioObjectPropertyScopeGlobal,
|
|
.mElement = kAudioObjectPropertyElementMain
|
|
};
|
|
AudioDeviceID deviceID = kAudioObjectUnknown;
|
|
UInt32 size = sizeof(AudioDeviceID);
|
|
OSStatus status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &size, &deviceID);
|
|
if (status == noErr) {
|
|
return deviceID;
|
|
}
|
|
return kAudioObjectUnknown;
|
|
}
|
|
|
|
+ (NSArray<MMAudioDevice *> *)sampleAudioDevices {
|
|
AudioObjectPropertyAddress address = {
|
|
.mSelector = kAudioHardwarePropertyDevices,
|
|
.mScope = kAudioObjectPropertyScopeGlobal,
|
|
.mElement = kAudioObjectPropertyElementMain
|
|
};
|
|
|
|
UInt32 dataSize = 0;
|
|
OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &address, 0, NULL, &dataSize);
|
|
if (status != noErr || dataSize == 0) {
|
|
return @[];
|
|
}
|
|
|
|
UInt32 deviceCount = dataSize / sizeof(AudioDeviceID);
|
|
AudioDeviceID *deviceIDs = (AudioDeviceID *)malloc(dataSize);
|
|
if (!deviceIDs) return @[];
|
|
|
|
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &dataSize, deviceIDs);
|
|
if (status != noErr) {
|
|
free(deviceIDs);
|
|
return @[];
|
|
}
|
|
|
|
AudioDeviceID defaultIn = [self defaultDeviceForProperty:kAudioHardwarePropertyDefaultInputDevice];
|
|
AudioDeviceID defaultOut = [self defaultDeviceForProperty:kAudioHardwarePropertyDefaultOutputDevice];
|
|
|
|
NSMutableArray<MMAudioDevice *> *devices = [NSMutableArray arrayWithCapacity:deviceCount];
|
|
|
|
for (UInt32 i = 0; i < deviceCount; i++) {
|
|
AudioDeviceID devID = deviceIDs[i];
|
|
|
|
// Name
|
|
CFStringRef nameRef = NULL;
|
|
UInt32 propSize = sizeof(CFStringRef);
|
|
AudioObjectPropertyAddress nameAddr = {
|
|
.mSelector = kAudioObjectPropertyName,
|
|
.mScope = kAudioObjectPropertyScopeGlobal,
|
|
.mElement = kAudioObjectPropertyElementMain
|
|
};
|
|
NSString *name = @"Audio Device";
|
|
if (AudioObjectGetPropertyData(devID, &nameAddr, 0, NULL, &propSize, &nameRef) == noErr && nameRef) {
|
|
name = (__bridge_transfer NSString *)nameRef;
|
|
}
|
|
|
|
// Manufacturer
|
|
CFStringRef mfrRef = NULL;
|
|
propSize = sizeof(CFStringRef);
|
|
AudioObjectPropertyAddress mfrAddr = {
|
|
.mSelector = kAudioObjectPropertyManufacturer,
|
|
.mScope = kAudioObjectPropertyScopeGlobal,
|
|
.mElement = kAudioObjectPropertyElementMain
|
|
};
|
|
NSString *mfr = @"Apple Inc.";
|
|
if (AudioObjectGetPropertyData(devID, &mfrAddr, 0, NULL, &propSize, &mfrRef) == noErr && mfrRef) {
|
|
mfr = (__bridge_transfer NSString *)mfrRef;
|
|
}
|
|
|
|
// UID
|
|
CFStringRef uidRef = NULL;
|
|
propSize = sizeof(CFStringRef);
|
|
AudioObjectPropertyAddress uidAddr = {
|
|
.mSelector = kAudioDevicePropertyDeviceUID,
|
|
.mScope = kAudioObjectPropertyScopeGlobal,
|
|
.mElement = kAudioObjectPropertyElementMain
|
|
};
|
|
NSString *uid = @"";
|
|
if (AudioObjectGetPropertyData(devID, &uidAddr, 0, NULL, &propSize, &uidRef) == noErr && uidRef) {
|
|
uid = (__bridge_transfer NSString *)uidRef;
|
|
}
|
|
|
|
// Channels & Input / Output determination
|
|
AudioObjectPropertyAddress inputStreamsAddr = {
|
|
.mSelector = kAudioDevicePropertyStreams,
|
|
.mScope = kAudioDevicePropertyScopeInput,
|
|
.mElement = kAudioObjectPropertyElementMain
|
|
};
|
|
UInt32 inStreamSize = 0;
|
|
AudioObjectGetPropertyDataSize(devID, &inputStreamsAddr, 0, NULL, &inStreamSize);
|
|
BOOL isInput = (inStreamSize > 0);
|
|
|
|
AudioObjectPropertyAddress outputStreamsAddr = {
|
|
.mSelector = kAudioDevicePropertyStreams,
|
|
.mScope = kAudioDevicePropertyScopeOutput,
|
|
.mElement = kAudioObjectPropertyElementMain
|
|
};
|
|
UInt32 outStreamSize = 0;
|
|
AudioObjectGetPropertyDataSize(devID, &outputStreamsAddr, 0, NULL, &outStreamSize);
|
|
BOOL isOutput = (outStreamSize > 0);
|
|
|
|
// Sample rate
|
|
Float64 sampleRate = 0.0;
|
|
propSize = sizeof(Float64);
|
|
AudioObjectPropertyAddress rateAddr = {
|
|
.mSelector = kAudioDevicePropertyNominalSampleRate,
|
|
.mScope = kAudioObjectPropertyScopeGlobal,
|
|
.mElement = kAudioObjectPropertyElementMain
|
|
};
|
|
AudioObjectGetPropertyData(devID, &rateAddr, 0, NULL, &propSize, &sampleRate);
|
|
|
|
// Volume
|
|
Float32 volume = 0.0f;
|
|
AudioObjectPropertyScope volScope = isOutput ? kAudioDevicePropertyScopeOutput : kAudioDevicePropertyScopeInput;
|
|
AudioObjectPropertyAddress volAddr = {
|
|
.mSelector = kAudioDevicePropertyVolumeScalar,
|
|
.mScope = volScope,
|
|
.mElement = kAudioObjectPropertyElementMain
|
|
};
|
|
propSize = sizeof(Float32);
|
|
if (AudioObjectHasProperty(devID, &volAddr)) {
|
|
AudioObjectGetPropertyData(devID, &volAddr, 0, NULL, &propSize, &volume);
|
|
}
|
|
|
|
// Mute status
|
|
UInt32 mute = 0;
|
|
AudioObjectPropertyAddress muteAddr = {
|
|
.mSelector = kAudioDevicePropertyMute,
|
|
.mScope = volScope,
|
|
.mElement = kAudioObjectPropertyElementMain
|
|
};
|
|
propSize = sizeof(UInt32);
|
|
if (AudioObjectHasProperty(devID, &muteAddr)) {
|
|
AudioObjectGetPropertyData(devID, &muteAddr, 0, NULL, &propSize, &mute);
|
|
}
|
|
|
|
MMAudioDevice *device = [[MMAudioDevice alloc] init];
|
|
device.deviceID = devID;
|
|
device.name = name;
|
|
device.manufacturer = mfr;
|
|
device.uid = uid;
|
|
device.isInput = isInput;
|
|
device.isOutput = isOutput;
|
|
device.isDefaultInput = (devID == defaultIn);
|
|
device.isDefaultOutput = (devID == defaultOut);
|
|
device.sampleRate = (double)sampleRate;
|
|
device.channelCount = (inStreamSize / sizeof(AudioStreamID)) + (outStreamSize / sizeof(AudioStreamID));
|
|
device.volume = volume;
|
|
device.isMuted = (mute != 0);
|
|
|
|
[devices addObject:device];
|
|
}
|
|
|
|
free(deviceIDs);
|
|
return devices;
|
|
}
|
|
|
|
- (nullable NSDictionary<NSString *, id> *)sampleTelemetryWithError:(NSError **)error {
|
|
NSArray<MMAudioDevice *> *devices = [MMAudioTelemetryProvider sampleAudioDevices];
|
|
NSMutableArray<NSDictionary<NSString *, id> *> *deviceList = [NSMutableArray arrayWithCapacity:devices.count];
|
|
|
|
MMAudioDevice *defaultIn = nil;
|
|
MMAudioDevice *defaultOut = nil;
|
|
|
|
for (MMAudioDevice *d in devices) {
|
|
[deviceList addObject:[d toDictionary]];
|
|
if (d.isDefaultInput) defaultIn = d;
|
|
if (d.isDefaultOutput) defaultOut = d;
|
|
}
|
|
|
|
return @{
|
|
@"devices": deviceList,
|
|
@"deviceCount": @(devices.count),
|
|
@"defaultInputDevice": defaultIn ? [defaultIn toDictionary] : [NSNull null],
|
|
@"defaultOutputDevice": defaultOut ? [defaultOut toDictionary] : [NSNull null]
|
|
};
|
|
}
|
|
|
|
@end
|