Skip to content

Tips_Callbacks

Wiki converter edited this page Sep 24, 2020 · 3 revisions

Tips - Callbacks

What is permissible in a PortAudio callback function?

(This page is a summary of discussions from the mailing list.)

Audio callbacks are often executed at a very high priority. In general the only things that should be happening in the callback routine are math and data movement. One should avoid:

  • memory allocation
  • file or device I/O
  • crossing language boundaries such as calling Java or Lua
  • calling graphics routines or other OS services

If you need to gather input from MIDI, keyboards, a mouse or files then do that in a foreground thread and feed it into the audio callback via a command or data FIFO. If the audio callback needs to produce any output such as log files or graphics then that information should be put into an in-memory FIFO that is serviced by another foreground thread.

  • Avoid acquiring locks which could be held by lower priority threads. This can cause priority inversion. http://en.wikipedia.org/wiki/Priority_inversion

  • The execution time of your callback function must be guaranteed to be less than the callback framesPerBuffer duration. It could become time-unbounded due to acquiring locks (directly or inside OS API calls), waiting for something unbounded (like disk access or graphics card access) or performing computationally unbounded operations such as unbounded garbage collection sweeps, anything significant with time complexity over O(1) and variable working set size.

The above two points are basic principles of real-time programming. Most of the rest of the advice we are giving derives from these two points. Very little third-party code you will encounter on a desktop OS can give you the above guarantees because desktop OSes are generally not real-time OSes. There is plenty of code out there which uses safe O(1) algorithms, bounded time incremental GC, special memory allocators, but you need to research these things and not assume you are using them (you probably aren't) because simplicity and performance trump real-time operation for most desktop OS tasks.

On some platforms (i.e. OS X) Apple has explicitly made stipulations about what you can and can't do in the callback because it runs in a special real-time thread (they're based on what I said above: basically, anything at the BSD level or higher is unsafe, some Mach stuff might be OK). On other platforms, if you set the latency high enough, you might be able to get a way with calling a lot of unkosher stuff -- but if you start up Microsoft Office while you're doing audio you may get a surprise. From a portability point of view, in principle, on a target OS the callback could run in a special IOProc, interrupt or some other context where regular OS calls are not even available.

PortAudio often uses a very high priority thread for the audio callback -- it's usually not appropriate to make some calls on such a thread (forgetting real-time appropriateness) such as GUI drawing, because this will reduce the overall responsiveness of your machine and potentially starve out other tasks.

Transferring data to/from your application

Transferring data between PortAudio and your application is typically accomplished using a ring buffer. A buffer is set up to hold approximately one-half second of audio data. During the callback function PortAudio reads data from or writes data to this buffer and keeps track of how much data has been read or written. A separate thread containing a timer "wakes up" at intervals of approximately 10 to 100 milliseconds and reads or writes this data from/to disk, performs graphics operations, memory allocation, etc. which involve calls to the operating system. The program pa_ringbuffer.c, available with the PortAudio source code, can be used for this purpose.

If you are simply capturing audio and writing it to disk or reading audio from disk and playing it to a sound card or audio interface, the blocking interface is suitable. If you are also doing low-latency processing or monitoring then you should use the callback interface. In the callback, read or write your data from/to a ring buffer and then use another thread to perform the file I/O.

Regarding crossing language boundaries such as calling Java or Lua

In general it should be avoided. But, in fact, Lua has a bounded time GC (except in rare circumstances involving huge tables, and that's well documented) so, like the Supercollider language, it could be used in a PortAudio callback so long as you make sure it doesn't violate the points I made above: i.e. avoid calling the OS level memory allocator, file I/O, or doing other things which violate the above in your Lua user functions. (Lua AV used to run audio processing in the callback). That said, running Lua in a PortAudio callback is definitely at the experimental end of the spectrum.

Clone this wiki locally