From 97ed738846f3e19703f767bce4090ed73cf2daf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wei=C3=9F?= Date: Sat, 1 Feb 2020 13:59:30 +0100 Subject: [PATCH] Improve output handling. --- Audio/Output/OutputCoreAudio.m | 112 ++++++++++++++++--- Preferences/General/OutputsArrayController.m | 13 ++- 2 files changed, 102 insertions(+), 23 deletions(-) diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index a1f835be0..b15b13410 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -66,21 +66,15 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"]; - NSNumber *deviceID = [device objectForKey:@"deviceID"]; - - [self setOutputDevice:(AudioDeviceID)[deviceID longValue]]; + [self setOutputDeviceWithDeviceDict:device]; } } - -- (BOOL)setOutputDevice:(AudioDeviceID)outputDevice -{ - // Set the output device - AudioDeviceID deviceID = outputDevice; //XXX use default if null +- (OSStatus)setOutputDeviceByID:(AudioDeviceID)deviceID { OSStatus err; - if (outputDevice == -1) { + if (deviceID == -1) { DLog(@"DEVICE IS -1"); UInt32 size = sizeof(AudioDeviceID); AudioObjectPropertyAddress theAddress = { @@ -91,9 +85,9 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &size, &deviceID); if (err != noErr) { - ALog(@"THERES NO DEFAULT OUTPUT DEVICE"); + ALog(@"THERE'S NO DEFAULT OUTPUT DEVICE"); - return NO; + return err; } } @@ -101,13 +95,39 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc outputDeviceID = deviceID; err = AudioUnitSetProperty(outputUnit, - kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Output, - 0, - &deviceID, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Output, + 0, + &deviceID, 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) { + // Try matching by name. + NSString *userDeviceName = deviceDict[@"name"]; + [self enumerateAudioOutputsUsingBlock: + ^(NSString *deviceName, AudioDeviceID deviceID, BOOL *stop) { + if ([deviceName isEqualToString:userDeviceName]) { + err = [self setOutputDeviceByID:deviceID]; + + // Update `outputDevice`, in case the ID has changed. + NSDictionary *deviceInfo = @{ + @"name": deviceName, + @"deviceID": [NSNumber numberWithUnsignedInt:deviceID], + }; + [[NSUserDefaults standardUserDefaults] setObject:deviceInfo forKey:@"outputDevice"]; + } + }]; + ALog(@"No output device could be found, your random error code is %d. Have a nice day!", err); return NO; @@ -116,6 +136,62 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc 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, BOOL *stop))block { + 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)); + + theAddress.mScope = kAudioDevicePropertyScopeOutput; + + for (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], + &stop); + + CFRelease(name); + + if (stop) { + break; + } + } + + free(devids); +} + - (BOOL)setup { if (outputUnit) @@ -151,16 +227,16 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc // Setup the output device before mucking with settings NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"]; if (device) { - BOOL ok = [self setOutputDevice:(AudioDeviceID)[[device objectForKey:@"deviceID"] longValue]]; + BOOL ok = [self setOutputDeviceWithDeviceDict:device]; if (!ok) { //Ruh roh. - [self setOutputDevice: -1]; + [self setOutputDeviceWithDeviceDict:nil]; [[[NSUserDefaultsController sharedUserDefaultsController] defaults] removeObjectForKey:@"outputDevice"]; } } else { - [self setOutputDevice: -1]; + [self setOutputDeviceWithDeviceDict:nil]; } UInt32 size = sizeof (AudioStreamBasicDescription); diff --git a/Preferences/General/OutputsArrayController.m b/Preferences/General/OutputsArrayController.m index 99e0ee7d7..2b2236da6 100644 --- a/Preferences/General/OutputsArrayController.m +++ b/Preferences/General/OutputsArrayController.m @@ -48,17 +48,20 @@ if (!bufferCount) continue; - NSDictionary *deviceInfo = [NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithString:(__bridge NSString*)name], @"name", - [NSNumber numberWithLong:devids[i]], @"deviceID", - nil]; + NSDictionary *deviceInfo = @{ + @"name": [NSString stringWithString:(__bridge NSString*)name], + @"deviceID": [NSNumber numberWithUnsignedInt:devids[i]], + }; [self addObject:deviceInfo]; CFRelease(name); 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]]; + // Update `outputDevice`, in case the ID has changed. + [[NSUserDefaults standardUserDefaults] setObject:deviceInfo forKey:@"outputDevice"]; } } else {