Caution: This version of this document is no longer maintained. For the latest documentation, see

Handling PCM Audio Data

In this chapter...

What's a PCM device?

In this architecture, a PCM device is a device capable of supporting either a PCM capture channel, or a PCM playback channel, or both. A PCM capture channel converts an analog signal to a digital PCM stream, whereas a PCM playback channel takes a digital PCM stream and converts it to analog. A PCM device may also support converting multiple PCM streams simultaneously; each of these streams is called a PCM subchannel.

Creating a PCM device

As described earlier, your Audio HW DLL must provide an entry point called ctrl_init(). The Organization of a Driver chapter describes the initialization that this function must do no matter what features your DLL supports.

In much the same way that we created a mixer device in the previous chapter, we now create a PCM device using the ado_pcm_create() function. This informs the upper levels of software that this card now supports a PCM device. If you call this function again, it creates additional devices.


The prototype of the ado_pcm_create() function is:

int32_t ado_pcm_create( ado_card_t *card,
                        char *name,
                        uint32_t flags,
                        char *id,
                        uint32_t play_subchns,
                        ado_pcm_cap_t *play_cap,
                        ado_pcm_hw_t *play_hw,
                        uint32_t cap_subchns,
                        ado_pcm_cap_t *cap_cap,
                        ado_pcm_hw_t *cap_hw,
                        ado_pcm_t **rpcm );

The arguments are:

The card argument that io-audio passed to your Audio HW DLL's ctrl_init() function. The library uses this argument as the card to link the new device onto.
name, id
Text names for the PCM device, usually a variation of the card name. They're used only for information display by client applications.
Information about the device for the use of client applications, for example, indicating whether or not both the playback and capture can be used at the same time. These flags are informational only; enforcing any of these conditions is done by code and isn't based on these flags.
play_subchns, cap_subchns
The number of playback and capture subchannels supported. If zero, the channel isn't supported.
play_cap, cap_cap
Structures listing the full capabilities of the device's playback and capture channels. These include format, rate, and voices supported. The upper driver layers use this information to verify a client request before allowing the request to pass to the hardware. This information is also passed back to the client as the static capabilities of the device.
play_hw, cap_hw
Structures containing the playback and capture callback functions to be called by the upper layers of the driver. It's in these functions that you actually program the hardware. For more information, see How does the PCM stream operate? later in this chapter.
The location in which to store a pointer to the internal PCM device structure. You'll need this pointer for additional PCM function calls.

How does the PCM stream operate?

In order to make the PCM device work, you need to define the callback functions in the ado_pcm_hw_t structures for the capture and playback portions of the PCM device.

Before we look at them in detail, let's first review how a PCM stream operates in hardware. The model used in this architecture is a DMA buffer in memory that's divided into two or more buffer fragments. When instructed to do so, the hardware acts on a fragment using DMA, and then generates an interrupt on completing the fragment.

So, if we consider the simplified case of playback with a 50K buffer, composed of two fragments, here's what happens when the client application sends data:

From a programming perspective, if the hardware can be set up to do a looping DMA buffer playback with an interrupt every x bytes, implementing this model is very straightforward. A variation on this theme is to reprogram the DMA engine after every fragment in the interrupt routine. In the general case, the client suggests the fragment size and number of fragments, but the driver has the ultimate authority on these parameters.