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] 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