Also known as: push vs pull API, callback vs blocking read
Callback vs stream API is the choice between two ways an SDR driver hands samples to your
program: a callback (push) model, where the driver calls a function you registered each time
a buffer of samples is ready, versus a stream (pull) model, where your code calls a
blocking read-style function to fetch the next samples when it wants them.1 Every
SDR library exposes one, the other, or both, and the
pattern you build around shapes threading, buffering, and how you handle overruns.
How it works
In the callback model you register a function and start the stream; the driver spawns (or borrows) a thread that, each time a USB or network buffer of samples arrives, invokes your function with a pointer to that buffer. Control is inverted — the driver decides when your code runs, and it runs in the driver’s thread context. The iron rule is that a callback must return fast: it runs on the thread that also has to service the next buffer, so any heavy DSP done inline stalls the driver and causes an overrun. The correct pattern is to copy or hand off the samples into a ring buffer and return immediately, letting your own worker threads do the real processing.
In the stream model you own the loop. Your thread calls a blocking readStream /
read_samples function that returns the next N samples (or blocks until they are available),
and you process them and loop. Control stays with your code, which many people find easier to
reason about — it reads like ordinary sequential I/O — and it composes naturally with a
threading model you design yourself. The driver still buffers internally, and if your loop is
too slow to keep calling read, that buffer overflows and you drop samples, so the same
overrun discipline applies, just detected at the read call instead of inside a callback.
Key differences that drive the design:
- Who controls timing. Callback: the driver. Stream: your loop. This decides where back-pressure and pacing live.
- Thread context. Callback code runs on the driver’s thread and must be short and non-blocking; stream code runs on your own thread and may block.
- Error surfacing. Streams return an overrun status from the read call; callbacks usually signal it out-of-band, so you must watch for it.
- Composability. A stream API drops cleanly into a language’s normal blocking-I/O and concurrency idioms; a callback often needs an adapter to bridge into them.
In practice
The two are easy to convert between, and robust programs usually normalize to one internal
model. A common bridge is to wrap a callback API in a small shim that pushes each delivered
buffer into a ring buffer, then expose a blocking read on the other side — turning push into
pull so the rest of the application sees a clean stream. This is exactly why libraries differ:
librtlsdr is fundamentally callback-driven (rtlsdr_read_async calls
your function per buffer), while SoapySDR presents a unified
readStream pull interface across many devices. Whichever the hardware offers, the goal is the
same — get samples off the driver’s thread and into your own buffered pipeline before doing
real work, so a slow DSP stage applies back-pressure or drops
deliberately rather than corrupting the driver’s timing.
Relevance to SDR
This choice is the first thing you meet when writing SDR software, because it is the seam between the vendor driver and your DSP. GopherTrunk faces it at every source: some device back-ends are callback-based and others stream-based, so it adapts each into a consistent internal sample-stream feeding its decode chain, and its file/replay source is naturally a pull stream. Framing this honestly, the pattern is not GopherTrunk-specific — it is universal to SDR applications, and understanding it explains why nearly every real receiver puts a ring buffer immediately behind the source: it decouples the driver’s delivery model from the program’s processing model, so a callback that must return in microseconds and a decode loop that may pause for milliseconds can coexist without dropping samples.
Sources
-
Callback (computer programming) — Wikipedia, on inversion of control and registered functions invoked by a driver or framework. ↩