Skip to content

Continuous Streaming

This application note only applies to the products listed at the top of this page.

Data streams are guaranteed to be continuous in with the AirStack driver, SoapyAIRT. That is, there is no gap in time between the last sample in a buffer and the first sample in the following buffer. The following sections outline best practices to maintain a continuous data stream as well as highlighting conditions to avoid where streams may become discontinuous.

Multiple Channel Synchronization

For streams with multiple channels, there are normally no guarantees that the channels are synchronized. In the general RX case, reception starts at an arbitrary time, with each channel starting immediately after the previous channel. For TX, each channel's buffer is generally transmitted as quickly as possible, with no attempt to transmit each buffer at the same instant in time. With that said, AirStack 1.0 introduced the Time API and the capability to synchronize channels using timestamps. See the Time API Tutorial for details. Users of earlier versions of AirStack can also synchronize RX channels via use of the HW trigger functionality detailed in the Triggering Tutorial.

For RX, it is important to note that the Time API and triggering functionality operate on activateStream(). That is, once a stream is activated at a particular time or due to a triggering signal, all channels in the stream have started signal reception at the exact same instant in time. If there are any stream errors during reception, not only are all channels in the stream no longer synchronized, but the stream of data is no longer continuous. As a result, for RX, any stream error should generally be handled by calling deactivateStream() and then activateStream() to re-synchronize the stream. For TX related considerations, please see the section below entitled "TX Streams".

Changing SDR Settings

There are specific cases where changing a SDR setting will result in discontinuous data.

First, if the sample rate is changed, any active stream that uses the channel affected by the change will be flushed to remove any samples with the old setting applied. As a result, the data stream will no longer be continuous.

Second, all active data streams are no longer continuous if frequency is changed. This occurs because the RF front end must recalibrate for LO suppression and quadrature error correction. Note that this means that changing the TX frequency may affect RX streams and vice-versa.
Overall, the best practice is to change SDR settings/parameters without an active stream (i.e., deactivate all streams, apply the new setting, and then re-active the streams). We perform these steps inside of the AirStack drivers if an active stream is detected while SDR settings are being changed because we recognize that not all applications can control when settings are being manipulated. With that said, we recommend restarting streams manually in the application code itself in order to explicitly document the fact that a discontinuity will occur when the setting is being changed.

TX Streams

For the general case of synchronous I/O1, the SoapyAIRT API expects all TX buffers passed to it to be continuous, unless the SOAPY_SDR_END_BURST flag is set when calling writeStream(). That is, without the flag set, the API expects each buffer to be part of a continuous transmission. The flag being set indicates that a transmission has ended and the API expects there to be a gap in time between the current call to writeStream() and any subsequent data transfer.

Note that, by default, underflow conditions (gaps in time between two buffers we are expecting to be continuous) are not reported by the SoapyAIRT API. This is due to compatibility issues with returning the error code and how other applications handle the error. Users can enable underflow checking/reporting by adding report_tx_underflow=true to the device arguments passed in to the SoapySDR constructor (it is not currently recommended to enable this feature in any GNU Radio applications).

As of AirStack 0.5.3, the transmit buffer can be managed through the SoapySDR API via the tx_buffer_size device/stream argument. We recommend that you set the TX buffer size to the largest transfer size (in number of samples) you expect to make in your application, as transfers larger than this will result in a truncated transfer (i.e., only the number of samples will be transmitted that can fit in the buffer). To set the tx_buffer_size, either set it in the device constructor (e.g., sdr = SoapySDR.Device(dict(driver="SoapyAIRT", tx_buffer_size="4096")) or when setting up a TX stream as shown below.

stream_args = dict(tx_buffer_size="4096")
tx_stream = sdr.setupStream(
    SOAPY_SDR_TX, 
    SOAPY_SDR_CS16,
    channel_indexes,
    stream_args
)

For advanced users interested in eking out every ounce of performance, the tx_buffer_size can be set to zero. This creates a scenario where the buffer passed to writeStream() will be used directly for DMA transfers (aka. "zero copy"). This may or may not result in a performance increase for your application, as a memory copy operation is replaced with a memory mapping operation. Benchmarking calls to writeStream() is recommended to test if and how much performance is improved. This feature can only be enabled for CS16 streams and requires that the buffer passed to writeStream() is aligned to a 128 byte boundary and is a multiple in size of 128 bytes. The best way to ensure such alignment is to use the C++ SoapySDR API and to allocate your TX buffer with std::aligned_alloc().

RX Streams

For RX, samples are continuously read from the device into a kernel buffer. This buffer is sized by default to prevent overflows as much as possible without totally monopolizing memory usage. Advanced users may want to change the size of this kernel buffer for various memory usage and performance considerations. For example, if your application calls for capturing a small number of samples before tuning to a new frequency, you may want to reduce the buffer size to prevent having to allocate a large (mostly unused) buffer. To do so, run the following commands on the AIR-T, with no active SDR applications running. Note that the units for buffer size are number of memory pages, where each page on the AIR-T is 4096 bytes. This setting does not persist between restarts of the AIR-T, but it can be scripted/automated to do so if desired.

sudo rmmod xdma
sudo modprobe xdma rx_buffer_size_pages=512

Errors

Anytime the SoapyAIRT API returns an error code, any currently active data stream can no longer be considered continuous. Different data streams share a common hardware resource, so errors with one stream can affect others. As a result, users may want to restart all streams if an error is encountered. For application compatibility, we do attempt to restart any stream automatically, however it is STRONGLY recommended that users manually restart affected streams to attempt to fix the root cause of the error. Error codes and their possible root causes are shown below:

Error Code Error Name Description
-1 SOAPY_SDR_TIMEOUT Inactive hardware that has not been properly configured. Has the stream not been activated?
-2 SOAPY_SDR_STREAM_ERROR Generic streaming error. See logs for more info.
-3 SOAPY_SDR_CORRUPTION Multiple channels in a single stream are reporting different issues (likely cause is a single channel had an overflow). See kernel logs for specific details.
-4 SOAPY_SDR_OVERFLOW All channels in the stream have overflowed. The application is not reading samples from the radio fast enough.
-5 SOAPY_SDR_NOT_SUPPORTED You are asking the AIR-T to do something that it cannot do.
-6 SOAPY_SDR_TIME_ERROR As of AirStack 0.5 you should not see this error reported by the AIR-T.
-7 SOAPY_SDR_UNDERFLOW See "TX Streams" below. You are not providing samples to the radio fast enough.

Note that SOAPY_SDR_TIMEOUT can also be returned in the event a HW triggered or timed command has been configured, but the triggering event (timestamp or trigger signal rising edge) has not occurred within a specified timeout. If this occurs, please either retry the same API call that returned the error, or increase your timeout accordingly.

Finally, it should be noted that in order to make logging more verbose (especially useful when providing log information / output to customer support) that an environment variable may be set appropriately. This is done by export SOAPY_SDR_LOG_LEVEL=DEBUG in the terminal.


  1. AirStack 1.0 introduced the concept of Asynchronous TX, where a TX buffer is first scheduled via writeStream() and then the status of whether or not the buffer was actually transmitted is reported via readStreamStatus(). See the Transmit Tutorial for details. In terms of continuity, due to how the buffers are scheduled, it is generally very difficult to ensure that two timed or triggered TX buffers occur without any gaps in transmission. As a result, underflows are only reported for synchronous (i.e., not timed or triggered) TX.