Field Guide · concept

Also known as: stream vs message passing, streams and messages

Stream vs message passing is the distinction between the continuous stream of samples that flows between DSP blocks and the asynchronous, self-contained messages used for control and discrete events.1 A stream is the data plane — an unbroken sequence of samples clocked through the flowgraph at the sample rate. A message is the control plane — an occasional, standalone object (a retune command, a decoded packet, a “carrier detected” event) delivered when it happens, not tied to any particular sample instant. Real radios need both, and knowing which to use for a given piece of information is a core design decision in block-based SDR.

block A block B stream: continuous samples messages: occasional events
Streams carry evenly clocked samples (the data plane); messages carry occasional, self-contained events (the control plane) on a separate asynchronous port.

How it works

A stream connection is synchronous and rate-locked. The upstream block produces items and the downstream block consumes them in lockstep with the sample clock; the scheduler meters exactly how many items move each time a block runs. Streams are the right tool for anything that exists at every sample instant: IQ, filtered baseband, a demodulated soft-symbol waveform.

A message connection is asynchronous and decoupled from the clock. A block posts a discrete message object to a named message port; the runtime delivers it to whatever ports are connected, whenever the receiver next services its queue. There is no rate, no per-sample alignment, and no back-pressure in the streaming sense — a message is a whole thing that either arrives or does not. In GNU Radio, messages are PMTs (Polymorphic Types): tagged, serializable containers that can hold a number, a string, a dictionary, or a “PDU” pairing metadata with a vector of bytes.

The dividing line is cardinality and timing. Information that is present continuously and must stay sample-aligned belongs on a stream. Information that is sparse, event-driven, or addressed to the flowgraph as a whole — “tune to 851.0125 MHz,” “here is a completed frame,” “squelch opened” — belongs in a message.

In practice

A middle ground exists: stream tags attach metadata to a specific sample index within a stream, so you get message-like annotation that stays exactly aligned to the data (a burst start, a detected preamble, a rate change). The rule of thumb: use a tag when the metadata must travel with a known sample, and a message when it is independent of any sample — a tag rides the data plane, a message rides the control plane.

Messages also break the strictly-downstream shape of a pure stream flowgraph. Because a message port can connect a downstream block back to an upstream one, a decoder can send a retune request back to the source, or a control GUI can push parameter changes into a running graph — feedback loops that a one-directional sample stream cannot express.

Relevance to SDR

Nearly every real receiver mixes the two: a high-rate sample stream carrying the signal, plus a trickle of control and result messages — retunes, decoded packets, lock/unlock events. Getting the split right keeps the hot DSP path free of branchy control logic and keeps events from being forced awkwardly into a fixed-rate stream.

GopherTrunk is pure Go and does not use GNU Radio’s PMT machinery, but it embodies exactly this separation: the IQ and demodulated symbols move as high-rate streams through the decode chain, while control-channel results — channel grants, talkgroup and radio IDs, tuning decisions for the voice follower — are discrete events passed as Go structs over channels, the language’s native asynchronous message-passing primitive. The framework vocabulary differs, but the data-plane/control-plane split is the same, and recognizing it is what makes a trunking decoder’s architecture legible.

Sources

  1. Message Passing — GNU Radio Wiki, on asynchronous message ports, PMTs, and PDUs versus synchronous stream connections. 

See also