Guidelines for avoiding audio streaming errors.
This page provides an overview of best practices for avoiding streaming errors and achieving good performance for audio processing on real-time threads.
These recommendations are based on observations we have made when reviewing common Pro Tools streaming errors, especially those caused by plug-ins, as well as on information we have gathered from a number of partners and other experts in the field.
- See also
- Plug-In Causes Audio Streaming Errors
Things NOT To Do In An Audio Plug-In Render Callback
- No unbounded calls/loops
- No access to paged memory or files
- No system calls
- No memory allocations or deallocations
- No exceptions
- No locks (priority inversions)
- No data races
- Avoid context switches
- No Objective-C or Swift code. These can incur system calls and take locks - see this article for more information.
- Do not use the JUCE callAsync() function - it is not real-time safe. As an alternative, you can use a separate dedicated thread that wakes on a timer or the AAX TimerWakeup() method to handle non-real time work.
- Never perform PACE license checks in audio processing thread; always add code annotations to prevent license checks in any code which will be executed from the audio processing callback.
Things To Do In An Audio Plug-In Render Callback
-
If passing data, always use lock-free FIFOs
-
When making data from other parts of the plug-in available to its real-time callback you should always use the AAX packet system; this will ensure thread safety, proper timing of the data delivery with respect to the audio being processed, and optimal real-time thread performance.
-
There is a nice reference implementation for a general-purpose FIFO in the farbot project
-
Lock-free FIFOs are also good for passing data from the real-time thread to a low-priority thread in order to do heavy lifting like writes to disk
-
If sharing small amounts of data (<= 8 bytes), use atomics
-
Make a local copy/cache of any atomic values that need to be read multiple times from your render function
-
When using atomics, always make the compiler prove that its implementation is lock-free e.g. using a static_assert that std::atomic<T>::is_always_lock_free
-
When sharing larger data, if it is acceptable if the data sometimes cannot be accessed, use a try_lock() in the real-time thread and a lock() in any non-real-time threads.
-
When using this strategy you should use a spin lock, not a std::mutex; std::mutex::unlock() can block in a system call to wake the waiting thread
-
When sharing larger data, if it is acceptable to access a stale copy of the data, then use a compare-and-exchange loop
-
Be careful about memory leaks with this strategy
-
See the NonRealtimeMutable template in the farbot project
Good Resources And Examples