GNU Radio 3.7.1 C++ API
|
Welcome to GNU Radio!
For details about GNU Radio and using it, please see the main project page.
Other information about the project and discussion about GNU Radio, software radio, and communication theory in general can be found at the GNU Radio blog.
See the Build Instructions and Information page for details about the project's dependencies and build process.
GNU Radio uses discrete signal processing blocks that are connected together to perform your signal processing application. This manual contain a list of all GNU Radio C++ Blocks.
Please note that at this time, we haven't found an acceptable way to provide unified documentation for the C++ parts of the system and the parts written in Python (mostly hierarchical blocks). Until this gets worked out, please bear with us, or better yet, solve it for us!
More details on packages in GNU Radio:
More details on GNU Radio concepts:
The basic data structure in GNU Radio is the flowgraph, which represents the connections of the blocks through which a continuous stream of samples flows. The concept of a flowgraph is an acyclic directional graph with one or more source blocks (to insert samples into the flowgraph), one or more sink blocks (to terminate or export samples from the flowgraph), and any signal processing blocks in between.
A program must at least create a GNU Radio 'top_block', which represents the top-most structure of the flowgraph. The top blocks provide the overall control and hold methods such as 'start,' 'stop,' and 'wait.'
The general construction of a GNU Radio application is to create a gr_top_block, instantiate the blocks, connect the blocks together, and then start the gr_top_block. The following program shows how this is done. A single source and sink are used with a FIR filter between them.
from gnuradio import gr, filter, analog class my_topblock(gr.top_block): def __init__(self): gr.top_block.__init__(self) amp = 1 taps = filter.firdes.low_pass(1, 1, 0.1, 0.01) self.src = analog.noise_source_c(gr.GR_GAUSSIAN, amp) self.flt = filter.fir_filter_ccf(1, taps) self.snk = blocks.null_sink(gr.sizeof_gr_complex) self.connect(self.src, self.flt, self.snk) if __name__ == "__main__": tb = my_topblock() tb.start() tb.wait()
The 'tb.start()' starts the data flowing through the flowgraph while the 'tb.wait()' is the equivalent of a thread's 'join' operation and blocks until the gr_top_block is done.
An alternative to using the 'start' and 'wait' methods, a 'run' method is also provided for convenience that is a blocking start call; equivalent to the above 'start' followed by a 'wait.'
By default, GNU Radio runs a scheduler that attempts to optimize throughput. Using a dynamic scheduler, blocks in a flowgraph pass chunks of items from sources to sinks. The sizes of these chunks will vary depending on the speed of processing. For each block, the number of items is can process is dependent on how much space it has in its output buffer(s) and how many items are available on the input buffer(s).
The consequence of this is that often a block may be called with a very large number of items to process (several thousand). In terms of speed, this is efficient since now the majority of the processing time is taken up with processing samples. Smaller chunks mean more calls into the scheduler to retrieve more data. The downside to this is that it can lead to large latency while a block is processing a large chunk of data.
To combat this problem, the gr_top_block can be passed a limit on the number of output items a block will ever receive. A block may get less than this number, but never more, and so it serves as an upper limit to the latency any block will exhibit. By limiting the number of items per call to a block, though, we increase the overhead of the scheduler, and so reduce the overall efficiency of the application.
To set the maximum number of output items, we pass a value into the 'start' or 'run' method of the gr_top_block:
tb.start(1000) tb.wait() or tb.run(1000)
Using this method, we place a global restriction on the size of items to all blocks. Each block, though, has the ability to overwrite this with its own limit. Using the 'set_max_noutput_items(m)' method for an individual block will overwrite the global setting. For example, in the following code, the global setting is 1000 items max, except for the FIR filter, which can receive up to 2000 items.
tb.flt.set_max_noutput_items(2000) tb.run(1000)
In some situations, you might actually want to restrict the size of the buffer itself. This can help to prevent a buffer who is blocked for data from just increasing the amount of items in its buffer, which will then cause an increased latency for new samples. You can set the size of an output buffer for each output port for every block.
WARNING: This is an advanced feature in GNU Radio and should not be used without a full understanding of this concept as explained below.
To set the output buffer size of a block, you simply call:
tb.blk0.set_max_output_buffer(2000) tb.blk1.set_max_output_buffer(1, 2000) tb.start() print tb.blk1.max_output_buffer(0) print tb.blk1.max_output_buffer(1)
In the above example, all ports of blk0 are set to a buffer size of 2000 in _items_ (not bytes), and blk1 only sets the size for output port 1, any and all other ports use the default. The third and fourth lines just print out the buffer sizes for ports 0 and 1 of blk1. This is done after start() is called because the values are updated based on what is actually allocated to the block's buffers.
NOTES:
1. Buffer length assignment is done once at runtime (i.e., when run() or start() is called). So to set the max buffer lengths, the set_max_output_buffer calls must be done before this.
2. Once the flowgraph is started, the buffer lengths for a block are set and cannot be dynamically changed, even during a lock()/unlock(). If you need to change the buffer size, you will have to delete the block and rebuild it, and therefore must disconnect and reconnect the blocks.
3. This can affect throughput. Large buffers are designed to improve the efficiency and speed of the program at the expense of latency. Limiting the size of the buffer may decrease performance.
4. The real buffer size is actually based on a minimum granularity of the system. Typically, this is a page size, which is typically 4096 bytes. This means that any buffer size that is specified with this command will get rounded up to the nearest granularity (e.g., page) size. When calling max_output_buffer(port) after the flowgraph is started, you will get how many items were actually allocated in the buffer, which may be different than what was initially specified.
It is possible to reconfigure the flowgraph at runtime. The reconfiguration is meant for changes in the flowgraph structure, not individual parameter settings of the blocks. For example, changing the constant in a gr::blocks::add_const_cc block can be done while the flowgraph is running using the 'set_k(k)' method.
Reconfiguration is done by locking the flowgraph, which stops it from running and processing data, performing the reconfiguration, and then restarting the graph by unlocking it.
The following example code shows a graph that first adds two gr::analog::noise_source_c blocks and then replaces the gr::blocks::add_cc block with a gr::blocks::sub_cc block to then subtract the sources.
from gnuradio import gr, analog, blocks import time class mytb(gr.top_block): def __init__(self): gr.top_block.__init__(self) self.src0 = analog.noise_source_c(analog.GR_GAUSSIAN, 1) self.src1 = analog.noise_source_c(analog.GR_GAUSSIAN, 1) self.add = blocks.add_cc() self.sub = blocks.sub_cc() self.head = blocks.head(gr.sizeof_gr_complex, 1000000) self.snk = blocks.file_sink(gr.sizeof_gr_complex, "output.32fc") self.connect(self.src0, (self.add,0)) self.connect(self.src1, (self.add,1)) self.connect(self.add, self.head) self.connect(self.head, self.snk) def main(): tb = mytb() tb.start() time.sleep(0.01) # Stop flowgraph and disconnect the add block tb.lock() tb.disconnect(tb.add, tb.head) tb.disconnect(tb.src0, (tb.add,0)) tb.disconnect(tb.src1, (tb.add,1)) # Connect the sub block and restart tb.connect(tb.sub, tb.head) tb.connect(tb.src0, (tb.sub,0)) tb.connect(tb.src1, (tb.sub,1)) tb.unlock() tb.wait() if __name__ == "__main__": main()
During reconfiguration, the maximum noutput_items value can be changed either globally using the 'set_max_noutput_items(m)' on the gr_top_block object or locally using the 'set_max_noutput_items(m)' on any given block object.
A block also has a 'unset_max_noutput_items()' method that unsets the local max noutput_items value so that block reverts back to using the global value.
The following example expands the previous example but sets and resets the max noutput_items both locally and globally.
from gnuradio import gr, analog, blocks import time class mytb(gr.top_block): def __init__(self): gr.top_block.__init__(self) self.src0 = analog.noise_source_c(analog.GR_GAUSSIAN, 1) self.src1 = analog.noise_source_c(analog.GR_GAUSSIAN, 1) self.add = blocks.add_cc() self.sub = blocks.sub_cc() self.head = blocks.head(gr.sizeof_gr_complex, 1000000) self.snk = blocks.file_sink(gr.sizeof_gr_complex, "output.32fc") self.connect(self.src0, (self.add,0)) self.connect(self.src1, (self.add,1)) self.connect(self.add, self.head) self.connect(self.head, self.snk) def main(): # Start the gr_top_block after setting some max noutput_items. tb = mytb() tb.src1.set_max_noutput_items(2000) tb.start(100) time.sleep(0.01) # Stop flowgraph and disconnect the add block tb.lock() tb.disconnect(tb.add, tb.head) tb.disconnect(tb.src0, (tb.add,0)) tb.disconnect(tb.src1, (tb.add,1)) # Connect the sub block tb.connect(tb.sub, tb.head) tb.connect(tb.src0, (tb.sub,0)) tb.connect(tb.src1, (tb.sub,1)) # Set new max_noutput_items for the gr_top_block # and unset the local value for src1 tb.set_max_noutput_items(1000) tb.src1.unset_max_noutput_items() tb.unlock() tb.wait() if __name__ == "__main__": main()
The Instructions for using Volk in GNU Radio page provides an overview of how to incorporate and use Volk in GNU Radio blocks.
Many blocks have already been converted to use Volk in their calls, so they can also serve as examples. See the gr::blocks::complex_to_<type>.h files for examples of various blocks that make use of Volk.
GNU Radio defines some of its basic behavior through a set of configuration files located in ${prefix}/etc/gnuradio/conf.d. Different components have different files listed in here for the various properties. These will be read once when starting a GNU Radio application, so updates during runtime will not affect them.
The configuration files use the following format:
# Stuff from section 1 [section1] var1 = value1 var2 = value2 # value of 2 # Stuff from section 2 [section2] var3 = value3
In this file, the hash mark ('#') indicates a comment and blank lines are ignored. Section labels are defined inside square brackets as a group distinguisher. All options must be associated with a section name. The options are listed one per line with the option name is given followed by an equals ('=') sign and then the value. All section and option names must not have white spaces (actually, all white spaces are ignored).
The value of an option can be a string or number and retrieved through a few different interfaces. There is a single preference object created when GNU Radio is launched. In Python, you can get this by making a new variable:
p = gr.prefs()
Similarly, in C++, we get a reference to the object by explicitly calling for the singleton of the object:
prefs *p = prefs::singleton();
The methods associated with this preferences object are (from class gr::prefs):
bool has_section(string section) bool has_option(string section, string option) string get_string(string section, string option, string default_val) bool get_bool(string section, string option, bool default_val) long get_long(string section, string option, long default_val) double get_double(string section, string option, double default_val)
When setting a Boolean value, we can use 0, 1, "True", "true", "False", "false", "On", "on", "Off", and "off".
All configuration preferences in these files can also be overloaded by an environmental variable. The environmental variable is named based on the section and option name from the configuration file as:
GR_CONF_<SECTION>_<OPTION> = <value>
The "GR_CONF_" is a prefix to identify this as a GNU Radio configuration variable and the section and option names are in uppercase. The value is the same format that would be used in the config file itself.
New as of 3.6.5.
Using gr_modtool, each package comes with the ability to easily locate the gnuradio-runtime library using the 'find_package(GnuradioRuntime)' cmake command. This only locates that the library and include directories exist, which is enough for most simple projects.
As projects become more complicated and start needing to rely on other GNU Radio components like gnuradio-blocks or gnuradio-filter, for example, and when they become dependent on certain API compatibility versions of GNU Radio, we need something more. And so we have introduced the GnuradioConfig.cmake file.
When GNU Radio is installed, it also installs a GNU Radio-specific cmake config file that we can use for more advanced compatibility issues of our projects. This tool allows us to specific the API compatible version and a set of components that are required.
Taking the above example, say we have built against version 3.6.5 with features that were introduced in this version and we need the blocks and filter components as well as the main core library. We fist set a cmake variable GR_REQUIRED_COMPONENTS to the components we need. We then use the 'find_package' command and also set a minimum required API compatible version. Since we are on the 3.6 API version, the minimum required version is "3.6.5". The code in the CMakeLists.txt file would look like this:
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER)
find_package(Gnuradio 3.6.5)
Note that the capitalization is important on both lines.
If the installed version of GNU Radio is 3.6.4 or some other API version like 3.5 or 3.7, the Cmake configuration will fail with the version error. Likewise, if libgnuradio-filter was not installed as part of GNU Radio, the configuration will also fail.
Cmake has to know where to find these configuration files. They are installed into $prefix/lib/cmake/gnuradio. If $prefix is '/usr' or '/usr/local', then everything should work fine. If the GNU Radio install $prefix is something else, then Cmake must be told where to find it. This can be done in two ways. If you are installing the out-of-tree module into the same $prefix, then you would be setting '-DCMAKE_INSTALL_PREFIX' on the configuration command line. This is enough to tell Cmake where to look for the configuration files.
The other way to do it is to set the CMAKE_PREFIX_PATH environmental variable to $prefix. You can then install your component anywhere you'd like and it will be able to find and configure against the installed GNU Radio.