OP25 OP25 WAV Source for P25 VC Decoding

dudegt

KQ4EVS
Premium Subscriber
Joined
Aug 19, 2012
Messages
173
Location
Kennesaw, GA
I'm trying to run a weird experiment where I have a recording of an entire segment of a voice channel of a p25 system in my area and I'm wondering if I can demodulate it with op25. This is my current setup that is not working does anyone have any suggestions or am I doing this all wrong? I want to decode voice channel recordings, or is that not possible without a control channel?

JSON:
{

    "channels": [

        {

            "demod_type": "cqpsk",

            "destination": "udp://127.0.0.1:23456",

            "excess_bw": 0.2,

            "filter_type": "rc",

            "frequency": 8532250,

            "if_rate": 24000,

            "name": "first",

            "plot": "fft",

            "symbol_rate": 4800

        }

    ],

    "devices": [

        {

            "args": "wavsrc",

            "wav_file": "test5.wav",

            "wav_gain": 29,

            "gains": "",

            "name": "sdr0",

            "ppm": 0.0,

            "tunable": false

        }

      

    ],

    "trunking": {

        "module": "tk_p25.py",

        "chans": [

            {

                "nac": "0x170",

                "sysname": "tk_wav",

                "control_channel_list": "853.225",

                "whitelist": "",

                "tdma_cc": false,

                "tgid_tags_file": "cobb.tsv",

                "crypt_behavior": 0

            }

        ]

    }

}
 

boatbod

Member
Joined
Mar 3, 2007
Messages
3,409
Location
Talbot Co, MD
Boatbod op25 does support decoding from a .wav file using the fsk4 demodulator. Whether or not it works for your specific application I really cannot say; it's experimental and was developed so that I could take DSD .wav files and replay them. You'll need to use multi_rx.py and you'll probably need to play with the "wav_gain" parameter.

Example configuration (cfg.json):
Code:
{
    "channels": [
        {
            "name": "Playback", 
            "device": "sdr0",
            "trunking_sysname": "tkwav",
            "demod_type": "fsk4", 
            "destination": "udp://127.0.0.1:23446", 
            "meta_stream_name": "",
            "excess_bw": 0.2, 
            "filter_type": "rc", 
            "if_rate": 24000, 
            "plot": "",
            "symbol_rate": 4800,
            "enable_analog": "off",
            "whitelist": "",
            "blacklist": ""
        }
    ], 
    "devices": [
        {
            "args": "wavsrc", 
            "wav_file": "vc_abc123.wav",
            "wav_gain": 14.1,
            "gains": "", 
            "name": "sdr0", 
            "ppm": 0.0, 
            "tunable": false
        }
    ],
    "trunking": {
        "module": "tk_p25.py",
        "chans": [
            {
                "nac": "0x0",
                "sysname": "tkwav",
                "control_channel_list": "800.000000",
                "whitelist": "",
                "tgid_tags_file": "",
                "tdma_cc": false,
                "crypt_behavior": 0
            }
        ]
    },
    "metadata": {
        "module": "icemeta.py",
        "streams": [
            {
                "stream_name": "",
                "meta_format_idle": "[idle]",
                "meta_format_tgid": "[%TGID%]",
                "meta_format_tag":  "[%TGID%] %TAG%",
                "icecastServerAddress": "192.168.1.1:8080",
                "icecastMountpoint": "op25-0",
                "icecastMountExt": ".xspf",
                "icecastPass": "hackme",
                "delay": 0.0
            }
        ]
    },
    "audio": {
        "module": "sockaudio.py",
        "instances": [
            {
                "#instance_name": "",
                "instance_name": "audio0",
                "device_name": "pulse",
                "udp_port": 23446,
                "audio_gain": 1.0,
                "number_channels": 1
            }
        ]
    },
    "terminal": {
        "module": "terminal.py",
        "terminal_type": "curses",
        "curses_plot_interval": 0.1,
        "http_plot_interval": 1.0,
        "http_plot_directory": "../www/images",
        "tuning_step_large": 1200,
        "tuning_step_small": 100
    }
}
 

dudegt

KQ4EVS
Premium Subscriber
Joined
Aug 19, 2012
Messages
173
Location
Kennesaw, GA
Boatbod op25 does support decoding from a .wav file using the fsk4 demodulator. Whether or not it works for your specific application I really cannot say; it's experimental and was developed so that I could take DSD .wav files and replay them. You'll need to use multi_rx.py and you'll probably need to play with the "wav_gain" parameter.

Example configuration (cfg.json):
Code:
{
    "channels": [
        {
            "name": "Playback",
            "device": "sdr0",
            "trunking_sysname": "tkwav",
            "demod_type": "fsk4",
            "destination": "udp://127.0.0.1:23446",
            "meta_stream_name": "",
            "excess_bw": 0.2,
            "filter_type": "rc",
            "if_rate": 24000,
            "plot": "",
            "symbol_rate": 4800,
            "enable_analog": "off",
            "whitelist": "",
            "blacklist": ""
        }
    ],
    "devices": [
        {
            "args": "wavsrc",
            "wav_file": "vc_abc123.wav",
            "wav_gain": 14.1,
            "gains": "",
            "name": "sdr0",
            "ppm": 0.0,
            "tunable": false
        }
    ],
    "trunking": {
        "module": "tk_p25.py",
        "chans": [
            {
                "nac": "0x0",
                "sysname": "tkwav",
                "control_channel_list": "800.000000",
                "whitelist": "",
                "tgid_tags_file": "",
                "tdma_cc": false,
                "crypt_behavior": 0
            }
        ]
    },
    "metadata": {
        "module": "icemeta.py",
        "streams": [
            {
                "stream_name": "",
                "meta_format_idle": "[idle]",
                "meta_format_tgid": "[%TGID%]",
                "meta_format_tag":  "[%TGID%] %TAG%",
                "icecastServerAddress": "192.168.1.1:8080",
                "icecastMountpoint": "op25-0",
                "icecastMountExt": ".xspf",
                "icecastPass": "hackme",
                "delay": 0.0
            }
        ]
    },
    "audio": {
        "module": "sockaudio.py",
        "instances": [
            {
                "#instance_name": "",
                "instance_name": "audio0",
                "device_name": "pulse",
                "udp_port": 23446,
                "audio_gain": 1.0,
                "number_channels": 1
            }
        ]
    },
    "terminal": {
        "module": "terminal.py",
        "terminal_type": "curses",
        "curses_plot_interval": 0.1,
        "http_plot_interval": 1.0,
        "http_plot_directory": "../www/images",
        "tuning_step_large": 1200,
        "tuning_step_small": 100
    }
}
so I can only use FSK4 not CPQSK because the site I want to demodulate is a Phase 2 Simulcast my config I posted was based on your example for WAV files I modified it slightly
 

boatbod

Member
Joined
Mar 3, 2007
Messages
3,409
Location
Talbot Co, MD
so I can only use FSK4 not CPQSK because the site I want to demodulate is a Phase 2 Simulcast my config I posted was based on your example for WAV files I modified it slightly
CQPSK demod chain requires complex (iq) samples, whereas the FSK4 demod chain is based on real (float) samples. Perhaps more importantly, to be able to decode p25 phase 2 voice you'll need to already be receiving the control channel in order to have all the information (wacn, sysid) necessary for unscrambling to take place.
 

dudegt

KQ4EVS
Premium Subscriber
Joined
Aug 19, 2012
Messages
173
Location
Kennesaw, GA
CQPSK demod chain requires complex (iq) samples, whereas the FSK4 demod chain is based on real (float) samples. Perhaps more importantly, to be able to decode p25 phase 2 voice you'll need to already be receiving the control channel in order to have all the information (wacn, sysid) necessary for unscrambling to take place.
got it, I was thinking maybe specifying those in the config might suffice. But if I were to get a sample of Phase 1 traffic would I get better luck?
 

boatbod

Member
Joined
Mar 3, 2007
Messages
3,409
Location
Talbot Co, MD
got it, I was thinking maybe specifying those in the config might suffice. But if I were to get a sample of Phase 1 traffic would I get better luck?
Phase 1 audio can be decoded straight from the voice channel. IMBE does not scramble the frames.
 

dudegt

KQ4EVS
Premium Subscriber
Joined
Aug 19, 2012
Messages
173
Location
Kennesaw, GA
Phase 1 audio can be decoded straight from the voice channel. IMBE does not scramble the frames.
That seems to have done the trick, one last thing I saw some old threads on trying to save audio to a file is that at all possible as well or is it deprecated?
 

boatbod

Member
Joined
Mar 3, 2007
Messages
3,409
Location
Talbot Co, MD
That seems to have done the trick, one last thing I saw some old threads on trying to save audio to a file is that at all possible as well or is it deprecated?
I think osmocom op25 might still be able to do it, but the "logfile_workers" code in boatbod op25 rx.py is broken and unmaintained.
 

dudegt

KQ4EVS
Premium Subscriber
Joined
Aug 19, 2012
Messages
173
Location
Kennesaw, GA
Liquidsoap accepts (receives) UDP audio packets from either rx.py or multi_rx.py applications.
got it I managed to feed a liquid soap udp stream to a .liq file with the following contents to save it as an mp3 then


Code:
set("frame.audio.samplerate", 8000)
input = mksafe(input.external(buffer=0.25, channels=2, samplerate=8000, restart_on_error=false, "./audio.py -u 23446 -x 2 -s"))
output.file(%mp3, "TEST.MP3", input)
 

wgbecks

Active Member
Joined
Jan 17, 2005
Messages
1,020
Location
NE Wisconsin
got it I managed to feed a liquid soap udp stream to a .liq file with the following contents to save it as an mp3 then

I am using Liquidsoap to feed my private streams as well as to save mp3 audio archives to an Apache server that are set for auto deletion after seven days. Very handy tool!
 
Last edited:

dudegt

KQ4EVS
Premium Subscriber
Joined
Aug 19, 2012
Messages
173
Location
Kennesaw, GA
I am using Liquidsoap to feed my private streams as well as to save mp3 audio archives to an Apache server that are set for auto deletion after seven days. Very handy tool!
It is indeed just wish that logfile workers worked, I want to have multiple OP25 instances, and having to configure 5-6 UDP and liquid soap instances isn't the most convenient
 

dudegt

KQ4EVS
Premium Subscriber
Joined
Aug 19, 2012
Messages
173
Location
Kennesaw, GA
I am struggling with properly closing the UDP ports Im using for audio feeding into liquidsoap. I have to keep finding new ports because it thinks its already in use
 

boatbod

Member
Joined
Mar 3, 2007
Messages
3,409
Location
Talbot Co, MD
It is indeed just wish that logfile workers worked, I want to have multiple OP25 instances, and having to configure 5-6 UDP and liquid soap instances isn't the most convenient

@boatbod I am going to try and tinker with multi_rx and rx to see if I can migrate the logfile worker process into mulit_rx

Have fun! If you want to make it work in trunking.py (for rx.py) I'd be happy to accept a pull request for those updates, but I'm not going to propagate it into tk_p25.py (for multi_rx.py).

I am struggling with properly closing the UDP ports Im using for audio feeding into liquidsoap. I have to keep finding new ports because it thinks its already in use
You just need to disable the audio player(s) in cfg.json by setting the instance_name(s) to ""
 

dudegt

KQ4EVS
Premium Subscriber
Joined
Aug 19, 2012
Messages
173
Location
Kennesaw, GA
Have fun! If you want to make it work in trunking.py (for rx.py) I'd be happy to accept a pull request for those updates, but I'm not going to propagate it into tk_p25.py (for multi_rx.py).


You just need to disable the audio player(s) in cfg.json by setting the instance_name(s) to ""
setting it blank will still allow audio feeding to liquidsoap? What does leaving it blank do just not play the audio to the hardware soundcard?
 

wgbecks

Active Member
Joined
Jan 17, 2005
Messages
1,020
Location
NE Wisconsin
Setting "module" to blank prevents your JSON from opening sockaudio.py. This is necessary when using Liquidsoap to avoid
conflicts in that the source input() function contained in the LIQ script calls audio.py that in turn imports sockaudio.py.

You still have the option of using local speaker or headphone audio in addition to streaming by enabling the appropriate
"Local Audio Output" operator in your LIQ script.

Keep in mind the "Default" UDP audio port in op25 is 23456 and you'll need to set the "Channel Destination" to this port, or you
can opt to configure audio.py (inside of your LIQ script) to match a different port selected for your "Channel Destination".

JSON:
 "audio": {
        "module": "",
        "instances": [
            {
                "instance_name": "audio0",
                "device_name": "default",
                "udp_port": 23466,
                "audio_gain": 1.0,
                "number_channels": 1
            }
 
Last edited:
Top