Also known as: DSP buffer, inter-block buffer, sample buffer
A sample buffer is the shared memory region between two DSP blocks that one block writes samples into and the next reads out of.1 It is the edge of a flowgraph made concrete: every connection between blocks is backed by a buffer, and that buffer is what lets a producer and a consumer run at slightly different speeds without either losing data. Buffers are where the practical realities of streaming SDR live — flow control, back-pressure, latency, and the overruns and underruns that show up when the rates diverge too far.
How it works
A sample buffer is a fixed-size region with a write pointer (where the producer appends) and a read pointer (where the consumer takes from). The producer may only write up to the free space; the consumer may only read up to what has been written. The difference between the pointers is the fill level — the amount of queued, not-yet-consumed data.
Its purpose is decoupling. Real DSP stages do not run in perfect lockstep: a block runs in bursts as the scheduler gives it CPU, a hardware source delivers samples in USB transfers, a consumer occasionally stalls on I/O. The buffer absorbs that jitter so short-term speed differences do not stall or starve the neighbours. Two failure modes bound it:
- Overrun — the producer wants to write but the buffer is full because the consumer is too slow. Something must give: block the producer (back-pressure) or drop samples.
- Underrun — the consumer wants to read but the buffer is empty because the producer is too slow. The consumer stalls or emits a gap.
Which behaviour is chosen distinguishes the two regimes. Between software blocks, a full buffer usually blocks the producer — back-pressure — so no data is lost; throughput just settles to the slowest stage. At a real-time hardware boundary the source cannot be paused, so a full buffer means dropped samples: an overrun.
In practice
Most inter-block buffers are ring buffers — circular arrays where the pointers wrap around, giving continuous streaming without ever copying data back to the front. GNU Radio goes further with a double-mapped (“vmcircbuf”) trick: the same physical pages are mapped twice, back to back, so a wrapped read still looks contiguous and blocks can process across the wrap with no special-casing — a form of zero-copy streaming.
Buffer size is the central tuning knob and a direct throughput-versus-latency trade. Larger buffers tolerate more jitter and amortize per-call overhead (higher throughput) but hold more samples in flight, raising latency. Smaller buffers cut latency but schedule more often and are less forgiving of a momentary stall. Real-time receive from live hardware forces the issue: the buffer must be large enough that the consumer never falls irrecoverably behind, or samples are lost at the antenna.
Relevance to SDR
Buffers are unavoidable in streaming SDR — any two stages running at their own pace need one between them — so nearly every SDR performance question routes through buffer behaviour. “Why am I getting overruns” is a buffer question; “why is my latency high” is often a buffer-size question; “why does one slow block wedge the whole chain” is back-pressure through a buffer.
GopherTrunk is a real pure-Go SDR application and depends on exactly these mechanics: buffered Go channels and slices carry IQ and symbols between its decode stages, the Go runtime provides back-pressure when a channel fills, and the radio-source boundary is where overruns manifest if the decode chain can’t keep up — the same failure the ring buffer and overrun concepts describe. GopherTrunk’s decode chain is also rate-invariant at its core: it normalizes to a fixed per-protocol channel rate, so buffer sizing is governed by that steady output rate rather than by whatever the capture rate happened to be. Understanding sample buffers is thus directly load- bearing for reasoning about GopherTrunk’s real-time behaviour, not just GNU Radio’s.
Sources
-
Circular buffer — Wikipedia, on the producer/consumer ring-buffer structure with wrapping read and write pointers used to decouple DSP stages. ↩