libsdr
0.1.0
A simple SDR library
|
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.
The following examples shows a trivial application that recods some audio from the systems default audio source and play it back.
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.
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.
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.
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.
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.
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, 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
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.