From 6179b304d04935435afe05872bf4238bfb040fec Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Thu, 9 Jun 2022 00:27:55 -0700 Subject: [PATCH] [Audio Threads] Join output device workgroup On Big Sur or newer, it is possible to join the audio threads to the same OS workgroup as the audio output device, improving response. Signed-off-by: Christopher Snowhill --- Audio/Chain/ConverterNode.mm | 3 +++ Audio/Chain/InputNode.m | 3 +++ Audio/Chain/Node.h | 8 ++++++++ Audio/Chain/Node.m | 33 +++++++++++++++++++++++++++++++++ Audio/Output/OutputCoreAudio.h | 7 +++++++ Audio/Output/OutputCoreAudio.m | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 88 insertions(+) diff --git a/Audio/Chain/ConverterNode.mm b/Audio/Chain/ConverterNode.mm index 708a16533..bc3a59dd4 100644 --- a/Audio/Chain/ConverterNode.mm +++ b/Audio/Chain/ConverterNode.mm @@ -424,6 +424,8 @@ static void convert_be_to_le(uint8_t *buffer, size_t bitsPerSample, size_t bytes // when the end of stream is reached. Convert function instead processes what it can, // and returns 0 samples when it has nothing more to process at the end of stream. while([self shouldContinue] == YES) { + [self followWorkgroup]; + int amountConverted; while(paused) { usleep(500); @@ -435,6 +437,7 @@ static void convert_be_to_le(uint8_t *buffer, size_t bitsPerSample, size_t bytes if(paused) { continue; } else if(streamFormatChanged) { + [self leaveWorkgroup]; [self cleanUp]; [self setupWithInputFormat:newInputFormat withInputConfig:newInputChannelConfig outputFormat:outputFormat outputConfig:outputChannelConfig isLossless:rememberedLossless]; continue; diff --git a/Audio/Chain/InputNode.m b/Audio/Chain/InputNode.m index 4b739248f..792c2361a 100644 --- a/Audio/Chain/InputNode.m +++ b/Audio/Chain/InputNode.m @@ -148,6 +148,7 @@ while([self shouldContinue] == YES && [self endOfStream] == NO) { if(shouldSeek == YES) { + [self leaveWorkgroup]; BufferChain *bufferChain = [[controller controller] bufferChain]; ConverterNode *converter = [bufferChain converter]; DLog(@"SEEKING! Resetting Buffer"); @@ -173,6 +174,8 @@ } if(amountInBuffer < CHUNK_SIZE) { + [self followWorkgroup]; + int framesToRead = CHUNK_SIZE - amountInBuffer; int framesRead; @autoreleasepool { diff --git a/Audio/Chain/Node.h b/Audio/Chain/Node.h index 03ea57cff..b4dcdeea1 100644 --- a/Audio/Chain/Node.h +++ b/Audio/Chain/Node.h @@ -10,6 +10,8 @@ #import "Semaphore.h" #import +#import + #define BUFFER_SIZE 1024 * 1024 #define CHUNK_SIZE 16 * 1024 @@ -31,6 +33,9 @@ AudioStreamBasicDescription nodeFormat; uint32_t nodeChannelConfig; BOOL nodeLossless; + + os_workgroup_t wg; + os_workgroup_join_token_s wgToken; } - (id _Nullable)initWithController:(id _Nonnull)c previous:(id _Nullable)p; @@ -42,6 +47,9 @@ - (void)process; // Should be overwriten by subclass - (void)threadEntry:(id _Nullable)arg; +- (void)followWorkgroup; +- (void)leaveWorkgroup; + - (void)launchThread; - (void)setShouldReset:(BOOL)s; diff --git a/Audio/Chain/Node.m b/Audio/Chain/Node.m index fe9d877a5..7150c02f2 100644 --- a/Audio/Chain/Node.m +++ b/Audio/Chain/Node.m @@ -11,6 +11,8 @@ #import "BufferChain.h" #import "Logging.h" +#import "OutputCoreAudio.h" + @implementation Node - (id)initWithController:(id)c previous:(id)p { @@ -91,7 +93,38 @@ - (void)threadEntry:(id)arg { @autoreleasepool { + [self followWorkgroup]; [self process]; + [self leaveWorkgroup]; + } +} + +- (void)followWorkgroup { + if(@available(macOS 11, *)) { + if(currentWorkgroup != wg) { + if(wg) { + os_workgroup_leave(wg, &wgToken); + } + wg = currentWorkgroup; + if(wg) { + int result = os_workgroup_join(wg, &wgToken); + if(result == 0) return; + if(result == EALREADY) { + DLog(@"Thread already in workgroup"); + } else { + DLog(@"Cannot join workgroup, error %d", result); + } + } + } + } +} + +- (void)leaveWorkgroup { + if(@available(macOS 11, *)) { + if(wg) { + os_workgroup_leave(wg, &wgToken); + wg = nil; + } } } diff --git a/Audio/Output/OutputCoreAudio.h b/Audio/Output/OutputCoreAudio.h index 9cff85505..a2d45838a 100644 --- a/Audio/Output/OutputCoreAudio.h +++ b/Audio/Output/OutputCoreAudio.h @@ -33,6 +33,10 @@ using std::atomic_long; #import #endif +#import + +extern volatile os_workgroup_t currentWorkgroup; + @class OutputNode; @interface OutputCoreAudio : NSObject { @@ -84,6 +88,9 @@ using std::atomic_long; VisualizationController *visController; + os_workgroup_t wg; + os_workgroup_join_token_s wgToken; + #ifdef OUTPUT_LOG FILE *_logFile; #endif diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index cbeec309a..4a828911f 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -17,6 +17,8 @@ #import +volatile os_workgroup_t currentWorkgroup; + extern void scale_by_volume(float *buffer, size_t count, float volume); static NSString *CogPlaybackDidBeginNotficiation = @"CogPlaybackDidBeginNotficiation"; @@ -260,6 +262,22 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const atomic_store(&bytesRendered, 0); NSMutableArray *delayedEvents = [[NSMutableArray alloc] init]; BOOL delayedEventsPopped = YES; + + if(@available(macOS 11, *)) { + if(currentWorkgroup) { + wg = currentWorkgroup; + int result = os_workgroup_join(wg, &wgToken); + if(result != 0) { + if(result == EALREADY) { + DLog(@"Output thread already in workgroup"); + } else { + DLog(@"Output thread could not be added to workgroup, error = %d", result); + } + wg = nil; + } + } + } + while(!stopping) { if(++eventCount == 48) { [self resetIfOutputChanged]; @@ -344,6 +362,14 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const [readSemaphore signal]; [writeSemaphore timedWait:5000]; } + + if(@available(macOS 11, *)) { + if(wg) { + os_workgroup_leave(wg, &wgToken); + wg = nil; + } + } + stopped = YES; if(!stopInvoked) [self stop]; @@ -780,6 +806,10 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const visController = [VisualizationController sharedController]; + if(@available(macOS 11, *)) { + currentWorkgroup = _au.osWorkgroup; + } + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.outputDevice" options:0 context:NULL]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.GraphicEQenable" options:0 context:NULL]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.eqPreamp" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:NULL]; @@ -809,6 +839,10 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const } - (void)stop { + if(@available(macOS 11, *)) { + currentWorkgroup = nil; + } + stopInvoked = YES; if(observersapplied) { observersapplied = NO;