Skip to content

Make use of transcribed audio calls streamed from trunk-recorder

Notifications You must be signed in to change notification settings

lilhoser/pizzawave

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Introduction

pizzawave is a set of cross-platform .NET applications and tools for processing audio data streamed by the callstream plugin of trunk-recorder. The audio data consists of calls recorded by trunk-recorder from conventional and trunked radio systems, such as local fire/rescue/EMS. pizzawave tooling transcribes these calls to text using OpenAI's Whisper AI model as exposed through whisper.net toolchain. Among other features, the application allows you to monitor and set alerts for keywords of interest.

The pizzawave Visual Studio solution consists of these tools:

  • A windows-only .NET UI (pizzaui in source)
  • A cross-platform .NET command line application (pizzacmd in source)
  • A cross-platform .NET library (pizzalib in source), used by the UI and CLI application

Please be sure to read the README for each individual tool.

Requirements

Regardless of whether you choose to use the UI, command line application, or roll your own application that uses the cross-platform library, you will need to observe these requirements:

  • A Linux system running trunk-recorder with the callstream plugin configured correctly
  • An operating system capable of running .NET 8.0 runtime (e.g, Win, Lin or Mac)
    • The pizzawave tools currently target .NET 8.0, but if you are building from source, earlier versions should work as well.
  • The requirements as specified in the tool of choice:
    • pizzaui: Windows-only | README
    • pizzacmd: All supported platforms | README
    • pizzalib: All supported platforms | README

Architecture

As shown in the illustration, pizzawave uses a server-client model, where the server is either the pizzawave UI or command line application and the client is one or more trunk-recorder systems. This design allows pizzawave to accept radio transmissions from multiple instances of trunk-recorder, which might be recording audio data from separate SDR device arrays monitoring broadcasts from different trunked radio systems.

Pizzawave listens for audio data from trunk-recorder systems, translates the data into textual transcriptions using Whisper AI, and processes alert rules to notify you of interesting broadcasts.

Note that it is possible (and even desirable) to run pizzawave on the same system running trunk-recorder. In this setup, you would of course need to use pizzacmd which is cross-platform.

Building from Source

Windows

You can use Visual Studio Community Edition for free.

Mac and Linux

Developing on Linux/Mac

You will want to develop pizzacmd using VS Code on Linux/Mac using the C# extension. To debug the program, use a launch configuration such as:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "dotnet: build /home/<USER>/pizzawave/pizzacmd/pizzacmd.csproj",
            "program": "/home/<USER>/pizzawave/pizzacmd/bin/Debug/net8.0/pizzacmd",
            "name": "Test pizzawave",
            "args": ["--talkgroups=/home/<USER>/my_talkgroups.csv"]
        }
    ]
}

Configuration

Pizzawave configuration lives in <user profile>\pizzawave\settings.json. On Windows, this is Users\<user>\AppData\Roaming\pizzawave\settings.json. Please see the READMEs for each individual tool you are using for what settings options are available and how to use them in your setup. pizzaui includes a feature that allows you to setup your configuration in a more automated way, but you can always create the file manually. If you run the UI or command line application without a settings file, the default one will be created in the location specified above.

Important: Make sure your trunk-recorder system is configured to connect to the right IP address. In an exotic scenario where you're running pizzacmd from both a Windows host system and a WSL2 Ubuntu system, the host system and the virtual Ubuntu system will have different IP addresses! In this scenario, you might forget to set the correct IP address on the trunk-recorder system, and only one of these machines will receive audio data, while the other might be stuck on this:

StreamServer Verbose: 1 : 3/22/2024 3:39 PM: Listening on port 9123

Running

Live captures

To create a live audio capture within PizzaUI, navigate to File->Call Manager->Start. This will connect to the callstream plugin running on your configured trunk-recorder system.

Whether you use pizzaui, pizzacmd or your own .NET application built on pizzalib, all calls streamed in real-time from a callstream server will be stored in a capture, which is a folder in the root working directory (<user profile>\pizzawave\). When you stop your live session with the callstream server, the capture is ended and a new capture will be created if you reconnect later. Older captures can be loaded in pizzawave tooling later by opening the capture folder directly.

The capture folder consists of:

  • calljournal.json: Each line contains a JSON-serialized TranscribedCall structure. The audio data can be linked to this record via the Location field.
  • <timestamp>.mp3: call audio files

The call journal can be deserialized into a list of TranscribedCall objects using NewtonSoft.Json as follows:

var lines = File.ReadAllLines("calljournal.json");
var calls = new List<TranscribedCall>();
foreach (var line in lines)
{
    var call = (TranscribedCall)JsonConvert.DeserializeObject(line, typeof(TranscribedCall))!;
    calls.Add(call);
}

Offline captures

The callstream plugin allows you to redirect call records to an SFTP server. These call records are stored on disk in a raw binary format identical to data streamed to a live capture. These are referred to as offline captures in pizzawave parlance. The callstream plugin uploads offline capture records to the SFTP server according to the following naming and organization convention:

  • YEAR
    • MONTH
      • DAY
        • HOUR
          • YEAR-MONTH-DAY.HOURMINUTESECOND.bin.bin

Offline captures can be loaded at any directory level by pizzaui or by the following code (pizzalib required):

var targets = Directory.GetFiles(offlineDir, "*.*", SearchOption.AllDirectories).ToList();
foreach (var file in targets)
{
    using (var stream = new MemoryStream(File.ReadAllBytes(file)))
    {
        var wavStream = new WavStreamData(m_Settings);
        var cancelSource = new CancellationTokenSource();
        var result = await wavStream.ProcessClientData(stream, cancelSource);
        if (result)
        {
            var call = new TranscribedCall();
            call.UniqueId = Guid.NewGuid();

            try
            {
                var jsonObject = wavStream.GetJsonObject();
                call.StopTime = jsonObject["StopTime"]!.ToObject<long>();
                call.StartTime = jsonObject["StartTime"]!.ToObject<long>();
                call.CallId = jsonObject["CallId"]!.ToObject<long>();
                call.Source = jsonObject["Source"]!.ToObject<int>();
                call.Talkgroup = jsonObject["Talkgroup"]!.ToObject<long>();
                call.PatchedTalkgroups = jsonObject["PatchedTalkgroups"]!.ToObject<List<long>>();
                call.Frequency = jsonObject["Frequency"]!.ToObject<double>();
                call.SystemShortName = jsonObject["SystemShortName"]!.ToObject<string>();
            }
            catch (Exception ex)
            {
                var err = $"Unable to parse JSON data: {ex.Message}";
                throw new Exception(err);
            }
            
            try
            {
                //
                // Transcribe the wav audio
                //
                call.Transcription = await m_Whisper.TranscribeCall(wavStream.GetRawStream());
                wavStream.RewindStream();
            }
            catch (Exception ex)
            {
                throw; // back up to worker thread
            }
        }
    }
}

Offline captures are slow to load, because many audio recordings are being transcribed at one time (whereas in live mode, calls are transcribed as they are received over the air). As a result, after an offline capture is loaded, the contained call records are also exported to a live capture for easier retrieval later.

Alerts when loading captures

Alerts are NOT processed when older captures are loaded, for both live and offline captures. You can see what alerts would match a loaded capture by navigating to View->Show alert matches only.

Other

Diagnostics

All logs, model files, settings files, and alert data can be found in your operating system's user profile folder.

  • alerts - this folder contains WAV data for matched alerts
  • Logs - this folder contains all log files
  • model - this folder contains all auto-downloaded GGML model files

If your logs are not detailed enough, adjust the TraceLevelApp parameter in settings.json.

What's up with the name?

I dunno, I like pizza and Teenage Mutant Ninja Turtles, so it seemed to work.

My trunk-recorder/SDR setup

For those that are new to SDRs (also check out this Getting Started Guide), I thought it might be helpful to show how I setup my SDR array:

The discone antenna is mounted about 20 feet in the air above my workshop, using a 1.5" PVC mast and a sturdy set of stand-off mounts. I have a short 8-ft run of UHF cable that brings the signal indoors to a wall-mounted, inline amplifier, which then connects to a 25-ft run of UHF cable that splits the signal 6-ways to an SDR array. I did my best to match up the impedence among all these cables and adapters to 50-ohm. For a similar rig that favors transmission insteead of Rx-only, 75-ohm would be better.

Here is the parts list, all of which can be purchased on Amazon:

Also, here are some sample configurations to get you started:

Resources

About

Make use of transcribed audio calls streamed from trunk-recorder

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages