Phil Burk wrote:
> Callbacks have the advantage of being able to run in the same thread as
> the lower level audio processing. These threads are often at a high
> priority. If the audio processing is done in the callback then it can
> reduce latency.
>
> Blocking I/O involves passing data between threads and switching
> contexts. Context switches can be pretty quick but are non-trivial.
>
> Phil Bur
I think there's more to the story. There can be many levels of
abstraction in operating systems and applications. The interface between
abstraction layers can be based on blocking or callbacks. If a layer
offers the same flavor of interface (callback or blocking) as the lower
layer it is built on, then that will generally be simpler than the case
where the flavors differ.
To build a blocking I/O interface based on callbacks from a lower level,
the lower-level callback will have to signal the blocked thread when
data is available, and a context switch must occur to resume the blocked
thread. Data may also have to be copied from the callback to a buffer
area for use by the blocked thread. This is the case Phil described.
On the other hand, to build a callback interface over a lower level that
is blocking, you need to create a thread that will block until data is
ready, then call the callback function. This may not represent extra
work: if the callback actually runs the application's code, then it may
not matter whether the work is done by the callback interface or by a
thread created by the application. I think this is the basis for
claiming callbacks are more efficient, lower latency, etc.
However, if there's a layer at some higher level of abstraction that
offers a blocking interface, then inserting a callback interface at some
intermediate abstraction layer will cause extra context switches,
buffering, and latency.
Here's a real example: Suppose your application is written in Python
which cannot run asynchronous callbacks and therefore must use a
blocking interface. And suppose that the low-level system is ALSA, where
the interface is basically a blocking write. Then if PortAudio is an
intermediate abstraction layer, using a callback interface is going to
create a thread that calls the application for samples and writes them
to ALSA, but this thread will have to pull samples from a buffer filled
by Python, so there's an extra buffer and an extra context switch
between the PortAudio thread and the Python thread. On the other hand,
if the PortAudio layer uses a blocking interface, no intermediate thread
is created, and the Python thread will deliver data all the way to where
ALSA makes a kernel call to write the data.
I think there's a symmetry argument here: if callbacks go all the way up
from the the kernel to the application and process samples there, things
are efficient. Similarly, if the application thread computes samples and
calls through blocking interfaces all the way down to the kernel, you
have similar efficiency.
An added complication is that most operating systems have blocking
interfaces but they may be hidden by callback-based libraries. Also,
Jack is based on blocking interfaces at the implementation level, but
the libraries that clients are "supposed" to use are callback based and
do not offer direct access to the native blocking I/O protocol (except
through the open source code). Context switching issues are going to
look a lot different when everyone is running 4 or maybe 1024 processing
cores.
One final point is that callbacks give libraries more power over the
scheduling of audio processing threads, which is generally a good thing
if the application is actually able to use the callback thread to do its
work.
_______________________________________________
Portaudio mailing list
Portaudio@...
http://techweb.rfa.org/mailman/listinfo/portaudio