Improve output handling, 2.

Fix issues with above.
CQTexperiment
Jan Weiß 2020-02-01 14:44:07 +01:00
parent 57ebc65ba0
commit b22c5964e4
2 changed files with 81 additions and 54 deletions

View File

@ -115,18 +115,26 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc
// Try matching by name. // Try matching by name.
NSString *userDeviceName = deviceDict[@"name"]; NSString *userDeviceName = deviceDict[@"name"];
[self enumerateAudioOutputsUsingBlock: [self enumerateAudioOutputsUsingBlock:
^(NSString *deviceName, AudioDeviceID deviceID, BOOL *stop) { ^(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop) {
if ([deviceName isEqualToString:userDeviceName]) { if ([deviceName isEqualToString:userDeviceName]) {
err = [self setOutputDeviceByID:deviceID]; 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. // Update `outputDevice`, in case the ID has changed.
NSDictionary *deviceInfo = @{ NSDictionary *deviceInfo = @{
@"name": deviceName, @"name": deviceName,
@"deviceID": [NSNumber numberWithUnsignedInt:deviceID], @"deviceID": [NSNumber numberWithUnsignedInt:deviceID],
}; };
[[NSUserDefaults standardUserDefaults] setObject:deviceInfo forKey:@"outputDevice"]; [[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);
@ -138,18 +146,19 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc
// The following is largely a copy pasta of -awakeFromNib from "OutputsArrayController.m". // The following is largely a copy pasta of -awakeFromNib from "OutputsArrayController.m".
// TODO: Share the code. (How to do this across xcodeproj?) // 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; UInt32 propsize;
AudioObjectPropertyAddress theAddress = { AudioObjectPropertyAddress theAddress = {
.mSelector = kAudioHardwarePropertyDevices, .mSelector = kAudioHardwarePropertyDevices,
.mScope = kAudioObjectPropertyScopeGlobal, .mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMaster .mElement = kAudioObjectPropertyElementMaster
}; };
__Verify_noErr(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize)); __Verify_noErr(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize));
int nDevices = propsize / sizeof(AudioDeviceID); UInt32 nDevices = propsize / (UInt32)sizeof(AudioDeviceID);
AudioDeviceID *devids = malloc(propsize); AudioDeviceID *devids = malloc(propsize);
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids)); __Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids));
int i;
theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
AudioDeviceID systemDefault; AudioDeviceID systemDefault;
@ -158,7 +167,7 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc
theAddress.mScope = kAudioDevicePropertyScopeOutput; theAddress.mScope = kAudioDevicePropertyScopeOutput;
for (i = 0; i < nDevices; ++i) { for (UInt32 i = 0; i < nDevices; ++i) {
CFStringRef name = NULL; CFStringRef name = NULL;
propsize = sizeof(name); propsize = sizeof(name);
theAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; theAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
@ -180,6 +189,7 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc
BOOL stop = NO; BOOL stop = NO;
block([NSString stringWithString:(__bridge NSString *)name], block([NSString stringWithString:(__bridge NSString *)name],
devids[i], devids[i],
systemDefault,
&stop); &stop);
CFRelease(name); CFRelease(name);

View File

@ -8,28 +8,58 @@
[self setSelectsInsertedObjects:NO]; [self setSelectsInsertedObjects:NO];
NSDictionary *defaultDevice = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"];
[self enumerateAudioOutputsUsingBlock:
^(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop) {
NSDictionary *deviceInfo = @{
@"name": deviceName,
@"deviceID": [NSNumber numberWithUnsignedInt:deviceID],
};
[self addObject:deviceInfo];
if (defaultDevice) {
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 (deviceID == systemDefaultID) {
[self setSelectedObjects:[NSArray arrayWithObject:deviceInfo]];
}
}
}];
if (!defaultDevice) {
[self setSelectionIndex:0];
}
}
- (void)enumerateAudioOutputsUsingBlock:(void (NS_NOESCAPE ^ _Nonnull)(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop))block
{
UInt32 propsize; UInt32 propsize;
AudioObjectPropertyAddress theAddress = { AudioObjectPropertyAddress theAddress = {
.mSelector = kAudioHardwarePropertyDevices, .mSelector = kAudioHardwarePropertyDevices,
.mScope = kAudioObjectPropertyScopeGlobal, .mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMaster .mElement = kAudioObjectPropertyElementMaster
}; };
__Verify_noErr(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize)); __Verify_noErr(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize));
int nDevices = propsize / sizeof(AudioDeviceID); UInt32 nDevices = propsize / (UInt32)sizeof(AudioDeviceID);
AudioDeviceID *devids = malloc(propsize); AudioDeviceID *devids = malloc(propsize);
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids)); __Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids));
int i;
theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
AudioDeviceID systemDefault; AudioDeviceID systemDefault;
propsize = sizeof(systemDefault); propsize = sizeof(systemDefault);
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, &systemDefault)); __Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, &systemDefault));
NSDictionary *defaultDevice = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"];
theAddress.mScope = kAudioDevicePropertyScopeOutput; theAddress.mScope = kAudioDevicePropertyScopeOutput;
for (i = 0; i < nDevices; ++i) { for (UInt32 i = 0; i < nDevices; ++i) {
CFStringRef name = NULL; CFStringRef name = NULL;
propsize = sizeof(name); propsize = sizeof(name);
theAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; theAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
@ -48,33 +78,20 @@
if (!bufferCount) continue; if (!bufferCount) continue;
NSDictionary *deviceInfo = @{ BOOL stop = NO;
@"name": [NSString stringWithString:(__bridge NSString*)name], block([NSString stringWithString:(__bridge NSString *)name],
@"deviceID": [NSNumber numberWithUnsignedInt:devids[i]], devids[i],
}; systemDefault,
[self addObject:deviceInfo]; &stop);
CFRelease(name); CFRelease(name);
if (defaultDevice) { if (stop) {
if (([[defaultDevice objectForKey:@"deviceID"] isEqualToNumber:[deviceInfo objectForKey:@"deviceID"]]) || break;
([[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 ) {
[self setSelectedObjects:[NSArray arrayWithObject:deviceInfo]];
}
} }
} }
free(devids); free(devids);
if (!defaultDevice)
[self setSelectionIndex:0];
} }
@end @end