Before you start#
Read ALSA and audio timing and userspace processes and system calls. This chapter studies alsa-lib v1.2.12 as a historical source baseline, separate from Linux v6.12. No exercise opens an audio device or server.
Learning goals#
Distinguish frames, samples and storage widths; trace parameter setup and interleaved PCM writes; explain short transfers and recovery. Identify the configured plugin path before assuming that a userspace PCM handle reaches a kernel device directly.
Source walkthrough#
- Application
Chooses frames, format and stream state
- alsa-lib PCM
Negotiates parameters and dispatches operations
- Configured plugin
May convert, mix, or forward data
- Backend
A kernel device or another audio service
ALSA is both a Linux kernel sound subsystem and a collection of userspace interfaces. alsa-lib exposes a PCM API to applications. A snd_pcm_t is an opaque userspace handle with state and operations selected by the opened PCM implementation. It is not itself a hardware device, sound card, or promise of a particular speaker. A configured name can choose the hardware plugin, a conversion plugin, a software mixer, or a plugin forwarding to an audio server. The actual route depends on installed configuration and plugins. A program that opens the default PCM cannot infer that it has directly opened a kernel device or that its format is the hardware's native format. See alsa-lib v1.2.12 PCM implementation and hardware plugin.
A sample is one channel's value at one sampling instant. A frame contains one sample for every channel at that instant. For stereo, one frame has a left and a right sample. The sample rate counts frames per second per channel, not the total number of channel samples. Interleaved stereo stores the two channel samples of each frame together; non-interleaved access keeps channels in separate areas. A period is a configured processing granularity and a buffer contains multiple frames, often multiple periods. Actual hardware and plugins can choose constraints that differ from a requested value.
At 48,000 frames per second, a 256-frame period lasts 256/48,000 seconds, approximately 5.333 milliseconds. A 1,024-frame ring contains approximately 21.333 milliseconds of audio at that rate. For stereo signed 16-bit samples stored as two bytes per sample, a frame occupies four bytes; a period occupies 1,024 bytes and the full buffer 4,096 bytes. These are capacity and duration calculations, not a measurement of end-to-end latency. Application buffering, server scheduling, hardware, resampling and device processing can add delay. Capture and playback have different directions even if the same arithmetic applies.
Synthetic teaching data, not a device capture.
Select a field to connect its position, bytes, and meaning.
4 bytes in file order. Offsets start at zero.
Selected: left sample, low byte. Synthetic left sample 0x1234, least significant byte first.
Read the byte table
| Offset | Field | Hex bytes | Interpretation |
|---|---|---|---|
| 0 | left sample, low byte | 34 | Synthetic left sample 0x1234, least significant byte first |
| 1 | left sample, high byte | 12 | Most significant byte of the synthetic left sample |
| 2 | right sample, low byte | cd | Synthetic right sample 0xabcd, least significant byte first |
| 3 | right sample, high byte | ab | Most significant byte of the synthetic right sample |
Sample precision and physical storage width are distinct. A 24-bit sample can be packed in three bytes or stored in a four-byte container, depending on the exact ALSA format. Two stereo frames of packed 24-bit audio occupy 12 bytes; two frames using 24 useful bits in 32-bit containers occupy 16. Endianness, significant-bit placement and format names also matter. A buffer allocation using only useful bits while the selected format requires a wider container can underallocate and corrupt memory. Ask the format API for physical width rather than deriving every layout from a human-readable label.
Parameter negotiation and state
A typical client opens a PCM, allocates or initializes hardware-parameter storage, and refines constraints for access mode, sample format, channels, rate, period and buffer size. Hardware parameters are a set of constraints, not always a single immediate assignment: a near-setting operation can return a value different from the request. The application must inspect the negotiated result and decide whether it can produce or consume that format. Software parameters configure behavior such as start and availability thresholds. Applying parameters allocates or configures backend resources and changes PCM state; it can fail if the combination is unsupported or the device disappears.
A common state sequence is open, setup, prepared and running. snd_pcm_prepare prepares the selected backend after setup; playback can start explicitly or automatically once a configured threshold is reached. An application that assumes every write starts playback immediately ignores software threshold and plugin behavior. Closing a handle releases its own resources, but a process sharing an underlying server or kernel object has separate references. A failed open leaves no valid handle to close; a partially initialized later setup needs cleanup of the resources actually acquired.
- SND_PCM_STATE_OPEN
Handle exists; no setup has been installed yet.
- SND_PCM_STATE_SETUP
Hardware and software parameters are installed.
- SND_PCM_STATE_PREPARED
Ready to start; playback begins later by threshold or an explicit start.
- SND_PCM_STATE_RUNNING
Frames are being transferred.
- SND_PCM_STATE_XRUN
Stopped after an underrun or overrun; recovery is needed.
- SND_PCM_STATE_DISCONNECTED
Hardware is disconnected; stop using the handle.
From alsa-lib v1.2.12 src/pcm/pcm.c, snd_pcm_prepare, LGPL-2.1-or-later as stated in the file; earlier setup checks and the return are omitted:
snd_pcm_lock(pcm->fast_op_arg);
if (pcm->fast_ops->prepare)
err = pcm->fast_ops->prepare(pcm->fast_op_arg);
else
err = -ENOSYS;
snd_pcm_unlock(pcm->fast_op_arg);
The handle's fast-operation argument is locked under the library's configured locking rules. The selected implementation's prepare callback is invoked when present; otherwise the function reports an unsupported operation. The final statement unlocks that argument. The surrounding function first checks that the PCM has been set up and that its state permits the operation. This is dispatch through the selected PCM implementation, not an unconditional direct ioctl on a hardware device. The source also qualifies thread safety by build option; an application still needs its own synchronization for shared buffers and state.
Interleaved write and a short result
For interleaved access, snd_pcm_writei takes a buffer and a count in frames. A positive return can be smaller than the requested count. The caller must advance by returned frames times bytes per frame, retain the remaining data, and retry when appropriate. Zero progress and a negative error require different handling from successful completion. A nonblocking stream can return -EAGAIN; waiting for readiness and retrying is preferable to a busy loop. For capture, snd_pcm_readi fills available frames and the application must only process the returned amount.
From snd_pcm_writei in the same pinned file and license; earlier setup validation and later function end are omitted:
if (pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED) {
SNDMSG("invalid access type %s", snd_pcm_access_name(pcm->access));
return -EINVAL;
}
The condition checks the negotiated access type. A mismatch is logged and returns -EINVAL; the function does not reinterpret a non-interleaved buffer as interleaved. The surrounding path also validates setup and a runnable state before dispatching to _snd_pcm_writei. The hardware plugin's operations ultimately use its own kernel-facing implementation, while a plugin chain can transform or forward the write. The Linux v6.12 PCM userspace ABI is relevant to a hardware path, not evidence that all plugin paths issue the same ioctl.
For a concrete example, an application has 256 stereo S16 frames, or 1,024 bytes. It requests a write of 256 frames and receives 96. The accepted portion is 96 times four, or 384 bytes; 160 frames and 640 bytes remain. The next pointer begins 384 bytes later. If the next call returns -EAGAIN, no additional frames are accepted by that call; wait and retry the same remaining region. If it returns 160, all requested data has been accepted by this PCM interface. Acceptance does not prove that the listener heard it or that it reached hardware without conversion.
Readiness, mmap and errors
An application can obtain poll descriptors from the PCM and later ask snd_pcm_poll_descriptors_revents to interpret the returned events. The library's translated result matters because plugins may use several descriptors or translate backend readiness; simply assuming one raw fd's POLLOUT means a certain amount of audio space can be wrong. Poll readiness can become stale before the next operation, so callers still handle short transfers and errors. A disconnect or POLLERR needs an error path, not an infinite readiness loop.
Memory-mapped PCM access uses a begin/commit protocol. The application obtains channel areas and a frame offset and count, accesses only the granted region in the negotiated layout, then commits the frames it actually handled. An area includes address and bit offsets/steps, so a generic client must not assume every layout is a flat byte array. A successful mmap setup does not make the buffer permanently owned by the application or allow it to ignore ring wraparound. The plugin may emulate or transform mmap access. A failed or short commit must be accounted for before advancing application state.
An xrun is an underrun during playback or an overrun during capture: producer and consumer have fallen out of the required timing relationship. The API commonly reports -EPIPE. snd_pcm_recover handles selected errors; in the inspected v1.2.12 source, it calls prepare for -EPIPE and returns an error if prepare fails. For -ESTRPIPE it attempts resume, waits while resume returns -EAGAIN, and can fall back to prepare. This helper can wait, so it is not appropriate to call blindly in a hard real-time callback. -EINTR and other errors have their own paths; not every error is recoverable. See the recover implementation.
In src/pcm/pcm.c, alsa-lib v1.2.12, LGPL-2.1-or-later as stated in the file (the -ESTRPIPE branch and the rest of the function are omitted):
int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent)
{
if (err > 0)
err = -err;
if (err == -EINTR) /* nothing to do, continue */
return 0;
if (err == -EPIPE) {
const char *s;
if (snd_pcm_stream(pcm) == SND_PCM_STREAM_PLAYBACK)
s = "underrun";
else
s = "overrun";
if (!silent)
SNDERR("%s occurred", s);
err = snd_pcm_prepare(pcm);
if (err < 0) {
SNDERR("cannot recovery from %s, prepare failed: %s", s, snd_strerror(err));
return err;
}
return 0;
}
The -EPIPE branch calls snd_pcm_prepare and reports a failed prepare as a recovery error. The excerpt does not show what prepare discards from queued audio, so it does not decide whether frames are replayed, dropped or left as a gap; those are application timing decisions.
Suppose the 160 remaining frames in the earlier example encounter -EPIPE. The application records that stream continuity was lost, calls an appropriate recovery method outside a time-critical path, checks success, and refills or restarts according to its policy. It cannot assume those 160 frames were played exactly once; queued audio might have been discarded by prepare. It may need to reset timestamps, notify the user, or drop stale audio. If recovery returns an error or the device disconnects, close the handle and report failure or deliberately reopen under application policy. Reopening is not guaranteed to select the same physical device.
Plugins, policy and lifetime
The hardware PCM plugin is one possible backend. plug can adapt format, rate or channel layout when configured and supported; rate provides rate conversion; dmix can combine compatible streams; file can record or play data through files under its configuration. Availability, constraints, conversion quality and exact topology depend on the installed alsa-lib and plugins. PipeWire or PulseAudio compatibility plugins can forward PCM operations to a server. A successful application-level parameter negotiation can therefore conceal a different device-side rate or format. This is useful for compatibility but makes it essential to state where a measurement was taken.
Use Case Manager, or UCM, describes policy for hardware use cases such as device routing and mixer control for supported cards. It is not a replacement for the kernel driver, nor a guarantee that every desktop uses UCM in the same way. A session manager may interpret UCM data when choosing profiles and routes. Changing a PCM sample format does not necessarily select a hardware route, and a successful route change does not prove a PCM is prepared. These are separate control and data paths.
A snd_pcm_t and its configuration objects have explicit allocation and close/free contracts. Do not close a PCM concurrently with a worker that can still call its operations unless the application establishes a safe shutdown protocol. Stop submissions, coordinate with the worker, handle pending callback or poll activity, then close the handle. A plugin may own additional descriptors and shared state that it releases through its close operation. Reference counts or internal locks do not automatically preserve an application's separate sample buffer after its owner frees it.
A device can disappear between readiness notification and write. A handle can transition to an error or disconnected state; the caller must stop using stale state and release resources. A configuration route to a server can also fail when the server disconnects, even though no physical device was unplugged. Conversely a server can change its device while an application retains the same logical PCM. These distinctions make a single name such as default inadequate as a hardware identity.
Negotiated values and discontinuities
Consider a request for stereo at 48 kHz where a particular backend supports only 44.1 kHz. An exact rate constraint can fail during refinement or application of parameters. A near-rate operation may instead return an available value, but the application must inspect that returned rate and either adapt or reject it. Treating the original requested number as the negotiated rate makes all subsequent timing and byte-position calculations misleading. A conversion plugin can produce a different result from a direct hardware path because it adds capabilities upstream of the device; which path was opened is essential evidence.
A capture overrun has a different data consequence from a playback underrun. In capture, previously produced input can be lost before the application reads it. After recovery, a later frame does not fill the missing interval merely because it occupies the next position in a userspace array. Record discontinuity in the application's timing model and decide whether to discard, insert silence or notify downstream processing. In playback, resending old data after prepare may repeat sound, while dropping it may introduce a gap. The right policy depends on the application's semantics, not on the mere fact that recovery returned zero.
Practice#
This private arithmetic model performs no ALSA calls and opens no device:
python3 - <<'PY'
rate, channels, bytes_per_sample = 48000, 2, 2
period, buffer = 256, 1024
accepted = 96
print('period ms:', round(1000 * period / rate, 3))
print('period bytes:', period * channels * bytes_per_sample)
print('remaining frames:', period - accepted)
print('remaining bytes:', (period - accepted) * channels * bytes_per_sample)
assert buffer * channels * bytes_per_sample == 4096
PY
Expected values are 5.333 ms, 1,024 bytes, 160 frames and 640 bytes. A counterexample is computing 256 bytes by treating frames as bytes; that omits both channels and physical sample width. As a source-only alternative, follow the supplied 256-to-96-to-160 sequence and the -EPIPE recovery path: write down the buffer offset and state before and after each call, including prepare failure. This is arithmetic and source reasoning, not PCM execution or an audio measurement.
Knowledge check#
- How many bytes are in 256 stereo S16 frames?
- Why might the application's PCM rate differ from the physical device rate?
- What does the shown
snd_pcm_writeiaccess check reject? - Does a positive short write mean the remaining frames were accepted?
- Why does successful xrun recovery not establish gapless playback?
Answers#
- Each frame has two two-byte samples, so 256 frames occupy 1,024 bytes.
- A configured plugin or server can perform conversion. The negotiated application-side format is not necessarily the hardware format.
- It rejects a handle whose negotiated access mode is not interleaved, returning
-EINVALrather than guessing the layout. - No. The returned value is the accepted frame count; the caller retains and retries the remainder as appropriate.
- An underrun already broke continuity, and prepare can discard or reset queued state. The application needs an explicit restart and timing policy.
Checkpoint#
Submit the frame/byte table, short-write pointer calculation and xrun failure path. Pass when units are correct, plugin and hardware routes are distinguished, and recovery failure leads to cleanup rather than a claim of successful playback. If not, revisit the write and prepare excerpts and repeat the arithmetic. This is practice, not certification.
Sources#
Read alsa-lib PCM implementation, alsa-lib hardware plugin, Linux PCM ABI, and alsa-lib PCM API documentation. No sound device or audio service was opened.