diff --git a/Audio/Chain/Node.m b/Audio/Chain/Node.m index ec87734b3..d02953569 100644 --- a/Audio/Chain/Node.m +++ b/Audio/Chain/Node.m @@ -13,10 +13,12 @@ #import "OutputCoreAudio.h" +#import + #import -// 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); diff --git a/Audio/CogAudio.xcodeproj/project.pbxproj b/Audio/CogAudio.xcodeproj/project.pbxproj index 9123faabb..f892a6251 100644 --- a/Audio/CogAudio.xcodeproj/project.pbxproj +++ b/Audio/CogAudio.xcodeproj/project.pbxproj @@ -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 = ""; }; 8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = ""; }; 8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = ""; }; - 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 = ""; }; 83B69B6B2845DF6500D2435A /* pffft_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pffft_double.h; sourceTree = ""; }; 83B69B6D2845DF6500D2435A /* pf_neon_double_from_avx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_neon_double_from_avx.h; sourceTree = ""; }; 83B69B6E2845DF6500D2435A /* pf_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_double.h; sourceTree = ""; }; @@ -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 */,