Skip to main content

software-defined radio digital signal processing library

Project description

liquid-dsp documentation

Software-Defined Radio Digital Signal Processing Library - https://liquidsdr.org

liquid-dsp is a free and open-source digital signal processing (DSP) library designed specifically for software-defined radios on embedded platforms. The aim is to provide a lightweight DSP library that does not rely on a myriad of external dependencies or proprietary and otherwise cumbersome frameworks. All signal processing elements are designed to be flexible, scalable, and dynamic, including filters, filter design, oscillators, modems, synchronizers, complex mathematical operations, and much more.

// get in, process data, get out
#include <liquid/liquid.h>
int main() {
    unsigned int M  = 4;     // interpolation factor
    unsigned int m  = 12;    // filter delay [symbols]
    float        As = 60.0f; // filter stop-band attenuation [dB]

    // create interpolator from prototype
    firinterp_crcf interp = firinterp_crcf_create_kaiser(M,m,As);
    float complex x = 1.0f;  // input sample
    float complex y[M];      // interpolated output buffer

    // repeat on input sample data as needed
    {
        firinterp_crcf_execute(interp, x, y);
    }

    // destroy interpolator object
    firinterp_crcf_destroy(interp);
    return 0;
}

For more information, please refer to the documentation online.

Installation and Dependencies

liquid-dsp only relies on libc and libm (standard C and math) libraries to run; however liquid will take advantage of other libraries (such as FFTW) if they are available. Starting with version 1.7.0, liquid-dsp has moved to the CMake build system which can be installed with brew install cmake on macOS, sudo apt-get install cmake on Debian variants.

Installation

The recommended way to obtain the source code is to clone the entire repository from GitHub:

git clone git://github.com/jgaeddert/liquid-dsp.git

Building and installing the main library is a simple as

mkdir build
cd build
cmake ..
make
sudo make install

If you are installing on Linux for the first time, you will also need to rebind your dynamic libraries with sudo ldconfig to make the shared object available. This is not necessary on macOS.

Build Options

Here is a table of CMake options available for configuring liquid:

Option

Default

Description

BUILD_EXAMPLES

ON

Compile example programs

BUILD_AUTOTESTS

ON

Parse and compile autotests into executable binary

BUILD_BENCHMARKS

ON

Parse and compile benchmarks into executable binary

BUILD_SHARED_LIBS

ON

Build shared library

BUILD_STATIC_LIBS

OFF

Build static library

ENABLE_SIMD

ON

Enable use of single instruction, multiple data (SIMD) extensions

FIND_SIMD

ON

Try to find available SIMD instruction sets on host computer

FIND_THREADS

ON

Try to find available threading library for concurrency

FIND_FFTW

ON

Try to find FFTW if available

BUILD_SANDBOX

OFF

Compile sandbox (testing) programs

BUILD_DOC

OFF

Generate documentation

BUILD_PYTHON

OFF

Set flags to enable building python bindings

COVERAGE

OFF

Set flags to enable code coverage testing

ENABLE_LOGGING

ON

Enable global logging capabilities

LOGGING_LEVEL

trace

Set minimum logging level to compile; select from “trace”, “debug”, “info”, “warn”, “error”, or “fatal”

ENABLE_COLOR

ON

Enable use of color terminal output

For example, if you want to benchmark how fast a vector dot product runs without SIMD extensions, you could run the following:

cmake -DENABLE_SIMD=OFF -DBUILD_BENCHMARKS=ON ..
make
./benchmark -s dotprod_rrrf

Running Autotests

Source code validation is a critical step in any software library, particularly for verifying the portability of code to different processors and platforms. Packaged with liquid-dsp are a number of automatic test scripts to validate the correctness of the source code. The test scripts are located under each module’s tests/ directory and take the form of a C source file. When configured with BUILD_AUTOTESTS enabled, these tests are parsed, compiled, and linked into an executable which will run the tests.

./xautotest
# ...
# 13:31:14 [info ] =========== autotest summary ===========
# 13:31:14 [info ] tests:
# 13:31:14 [info ]   pass     : 1341
# 13:31:14 [info ]   fail     : 0
# 13:31:14 [info ]   skip     : 0
# 13:31:14 [info ] checks:
# 13:31:14 [info ]   pass     : 716520
# 13:31:14 [info ]   fail     : 0
# 13:31:14 [info ] overall:
# 13:31:14 [info ]   warn     : 55
# 13:31:14 [info ]   PASS

There are currently more than 700,000 checks across 1,316 tests to verify functional correctness. Drop me a line if these aren’t running on your platform.

Testing Code Coverage

In addition to the full test suite, you can configure gcc to export symbol files to check for code coverage and then use gcovr to generate a full report of precisely which lines are covered in the autotests. These symbol files aren’t generated by default and need to be enabled at compile-time through a CMake option:

cmake -DBUILD_AUTOTESTS=ON -DCOVERAGE=ON ..

A coverage report can be generated by running the autotests and running gcovr:

make -j4 xautotest
./xautotest -q -o autotest.json
cd ..
gcovr --filter="src/.*/src/.*.c" --print-summary
# ...
# ------------------------------------------------------------------------------
# TOTAL                                      20922   17396    83%
# ------------------------------------------------------------------------------
# lines: 83.1% (17396 out of 20922)
# functions: 63.9% (1787 out of 2798)
# branches: 64.8% (5762 out of 8896)

Running Example Programs

Nearly all signal processing elements have a corresponding example in the examples/ directory. Most example scripts generate an output .m file for plotting with GNU octave All examples are built as stand-alone programs and can be compiled with the BUILD_EXAMPLES CMake flag:

cmake -DBUILD_EXAMPLES=ON ..
make
./examples/modem_example -m qpsk
# <liquid.modemcf, scheme="qpsk", order=4>
#    0 :   0.70710677 + j*  0.70710677
#    1 :  -0.70710677 + j*  0.70710677
#    2 :   0.70710677 + j* -0.70710677
#    3 :  -0.70710677 + j* -0.70710677
# num sym errors:    0 /    4
# num bit errors:    0 /    8
# results written to modem_example.m.

Benchmarking Tool

Packaged with liquid are benchmarks to determine the speed each signal processing element can run on your machine. Initially the tool provides an estimate of the processor’s clock frequency and will then estimate the number of trials so that each benchmark will take between 50 and 500 ms to run. You can build and run the benchmark program with the following command:

make bench

Linking from C++

Compiling and linking to C++ programs is straightforward. Just include <complex> before <liquid/liquid.h> and use std::complex<float> in favor of float complex. Here is the same example as the one above but in C++ instead of C:

// get in, process data, get out
#include <complex>
#include <liquid/liquid.h>
int main() {
    unsigned int M  = 4;     // interpolation factor
    unsigned int m  = 12;    // filter delay [symbols]
    float        As = 60.0f; // filter stop-band attenuation [dB]

    // create interpolator from prototype
    firinterp_crcf interp = firinterp_crcf_create_kaiser(M,m,As);
    std::complex<float> x = 1.0f;   // input sample
    std::complex<float> y[M];       // interpolated output buffer

    // repeat on input sample data as needed
    {
        firinterp_crcf_execute(interp, x, y);
    }

    // destroy interpolator object
    firinterp_crcf_destroy(interp);
    return 0;
}

Linking from External Project

Installing with CMake provides an exportable interface (liquid::liquid) that allows easy integration into external applications. For example, say you have a simple application in main.c that requires liquid-dsp as a dependency:

// main.c - test linking to liquid
#include <stdio.h>
#include <stdlib.h>
#include <liquid/liquid.h>

int main()
{
    // create resampling object, print and return
    printf("creating test object...\n");
    resamp_crcf q = resamp_crcf_create(0.12345f, 12, 0.25f, 60.0f, 256);
    resamp_crcf_print(q);
    resamp_crcf_destroy(q);
    return 0;
}

You now have two options within your project CMakeLists.txt file:

  1. clone, build, and install using CMake, then use find_package

  2. dynamically fetch liquid-dsp with FetchContent to pull down the source and build completely within your application

Your CMakeLists.txt file might look something like this:

# CMakeLists.txt - test finding package and linking against it
cmake_minimum_required(VERSION 3.10)
project(liquid_test C)

# option 1: check for local installation
#find_package(liquid REQUIRED)

# option 2: dynamically fetch content
include(FetchContent)
FetchContent_Declare(
    liquid
    GIT_REPOSITORY https://github.com/jgaeddert/liquid-dsp.git
    GIT_TAG        v1.7.0
    )
set(BUILD_AUTOTESTS  OFF CACHE INTERNAL "Disable building liquid tests")
set(BUILD_BENCHMARKS OFF CACHE INTERNAL "Disable building liquid benchmarks")
set(BUILD_EXAMPLES   OFF CACHE INTERNAL "Disable building liquid examples")
FetchContent_MakeAvailable(liquid)
# FetchContent_Populate(liquid) <- older policy, see CMP0169

add_executable(main main.c)
target_link_libraries(main liquid)

You can then compile and run your application the typical way:

mkdir build
cd build
cmake ..
make
./main
# creating test object...
# <liquid.resamp_crcf, rate=0.12345, m=12, as=60.000, fc=0.25, npfb=256>

C++ Bindings

While C is (mostly) a subset of C++, sometimes having a class structure is more convenient than using C-style structs. These bindings do two things:

  1. Wrap the C-style functionality into a set of header-only C++ class libraries

  2. Bind these C++ classes into python3

The original C example can be re-written in C++ as follows:

// get in, process data, get out
#include "firinterp.hh"
int main() {
    unsigned int M  = 4;     // interpolation factor
    unsigned int m  = 12;    // filter delay [symbols]
    float        As = 60.0f; // filter stop-band attenuation [dB]

    // instantiate interpolator object from prototype
    liquid::firinterp interp(M,m,As);
    std::complex<float> x = 1.0f;  // input sample
    std::complex<float> y[M];      // interpolated output buffer

    // repeat on input sample data as needed
    {
        interp.execute(x, y);
    }
    return 0;
}

Python Bindings

Building python bindings depends on pybind11, the python3 development libraries, and a compatible C++14 compiler (e.g. brew install pybind11 on macOS or sudo apt-get install pybind11-dev on Debian variants). Once these dependencies are installed, you can build the liquid-dsp python library with

pip install .

From python3 simply use import liquid as dsp. Our interpolation example used throughout this document can be written in python3 as:

# get in, process data, get out
import liquid as dsp, numpy as np

# create the interpolator
interp = dsp.firinterp(M=4, m=12, As=60.)

# run on a single sample
buf = interp.execute(1+1j,)

PlatformIO

Cross-compling for embedded platforms is most easily achieved with platformio. Just add liquid-dsp to your platform.io list of dependencies:

[env:native]
platform = native
lib_deps = https://github.com/jgaeddert/liquid-dsp.git

To test this, compile the example program for a Raspberry Pi Pico microcontroller:

# create a virtual environment, install platformio, and compile an example
virtualenv pio
source pio/bin/activate
pip install platformio
pio ci --lib="." --board=pico examples/platformio_example.c
# ...
# Generating UF2 image
# elf2uf2 ".pio/build/pico/firmware.elf" ".pio/build/pico/firmware.uf2"
# Checking size .pio/build/pico/firmware.elf
# Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
# RAM:   [==        ]  15.5% (used 41820 bytes from 270336 bytes)
# Flash: [          ]   0.2% (used 5196 bytes from 2097152 bytes)
# Building .pio/build/pico/firmware.bin
# ===================== [SUCCESS] Took 23.63 seconds =====================

Available Modules

  • agc: automatic gain control, received signal strength

  • audio: source audio encoders/decoders: cvsd, filterbanks

  • buffer: internal buffering, circular/static, ports (threaded)

  • channel: additive noise, multi-path fading, carrier phase/frequency offsets, timing phase/rate offsets

  • core: core functionality (error handling, logging)

  • dotprod: inner dot products (real, complex), vector sum of squares

  • equalization: adaptive equalizers: least mean-squares, recursive least squares, semi-blind

  • fec: basic forward error correction codes including several Hamming codes, single error correction/double error detection, Golay block code, as well as several checksums and cyclic redundancy checks, interleaving, soft decoding

  • fft: fast Fourier transforms (arbitrary length), discrete sin/cos transforms

  • filter: finite/infinite impulse response, polyphase, hilbert, interpolation, decimation, filter design, resampling, symbol timing recovery

  • framing: flexible framing structures for amazingly easy packet software radio; dynamically adjust modulation and coding on the fly with single- and multi-carrier framing structures

  • math: transcendental functions not in the C standard library (gamma, besseli, etc.), polynomial operations (curve-fitting, root-finding, etc.)

  • matrix: basic math, LU/QR/Cholesky factorization, inversion, Gauss elimination, Gram-Schmidt decomposition, linear solver, sparse matrix representation

  • modem: modulate, demodulate, PSK, differential PSK, QAM, optimal QAM, as well as analog and non-linear digital modulations GMSK)

  • multichannel: filterbank channelizers, OFDM

  • nco: numerically-controlled oscillator: mixing, frequency synthesis, phase-locked loops

  • optim: (non-linear optimization) Newton-Raphson, evoluationary algorithms, gradient descent, line search

  • quantization: analog/digital converters, compression/expansion

  • random: (random number generators) uniform, exponential, gamma, Nakagami-m, Gauss, Rice-K, Weibull

  • sequence: linear feedback shift registers, complementary codes, maximal-length sequences

  • utility: useful miscellany, mostly bit manipulation (shifting, packing, and unpacking of arrays)

  • vector: generic vector operations

License

liquid projects are released under the X11/MIT license. By default, this project will try to link to FFTW if it is available on your build platform. Because FFTW starting with version 1.3 is licensed under the GNU General Public License v2 this unfortunately means that (and I’m clearly not a lawyer, here) you cannot distribute liquid-dsp without also distributing the source code if you link to FFTW. This is a similar situation with the classic libfec which uses the GNU Lesser GPL. Finally, liquid-dsp makes extensive use of GNU autoconf, automake, and related tools. These are fantastic libraries with amazing functionality and their authors should be lauded for their efforts. In a similar vain, much the software I write for a living I give away for free; however I believe in more permissive licenses to allow individuals the flexibility to use software with fewer limitations. If these restrictions are not acceptable, liquid-dsp can be compiled and run without use of these external libraries, albeit a bit slower and with limited functionality.

Short version: this code is copyrighted to me (Joseph D. Gaeddert), I give you full permission to do whatever you want with it except remove my name from the credits. Seriously, go nuts! but take caution when linking to other libraries with different licenses. See the license for specific terms.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

liquid_dsp-1.8.0.tar.gz (1.0 MB view details)

Uploaded Source

File details

Details for the file liquid_dsp-1.8.0.tar.gz.

File metadata

  • Download URL: liquid_dsp-1.8.0.tar.gz
  • Upload date:
  • Size: 1.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for liquid_dsp-1.8.0.tar.gz
Algorithm Hash digest
SHA256 aeb6474b50d955133816f617d2817b93de8c0578ae6d0dd567e5981d6f07cc4c
MD5 ddd7a0dc78320cd5033e5ece1e7e9f0b
BLAKE2b-256 1bf0af8274f432ca62bda0d55d28b34af987bbe870490aedf7673cc9100cde7a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page