|mp4-sa-> sfront reference manual-> audio drivers|
Audio Driver Functionsasys_osetup  asys_isetup  asys_iosetup  asys_preamble  asys_putbuf  asys_getbuf  asys_main  asys_orun   asys_iorun  ksyncinit  ksync  asys_oshutdown  asys_ishutdown  asys_ioshutdown
In this chapter, we describe how to add support for new audio file formats and audio hardware devices to sfront. Adding a new format or device involves writing an audio driver that is included in the C file sfront creates.
Users select the new audio driver through the
We begin the chapter by discussing the structure of an audio driver. We describe the functions that an audio driver declares to handle initialization, data movement, and synchronization.
We also describe how to register a new driver with sfront, so that a command-line invocation results in the inclusion of the audio driver into the sa.c file.
An audio driver is a file containing a set of C functions. This file is embedded into the sfront executable during compilation, and is copied into the C program sfront creates. Look in sfront/src/lib/asys/ to see examples of audio driver files.
The right panel shows the set of functions contained in an audio driver. In this chapter, we describe the semantics for these functions.
Not all audio drivers use all of the functions listed on the right hand panel. Several factors determine the subset of functions that are in use:
Audio drivers must be careful when defining functions, variables, and pre-processor symbols for its own use, to avoid name-space collisions.
Audio drivers may define elements that begin with asysn_drivername_  and ASYS_DRIVERNAME_ , where drivername is the name of the audio driver file (sans extension) located in sfront/src/lib/asys/
If ASYS_KSYNC exists, elements that begin with sync_  and SYNC_  are also permissible.
In addition, if ASYS_HASOUTPUT exists, elements that begin with asyso_ or ASYSO_ are permissible. If ASYS_HASINPUT exists, elements that begin with asysi_ or ASYSI_ are permissible.
If both ASYS_HASINPUT and ASYS_HASOUTPUT exist, elements that begin with asysio_ or ASYSIO_ are permissible.
Audio Driver Functions
(function arguments not shown) Initialization int asys_osetup(); int asys_isetup(); int asys_iosetup(); Passive Audio Output int asys_preamble(); int asys_putbuf(); Passive Audio Input int asys_getbuf(); Active Drivers /* declared by driver */ void asys_main(); /* called by driver */ int asys_orun(); int asys_iorun(); Synchronization int ksyncinit(); int ksync(); Shutdown int asys_oshutdown(); int asys_ishutdown(); int asys_ioshutdown();
Users select audio input and output drivers through the
Depending on the selection, a particular audio driver may be required to supply audio input, audio output, or both audio input and audio output.
An audio driver determines its role in an sa.c file by detecting if certain pre-processor symbols have been defined, using the pre-processor utilities  ifdef  or  defined .
Specifically, if both ASYS_HASINPUT and ASYS_HASOUTPUT symbols are defined, an audio driver should perform audio input and audio output. If only one of these symbols is defined, the audio driver should only perform audio input (ASYS_HASINPUT defined) or audio output (ASYS_HASOUTPUT defined).
An audio driver declares an initialization function, which is called at the start of program execution. The exact form of the initialization function depends on the status of ASYS_HASINPUT and ASYS_HASOUTPUT symbols; see the right panel for details.
The initialization function should perform the initial setup needed for the audio file type or the audio hardware. If this setup is successful, the function should return ASYS_DONE, a pre-defined constant. If driver setup failed, the function should return ASYS_ERROR.
The arguments passed by the initialization function describe the audio input and output environment; the argument list may include:
The exact argument list depends on the function type.
Audio data types
The audio driver states its data type preference as part of the registration procedure. This preference is coded in the osample and isample variables, which may take on the pre-defined constant values ASYS_SHORT (for 16-bit signed integers) or ASYS_FLOAT (for 32-bit floats).
The pre-defined constant ASYS_OTYPENAME has the same value as osample, and the pre-defined constant ASYS_ITYPENAME has the same value as isample.
The correct C keywords for the input and output datatypes are held in the symbols ASYS_OTYPE and ASYS_ITYPE, and may be used in variable declarations.
Driver identity symbols
In some situations, an audio driver may need to know the presence of other drivers active in the system. The symbols ASYS_OUTDRIVER_XX, ASYS_INDRIVER_YY, and CSYS_CDRIVER_ZZ are defined if an audio output driver, audio input driver, or a control driver is present in the system.
The actual strings for XX, YY, and ZZ are the fully-capitalized versions of the driver names (either the full symbol following -cin, -ain, and -aout, or the extension for filename drivers).
An audio driver may define command-line options. Users types these options when executing an sa.c program that contains the driver, to dynamically configure driver parameters (for example, setting a maximum file size).
The right panel describes audio driver parameters in detail.
The audio driver declares one of three functions for initialization, depending on user requests. This function will be called once, at the start of program execution. If the user requests only output driver service, the symbol ASYS_HASOUTPUT is defined, and the driver should declare: int asys_osetup(int srate, int ochannels, int osample, char * oname, int toption) If the user requests only input driver service, the symbol ASYS_HASINPUT is defined, and the driver should declare: int asys_isetup(int srate, int ichannels, int isample, char * iname, int toption) If the user requests both input and output driver service, both ASYS_HASINPUT and ASYS_HASOUTPUT are defined, and the driver should declare: int asys_iosetup(int srate, int ichannels, int ochannels, int isample, int osample, char * iname, char * oname, int toption) These functions should return ASYS_DONE if the driver is able to operate, and ASYS_ERROR if the initialization failed. See left panel for description of function parameters.
Audio Driver Parameters
An audio driver may parse the command line of its sa.c file it is contained in, by accessing: int asys_argc; char ** asys_argv An audio driver may define its own command-line flags, so that users can configure drivers at runtime. A driver must use this convention: -asys_xxx[_yyy] [p1 p2] where: xxx is the driver name yyy is the option name (if needed) p# is a parameter for the option. options can have zero, one, or more than one parameters. a parameter can not start with a - (except as the sign of a number) and may not contain spaces. For example, the linux driver could define: -asys_linux_size p1 as an option for setting the size of the output data, where -asys_linux_size 8bit -asys_linux_size 16bit are the legal parameter values.
In this section, we describe the functions asys_preamble and asys_putbuf (see right panel for function definitions). Passive audio drivers define these functions to handle audio output.
A passive audio driver does not always define the audio output functions: in some cases, the user may only wish to perform audio input. Passive audio drivers should define the audio output functions only if the symbol ASYS_HASOUTPUT is defined.
The asys_preamble function is called once, at the start of audio playback. The audio driver uses this call to allocate a buffer for audio data, whose location and size it returns to the calling program.
The first call to asys_putbuf returns this buffer to the audio driver, filled with audio sample data. The audio driver outputs the audio data, and returns a memory buffer to be filled with more samples. This cycle repeats until the program ends.
An audio output driver that controls a soundcard device should output a period of silence to the soundcard during the asys_preamble call. The silent period provides an extra margin of time that sfront may use to compute new sound samples without overruning the soundcard. The silent period also sets the latency of the system.
The appropriate length to choose for the silence period depends on the application. Interactive applications demand latencies of a few milliseconds; audio playback applications work best with several hundred milliseconds of latency.
The pre-defined symbol ASYS_LATENCYTYPE indicates the application type, taking on values ASYS_HIGHLATENCY (audio streaming) or ASYS_LOWLATENCY (real-time interaction).
Audio drivers may use the application type to determine an appropriate latency value, or may use the suggested latency value ASYS_LATENCY, which is a floating-point constant with units of seconds. If the pre-defined symbol ASYS_USERLATENCY is set to 1, the value of ASYS_LATENCY is a user suggestion; if ASYS_USERLATENCY is set to 0, the value is a system default value.
Passive audio output drivers set the nominal buffer size of an audio output operation, by choosing the return value of *osize in the asys_preamble and asys_putbuf functions.
In a real-time system, the buffer size should be set to be a small integral fraction of the starting silence period. For example, if a silent period of 3 ms is chosen, a good choice for the buffer size is the amount of storage needed to hold 0.75 ms of audio. This approach maximizes the efficiency of sfront execution, while minimizing the risk that a single late buffer can overrun the system.
If an sa.c program is set up as a real-time system, the penalty for computing audio buffers too slowly is obvious: if the interval between asys_putbuf calls is too long, the audio buffer may underrun, producing clicks and glitches.
However, computing audio buffers too quickly also brings problems. For interactive applications, computing ahead too quickly increases the latency between user action and sound output. For streaming applications, computing too far ahead results in too much memory usage to hold the future audio. To avoid these problems, real-time audio drivers should take steps to limit the compute-ahead of the program.
One way to limit the compute-ahead is to allocate a fixed number of buffers during the asys_preamble call, whose combined audio time equals the starting silence period. Buffers from this pool are passed to asys_putbuf to be filled, then passed to the soundcard to be played, which returns them once the audio plays out. If no free buffers are available to return to asys_putbuf, the audio driver sleeps until the soundcard returns a buffer.
This scheme limits the compute-ahead of the program, without the use of explicit timekeeping; it works best with soundcard interfaces such as OSS which maintain fixed buffer pools internally.
An alternative way to limit the compute-ahead is to explicitly keep track of elapsed time inside the audio driver, using operating system time functions. Audio drivers that use this approach may find it easiest to perform compute-ahead monitoring in the ksync function, described in a later section of this chapter.
int asys_preamble( ASYS_OTYPE * asys_obuf int * osize ) Called once, at the start of audio playback. When called, *asys_obuf will be set to NULL, and *osize will be set to 0. On return, *asys_obuf must point to an array of ASYS_OTYPE, and *osize must be set to the number of elements in the array to be filled with audio. The *osize value must be evenly divisible by the number of audio output channels, and must not be zero. Return value should be ASYS_DONE if preamble succeeded, ASYS_ERROR to terminate the calling program.
int asys_putbuf( ASYS_OTYPE * asys_obuf int * osize ) asys_obuf points to an array of audio samples to send to the audio output. It is guaranteed to be filled on channel-contiguous boundaries. asys_obuf points to the buffer returned by asys_putbuf() on the previous call, or for the first call to asys_putbuf(), the buffer returned by sys_preamble(). In most cases, *osize will be identical to the requested *osize from the last call. In some cases (such as the end of the orchestra) it may be smaller (but never greater). On return, *asys_obuf must point to an array of ASYS_OTYPEs, and *osize must be set to the number of elements in the array to be filled with audio. The *osize value must be divisible by the number of audio output channels, and must not be zero. Return value should be ASYS_DONE if preamble succeeded, ASYS_ERROR to terminate the calling program.
The pre-defined constant ASYS_TIMEOPTION codes the temporal mode of the program. It may reflect the presence of the -render, -playback, or -timesync option on the sfront command line; if these flags do not appear on the command line, sfront chooses a default value. The -render option requests off-line file processing: ASYS_TIMEOPTION has the value ASYS_RENDER. The -playback and -timesync options requests real-time processing: ASYS_TIMEOPTION takes on the value ASYS_PLAYBACK or ASYS_TIMESYNC in this case. If an audio output driver that interfaces to a soundcard finds that -render is in effect, it is probably a user error: the program may ignore the request, or may force an exit via an ASYS_ERROR return during initialization.
Passive audio input drivers route audio input data from a sound file or audio device into the input_bus of a SAOL program.
The function asys_getbuf embodies the passive audio input driver. See the right panel for a complete description of this function.
An audio driver should only define the asys_getbuf function if the ASYS_HASINPUT symbol is defined, indicating the driver was selected via the -ain sfront command-line argument.
As described on the right panel, the calling program does not request a specific number of audio samples, and does not provide a buffer to fill.
Instead, the audio input driver is free to create an input buffering scheme that works well with the underlying file format or hardware device.
If an audio input driver supports a real-time input device, the asys_getbuf may be called before new input samples are ready.
In this case, the audio input driver should not return control to the calling program until the new samples are ready. If possible, the driver should block while waiting for new data, so that other processes may run on the machine.
Note that by holding control in the fashion, the audio input driver acts to prevent the SAOL program from computing ahead too far.
As a result, real-time passive audio drivers usually do not need to provide explicit compute-ahead protection if both ASYS_HASINPUT and ASYS_HASOUTPUT are defined.
int asys_getbuf( ASYS_ITYPE * asys_ibuf, int * isize ); The function asys_getbuf is called when the sa.c program needs new input_bus samples in order to continue SAOL processing. On the first call to asys_getbuf, *asys_ibuf will be set to NULL and *isize will be set to 0. Upon return, *asys_ibuf should point to a buffer of *isize audio samples, filled with data from the input device. ASYS_ITYPE is is the sample type indicated by isample during initialization. On subsequent calls to asys_getbuf, *asys_ibuf and *isize hold the values provided in the previous call. Upon return, *asys_ibuf should point to a buffer of fresh audio samples, of size *isize. The contents of the buffer that asys_getbuf should not be changed by the audio driver until the next call to asys_getbuf. An *isize return value of 0 indicates EOF. asys_getbuf() should return ASYS_DONE if it was possible to process the request, including the EOF case. ASYS_ERROR should be returned for non-EOF error conditions (filesystem error, etc).
The passive audio driver concept is not a good match for a soundcard interface that uses callback semantics, such as MacOS X coreaudio.
In a callback architecture, applications supply a callback function to the soundcard interface as part of the initialization process. The soundcard interface invokes the callback function whenever an audio data transfer is needed.
To support callback semantics, sfront provides an active driver interface as an alternative to normal passive drivers. An audio driver identifies itself as passive or active as part of the registration procedure.
Users specify active drivers using the same
An active driver may be selected for input while a passive driver is selected for output, or vice versa; in addition, the same active driver type may be specified as the input and the output driver. However, two different different active drivers may not be selected for use at the same.
Active output drivers declare the single function asys_main, shown on the right panel. This function is called after the SAOL program is initialized, and after all asys_setup functions are called.
Once asys_main is called, it is the responsibility of the active audio driver to control SAOL program execution; the driver exercises this control by calling a service function to compute audio samples. The audio driver exits the asys_main function once the SAOL program completes execution.
We first discuss active audio drivers that interact with SAOL programs that do not use the input_bus. In this case, the symbol ASYS_ACTIVE_O is defined, and the service function asys_orun is provided to compute new audio samples. See the right panel for a full description of asys_orun.
To support a callback system, an active audio driver would define a callback function for the soundcard interface to call. This callback function would in turn call asys_orun to generate the required number of audio samples on demand.
In this approach, the asys_main function would go to sleep once the callback function was installed. The callback function would awaken the asys_main function once the SAOL program ended, so that the asys_main function could return control to the calling program. The callback function becomes aware of the end of the SAOL program by checking the return value of the asys_orun function, which returns ASYS_EXIT once the program ends.
void asys_main(void) This function is called after SAOL program initialization is complete, and after all asys_setup functions are called. When asys_main returns, asys_shutdown functions are called, and the program exits.
int asys_orun( ASYS_OTYPE obuf, int * osize) asys_orun is a service function, provided for use by the active audio driver if the SAOL program does not use the input_bus. This function exists if the symbol ASYS_ACTIVE_O exists. asys_orun takes the sample buffer pointer obuf as an argument, writes at most the next *osize channel- interleaved sample values into the buffer, and returns. The *osize value received by asys_orun must correspond to an integral number of audio sample periods. The return value of *osize is the actual number of ASYS_OTYPE samples written into obuf. If the SAOL program is still running, asys_orun returns ASYS_DONE; if the SAOL program has finished, asys_orun returns ASYS_EXIT.
SAOL programs that use input_bus
If the SAOL program uses the input_bus, sfront provides the asys_iorun service function for active drivers to use (see right panel for details). This function accepts two audio buffers: an input array to route to input_bus, and an output array to hold generated output_bus samples.
An active audio driver senses the presence of the asys_iorun function by testing to see if the symbol ASYS_ACTIVE_IO is defined. If ASYS_ACTIVE_IO is defined, ASYS_ACTIVE_O is not defined, and asys_orun is not available.
If an active audio driver is expected to provide audio input and audio output from its device, the symbols ASYS_HASINPUT and ASYS_HASOUTPUT are defined. In this case, the driver simply passes the audio input to asys_iorun as it becomes available, and routes audio output to its device each time asys_iorun returns.
However, if ASYS_HASINPUT or ASYS_HASOUTPUT is not defined, the user has specified a passive driver for input or output. In this case, the active driver calls the passive driver functions, in keeping with the semantics of the audio passive driver interface.
For example, if ASYS_HASINPUT is not defined, the active audio driver calls the asys_getbuf function, and passes the buffer returned by this function to asys_iorun.
If ASYS_HASOUTPUT is not defined, the active audio driver calls asys_preamble to initialize the passive audio output driver, and then uses asys_putbuf to process the audio buffers returned by asys_iorun.
Basic information about the passive driver, such as channel size and sample data format, may be discovered by examining pre-defined constants such as ASYS_OCHAN and ASYS_OTYPE. See the initialization section for a description of these constants.
int asys_iorun( ASYS_ITYPE ibuf, int * isize, ASYS_OTYPE obuf, int * osize) asys_iorun is a service function, provided for use by the active audio driver if the SAOL program uses the input_bus. This function exists if the symbol ASYS_ACTIVE_IO exists. asys_iorun takes two audio sample buffers as input. The buffer pointer ibuf supplies audio input data to route to the SAOL input_bus. it contains *isize samples, which must correspond to an integral number of sample periods. asys_iorun also takes the sample buffer pointer obuf as an argument. it writes, at most, the next *osize channel-interleaved sample values into the buffer, and returns. asys_iorun handles any combination of *isize and *osize buffers well -- it computes new audio samples until ibuf is exhausted, obus is fill, or the SAOL program ends. If the SAOL program is still running, asys_iorun returns ASYS_DONE; if the SAOL program has finished, asys_iorun returns ASYS_EXIT.
SAOL programs may read the k-rate standard name cputime, to sense the real-time performance of the program. This floating-point variables has a range of 0.0 to 1.0, and codes CPU utilization as in percentage terms. A cputime value of 1.0 indicates the edge of real-time performance: any additional load may cause a buffer overrun.
Audio drivers can take responsibility for estimating the cputime value seen by the SAOL program. A driver requests this task as part of the registration procedure; if this request is granted, the audio driver finds the symbol ASYS_KSYNC defined. See the right panel for details about the selection algorithm sfront uses for cputime duties.
If ASYS_KSYNC is defined, the audio driver defines the ksync and ksyncinit functions (see right panel for details).
The ksync function is called once per execution cycle, after all audio samples have been computed. The return value of ksync is the new estimate of cputime. The ksyncinit function is called once, right before SAOL program execution begins.
To compute cputime, audio drivers estimate the real time used to compute the audio samples, and normalize by the total amount of audio generated during the cycle. The predefined floating-point constant KTIME indicates the length of audio generated each execution cycle, in units of seconds.
Audio drivers may need to actively limit the length of audio compute-ahead during program execution (we discuss compute-ahead issues in detail in an earlier section of this chapter). The ksync function is a convenient place to limit compute-head time.
To limit compute-ahead, the audio driver compares the elapsed real time with the computer audio time during each ksync invocation, and sleeps (or less preferably, spins) if the compute-ahead time exceeds the desired latency of the system.
A user may explicitly request this type of active compute-head limit, by using the -timesync sfront command-line option. If this option is selected, the constant ASYS_TIMEOPTION has the value ASYS_TIMESYNC.
Sfront allocates cputime calculation chores using the following algorithm. If offline render mode is in effect (ASYS_TIMEOPTION set to ASYS_RENDER), the cputime is always set to 0, and no audio driver is assigned cputime duties. Elsewise, the audio output driver has first priority for cputime calculation, the audio input driver has second priority, and a default UNIX-only algorithm for cputime calculation is used if neither audio driver requests cputime duties.
void ksyncinit(void) This function is called at the start of SAOL program execution. Audio drivers may use this call to initialize global state variables. Audio driver state variables and functions related to synchronization should use names that begin with sync_
float ksync(void) This function is called at the end of the audio output creation phase of each execution cycle. The return value for this function is the cputime value presented to the SAOL program during the next execution cycle. It should be a number in the range [0.0, 1.0]
An audio driver declares a shutdown function, which is called at the end of SAOL program execution. Audio drivers use the shutdown function to cleanly close audio files and hardware devices.
The name of the shutdown function depends on the status of ASYS_HASOUTPUT and ASYS_HASINPUT, as described on the right panel.
The audio driver declares one of three functions for shutdown, depending on its role as an input driver, output driver, or input and output driver. If the user requests only output driver service, the symbol ASYS_HASOUTPUT is defined, and the driver should declare: void asys_oshutdown(void) If the user requests only input driver service, the symbol ASYS_HASINPUT is defined, and the driver should declare: void asys_ishutdown(void) If the user requests both input and output driver service, both ASYS_HASINPUT and ASYS_HASOUTPUT are defined, and the driver should declare: void asys_ioshutdown(void) These functions should do an orderly shutdown of the audio hardware or file processing operations.
The right panel shows how to register your audio driver with the sfront sources. Registration is necessary in order for sfront to add your control driver flag to the permissible arguments to the -ain and -aout command line options.
Step 1: Create Libraries
 Place your audio driver foo in sfront/src/lib/asys/mydriver.c  cd sfront/src/lib  Edit Makefile, and add name of your control driver to the ASYS list.  Type "make". This will create: sfront/src/asyslib.c sfront/src/asyslib.h which includes an embedded version of your driver. Search in the files for your driver name to verify.  Whenever you change your driver code in sfront/src/lib/asys you will need to remake the asyslib.c file.
Step 2: Edit sfront/src/audio.c
In sfront/src/audio.c, make these additions, to add driver "mydriver".  Add the constant definition #define ADRIVER_MYDRIVER top the top of the file. Give it the current numerical value of ADRIVER_END, and then increase the value of ADRIVER_END by 1.  Add a printf line to the function void printaudiohelp(void) that describes the mydriver flags. This will be printed out when "sfront -help" is invoked.  Add if statements to the aoutfilecheck and ainfilecheck functions, to register the driver. If the driver is an active driver, add the lines aoutflow = ACTIVE_FLOW; to the aoutfilecheck addition and add ainflow = ACTIVE_FLOW; to the ainfilecheck addition. Also in ainfilecheck, set ainlatency to indicate interactive input (like a microphone): ainlatency = LOW_LATENCY_DRIVER; or streaming input (like a fileread): ainlatency = HIGH_LATENCY_DRIVER;  Add an entry for your driver to: makeaudiotypeout makeaudiotypein makeaoutsync makeainsync makeaouttimedefault makeaintimedefault makeainparams if needed.  Add your driver to: void makeaudiodriver(int num) case DRIVER_MYDRIVER: makemydriver(); break; This call actually does the code insertion into the sa.c file: it calls the function created in asyslib.c from your driver file, which is named makemydriver.
Step 3: Compile sfront
Type "make" in sfront/src to compile sfront with your driver. During driver development, you should edit your original driver source in sfront/src/lib/asys/mydriver.c then to test first: cd sfront/src/lib/ make and then: cd sfront/src make
For now, this is the end of the sfront reference manual. Soon, I hope to add new chapters describing sfront and sa.c internals.
Return to Table of Contents.
|mp4-sa-> sfront reference manual-> adding drivers|
Copyright 1999 John Lazzaro and John Wawrzynek.