Proposed Enhancements to PortAudio API

003 - Improve Latency Specification Interface

Enhancement Proposals Index, PortAudio Home Page

Updated: September 18, 2003

Status

This proposal is sufficiently well defined to be implemented. It is implemented in the v19-devel branch.

Background

The current mechanism for setting latency is not considered optimal by all clients of the API. There seems to be some tension between using the framesPerBuffer parameter of Pa_OpenStream as a latency control parameter and as a specifier for the number of frames supplied to the callback. Specifying latency as a single millisecond value would be more user friendly for some users, however some host APIs need latency to be tuned by specifying buffer sizes and number of buffers. Additionally, it has been suggested that separate input and output buffer counts would allow tuning of lower latencies in some circumstances.

A related issue is the need to improve the interface available to determine default latency parameters - both for simple applications that just want to use the default values, and for applications that want to give user's the ability to tune latency values starting from sane defaults.

See this thread: http://techweb.rfa.org/pipermail/portaudio/2001-October/000196.html

Proposal

The numBuffers parameter to Pa_OpenStream() will be removed, and replaced by two new parameters suggestedInputLatency and suggestedOutputLatency, both expressed in seconds. These parameters allow clients to fine-tune latency in a portable manner. Where practical, PortAudio implementations should select buffer sizes based on these parameters, otherwise they may choose the closest viable buffer size and latency instead. Unless the suggested latency is greater than the upper limit for the device, PortAudio implementations should aim to round the suggestedLatency up to the next practial value - ie to provide an equal or higher latency than requested whereever possibe.

The following fields will be added to PaDevice info to expose the default input and output latency values for each device. Separate default latency values are provided for (1) interactive applications where low latency is important to useful operation (defaultLowInputLatency, defaultLowOutputLatency), and (2) robust applications where additional latency is acceptable if it will result in more stable performance or place a reduced load on the system (defaultHighInputLatcncy, defaultHighOutputLatency).

typedef double PaTime; /* time expressed in seconds */

typedef struct PaDeviceInfo {
...
    /* Default latency values for interactive performance. */
    PaTime defaultLowInputLatency;
    PaTime defaultLowOutputLatency;
    /* Default latency values for robust non-interactive applications (eg. playing sound files). */
    PaTime defaultHighInputLatency;
    PaTime defaultHighOutputLatency;
...
} PaDeviceInfo;

Currently, when numBuffers is greater than 0, Pa_OpenStream will constrain the actual numBuffers so that the latency is within a valid range determined by the host API, or an environment variables such as PA_MIN_LATENCY_MSEC. Propose changing the behavior so that the requested value is honored whenever possible. This will allow the user to override the recommended minimum latency if they know their system can handle it. This might be used, for example on patched Linux kernels.

A method will be provided to retrieve the actual input and output latency of a stream. The following is proposed: A new PaStreamInfo structure will be defined along with a Pa_GetStreamInfo() function as follows:

/** A structure containing unchanging information about an open stream.
 @see Pa_GetStreamInfo
*/

typedef struct PaStreamInfo
{
    /** this is struct version 1 */
    int structVersion;

    /** The input latency of the stream in seconds. This value provides the most
     accurate estimate of input latency available to the implementation. It may
     differ significantly from the suggestedLatency value passed to Pa_OpenStream().
     The value of this field will be zero (0.) for output-only streams.
     @see PaTime
    */
    PaTime inputLatency;

    /** The output latency of the stream in seconds. This value provides the most
     accurate estimate of output latency available to the implementation. It may
     differ significantly from the suggestedLatency value passed to Pa_OpenStream().
     The value of this field will be zero (0.) for input-only streams.
     @see PaTime
    */
    PaTime outputLatency;

	... (other fields) ...
    
} PaStreamInfo;


/** Retrieve a pointer to a PaStreamInfo structure containing information
 about the specified stream.
 @return A pointer to an immutable PaStreamInfo structure. If the stream
 parameter invalid, or an error is encountered, the function returns NULL.

 @param stream A pointer to an open stream previously created with Pa_OpenStream.

 @note PortAudio manages the memory referenced by the returned pointer,
 the client must not manipulate or free the memory. The pointer is only
 guaranteed to be valid until the specified stream is closed.

 @see PaStreamInfo
*/
const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream );

Where the host API does not provide this information directly, an estimate should be derived from the size and number of buffers used by the host API. For a multi-buffered system, the latency can be calculated as (oneBufferDuration * (numberOfBuffers-1)).

The Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ) function will be removed, as it's functionality is fulfilled by the get recommended latency functions.

In addition to the portable latency setting mechanism described above, implementations may use the inputStreamInfo and outputStreamInfo Pa_OpenStream() parameters to provide host API specific latency setting mechanisms which directly reflect the underlying buffer passing scheme. For example, the MME streamInfo structure could provide a way to directly set the bufferSize and numberOfBuffers parameters for input and output. When streamInfo structures are passed to Pa_OpenStream(), using 0 values for their API specific latency settings PortAudio could use the generic latency parameters.

In cases where Host API specific latency parameters may be limited to certain allowable ranges (buffer sizes in ASIO for example) a method for querying these limits should be provided. This will consist of host-API specific query functions declared in a host-api specific header file. These header files will also contain the declaration of the host API specific driverInfo structure.

Discussion

This proposal provides both a high level mechanism for portable latency tuning, and suggests a method for providing a host-API specific latency tuning interface. Default latency query functions are provided to support simple portable applications, and to provide starting values for applications that present a generic latency tuning user interface.

Originally default latency values were retrieved using query functions listed below, however providing these values in the PaDeviceInfo structure seemed like a more natural fit to the rest of the API.

 
typedef double PaTime; /* time expressed in seconds */

PaTime Pa_GetDefaultLowInputLatency( PaDeviceIndex deviceID ); /* For interactive performance. */
PaTime Pa_GetDefaultHighInputLatency( PaDeviceIndex deviceID ); /* For playing sound files. */
PaTime Pa_GetDefaultLowOutputLatency( PaDeviceIndex deviceID ); /* For interactive performance. */
PaTime Pa_GetDefaultHighOutputLatency( PaDeviceIndex deviceID ); /* For playing sound files. */

Expressing suggestedLatency in frames was considered, but rejected because it makes sense to return the actual latency from Pa_GetStreamInputLatency() in seconds, and being consistent was deemed to be important for clarity and avoiding usage errors.

Initially the following two functions were proposed to retrieve the actual input and output latency of a stream. However, the potential need to retrieve additional information about a stream was identified, and so the PaStreamInfo structure was introduced instead.

/* the following would operate directly on streams */
PaTime Pa_GetStreamInputLatency( PaStream *stream );
PaTime Pa_GetStreamOutputLatency( PaStream *stream );

Impact Analysis

This proposal will require all clients to alter their calls to Pa_OpenStream(). Clients who passed a 0 value for numBuffers will not need to significantly alter their code. Clients who manipulated numBuffers will need to either use the latency parameters, or the host API specific interfaces, as they become available.