OP25 Setting up op25 to stream to broadcastify

Status
Not open for further replies.

boatbod

Member
Joined
Mar 3, 2007
Messages
3,316
Location
Talbot Co, MD
I have a headless ubuntu 16.04 machine running multiple instances of op25 and darkice that I use to send two streams to Broadcastify. It took me a while to figure just out how to get everything set up and able to auto-start itself at boot time. Here's what I did:

Set up ALSA loopback devices
Any streaming setup is going to require a way to take the audio from your application (op25) and deliver it to your streaming client (darkice). Since this is a headless server, I have no need to listen to the audio locally, therefore a simple alsa loopback is needed per stream.

First make sure the snd-aloop device with 2 separate substreams is loaded at boot time. This is hardware dependent, but if your system uses the hda-intel card you can use this script.
Code:
[b]/etc/modprobe.d/hda-intel.conf[/b]
softdep snd-hda-intel post: snd-aloop
options snd-aloop enable=1 index=1 pcm_substreams=2
A Raspberry PI3 has different sound hardware and would require the following instead.
Code:
[b]/etc/modprobe.d/bcm2835.conf[/b]
softdep snd-bcm2835 post: snd-aloop
options snd-aloop enable=1 index=1 pcm_substreams=2

Next, set up the appropriate asound.conf to define loopback device names loop0 and loop1
Code:
[b]/etc/asound.conf[/b]
# output device
pcm.loopout0 {
  type plug
  slave.pcm "hw:Loopback,0,0"
}

# input device
pcm.loopin0 {
  type dsnoop
  ipc_key 686592
  slave.pcm "hw:Loopback,1,0"
}

# duplex plug device
pcm.loop0 {
  type plug
  slave {
    pcm {
      type asym
      playback.pcm "loopout0"
      capture.pcm "loopin0"
    }
  }
}

# output device
pcm.loopout1 {
  type plug
  slave.pcm "hw:Loopback,0,1"
}

# input device
pcm.loopin1 {
  type dsnoop
  ipc_key 686593
  slave.pcm "hw:Loopback,1,1"
}

# duplex plug device
pcm.loop1 {
  type plug
  slave {
    pcm {
      type asym
      playback.pcm "loopout1"
      capture.pcm "loopin1"
    }
  }
}

Set up Darkice
Install the package.
Code:
sudo apt-get install darkice
Edit the basic configuration files to suit your server's password and mount point.
Code:
[b]/etc/darkice-stream0.cfg[/b]
# sample DarkIce configuration file, edit for your needs before using
# see the darkice.cfg man page for details

# this section describes general aspects of the live streaming session
[general]
duration        = 0        # duration of encoding, in seconds. 0 means forever
bufferSecs      = 1         # size of internal slip buffer, in seconds
reconnect       = yes       # reconnect to the server(s) if disconnected

# this section describes the audio input that will be streamed
[input]
device          = loop0  # OSS DSP soundcard device for the audio input
sampleRate      = 22050  # sample rate in Hz. 22050 is the RR Standard
bitsPerSample   = 16     # bits per sample. 16 for mono feeds, 32 for stereo feeds
channel         = 1      # channels. 1 = mono, 2 = stereo

# this section describes a streaming connection to an IceCast server
# there may be up to 8 of these sections, named [icecast-0] ... [icecast-7]
# these can be mixed with [icecast2-x] and [shoutcast-x] sections
 
# If you want to send to your own icecast server, you basically copy
# this whole section and put in the values to point the feed to your own
# server instead of the RR information below
 
# Don't forget, the name [icecast2-0] needs to change to [icecast2-1]
# and 2 and 3 as you add more feed destinations.
 
[icecast2-0]
# The audio format to encode to, we're using mp3 here
format          = mp3

# The bit rate mode to use, the commented out lines are for a fixed bit
# rate "constant bit rate" at 16 bits.  I prefer the variable bit rate
# for the sound quality it has and the throughput is very similar on avg

#bitrateMode    = cbr (do not use vbr)
#bitrate        = 16 or 32 (16 for mono feeds, 32 for stereo feeds)

sampleRate      = 22050     # sample rate in Hz. 22050 is the RR Standard
bitrateMode     = cbr
bitrate		= 16

# Quality at .1, max is 1.0, but as you increase, so does the bandwidth
# used.  For mono scanner audio .1 is totally fine.
quality         = 0.1

# Takes the input and mixes it down to a mono output for the feed.
channel         = 1

# Cuts frequency response over 5kHz.  For scanner feed this mostly
# eliminates hiss and harshness on paging tones.  Saves a bunch of
# bandwidth as well.
lowpass         = 5000

# Server shown on your RR feed owner page that hosts your feed
# You need to log in to RR, go to Account -> My Live Audio Feeds ->
# click the feed owner page icon at the far right of your feed listing.
# Then you will see all this information.

# From Feed Technical Details, the "Server"
server          = SERVER.broadcastify.com

# From Feed Technical Details, the "Port"
port            = 80      # port of the IceCast server, usually 8000

# From Feed Technical Details, the Password exactly as shown
password        = PASSWORD

# From Feed Technical Details, the mount...remove the leading "/", it's
# not needed in this file.  So if it is /999999 make it just 999999
mountPoint      = MOUNTPOINT

# name of the stream
# On My Live Audio Feeds, it's the first part ending in a colon ":"
name            = Short name

# On My Live Audio Feeds, it's the part in parentheses in the Feed name
description     = Description

# I don't think this is really needed because RR takes care of links
url             = http://api.radioreference.com/js/?key=XXXXXXX&feedId=XXXX
genre           = Scanner
public          = yes

Set up OP25
Assuming you already have a working op25 installation, all you need to do is change the audio output device to match the appropriate ALSA loopback name.
Code:
[b]~/op25/op25/gr-op25_repeater/apps/op25-stream0.sh[/b]
./rx.py -l http:127.0.0.1:8080 --args "rtl=0" --gains 'lna:36' [b]-O loop0[/b] -D cqpsk -S 960000 -T trunk-fire.tsv -q 0 -d -200 -v 1 -2 -u 23456 -U 2> stderr-stream0.2

Set up systemd auto startup at boot time
First create the service files to define which commands and/or scripts need to be run and what they depend on.
Code:
[b]/etc/systemd/system/darkice-stream0.service[/b]
[Unit]
Description=darkice-stream0
After=syslog.target network.target nss-lookup.target network-online.target
Requires=network-online.target

[Service]
ExecStart=/usr/bin/darkice -c /etc/darkice-stream0.cfg 
RestartSec=5
Restart=on-failure

[Install]
WantedBy=multi-user.target
Code:
[b]/etc/systemd/system/op25-stream0.service[/b]
[Unit]
Description=op25-fire
After=syslog.target network.target nss-lookup.target network-online.target
Requires=network-online.target

[Service]
User=[b]username[/b]
Group=[b]group[/b]
WorkingDirectory=/home/homedir/op25/op25/gr-op25_repeater/apps
ExecStart=/bin/bash -- op25-stream0.sh
RestartSec=5
Restart=on-failure

[Install]
WantedBy=multi-user.target

Once the files are created, enable them using the systemctl command.
Code:
systemctl enable darkice-stream0
systemctl enable op25-stream0
Then either reboot, or start the services manually
Code:
systemctl start darkice-stream0
systemctl start op25-stream0

Notes
1. The systemctl process will monitor both the darkice executable and op25-stream0.sh script and restart it if it fails. To properly stop either process, use the systemctl stop command.
Code:
systemctl stop op25-stream0
If you want to permanently disable it from restarting after boot, use "systemctl disable"

2. You can add a second stream very easily by duplicating the darkice and systemd files (with appropriate renaming) and substituting "loop1" alsa device instead of "loop0".

3. I chose to use the http-based terminal type because it behaves nicely in a headless environment and can be accessed remotely across a network. Change the IP address in the op25 startup script accordingly.
 

hagensieker

Member
Premium Subscriber
Joined
Jul 23, 2017
Messages
201
I know this is an older post but just wanted to say "Thanks". I set up OP25 on a Pi and was trying to stream to Broadcastify and this post nailed it. Worked perfectly.
 

hagensieker

Member
Premium Subscriber
Joined
Jul 23, 2017
Messages
201
The only thing I'm flailing away at now is the systemd file won't run OP25. In fact I can't start it from crontab or anything. Have to start it manually over a VNC session. Small potatoes but you know how that kind of stuff can bug you. :)
 

boatbod

Member
Joined
Mar 3, 2007
Messages
3,316
Location
Talbot Co, MD
The only thing I'm flailing away at now is the systemd file won't run OP25. In fact I can't start it from crontab or anything. Have to start it manually over a VNC session. Small potatoes but you know how that kind of stuff can bug you. :)

Ah yes, it took me a long time to figure out how to auto start things in the correct order at boot time. The following are the .service files I'm using in /etc/systemd/system:

gnorbury@NUC7:/etc/systemd/system$ cat darkice-fire.service
Code:
[Unit]
Description=darkice-fire
After=syslog.target network.target nss-lookup.target network-online.target
Requires=network-online.target

[Service]
ExecStart=/usr/bin/darkice -c /etc/darkice-fire.cfg 
RestartSec=5
Restart=on-failure

[Install]
WantedBy=multi-user.target

gnorbury@NUC7:/etc/systemd/system$ cat op25-fire.service
Code:
[Unit]
Description=op25-fire
After=syslog.target network.target nss-lookup.target network-online.target
Requires=network-online.target

[Service]
User=gnorbury
Group=gnorbury
WorkingDirectory=/home/gnorbury/op25/op25/gr-op25_repeater/apps
ExecStart=/bin/bash -- first-fire.sh
RestartSec=5
Restart=on-failure

[Install]
WantedBy=multi-user.target

The op25 startup sequence relies on the existence of a shell script containing the correct parameters for rx.py. In this instance it's called first-fire.sh
You will obviously need to change the hard-coded paths to reflect your own system.

ETA: Starting op25 in a headless environment (i.e. anything started outside of a user login session) pretty much requires that you configure and use the http terminal type otherwise it will hang up or just plain refuse to work.
 

hagensieker

Member
Premium Subscriber
Joined
Jul 23, 2017
Messages
201
Ah yes, it took me a long time to figure out how to auto start things in the correct order at boot time. The following are the .service files I'm using in /etc/systemd/system:

gnorbury@NUC7:/etc/systemd/system$ cat darkice-fire.service
Code:
[Unit]
Description=darkice-fire
After=syslog.target network.target nss-lookup.target network-online.target
Requires=network-online.target

[Service]
ExecStart=/usr/bin/darkice -c /etc/darkice-fire.cfg 
RestartSec=5
Restart=on-failure

[Install]
WantedBy=multi-user.target

gnorbury@NUC7:/etc/systemd/system$ cat op25-fire.service
Code:
[Unit]
Description=op25-fire
After=syslog.target network.target nss-lookup.target network-online.target
Requires=network-online.target

[Service]
User=gnorbury
Group=gnorbury
WorkingDirectory=/home/gnorbury/op25/op25/gr-op25_repeater/apps
ExecStart=/bin/bash -- first-fire.sh
RestartSec=5
Restart=on-failure

[Install]
WantedBy=multi-user.target

The op25 startup sequence relies on the existence of a shell script containing the correct parameters for rx.py. In this instance it's called first-fire.sh
You will obviously need to change the hard-coded paths to reflect your own system.

ETA: Starting op25 in a headless environment (i.e. anything started outside of a user login session) pretty much requires that you configure and use the http terminal type otherwise it will hang up or just plain refuse to work.

I tried every possible variant of that yesterday with no luck. I do call op25 from a script (op25.sh in /home/pi/op25)

Code:
 #! /bin/sh

cd /home/pi/op25/op25/gr-op25_repeater/apps

./rx.py --args 'rtl' -N 'LNA:47' -S 2400000 -f 857.2625e6 -o 25000 -q -2 -O loop0 -T trunk.tsv -V -2 -U 2> stderr-stream0.2

What I did get to work was to start it from crontab like this:

Code:
@reboot /usr/bin/screen -d -m /home/pi/op25/op25.sh

This of course runs it under screen and it works fine. I'll keep hacking at the systemd script. Probably just fouling up something. Thanks for the help.
 

hagensieker

Member
Premium Subscriber
Joined
Jul 23, 2017
Messages
201
I should add I had no trouble with the darkice service file. Just the op25 running in a terminal thing.
 

hagensieker

Member
Premium Subscriber
Joined
Jul 23, 2017
Messages
201
I got it to start like this:

Used the same screen command and added Type=forking

Code:
[Unit]
Description=op25
After=syslog.target network.target nss-lookup.target network-online.target
Requires=network-online.target

[Service]
User=pi
Group=pi
Type=forking
ExecStart=/usr/bin/screen -d -m /home/pi/op25/op25.sh
RestartSec=5
Restart=on-failure

[Install]
WantedBy=multi-user.target
 

echo80

Newbie
Joined
Nov 2, 2016
Messages
5
@boatbod

This worked perfectly for me running Lubuntu 16.04, thank you! Also, I'm very interested in learning more about the Linux ALSA framework. Man pages are often woefully lacking on thorough documentation. Do you have other references that helped you to accomplish this?
 

boatbod

Member
Joined
Mar 3, 2007
Messages
3,316
Location
Talbot Co, MD
@boatbod

This worked perfectly for me running Lubuntu 16.04, thank you! Also, I'm very interested in learning more about the Linux ALSA framework. Man pages are often woefully lacking on thorough documentation. Do you have other references that helped you to accomplish this?
The online alsa library documentation was my main source. Information is scarce and sometimes you have to dig into the library our driver code to find out how stuff works.
 

james18211

Member
Joined
Oct 28, 2012
Messages
25
Thanks for the great instructions.

I've been able to get a single instance of op25 up and running, but if I start a second instance (on a second SDR), I get a number of errors about a socket already in use that seem to pertain to the audio out. I'm piping to a second Darkice instance, and if I stop the first instance of op25 the second does stream out just fine.

This is on an up to date Raspberry Pi 3 B+, with the boatbod fork. I'm running both instances manually through a VNC session so I can hold talk groups as needed.

Any suggestions would be much appreciated.

Edit:

I hadn't clued in to "-u" being the UDP audio server port. Altered that to be unique on both instances, and up and running. Might be worth adding a mention to that in the instructions, or bolding the UDP port.
 
Last edited:
Joined
Oct 8, 2005
Messages
46
Location
Metro Atlanta
Boatbod,

In your op25-stream.sh, what does this do?

-l http:127.0.0.1:8080

While I have IceCast2 running, and followed all instructions here, I can't get OP25 to feed IceCast2.


Any particular instructions I need to be aware of to get OP25 feeding Icecast?
 
Last edited:

boatbod

Member
Joined
Mar 3, 2007
Messages
3,316
Location
Talbot Co, MD
Boatbod,

In your op25-stream.sh, what does this do?

-l http:127.0.0.1:8080

While I have IceCast2 running, and followed all instructions here, I can't get OP25 to feed IceCast2.


Any particular instructions I need to be aware of to get OP25 feeding Icecast?

That option sets the terminal type to "http" (rather than curses). You can then access the terminal by pointing a web browser at RadioReference.com - Scanner Frequencies and Radio Frequency Reference

If you run two instances you need to change the port number of the second instance. (-l http:127.0.0.1:8081)

Also, there is no specific requirement to use the loopback address, and in fact it may be preferable not to do so if you will be accessing the terminal screen from a remote machine. In that case you substitute the loopback address for the machine's ip address. (e.g. -l http:192.168.1.110:8080)

My broadcastify setup uses darkice to stream to broadcastify. I did experiment with running my own local icecast2 server for testing purposes, but I'm definitely no expert on how to set that up.
 

ve2vag

Member
Feed Provider
Joined
Nov 24, 2011
Messages
35
Location
Quebec Canada
As stated in the other thread, I've done some tests. I'm using multi_rx.py with a json file, since I want to monitor 2 local ham repeaters (one DStar and one Fusion C4FM) which are 2,17 MHz apart. Using a RPI 3 with a fresh Stretch install.

Both services are confirmed working, snd-aloop module is loaded and present. Darkice's cfg file is configured to use loop0 as input, and the stream channel's setup info from Broadcastify has been inserted. Stream is online and working, but with no audio present.

I used raspi-config to setup the 3,5mm audio output as default in ALSA, instead of HDMI. The /etc/asound.conf file has been created per Boatbod's instructions.

In the multi_rx.py json file, both channels destinations are "destination": "udp://127.0.0.1:23456" for the C4FM Fusion one; and "destination": "udp://127.0.0.1:23458" for the DStar one.

I've connected speakers to the 3,5mm output, and by opening a terminal screen and using the ./audio.py script, ( ./audio.py -2), I was able to hear perfectly on them what was coming from the C4FM Fusion repeater. Everything was decoded A1. However, I was also monitoring the Broadcastify channel/stream, and no audio was heard on that.

I'm quite a Linux newbie, but I guess the snd-aloop module is not picking up what's coming from the 23456 port. Unless I made a config error or a typo somewhere, which chances are highly possible. When using audio.py, since audio was heard on the speakers, theorically snd-aloop is supposed to pick it uo and send it to loop0, and from there loop0 is streamed to Broadcastify ?

Thanks
 

boatbod

Member
Joined
Mar 3, 2007
Messages
3,316
Location
Talbot Co, MD
I'm quite a Linux newbie, but I guess the snd-aloop module is not picking up what's coming from the 23456 port. Unless I made a config error or a typo somewhere, which chances are highly possible. When using audio.py, since audio was heard on the speakers, theorically snd-aloop is supposed to pick it uo and send it to loop0, and from there loop0 is streamed to Broadcastify ?

Thanks

Please post your rx.py command lines and also your asound.conf and I should be able to advise further.

In a nutshell, unless you do some other asound.conf magic, the snd-aloop module isn't going to pick up anything that isn't directly sent to it. Right now I'm guessing you are outputting to either the 'default' device or to a hwplug:0.0 or similar. What you need to do is output to "loop0" and that will get turned around and squirted back out "loop0" to darkice BUT you won't hear it on the audio jack.
 
Last edited:

ve2vag

Member
Feed Provider
Joined
Nov 24, 2011
Messages
35
Location
Quebec Canada
I'm using multi_rx.py with a json file, since I want to monitor 2 local ham repeaters (one DStar and one Fusion C4FM) which are 2,17 MHz apart.

Command line is: ./multi_rx.py -c DIGITAL.json -v 9 in the /home/pi/op25/op25/gr-op25_repeater/apps directory.

Content of the DIGITAL.json file:

{
"channels": [

{
"demod_type": "fsk4",
"destination": "udp://127.0.0.1:23456",
"excess_bw": 0.2,
"filter_type": "rrc",
"frequency": 147120000,
"if_rate": 24000,
"name": "ysf",
"symbol_rate": 4800
},
{
"symbol_rate": 4800
"demod_type": "fsk4",
"destination": "udp://127.0.0.1:23458",
"excess_bw": 0.2,
"filter_type": "gmsk",
"frequency": 144950000,
"if_rate": 24000,
"name": "dstar",
"symbol_rate": 4800
}
],
"devices": [
{
"args": "rtl=0",
"frequency": 146035000,
"gains": "lna:48",
"name": "rtl0",
"offset": 0,
"ppm": 62,
"rate": 2400000,
"tunable": false
}
]
}

Content of the /etc/asound.conf file is the same as your tutorial in the first post:

# output device
pcm.loopout0 {
type plug
slave.pcm "hw:Loopback,0,0"
}

# input device
pcm.loopin0 {
type dsnoop
ipc_key 686592
slave.pcm "hw:Loopback,1,0"
}

# duplex plug device
pcm.loop0 {
type plug
slave {
pcm {
type asym
playback.pcm "loopout0"
capture.pcm "loopin0"
}
}
}

# output device
pcm.loopout1 {
type plug
slave.pcm "hw:Loopback,0,1"
}

# input device
pcm.loopin1 {
type dsnoop
ipc_key 686593
slave.pcm "hw:Loopback,1,1"
}

# duplex plug device
pcm.loop1 {
type plug
slave {
pcm {
type asym
playback.pcm "loopout1"
capture.pcm "loopin1"
}
}
}

I've ruled out the possibility that the RTL-SDR dongle is not working, since I can hear the audio on the speakers connected to the 3,5mm output. Also, 3,5mm out has been setup as default with raspi-config.

Thanks Boatbod
 

boatbod

Member
Joined
Mar 3, 2007
Messages
3,316
Location
Talbot Co, MD
I'm using multi_rx.py with a json file, since I want to monitor 2 local ham repeaters (one DStar and one Fusion C4FM) which are 2,17 MHz apart.

Command line is: ./multi_rx.py -c DIGITAL.json -v 9 in the /home/pi/op25/op25/gr-op25_repeater/apps directory.

Content of the DIGITAL.json file:

{
"channels": [

{
"demod_type": "fsk4",
"destination": "udp://127.0.0.1:23456",
"excess_bw": 0.2,
"filter_type": "rrc",
"frequency": 147120000,
"if_rate": 24000,
"name": "ysf",
"symbol_rate": 4800
},
{
"symbol_rate": 4800
"demod_type": "fsk4",
"destination": "udp://127.0.0.1:23458",
"excess_bw": 0.2,
"filter_type": "gmsk",
"frequency": 144950000,
"if_rate": 24000,
"name": "dstar",
"symbol_rate": 4800
}
],
"devices": [
{
"args": "rtl=0",
"frequency": 146035000,
"gains": "lna:48",
"name": "rtl0",
"offset": 0,
"ppm": 62,
"rate": 2400000,
"tunable": false
}
]
}

Content of the /etc/asound.conf file is the same as your tutorial in the first post:

# output device
pcm.loopout0 {
type plug
slave.pcm "hw:Loopback,0,0"
}

# input device
pcm.loopin0 {
type dsnoop
ipc_key 686592
slave.pcm "hw:Loopback,1,0"
}

# duplex plug device
pcm.loop0 {
type plug
slave {
pcm {
type asym
playback.pcm "loopout0"
capture.pcm "loopin0"
}
}
}

# output device
pcm.loopout1 {
type plug
slave.pcm "hw:Loopback,0,1"
}

# input device
pcm.loopin1 {
type dsnoop
ipc_key 686593
slave.pcm "hw:Loopback,1,1"
}

# duplex plug device
pcm.loop1 {
type plug
slave {
pcm {
type asym
playback.pcm "loopout1"
capture.pcm "loopin1"
}
}
}

I've ruled out the possibility that the RTL-SDR dongle is not working, since I can hear the audio on the speakers connected to the 3,5mm output. Also, 3,5mm out has been setup as default with raspi-config.

Thanks Boatbod

Oops, yes multi_rx so what's important is the audio.sh command line.
Try this one:
./audio.sh -2 -O loop0
 
Status
Not open for further replies.
Top