diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index a1f835be0..243e8ddbd 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,47 @@ 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, 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); return NO; @@ -116,6 +144,64 @@ 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, 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 { if (outputUnit) @@ -151,16 +237,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/FileTree/PathWatcher.h b/FileTree/PathWatcher.h index 3471b1ac1..6ca2774ff 100644 --- a/FileTree/PathWatcher.h +++ b/FileTree/PathWatcher.h @@ -28,4 +28,4 @@ - (void)pathDidChange:(NSString *)path; -@end \ No newline at end of file +@end diff --git a/Preferences/General/OutputsArrayController.m b/Preferences/General/OutputsArrayController.m index 99e0ee7d7..c6ac0139a 100644 --- a/Preferences/General/OutputsArrayController.m +++ b/Preferences/General/OutputsArrayController.m @@ -7,71 +7,91 @@ [self removeObjects:[self arrangedObjects]]; [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"]; - 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; - - NSDictionary *deviceInfo = [NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithString:(__bridge NSString*)name], @"name", - [NSNumber numberWithLong:devids[i]], @"deviceID", - nil]; + [self enumerateAudioOutputsUsingBlock: + ^(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop) { + NSDictionary *deviceInfo = @{ + @"name": deviceName, + @"deviceID": [NSNumber numberWithUnsignedInt:deviceID], + }; [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 { - if ( devids[i] == systemDefault ) { + if (deviceID == systemDefaultID) { [self setSelectedObjects:[NSArray arrayWithObject:deviceInfo]]; } } - } - free(devids); - + }]; - if (!defaultDevice) + if (!defaultDevice) { [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 diff --git a/Utils/NSString+FinderCompare.h b/Utils/NSString+FinderCompare.h index 7153f3970..ed1c41aa5 100644 --- a/Utils/NSString+FinderCompare.h +++ b/Utils/NSString+FinderCompare.h @@ -15,4 +15,4 @@ - (NSComparisonResult)finderCompare:(NSURL *)aURL; -@end \ No newline at end of file +@end