Merge pull request #38 from JanX2/output-handling

Output handling
CQTexperiment
Christopher Snowhill 2020-02-12 14:19:56 -08:00 committed by GitHub
commit 505325e309
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 177 additions and 71 deletions

View File

@ -66,21 +66,15 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc
NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"]; NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"];
NSNumber *deviceID = [device objectForKey:@"deviceID"]; [self setOutputDeviceWithDeviceDict:device];
[self setOutputDevice:(AudioDeviceID)[deviceID longValue]];
} }
} }
- (OSStatus)setOutputDeviceByID:(AudioDeviceID)deviceID {
- (BOOL)setOutputDevice:(AudioDeviceID)outputDevice
{
// Set the output device
AudioDeviceID deviceID = outputDevice; //XXX use default if null
OSStatus err; OSStatus err;
if (outputDevice == -1) { if (deviceID == -1) {
DLog(@"DEVICE IS -1"); DLog(@"DEVICE IS -1");
UInt32 size = sizeof(AudioDeviceID); UInt32 size = sizeof(AudioDeviceID);
AudioObjectPropertyAddress theAddress = { AudioObjectPropertyAddress theAddress = {
@ -91,9 +85,9 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &size, &deviceID); err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &size, &deviceID);
if (err != noErr) { if (err != noErr) {
ALog(@"THERES NO DEFAULT OUTPUT DEVICE"); ALog(@"THERE'S NO DEFAULT OUTPUT DEVICE");
return NO; return err;
} }
} }
@ -101,13 +95,47 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc
outputDeviceID = deviceID; outputDeviceID = deviceID;
err = AudioUnitSetProperty(outputUnit, err = AudioUnitSetProperty(outputUnit,
kAudioOutputUnitProperty_CurrentDevice, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Output, kAudioUnitScope_Output,
0, 0,
&deviceID, &deviceID,
sizeof(AudioDeviceID)); sizeof(AudioDeviceID));
return err;
}
- (BOOL)setOutputDeviceWithDeviceDict:(NSDictionary *)deviceDict
{
NSNumber *deviceIDNum = [deviceDict objectForKey:@"deviceID"];
AudioDeviceID outputDeviceID = [deviceIDNum unsignedIntValue] ?: -1;
__block OSStatus err = [self setOutputDeviceByID:outputDeviceID];
if (err != noErr) { if (err != noErr) {
// Try matching by name.
NSString *userDeviceName = deviceDict[@"name"];
[self enumerateAudioOutputsUsingBlock:
^(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop) {
if ([deviceName isEqualToString:userDeviceName]) {
err = [self setOutputDeviceByID:deviceID];
#if 0
// Disable. Would cause loop by triggering `-observeValueForKeyPath:ofObject:change:context:` above.
// Update `outputDevice`, in case the ID has changed.
NSDictionary *deviceInfo = @{
@"name": deviceName,
@"deviceID": [NSNumber numberWithUnsignedInt:deviceID],
};
[[NSUserDefaults standardUserDefaults] setObject:deviceInfo forKey:@"outputDevice"];
#endif
return;
}
}];
}
if (err != noErr) {
ALog(@"No output device could be found, your random error code is %d. Have a nice day!", err); ALog(@"No output device could be found, your random error code is %d. Have a nice day!", err);
return NO; return NO;
@ -116,6 +144,64 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc
return YES; return YES;
} }
// The following is largely a copy pasta of -awakeFromNib from "OutputsArrayController.m".
// TODO: Share the code. (How to do this across xcodeproj?)
- (void)enumerateAudioOutputsUsingBlock:(void (NS_NOESCAPE ^ _Nonnull)(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop))block
{
UInt32 propsize;
AudioObjectPropertyAddress theAddress = {
.mSelector = kAudioHardwarePropertyDevices,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMaster
};
__Verify_noErr(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize));
UInt32 nDevices = propsize / (UInt32)sizeof(AudioDeviceID);
AudioDeviceID *devids = malloc(propsize);
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids));
theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
AudioDeviceID systemDefault;
propsize = sizeof(systemDefault);
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, &systemDefault));
theAddress.mScope = kAudioDevicePropertyScopeOutput;
for (UInt32 i = 0; i < nDevices; ++i) {
CFStringRef name = NULL;
propsize = sizeof(name);
theAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
__Verify_noErr(AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propsize, &name));
propsize = 0;
theAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
__Verify_noErr(AudioObjectGetPropertyDataSize(devids[i], &theAddress, 0, NULL, &propsize));
if (propsize < sizeof(UInt32)) continue;
AudioBufferList * bufferList = (AudioBufferList *) malloc(propsize);
__Verify_noErr(AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propsize, bufferList));
UInt32 bufferCount = bufferList->mNumberBuffers;
free(bufferList);
if (!bufferCount) continue;
BOOL stop = NO;
block([NSString stringWithString:(__bridge NSString *)name],
devids[i],
systemDefault,
&stop);
CFRelease(name);
if (stop) {
break;
}
}
free(devids);
}
- (BOOL)setup - (BOOL)setup
{ {
if (outputUnit) if (outputUnit)
@ -151,16 +237,16 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc
// Setup the output device before mucking with settings // Setup the output device before mucking with settings
NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"]; NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"];
if (device) { if (device) {
BOOL ok = [self setOutputDevice:(AudioDeviceID)[[device objectForKey:@"deviceID"] longValue]]; BOOL ok = [self setOutputDeviceWithDeviceDict:device];
if (!ok) { if (!ok) {
//Ruh roh. //Ruh roh.
[self setOutputDevice: -1]; [self setOutputDeviceWithDeviceDict:nil];
[[[NSUserDefaultsController sharedUserDefaultsController] defaults] removeObjectForKey:@"outputDevice"]; [[[NSUserDefaultsController sharedUserDefaultsController] defaults] removeObjectForKey:@"outputDevice"];
} }
} }
else { else {
[self setOutputDevice: -1]; [self setOutputDeviceWithDeviceDict:nil];
} }
UInt32 size = sizeof (AudioStreamBasicDescription); UInt32 size = sizeof (AudioStreamBasicDescription);

View File

@ -28,4 +28,4 @@
- (void)pathDidChange:(NSString *)path; - (void)pathDidChange:(NSString *)path;
@end @end

View File

@ -7,71 +7,91 @@
[self removeObjects:[self arrangedObjects]]; [self removeObjects:[self arrangedObjects]];
[self setSelectsInsertedObjects:NO]; [self setSelectsInsertedObjects:NO];
UInt32 propsize;
AudioObjectPropertyAddress theAddress = {
.mSelector = kAudioHardwarePropertyDevices,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMaster
};
__Verify_noErr(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize));
int nDevices = propsize / sizeof(AudioDeviceID);
AudioDeviceID *devids = malloc(propsize);
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids));
int i;
theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
AudioDeviceID systemDefault;
propsize = sizeof(systemDefault);
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, &systemDefault));
NSDictionary *defaultDevice = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"]; NSDictionary *defaultDevice = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"];
theAddress.mScope = kAudioDevicePropertyScopeOutput; [self enumerateAudioOutputsUsingBlock:
^(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop) {
for (i = 0; i < nDevices; ++i) { NSDictionary *deviceInfo = @{
CFStringRef name = NULL; @"name": deviceName,
propsize = sizeof(name); @"deviceID": [NSNumber numberWithUnsignedInt:deviceID],
theAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; };
__Verify_noErr(AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propsize, &name));
propsize = 0;
theAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
__Verify_noErr(AudioObjectGetPropertyDataSize(devids[i], &theAddress, 0, NULL, &propsize));
if (propsize < sizeof(UInt32)) continue;
AudioBufferList * bufferList = (AudioBufferList *) malloc(propsize);
__Verify_noErr(AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propsize, bufferList));
UInt32 bufferCount = bufferList->mNumberBuffers;
free(bufferList);
if (!bufferCount) continue;
NSDictionary *deviceInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithString:(__bridge NSString*)name], @"name",
[NSNumber numberWithLong:devids[i]], @"deviceID",
nil];
[self addObject:deviceInfo]; [self addObject:deviceInfo];
CFRelease(name);
if (defaultDevice) { if (defaultDevice) {
if ([[defaultDevice objectForKey:@"deviceID"] isEqualToNumber:[deviceInfo objectForKey:@"deviceID"]]) { if (([[defaultDevice objectForKey:@"deviceID"] isEqualToNumber:[deviceInfo objectForKey:@"deviceID"]]) ||
([[defaultDevice objectForKey:@"name"] isEqualToString:[deviceInfo objectForKey:@"name"]])) {
[self setSelectedObjects:[NSArray arrayWithObject:deviceInfo]]; [self setSelectedObjects:[NSArray arrayWithObject:deviceInfo]];
// Update `outputDevice`, in case the ID has changed.
[[NSUserDefaults standardUserDefaults] setObject:deviceInfo forKey:@"outputDevice"];
} }
} }
else { else {
if ( devids[i] == systemDefault ) { if (deviceID == systemDefaultID) {
[self setSelectedObjects:[NSArray arrayWithObject:deviceInfo]]; [self setSelectedObjects:[NSArray arrayWithObject:deviceInfo]];
} }
} }
} }];
free(devids);
if (!defaultDevice) if (!defaultDevice) {
[self setSelectionIndex:0]; [self setSelectionIndex:0];
}
}
- (void)enumerateAudioOutputsUsingBlock:(void (NS_NOESCAPE ^ _Nonnull)(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop))block
{
UInt32 propsize;
AudioObjectPropertyAddress theAddress = {
.mSelector = kAudioHardwarePropertyDevices,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMaster
};
__Verify_noErr(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize));
UInt32 nDevices = propsize / (UInt32)sizeof(AudioDeviceID);
AudioDeviceID *devids = malloc(propsize);
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids));
theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
AudioDeviceID systemDefault;
propsize = sizeof(systemDefault);
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, &systemDefault));
theAddress.mScope = kAudioDevicePropertyScopeOutput;
for (UInt32 i = 0; i < nDevices; ++i) {
CFStringRef name = NULL;
propsize = sizeof(name);
theAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
__Verify_noErr(AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propsize, &name));
propsize = 0;
theAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
__Verify_noErr(AudioObjectGetPropertyDataSize(devids[i], &theAddress, 0, NULL, &propsize));
if (propsize < sizeof(UInt32)) continue;
AudioBufferList * bufferList = (AudioBufferList *) malloc(propsize);
__Verify_noErr(AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propsize, bufferList));
UInt32 bufferCount = bufferList->mNumberBuffers;
free(bufferList);
if (!bufferCount) continue;
BOOL stop = NO;
block([NSString stringWithString:(__bridge NSString *)name],
devids[i],
systemDefault,
&stop);
CFRelease(name);
if (stop) {
break;
}
}
free(devids);
} }
@end @end

View File

@ -15,4 +15,4 @@
- (NSComparisonResult)finderCompare:(NSURL *)aURL; - (NSComparisonResult)finderCompare:(NSURL *)aURL;
@end @end