Self-hosted service
TrunkRecorder/trunk-recorder avatar
TrunkRecorder/trunk-recorder

Trunk Recorder: following a trunked radio system with GNU Radio and OP25

Records calls from a Trunked Radio System (P25 & SmartNet)

1,153 stars260 forksC++GPL-3.0

At a glance

What is it?
Trunk Recorder is a C++ program that records calls from trunked and conventional radio systems by decoding the control channel and then following each granted frequency, using GNU Radio for the RF processing and the OP25 libraries for P25. It supports many SDRs and platforms, ships a Docker image, and the project is currently at the v5.2 line with small fixes still landing.
Who is it for?
Trunk Recorder suits a radio amateur, journalist or public records researcher who wants to capture the traffic of a P25, SmartNet, DMR or analog system that is otherwise locked inside a proprietary trunking scheme, because the control channel decoding and the per-channel monitoring are already implemented.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 26 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 October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why a control channel has to be decoded first

The README explains trunking for people who do not already know it, and the explanation is the clearest justification for the program's design.

Trunking systems allow a large number of user groups to share a limited number of radio frequencies by temporarily and dynamically assigning radio frequencies to talkgroups on demand. It is understood that most user groups actually use the radio very sporadically and don't need a dedicated frequency.

Most trunking system types, such as SmartNet and P25, set aside one of the radio frequencies as a control channel that manages and broadcasts frequency assignments. When someone presses Push to Talk, the radio sends a message to the system, the system assigns a voice frequency and broadcasts a Channel Grant message on the control channel, which tells the radio what frequency to transmit on and tells other radios set to the same talkgroup to listen.

So to record the traffic, the program has to do two things in sequence. It has to listen to and decode the control channel constantly, and when a frequency is granted to a talkgroup it creates a monitoring process which decodes the portion of the radio spectrum for that frequency from the SDR that is already pulling it in.

One detail closes the loop. No message is transmitted on the control channel when a conversation on a talkgroup is over, so the monitoring process keeps track of transmissions and, if there has been no activity for a specified period, it ends the recording.

That last point is the difference between a recorder that produces usable files and one that produces a growing pile of silence. The end of a transmission is inferred rather than announced, which is why a timeout is a required configuration value rather than a nicety.

GNU Radio for the RF, OP25 for the P25

The architecture is a division of labour between two projects, and the README is direct about which does what.

The SDRs capture large swathes of RF and then software processes what was received. GNU Radio is used to do this processing because it provides lots of convenient RF blocks that can be pieced together to allow for complex RF processing. The libraries from the OP25 project are used for a lot of the P25 functionality.

That split makes sense for the problem. P25 decoding is a large body of intricate, standardised work that has already been done, and the OP25 libraries supply a lot of the P25 functionality. The RF front end is a signal processing chain, and writing that from scratch in C++ would be a waste of effort when GNU Radio provides the blocks.

The supported systems are listed. Trunked P25 and SmartNet systems. Conventional P25, DMR and analog systems, where each talkgroup has a dedicated RF channel. And P25 Phase 1, P25 Phase 2 and analog voice channels.

The conventional case is the one to notice. A conventional system has no control channel to decode, each talkgroup has its own frequency, and the recording problem reduces to monitoring a set of fixed frequencies. That is a much easier configuration than a trunked system, and it is worth trying first if you are evaluating the software, because a failure there points at your SDR setup rather than at the trunking logic.

The hardware and platform list, and the GNU Radio ceiling

The supported SDRs are listed in one line: RTL-SDR dongles, HackRF, Ettus USRP B200, B210 and B205, BladeRF, Airspy and SDRplay.

That list spans a wide price range, and the RTL-SDR dongle at the bottom is the one most people will actually use. The examples directory reflects that, with config files for p25-rtl.json, p25-hackrf.json, p25-uhd.json, smartnet-rtl.json and smartnet-hackrf.json.

The platform list is Ubuntu, Raspberry Pi on Raspberry OS or Raspbian and Ubuntu, Arch Linux, Debian and macOS.

The constraint that matters more than any of these lists is the GNU Radio version, which is 3.7 to 3.10. That is a real ceiling. The Dockerfile makes it explicit, pulling gnuradio-dev in the builder stage and the specific runtime packages in the second stage, including libgnuradio-digital3.10.9t64, libgnuradio-analog3.10.9t64, libgnuradio-filter3.10.9t64, libgnuradio-network3.10.9t64 and libgnuradio-uhd3.10.9t64.

If you are on a current distribution with a newer GNU Radio, the source install path is the one to read, not the Docker path, and the pinned version numbers are the specification of what the code was written against.

Docker, source builds, and a container that talks to other containers

The install options are documented per platform, and Docker is offered for Linux and Raspberry Pi while macOS is source only.

The Dockerfile is worth reading for one non-obvious reason, which its own comment states: it installs docker for passing the socket to allow for intercontainer exec. The plugin mechanism, and the user_plugins directory in the repository, is how external code is loaded, and a plugin that runs in another container needs the Docker socket to reach it.

The build is a two stage image on ubuntu:24.04. The builder installs the full SDR development stack, including libhackrf-dev, librtlsdr-dev, libbladerf-dev, libairspy-dev, libairspyhf-dev, libmirisdr-dev, libsoapysdr-dev, libfreesrp-dev, libxtrx-dev, libuhd-dev and libosmosdr-dev, along with libiio-dev, ffmpeg, and the paho MQTT client and server libraries. It then clones the MQTT plugin from a separate repository into user_plugins, configures with cmake, and makes the install.

The second stage drops the build dependencies and keeps the runtime libraries, the GNU Radio 3.10.9 packages, the paho MQTT libraries, ffmpeg, and the Boost log and chrono components.

The plugin being fetched at build time rather than being a submodule is a meaningful detail. It means the Docker image is not a hermetic build of the repository alone, and a fork that wants a different plugin set has to change the Dockerfile.

Playback and the OpenMHz sharing layer

The README is blunt about the default state of the output. By default, Trunk Recorder just dumps a lot of recorded files into a directory.

That is the honest description of a recorder, and the project then lists the options for making the results browsable and shareable.

OpenMHz is described as a free hosted platform for sharing recordings. Trunk Player is a Python based server for running your own. Rdio Scanner provides a good looking, scanner style interface for listening. And there is Broadcastify Calls, with an API and a forum thread, plus a separate guide for streaming to Broadcastify with Liquidsoap, and an audioplayer.php page, and a live web page project from rosecitytransit.

So the recording half of the system is solved and complete, and the browsing half is where the community has been active. That is worth knowing before you start, because a directory of files is not much use on its own and the sharing layer is separate code you have to choose.

The plugin list follows the same pattern. There is an MQTT Status plugin publishing the current status of an instance over MQTT, an MQTT Statistics plugin, a decode rates logger that writes trunking control channel decode rates to a CSV file with a PHP file producing an SVG graph, a daily call log plugin that creates a log of calls instead of individual JSON files, and a Prometheus exporter publishing statistics to a metrics endpoint over HTTP.

The Prometheus exporter and the MQTT plugins are the ones that turn the recorder into something that fits into an existing monitoring stack.

Configuration files, plugins and a v5.2 line still taking fixes

The examples directory is the best documentation of the configuration surface, and it is a long list of JSON files. There are separate files for conventional analog, DMR conventional, DMR trunked, a pair of FRS files, a single-channel FRS setup, an IIO configuration, an IQ file input, a multi-system configuration, and the P25 and SmartNet variants for RTL-SDR, HackRF and UHD receivers.

Two CSV files sit alongside them, channel.csv and talkgroups.csv. The talkgroup file is the one to understand first, because it is the mapping from a talkgroup identifier to a name and a tag, and it is what makes recorded calls searchable rather than a pile of unnamed audio files.

The examples also carry the operational scripts. There is a trunk-recorder.service systemd unit, an auto-restart.sh and several variants of it including a WMATA-specific one, an example launchagent plist for macOS, and encode-upload.sh.sample and upload_gcs.sh for pushing recordings to cloud storage.

On the release side, the recent versions are v5.2.1 from 2026-04-08, described as small fixes around FFMPEG, v5.2.0 from 2026-04-06, named All the Small Things, and v5.1.1 from 2025-10-30, named Moar Color. The README announces V5.2 as the best release yet, and thanks the contributors who helped collect core dumps, which is a hint about where the reliability work went.

The last push was on 2026-09-27, so the tree is being worked on, and the repository has a CHANGELOG.md. The project is GPL-3.0, and it is supported by sponsors, with the Discord server named in the README as the place to share what works and what does not.

Editorial conclusion

Trunk Recorder suits a radio amateur, journalist or public records researcher who wants to capture the traffic of a P25, SmartNet, DMR or analog system that is otherwise locked inside a proprietary trunking scheme, because the control channel decoding and the per-channel monitoring are already implemented. It is the wrong tool if you only need a single conventional channel, since that needs no control channel decoding at all, and it is the wrong choice if your system uses a digital scheme OP25 does not cover. Before you set it up, read the configuring a system document and the FAQ, and check the SDR list, because the radio hardware and the GNU Radio version you install determine what the program can actually decode.

Frequently asked questions

What radio systems does Trunk Recorder support?

Trunked P25 and SmartNet systems, conventional P25, DMR and analog systems where each talkgroup has a dedicated RF channel, and P25 Phase 1, P25 Phase 2 and analog voice channels. Multiple radio systems can be recorded at the same time.

Which SDRs work with Trunk Recorder?

The README lists RTL-SDR dongles, HackRF, Ettus USRP B200, B210 and B205, BladeRF, Airspy and SDRplay. The example configuration files cover the RTL-SDR, HackRF and UHD variants for P25 and SmartNet.

What does Trunk Recorder need GNU Radio for?

The SDRs capture large swathes of RF and software processes what was received, and the README says GNU Radio is used for this processing because it provides convenient RF blocks that can be pieced together for complex RF processing. The OP25 libraries are used for much of the P25 functionality.

How do I install Trunk Recorder on Linux?

There are two documented routes, a Docker install and a from-source install, each with its own page in the docs/Install directory. The Raspberry Pi page adds a video walkthrough, and macOS is documented as a source install with a Homebrew section.

How do I share or browse Trunk Recorder output?

By default the program just dumps recorded files into a directory. The README lists OpenMHz as a free hosted sharing platform, Trunk Player as a self-hosted Python server, and Rdio Scanner as a scanner style listening interface, plus a Prometheus exporter plugin and MQTT plugins for status and statistics.

Official sources

  1. Issues
  2. License: GPL-3.0
  3. README
  4. Releases
  5. TrunkRecorder/trunk-recorder on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/trunkrecorder-trunk-recorder.svg)](https://hysenlabs.com/projects/trunkrecorder-trunk-recorder)

Was this useful?