Also known as: sound output, audio playback, soundcard output
Audio output is the final stage of a receive chain that hands demodulated audio to the computer’s sound card so a human can hear it. Its two jobs are to convert the audio to the sample rate the device actually runs at — commonly 48 kHz — and to keep the device’s playback buffer continuously fed, because a sound card that runs out of samples produces an audible gap or click called an underrun.1 Cross-platform toolkits like PortAudio and Linux’s ALSA provide the interface between application code and the audio hardware.
How it works
Sound cards run on their own clock and demand a fixed number of samples at a fixed rate. The application does not push audio whenever it likes; instead the audio library invokes a callback (or drains a queue) at regular intervals, asking for the next block of samples right now. The application’s job is to always have that block ready.
Two problems must be solved to keep the block ready:
- Rate conversion. The decoder rarely produces audio at the card’s rate — a voice codec might output 8 kHz while the device wants 48 kHz — so a resampler converts between them. The ratio must be exact and continuous; a wrong or drifting ratio slowly empties or overflows the buffer.
- Buffering. The decoder produces audio in bursts (a whole voice frame at once, then nothing), while the card consumes it in a steady trickle. A ring buffer sits between them: the decoder writes when it has data, the callback reads a fixed amount each time, and the buffer absorbs the mismatch.
If the callback asks for samples and the buffer is empty, that is an underrun (ALSA calls it an xrun): the card plays silence or repeats stale data, producing a click or dropout.2 The mirror problem, an overrun, occurs on the input side when a producer outpaces a consumer — both are covered under overruns and underruns.
In practice
Buffer size is the central tuning knob and a direct latency-versus-safety trade. A large buffer rarely underruns but adds delay between a transmission and hearing it; a small buffer is responsive but underruns the moment the CPU is briefly busy. Real systems pick the smallest buffer that survives normal scheduling jitter, often a few tens of milliseconds.
Because the callback runs on a time-critical audio thread, the golden rule is to do no slow or blocking work inside it — no file I/O, no lock that a slow thread holds, no memory allocation that might stall. The callback should only copy ready samples out of the ring buffer; all the heavy DSP happens on other threads. Sample-rate clock drift between the SDR’s clock and the sound card’s clock is a subtle long-run issue, handled by occasionally nudging the resampler ratio or dropping and inserting samples so the buffer neither drains nor overflows over minutes of playback.
PortAudio abstracts all of this across Windows, macOS, and Linux behind one callback API; on Linux, ALSA is the kernel-level interface PortAudio (and other layers like PulseAudio or PipeWire) sit on top of.
Relevance to SDR
Audio output is the payoff stage of any voice-receiving SDR — a scanner, a ham transceiver front-end, an air-band monitor — and getting the resample-and-buffer plumbing right is the difference between clean speech and a stream of clicks. The same producer/consumer, ring-buffer, callback pattern recurs throughout SDR at the device boundary, so audio output is a compact, audible lesson in real-time I/O discipline.
GopherTrunk decodes trunked voice, and its decode chain already normalizes to fixed per-protocol channel rates, so feeding a sound card is a resample-to-48-kHz-plus-ring-buffer problem of exactly this shape. GT is a pure-Go application and does not embed a GNU Radio audio stack; it interfaces with the platform’s audio through Go bindings to the same underlying PortAudio/ALSA facilities, and applies the standard discipline — keep the callback lightweight, size the buffer to survive scheduling jitter, and correct slow clock drift — to avoid underruns during live monitoring.
Sources
-
PortAudio — Wikipedia, on the cross-platform audio I/O library and its callback-driven streaming model. ↩
-
Advanced Linux Sound Architecture — Wikipedia, on the Linux kernel audio interface and buffer under/overrun (xrun) behavior. ↩