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 1/3] 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 { From 57ebc65ba0b727c9399787a4e8c26ce1edfe064f 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 2/3] Add missing newlines. --- FileTree/PathWatcher.h | 2 +- Utils/NSString+FinderCompare.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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 From b22c5964e471021936e310b9c973ac1b9cb3f88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wei=C3=9F?= Date: Sat, 1 Feb 2020 14:44:07 +0100 Subject: [PATCH 3/3] Improve output handling, 2. Fix issues with above. --- Audio/Output/OutputCoreAudio.m | 22 +++- Preferences/General/OutputsArrayController.m | 113 +++++++++++-------- 2 files changed, 81 insertions(+), 54 deletions(-) diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index b15b13410..243e8ddbd 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -115,19 +115,27 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc // Try matching by name. NSString *userDeviceName = deviceDict[@"name"]; [self enumerateAudioOutputsUsingBlock: - ^(NSString *deviceName, AudioDeviceID deviceID, BOOL *stop) { + ^(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; @@ -138,18 +146,19 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc // 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 { +- (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)); - int nDevices = propsize / sizeof(AudioDeviceID); + UInt32 nDevices = propsize / (UInt32)sizeof(AudioDeviceID); AudioDeviceID *devids = malloc(propsize); __Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids)); - int i; theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; AudioDeviceID systemDefault; @@ -158,7 +167,7 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc theAddress.mScope = kAudioDevicePropertyScopeOutput; - for (i = 0; i < nDevices; ++i) { + for (UInt32 i = 0; i < nDevices; ++i) { CFStringRef name = NULL; propsize = sizeof(name); theAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; @@ -180,6 +189,7 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc BOOL stop = NO; block([NSString stringWithString:(__bridge NSString *)name], devids[i], + systemDefault, &stop); CFRelease(name); diff --git a/Preferences/General/OutputsArrayController.m b/Preferences/General/OutputsArrayController.m index 2b2236da6..c6ac0139a 100644 --- a/Preferences/General/OutputsArrayController.m +++ b/Preferences/General/OutputsArrayController.m @@ -7,55 +7,17 @@ [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; - + [self enumerateAudioOutputsUsingBlock: + ^(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop) { NSDictionary *deviceInfo = @{ - @"name": [NSString stringWithString:(__bridge NSString*)name], - @"deviceID": [NSNumber numberWithUnsignedInt:devids[i]], + @"name": deviceName, + @"deviceID": [NSNumber numberWithUnsignedInt:deviceID], }; [self addObject:deviceInfo]; - - CFRelease(name); - + if (defaultDevice) { if (([[defaultDevice objectForKey:@"deviceID"] isEqualToNumber:[deviceInfo objectForKey:@"deviceID"]]) || ([[defaultDevice objectForKey:@"name"] isEqualToString:[deviceInfo objectForKey:@"name"]])) { @@ -65,16 +27,71 @@ } } 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