[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 <kode54@gmail.com>
This commit is contained in:
Christopher Snowhill 2022-06-09 00:27:55 -07:00
parent cdcbabd72f
commit 6179b304d0
6 changed files with 88 additions and 0 deletions

View file

@ -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, // 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. // and returns 0 samples when it has nothing more to process at the end of stream.
while([self shouldContinue] == YES) { while([self shouldContinue] == YES) {
[self followWorkgroup];
int amountConverted; int amountConverted;
while(paused) { while(paused) {
usleep(500); usleep(500);
@ -435,6 +437,7 @@ static void convert_be_to_le(uint8_t *buffer, size_t bitsPerSample, size_t bytes
if(paused) { if(paused) {
continue; continue;
} else if(streamFormatChanged) { } else if(streamFormatChanged) {
[self leaveWorkgroup];
[self cleanUp]; [self cleanUp];
[self setupWithInputFormat:newInputFormat withInputConfig:newInputChannelConfig outputFormat:outputFormat outputConfig:outputChannelConfig isLossless:rememberedLossless]; [self setupWithInputFormat:newInputFormat withInputConfig:newInputChannelConfig outputFormat:outputFormat outputConfig:outputChannelConfig isLossless:rememberedLossless];
continue; continue;

View file

@ -148,6 +148,7 @@
while([self shouldContinue] == YES && [self endOfStream] == NO) { while([self shouldContinue] == YES && [self endOfStream] == NO) {
if(shouldSeek == YES) { if(shouldSeek == YES) {
[self leaveWorkgroup];
BufferChain *bufferChain = [[controller controller] bufferChain]; BufferChain *bufferChain = [[controller controller] bufferChain];
ConverterNode *converter = [bufferChain converter]; ConverterNode *converter = [bufferChain converter];
DLog(@"SEEKING! Resetting Buffer"); DLog(@"SEEKING! Resetting Buffer");
@ -173,6 +174,8 @@
} }
if(amountInBuffer < CHUNK_SIZE) { if(amountInBuffer < CHUNK_SIZE) {
[self followWorkgroup];
int framesToRead = CHUNK_SIZE - amountInBuffer; int framesToRead = CHUNK_SIZE - amountInBuffer;
int framesRead; int framesRead;
@autoreleasepool { @autoreleasepool {

View file

@ -10,6 +10,8 @@
#import "Semaphore.h" #import "Semaphore.h"
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import <os/workgroup.h>
#define BUFFER_SIZE 1024 * 1024 #define BUFFER_SIZE 1024 * 1024
#define CHUNK_SIZE 16 * 1024 #define CHUNK_SIZE 16 * 1024
@ -31,6 +33,9 @@
AudioStreamBasicDescription nodeFormat; AudioStreamBasicDescription nodeFormat;
uint32_t nodeChannelConfig; uint32_t nodeChannelConfig;
BOOL nodeLossless; BOOL nodeLossless;
os_workgroup_t wg;
os_workgroup_join_token_s wgToken;
} }
- (id _Nullable)initWithController:(id _Nonnull)c previous:(id _Nullable)p; - (id _Nullable)initWithController:(id _Nonnull)c previous:(id _Nullable)p;
@ -42,6 +47,9 @@
- (void)process; // Should be overwriten by subclass - (void)process; // Should be overwriten by subclass
- (void)threadEntry:(id _Nullable)arg; - (void)threadEntry:(id _Nullable)arg;
- (void)followWorkgroup;
- (void)leaveWorkgroup;
- (void)launchThread; - (void)launchThread;
- (void)setShouldReset:(BOOL)s; - (void)setShouldReset:(BOOL)s;

View file

@ -11,6 +11,8 @@
#import "BufferChain.h" #import "BufferChain.h"
#import "Logging.h" #import "Logging.h"
#import "OutputCoreAudio.h"
@implementation Node @implementation Node
- (id)initWithController:(id)c previous:(id)p { - (id)initWithController:(id)c previous:(id)p {
@ -91,7 +93,38 @@
- (void)threadEntry:(id)arg { - (void)threadEntry:(id)arg {
@autoreleasepool { @autoreleasepool {
[self followWorkgroup];
[self process]; [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;
}
} }
} }

View file

@ -33,6 +33,10 @@ using std::atomic_long;
#import <stdio.h> #import <stdio.h>
#endif #endif
#import <os/workgroup.h>
extern volatile os_workgroup_t currentWorkgroup;
@class OutputNode; @class OutputNode;
@interface OutputCoreAudio : NSObject { @interface OutputCoreAudio : NSObject {
@ -84,6 +88,9 @@ using std::atomic_long;
VisualizationController *visController; VisualizationController *visController;
os_workgroup_t wg;
os_workgroup_join_token_s wgToken;
#ifdef OUTPUT_LOG #ifdef OUTPUT_LOG
FILE *_logFile; FILE *_logFile;
#endif #endif

View file

@ -17,6 +17,8 @@
#import <Accelerate/Accelerate.h> #import <Accelerate/Accelerate.h>
volatile os_workgroup_t currentWorkgroup;
extern void scale_by_volume(float *buffer, size_t count, float volume); extern void scale_by_volume(float *buffer, size_t count, float volume);
static NSString *CogPlaybackDidBeginNotficiation = @"CogPlaybackDidBeginNotficiation"; static NSString *CogPlaybackDidBeginNotficiation = @"CogPlaybackDidBeginNotficiation";
@ -260,6 +262,22 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
atomic_store(&bytesRendered, 0); atomic_store(&bytesRendered, 0);
NSMutableArray *delayedEvents = [[NSMutableArray alloc] init]; NSMutableArray *delayedEvents = [[NSMutableArray alloc] init];
BOOL delayedEventsPopped = YES; 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) { while(!stopping) {
if(++eventCount == 48) { if(++eventCount == 48) {
[self resetIfOutputChanged]; [self resetIfOutputChanged];
@ -344,6 +362,14 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
[readSemaphore signal]; [readSemaphore signal];
[writeSemaphore timedWait:5000]; [writeSemaphore timedWait:5000];
} }
if(@available(macOS 11, *)) {
if(wg) {
os_workgroup_leave(wg, &wgToken);
wg = nil;
}
}
stopped = YES; stopped = YES;
if(!stopInvoked) if(!stopInvoked)
[self stop]; [self stop];
@ -780,6 +806,10 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
visController = [VisualizationController sharedController]; 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.outputDevice" options:0 context:NULL];
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.GraphicEQenable" 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]; [[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 { - (void)stop {
if(@available(macOS 11, *)) {
currentWorkgroup = nil;
}
stopInvoked = YES; stopInvoked = YES;
if(observersapplied) { if(observersapplied) {
observersapplied = NO; observersapplied = NO;