liquid-dsp: a C signal processing library built for software-defined radio
digital signal processing library for software-defined radios
At a glance
- What is it?
- A dependency-light DSP library for embedded platforms, written in C, covering filters, modems, synchronizers and oscillators behind a consistent object API.
- Who is it for?
- liquid-dsp is the kind of library you reach for when the alternative is writing FIR design and resampling math yourself against a hardware radio front end. It earns its place on embedded targets precisely because it stays inside libc and libm, and the object lifecycle it shows in its own README example is the pattern every other module follows.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A DSP library that stops at libc and libm
The pitch in the first paragraph of the README is unusually specific for a library of this size: liquid-dsp is designed for embedded platforms and aims to avoid external dependencies entirely. The only hard requirements are libc and libm. Everything else, FFTW included, is opportunistic. The project will use FFTW if the host has it and carry on without it if not, which is the kind of arrangement that decides whether a library survives contact with a vendor BSP.
The repository is a C project with no release-tarball convention of its own, published under the MIT license, and carrying 2,304 stars with 513 forks. The last push was 2026-09-27, and the version line moved to v1.8.3 the day before that, so the project is at a point where the release cadence and the commit history agree with each other. The declared homepage is liquidsdr.org, and the README is explicit that the online documentation there is where to look for anything beyond the build instructions.
The library describes its own scope in one line: filters, filter design, oscillators, modems, synchronizers and complex math, all built to be flexible, scalable and dynamic. That word dynamic matters. Plenty of DSP codebases ship fixed-point tables compiled in at build time. liquid-dsp instead creates objects at runtime and hands back a handle, which is what makes the create, execute, destroy shape in the README example representative rather than a toy.
Building from source with CMake and the options that actually change behaviour
Since version 1.7.0 the build moved to CMake, and the README gives the clone, the out-of-source build and the install in four commands:
git clone git://github.com/jgaeddert/liquid-dsp.git
mkdir build
cd build
cmake ..
make
sudo make installOne platform note matters more than the rest: on Linux you need to rebind the dynamic libraries with `sudo ldconfig` after installing, so the shared object is found. The README says plainly that this is not needed on macOS, which is the kind of detail that saves an afternoon.
The option table is the most useful part of the README for deciding whether this fits your build. `BUILD_EXAMPLES`, `BUILD_AUTOTESTS` and `BUILD_BENCHMARKS` all default to ON, which is generous for a developer checkout and wrong for an embedded image. `BUILD_SHARED_LIBS` is ON and `BUILD_STATIC_LIBS` is OFF, so a cross-compiled firmware build needs the second one flipped. `ENABLE_SIMD` and `FIND_SIMD` are both on, and `ENABLE_LOGGING` is on with `LOGGING_LEVEL` set to `trace`, which is worth thinking about on a target where every log line costs something.
There is also an `ENABLE_STRICT` option that turns warnings into errors, and `ENABLE_TIMESTAMPS` which compiles dynamic build information into the library. The README demonstrates the option syntax with a benchmark run that deliberately disables SIMD:
cmake -DENABLE_SIMD=OFF -DBUILD_BENCHMARKS=ON ..
./benchmark -s dotprodThat single example tells you more about the project than any feature list would. The maintainer expects you to want to measure the vector operations against each other.
The create, execute, destroy pattern shown in the README example
The README opens with a complete C program, and it is worth reading closely because the naming convention it introduces is the whole API in miniature:
#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]
firinterp_crcf interp = firinterp_crcf_create_kaiser(M,m,As);
float complex x = 1.0f; // input sample
float complex y[M]; // interpolated output buffer
firinterp_crcf_execute(interp, x, y);
firinterp_crcf_destroy(interp);
return 0;
}The type name is `object_modality_format`. `firinterp` is the object, `cr` means complex in and real out, and `f` means float. Once you can read that, the module list stops looking like a wall of abbreviations. The three design parameters are passed at construction: an interpolation factor, a filter delay in symbols, and a stop-band attenuation in decibels handed to the Kaiser window designer.
What the example does not show is allocation failure handling, because `firinterp_crcf_create_kaiser` returns a pointer and the example never checks it. That is a documentation shortcut rather than a recommendation. The 1.8.2 release notes are candid that a set of old `assert` statements in the code were being cleaned up precisely because they terminated the program on failure instead of logging an error and returning, so a defensive caller now has somewhere to go other than a crash.
What the source tree says about how broad the scope really is
The repository root is short and legible: `src/`, `include/`, `examples/`, `autotest/`, `bench/`, `sandbox/`, `doc/`, `cmake/`, `scripts/`, `gentab/` and `library.json`. Two of those are worth pausing on. `autotest/` corresponds to the `BUILD_AUTOTESTS` option, which compiles the self-checks into an executable binary rather than a `make check` target, and the 1.8.3 notes add an `audit` flag to that binary for finding tests with missing metadata. `bench/` likewise holds the benchmark binary you can point at individual operations with `-s`.
The examples directory is the fastest way to understand the breadth. Filenames like `agc_crcf_example.c`, `agc_crcf_squelch_example.c`, `agc_crcf_qpsk_example.c`, `asgramcf_example.c`, `autocorr_cccf_example.c`, `bpacketsync_example.c`, `bsequence_example.c`, `cbufferf_example.c`, `cgsolve_example.c`, `channel_cccf_example.c`, `compand_cf_example.c`, `conversion_example.c` and `cpfskmodem_example.c` are not demos of a toy library. Automatic gain control, squelch, power spectral density, cyclic autocorrelation, packet synchronizers, circulant buffer maths, a channel model, companding and CPFSK modulation is the vocabulary of someone building an actual receiver chain.
Note what is absent. There is no `examples/` entry for a file writer, a network transport or a device abstraction. This is a signal processing layer, not a radio control layer, and a reader looking for a turnkey SDR application will not find one here.
Runtime SIMD selection and the shape of the 1.8 release line
The most interesting engineering in the 1.8 series is invisible from the README. The 1.8.1 notes describe logic for runtime mode selection, with a concrete example: a build server produces one shared object supporting SSE, AVX and AVX512, and deploys it to a machine that only has SSE and AVX. AltiVec support is on hold in the same release because memory alignment specifics prevented a runtime-only selection path, which is an honest reason to ship a gap.
The 1.8.1 notes also flag that autotools support continues with full feature support but is likely to be removed for 2.0.0. The 1.8.2 notes add the reproducibility work, including an option to disable timestamps for automated builds and a fix for SIMD selection disagreeing between build time, runtime and implementation. The 1.8.3 notes move the default resamp2 filter design to a windowed Kaiser, because the previous design produced a good filter but took prohibitively long on certain systems.
The last of those is the kind of change that matters in practice and never shows up in a feature list. Filter design cost is a real-time concern on a host that also has to keep up with an SDR stream, and quietly replacing a slow design path with a faster equivalent is worth more to a user than a new modem.
The build metadata also changed. Version, Git hash and date are compiled into the binary as a constant struct, so a deployed library can report what it was built from, and there is a strict compilation mode available for teams that want warnings treated as failures.
Where the README stops and the documentation site takes over
The README is an honest build guide with a feature paragraph attached, and it hands off quickly. Installation, dependencies, the option table and one example are all it gives you, and then it points at liquidsdr.org for documentation. For a library this large that is a reasonable division of labour, but it does mean the practical questions live elsewhere.
The questions the README leaves open are the ones that determine whether you adopt it. There is no statement about which architectures are supported, no thread-safety guidance, and no note on real-time behaviour, although `FIND_THREADS` is on by default and tries to locate a threading library. Whether the modems tolerate jitter, and what the memory cost of a configured filter chain looks like on your target, are both things you would have to go and measure.
What the repository does give you is a project structure that makes the answer findable. `src/` is where the implementation lives, `include/` holds the public headers, `doc/` holds the documentation sources, `sandbox/` holds the testing programs behind `BUILD_SANDBOX`, and `scripts/` and `cmake/` hold the build machinery. The CHANGELOG at the root and the tagged release notes fill in the version history that the README omits.
For a reader deciding between this and a heavier DSP framework, the comparison that matters is dependency count. liquid-dsp will build on a bare toolchain with nothing but libc and libm, and FFTW is a bonus rather than a prerequisite. If that constraint matches your target, the breadth visible in the examples directory is hard to argue with. If you need hardware control, file output or a ready-made application on top, you will be writing that layer yourself.
Editorial conclusion
liquid-dsp is the kind of library you reach for when the alternative is writing FIR design and resampling math yourself against a hardware radio front end. It earns its place on embedded targets precisely because it stays inside libc and libm, and the object lifecycle it shows in its own README example is the pattern every other module follows. What the README does not tell you is how much of the surface you only learn from the generated API docs at liquidsdr.org, which is where the filter, modem and synchronizer references actually live. Start by building it with the benchmark target on and SIMD enabled, then read the module list under src/ against the examples directory before you commit to it.
Frequently asked questions
What does liquid-dsp actually provide out of the box?
It provides the signal processing layer: filters and filter design, oscillators, modems, synchronizers, automatic gain control, squelch and complex math, all exposed as runtime objects with create, execute and destroy lifecycles. It does not provide device control, file output or a complete radio application, so you would write that layer yourself.
Do I need FFTW to build liquid-dsp?
No. The library only requires libc and libm at runtime. FFTW is used when the host happens to have it and skipped otherwise, and the `FIND_FFTW` option that controls the search is on by default rather than required.
How do I read liquid-dsp type names such as firinterp_crcf?
The convention is object, then modality, then format. `firinterp` is a finite impulse response interpolator, `cr` means complex input and real output, and the trailing `f` means float samples. Once that pattern is clear, the module list reads as a compact index of what the library covers.
Which build options matter most for an embedded target?
`BUILD_SHARED_LIBS` defaults to on with `BUILD_STATIC_LIBS` off, which usually needs flipping for firmware images. `BUILD_EXAMPLES`, `BUILD_AUTOTESTS` and `BUILD_BENCHMARKS` all default to on and are worth turning off for a release build, and `LOGGING_LEVEL` defaults to `trace`, which is unusually chatty for production.
Does liquid-dsp pick SIMD instructions at runtime?
Since 1.8.1 it can. The project describes building one shared object that carries SSE, AVX and AVX512 paths and running it on hardware that supports only some of them, with the host instruction set resolved at startup. AltiVec is on hold because memory alignment rules block a runtime-only selection for it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jgaeddert-liquid-dsp)