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
sdr::PortAudio::init();
// Create an audio source using PortAudio
sdr::PortSource<int16_t> source(44.1e3);
// Create an audio sink using PortAudio
sdr::PortSink sink;
// Connect them
source.connect(&sink);
// Read new data from audio sink if queue is idle
sdr::Queue::get().addIdle(&source, &sdr::PortSource<int16_t>::next);
// Get and start queue
sdr::Queue::get().start();
// Wait for queue to stop
sdr::Queue::get().wait();
// Terminate PortAudio system
sdr::PortAudio::terminate();
// 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
sdr::Queue::get().stop();
}
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
sdr::PortAudio::init();
// Create an audio source using PortAudio
sdr::PortSource<int16_t> source(44.1e3);
// Create an audio sink using PortAudio
sdr::PortSink sink;
// Connect them directly
source.connect(&sink, true);
// Loop to infinity7
while(true) { source.next(); }
// Terminate PortAudio system
sdr::PortAudio::terminate();
// 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...
sdr::Logger::get().addHandler(
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
sdr::Queue::get().stop();
}
int main(int argc, char *argv[]) {
// Register signal handler
signal(SIGINT, __sigint_handler);
// Initialize PortAudio system
sdr::PortAudio::init();
// Create an audio source using PortAudio
sdr::PortSource<int16_t> source(44.1e3);
// Create an audio sink using PortAudio
sdr::PortSink sink;
// Connect them
source.connect(&sink);
// Read new data from audio sink if queue is idle
sdr::Queue::get().addIdle(&source, &sdr::PortSource<int16_t>::next);
// Get and start queue
sdr::Queue::get().start();
// Wait for queue to stop
sdr::Queue::get().wait();
// Terminate PortAudio system
sdr::PortAudio::terminate();
// 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
sdr::Queue::get().stop();
}
int main(int argc, char *argv[]) {
// Register signal handler
signal(SIGINT, __sigint_handler);
// Initialize PortAudio system
sdr::PortAudio::init();
// 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
sdr::FMDemod<int8_t, int16_t> demod;
// Deemphesize the result (actually part of the demodulation)
sdr::FMDeemph<int16_t> deemph;
// Playback the final signal
sdr::PortSink sink;
// 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
sdr::Queue::get().addStart(&src, &sdr::RTLSource::start);
sdr::Queue::get().addStop(&src, &sdr::RTLSource::stop);
// Start queue
sdr::Queue::get().start();
// Wait for queue
sdr::Queue::get().wait();
// Terminate PortAudio system
sdr::PortAudio::terminate();
// Done...
return 0;
}