[Audio Threads] Correctly set real time priority

I'm not sure about macOS Ventura, but stable releases of macOS, at
least on Intel, require that threads joining Audio Interval
workgroups already be set to run as real-time before joining. Not
doing this results in an uncaught exception and a crash.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
swiftingly
Christopher Snowhill 2022-06-09 18:15:44 -07:00
parent 91898e9e77
commit 7813712df3
2 changed files with 90 additions and 8 deletions

View File

@ -13,10 +13,12 @@
#import "OutputCoreAudio.h"
#import <pthread.h>
#import <mach/mach_time.h>
// This workgroup attribute isn't currently used. Set it to NULL.
static os_workgroup_attr_t _Nullable attr = nil;
// This workgroup attribute needs to be initialized.
static os_workgroup_attr_s attr = OS_WORKGROUP_ATTR_INITIALIZER_DEFAULT;
// One nanosecond in seconds.
static const double kOneNanosecond = 1.0e9;
@ -27,6 +29,86 @@ static const double kIOIntervalTime = 0.020;
// The clock identifier that specifies interval timestamps.
static const os_clockid_t clockId = OS_CLOCK_MACH_ABSOLUTE_TIME;
// Enables time-contraint policy and priority suitable for low-latency,
// glitch-resistant audio.
void SetPriorityRealtimeAudio(mach_port_t mach_thread_id) {
kern_return_t result;
// Increase thread priority to real-time.
// Please note that the thread_policy_set() calls may fail in
// rare cases if the kernel decides the system is under heavy load
// and is unable to handle boosting the thread priority.
// In these cases we just return early and go on with life.
// Make thread fixed priority.
thread_extended_policy_data_t policy;
policy.timeshare = 0; // Set to 1 for a non-fixed thread.
result = thread_policy_set(mach_thread_id,
THREAD_EXTENDED_POLICY,
(thread_policy_t)&policy,
THREAD_EXTENDED_POLICY_COUNT);
if(result != KERN_SUCCESS) {
DLog(@"thread_policy_set extended policy failure: %d", result);
return;
}
// Set to relatively high priority.
thread_precedence_policy_data_t precedence;
precedence.importance = 63;
result = thread_policy_set(mach_thread_id,
THREAD_PRECEDENCE_POLICY,
(thread_policy_t)&precedence,
THREAD_PRECEDENCE_POLICY_COUNT);
if(result != KERN_SUCCESS) {
DLog(@"thread_policy_set precedence policy failure: %d", result);
return;
}
// Most important, set real-time constraints.
// Define the guaranteed and max fraction of time for the audio thread.
// These "duty cycle" values can range from 0 to 1. A value of 0.5
// means the scheduler would give half the time to the thread.
// These values have empirically been found to yield good behavior.
// Good means that audio performance is high and other threads won't starve.
const double kGuaranteedAudioDutyCycle = 0.75;
const double kMaxAudioDutyCycle = 0.85;
// Define constants determining how much time the audio thread can
// use in a given time quantum. All times are in milliseconds.
// About 128 frames @44.1KHz
const double kTimeQuantum = 2.9;
// Time guaranteed each quantum.
const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum;
// Maximum time each quantum.
const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum;
// Get the conversion factor from milliseconds to absolute time
// which is what the time-constraints call needs.
mach_timebase_info_data_t tb_info;
mach_timebase_info(&tb_info);
double ms_to_abs_time =
((double)tb_info.denom / (double)tb_info.numer) * 1000000;
thread_time_constraint_policy_data_t time_constraints;
time_constraints.period = kTimeQuantum * ms_to_abs_time;
time_constraints.computation = kAudioTimeNeeded * ms_to_abs_time;
time_constraints.constraint = kMaxTimeAllowed * ms_to_abs_time;
time_constraints.preemptible = 0;
result = thread_policy_set(mach_thread_id,
THREAD_TIME_CONSTRAINT_POLICY,
(thread_policy_t)&time_constraints,
THREAD_TIME_CONSTRAINT_POLICY_COUNT);
if(result != KERN_SUCCESS) {
DLog(@"thread_policy_set constraint policy failure: %d", result);
}
}
@implementation Node
- (id)initWithController:(id)c previous:(id)p {
@ -47,8 +129,6 @@ static const os_clockid_t clockId = OS_CLOCK_MACH_ABSOLUTE_TIME;
nodeLossless = NO;
if(@available(macOS 11, *)) {
workgroup = AudioWorkIntervalCreate("Node Work Interval", clockId, attr);
// Get the mach time info.
struct mach_timebase_info timeBaseInfo;
mach_timebase_info(&timeBaseInfo);
@ -131,6 +211,10 @@ static const os_clockid_t clockId = OS_CLOCK_MACH_ABSOLUTE_TIME;
- (void)followWorkgroup {
if(@available(macOS 11, *)) {
if(!wg) {
if(!workgroup) {
workgroup = AudioWorkIntervalCreate([[NSString stringWithFormat:@"%@ Work Interval", [self className]] UTF8String], clockId, &attr);
SetPriorityRealtimeAudio(pthread_mach_thread_np(pthread_self()));
}
wg = workgroup;
if(wg) {
int result = os_workgroup_join(wg, &wgToken);

View File

@ -77,7 +77,6 @@
839366681815923C006DD712 /* CogPluginMulti.m in Sources */ = {isa = PBXBuildFile; fileRef = 839366661815923C006DD712 /* CogPluginMulti.m */; };
8399CF2C27B5D1D5008751F1 /* NSDictionary+Merge.h in Headers */ = {isa = PBXBuildFile; fileRef = 8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */; };
8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */; };
83B69B262845A4B200D2435A /* pffft_double.c in Sources */ = {isa = PBXBuildFile; fileRef = 83B69B252845A4B200D2435A /* pffft_double.c */; };
83B69B752845DF6500D2435A /* pffft_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B6B2845DF6500D2435A /* pffft_double.h */; };
83B69B762845DF6500D2435A /* pf_neon_double_from_avx.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B6D2845DF6500D2435A /* pf_neon_double_from_avx.h */; };
83B69B772845DF6500D2435A /* pf_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B6E2845DF6500D2435A /* pf_double.h */; };
@ -86,6 +85,7 @@
83B69B7A2845DF6500D2435A /* pf_avx_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B712845DF6500D2435A /* pf_avx_double.h */; };
83B69B7B2845DF6500D2435A /* pf_scalar_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B722845DF6500D2435A /* pf_scalar_double.h */; };
83B69B7C2845DF6500D2435A /* pffft_priv_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B69B732845DF6500D2435A /* pffft_priv_impl.h */; };
83D40B572852CB3B003BB85C /* pffft_double.c in Sources */ = {isa = PBXBuildFile; fileRef = 83B69B742845DF6500D2435A /* pffft_double.c */; };
83F18B1E27D1E8EF00385946 /* CDSPHBDownsampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18ADF27D1E8EF00385946 /* CDSPHBDownsampler.h */; };
83F18B3327D1E8EF00385946 /* CDSPSincFilterGen.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18AF827D1E8EF00385946 /* CDSPSincFilterGen.h */; };
83F18B3427D1E8EF00385946 /* r8butil.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18AF927D1E8EF00385946 /* r8butil.h */; };
@ -209,7 +209,6 @@
839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = "<group>"; };
8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = "<group>"; };
8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = "<group>"; };
83B69B252845A4B200D2435A /* pffft_double.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = pffft_double.c; path = "ThirdParty/r8brain-free-src/pffft_double/pffft_double.c"; sourceTree = "<group>"; };
83B69B6B2845DF6500D2435A /* pffft_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pffft_double.h; sourceTree = "<group>"; };
83B69B6D2845DF6500D2435A /* pf_neon_double_from_avx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_neon_double_from_avx.h; sourceTree = "<group>"; };
83B69B6E2845DF6500D2435A /* pf_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_double.h; sourceTree = "<group>"; };
@ -279,7 +278,6 @@
0867D691FE84028FC02AAC07 /* CogAudio */ = {
isa = PBXGroup;
children = (
83B69B252845A4B200D2435A /* pffft_double.c */,
08FB77AEFE84172EC02AAC07 /* Classes */,
32C88DFF0371C24200C91783 /* Other Sources */,
089C1665FE841158C02AAC07 /* Resources */,
@ -698,7 +696,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
83B69B262845A4B200D2435A /* pffft_double.c in Sources */,
83D40B572852CB3B003BB85C /* pffft_double.c in Sources */,
835EDD7B279FE23A001EDCCE /* HeadphoneFilter.mm in Sources */,
17D21CA20B8BE4BA00D1EBDE /* BufferChain.m in Sources */,
17D21CA60B8BE4BA00D1EBDE /* InputNode.m in Sources */,