libsdr  0.1.0
A simple SDR library
A C++ library for software defined radio (SDR).

libsdr is a simple C++ library allowing to assemble software defined radio (SDR) applications easily. The library is a collection of (mostly) template classes implementing a wide varity of procssing nodes. By connecting these processing nodes, a stream-processing chain is constructed which turns raw input data into something meaningful.

Please note, I have written this library for my own amusement and to learn something about SDR. If you search for a more complete and efficient SDR library, consider GNU radio.

A processing node is either a sdr::Source, sdr::Sink, both or may provide one or more sources or sinks. A source is always connected to a sink. Another important object is the sdr::Queue. It is a singleton class that orchestrates the processing of the data. It may request further data from the sources once all present data has been processed. It also routes the date from the sources to the sinks.

A practical introduction

The following examples shows a trivial application that recods some audio from the systems default audio source and play it back.

#include <libsdr/sdr.hh>
int main(int argc, char *argv[]) {
// Initialize PortAudio system
// Create an audio source using PortAudio
sdr::PortSource<int16_t> source(44.1e3);
// Create an audio sink using PortAudio
// Connect them
source.connect(&sink);
// Read new data from audio sink if queue is idle
// Get and start queue
// Wait for queue to stop
// Terminate PortAudio system
// done.
return 0;
}

First, the PortAudio system gets initialized.

Then, the audio source is constructed. The argument specifies the sample rate in Hz. Here a sample rate of 44100 Hz is used. The template argument of sdr::PortSource specifies the input type. Here a signed 16bit integer is used. The audio source will have only one channel (mono).

The second node is the audio (playback) sink, which takes no arguments. It gets configured once the source is connected to the sink with source.connect(&sink).

In a next step, the sources next method gets connected to the "idle" signal of the queue. This is necessary as the audio source does not send data by its own. Whenever the next method gets called, the source will send a certain amount of captured data to the connected sinks. Some nodes will send data to the connected sinks without the need to explicit triggering. The sdr::PortSource node, however, needs that explicit triggering. The "idle" event gets emitted once the queue gets empty, means whenever all data has been processes (here, played back).

As mentioned aboce, the queue is a singleton class. Means that for every process, there is exactly one instance of the queue. This singleton instance is accessed by calling the static method sdr::Queue::get. By calling sdr::Queue::start, the queue is started in a separate thread. This threads is responsible for all the processing of the data which allows to perform other tasks in the main thread, i.e. GUI stuff. A call to sdr::Queue::wait, will wait for the processing thread to exit, which will never happen in that particular example.

The queue can be stopped by calling the sdr::Queue::stop method. This can be implemented for this example by the means of process signals.

#include <signal.h>
...
static void __sigint_handler(int signo) {
// On SIGINT -> stop queue properly
}
int main(int argc, char *argv[]) {
// Register signal handler
signal(SIGINT, __sigint_handler);
...
}

Whenever a SIGINT is send to the process, i.e. by pressing CTRL+C, the sdr::Queue::stop method gets called. This will cause the processing thread of the queue to exit and the call to sdr::Queue::wait to return.

Queue less operation

Sometimes, the queue is simply not needed. This is particularily the case if the data processing can happen in the main thread, i.e. if there is not GUI. The example above can be implemented without the Queue, as the main thread is just waiting for the processing thread to exit.

#include <libsdr/sdr.hh>
int main(int argc, char *argv[]) {
// Initialize PortAudio system
// Create an audio source using PortAudio
sdr::PortSource<int16_t> source(44.1e3);
// Create an audio sink using PortAudio
// Connect them directly
source.connect(&sink, true);
// Loop to infinity7
while(true) { source.next(); }
// Terminate PortAudio system
// done.
return 0;
}

The major difference between the first example and this one, is the way how the nodes are connected. The sdr::Source::connect method takes an optional argument specifying wheter the source is connected directly to the sink or not. If false (the default) is specified, the data of the source will be send to the Queue first. In a direct connection (passing true), the source will send the data directly to the sink, bypassing the queue.

Instead of starting the processing thread of the queue, here the main thread is doing all the work by calling the next mehtod of the audio source.

Log messages

During configuration and operation, processing nodes will send log messages of different levels (DEBUG, INFO, WARNING, ERROR), which allow to debug the operation of the complete processing chain. These log messages are passed around using the build-in sdr::Logger class. To make them visible, a log handler must be installed.

int main(int argc, char *argv[]) {
...
// Install the log handler...
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
...
}

Like the sdr::Queue, the logger is also a singleton object, which can be obtained by sdr::Logger::get. By calling sdr::Logger::addHandler, a new message handler is installed. In this example, a sdr::StreamLogHandler instance is installed, which serializes the log messages into std::cerr.

In summary

In summary, the complete example above using the queue including a singal handler to properly terminate the application by SIGINT and a log handler will look like

#include <libsdr/sdr.hh>
#include <signal.h>
static void __sigint_handler(int signo) {
// On SIGINT -> stop queue properly
}
int main(int argc, char *argv[]) {
// Register signal handler
signal(SIGINT, __sigint_handler);
// Initialize PortAudio system
// Create an audio source using PortAudio
sdr::PortSource<int16_t> source(44.1e3);
// Create an audio sink using PortAudio
// Connect them
source.connect(&sink);
// Read new data from audio sink if queue is idle
// Get and start queue
// Wait for queue to stop
// Terminate PortAudio system
// done.
return 0;
}

This may appear quiet bloated for such a simple application. I designed the library to be rather explicit. No feature is implicit and hidden from the user. This turns simple examples like the one above quite bloated but it is imediately clear how the example works whithout any knowledge of "hidden features" and the complexity does not suddenly increases for non-trivial examples.

Finally, we may have a look at a more relaistic example implementing a FM broadcast receiver using a RTL2832 based USB dongle as the input source.

#include <libsdr/sdr.hh>
#include <signal.h>
static void __sigint_handler(int signo) {
// On SIGINT -> stop queue properly
}
int main(int argc, char *argv[]) {
// Register signal handler
signal(SIGINT, __sigint_handler);
// Initialize PortAudio system
// Frequency of the FM station (in Hz)
double freq = 100e6;
// Create a RTL2832 input node
sdr::RTLSource src(freq);
// Filter 100kHz around the center frequency (0) with an 16th order FIR filter and
// subsample the result to a sample rate of approx. 100kHz.
sdr::IQBaseBand<int8_t> baseband(0, 100e3, 16, 0, 100e3);
// FM demodulator, takes a complex int8_t stream and returns a real int16_t stream
// Deemphesize the result (actually part of the demodulation)
// Playback the final signal
// Connect signals
src.connect(&baseband, true);
baseband.connect(&demod);
demod.connect(&deemph);
deemph.connect(&sink);
// Connect start and stop signals of Queue to RTL2832 source
// Start queue
// Wait for queue
// Terminate PortAudio system
// Done...
return 0;
}