RivendellJackIcecast

From OpenSourceRadio
Jump to: navigation, search

Serving Icecast with Rivendell via JACK

Assuming Rivendell and JACK are already installed, all we are doing is adding an Icecast feed directly from Rivendell. This technique also assumes that Jackd is installed and configured.

Overview

We install Liquidsoap, add a configuration file to connect to a remote (public) Icecast server, then patch Rivendell and Liquidsoap together with JACK.

Details

The following is a list of steps to add Liquidsoap to a running Rivendell system. The list includes sample configuration files in-line.

  • sudo apt-get install liquidsoap liquidsoap-plugin-icecast liquidsoap-plugin-jack liquidsoap-plugin-lame liquidsoap-plugin-ogg liquidsoap-plugin-vorbis
  • sudo rm /etc/init.d/liquidsoap
  • cat /etc/systemd/system/liquidsoap@.service
[Unit]
Description=PMW Icecast Liquidsoap Encoder
After=rdcatchd@%i.service
Requires=rdcatchd@%i.service

[Service]
User=%i
ExecStart=/usr/bin/liquidsoap --verbose /etc/liquidsoap/10-icecast-encoder.liq
ExecStartPost=/usr/local/bin/pmw-encoder-jack-connect.sh
Type=simple
Restart=always
KillSignal=SIGHUP
StandardOutput=syslog
StandardError=inherit
EnvironmentFile=-/etc/locale.conf

[Install]
WantedBy=rivendell.target

  • cat /etc/liquidsoap/10-icecast-encoder.liq
(Make sure to match the variables in this script with your icecast server configuration!)
#!/usr/bin/liquidsoap

##############################################################################
##
## Copyright 2015 Phantom Machine Works. See LICENSE for copying information.
##
## Encode from the on-board audio card via JACK, send to stlHost:stlPort
## where there is a listening icecast server.
##
## Note: the password for the source client in the icecast
## configuration MUST match the password used here

## Log dir
set ( "log.file.path", "/var/log/liquidsoap/10-stl-encoder.log" )

## Use the telnet server for run-time access
set ( "server.telnet", true )

## Get input from JACK
## NB: pay attention to JACK connections!
## See /usr/local/bin/pmw-encoder-jack-connect.sh if you make changes here!
feedFM = input.jack(
  id = "encode-fm",
  buffer_size = 1,
  clock_safe = true,
  server = "default"
)
feedHD2 = input.jack(
  id = "encode-hd2",
  buffer_size = 1,
  clock_safe = true,
  server = "default"
)

##############################################################################
## Settings used in the output stream
stlID   = "PMW Icecast Encoder"
stlProt = "http"
stlHost = "YOUR-STREAM-SERVER"
stlPort = YOUR-SERVER-PORT-NUMBER
stlMntFM  = "YOUR-FIRST-MOUNTPOINT"
stlMntHD2  = "YOUR-SECOND-MOUNTPOINT"
stlUser = "source"
stlPass = "YOUR-SOURCE-CLIENT-PASSWORD"
stlDesc = "YOUR-STREAM-DESCRIPTION"
stlURL  = "YOUR-STREAM-URL"

## create a new clock that will handle all the stream outputs
clock.assign_new (
  id = "stl",
  sync = true,
  [
    # output the FIRST STREAM audio to the icecast stream server in MP3 format
    output.icecast (
      start = true,
      id = stlID,
      protocol = stlProt,
      host = stlHost,
      port = stlPort,
      user = stlUser,
      password = stlPass,
      mount = stlMntFM,
      url = stlURL,
      description = stlDesc,

      # (unlabeled) format:
      # internal_quality: Lame algorithms internal quality. A value
      # between 0 and 9, 0 being highest quality and 9 the worst
      # (default: 2).
      %mp3( samplerate = 48000, stereo = true, bitrate = 160, internal_quality = 1 ),

      # (unlabeled) source:
      # buffer is necessary when using the internal liquidsoap clock
      mksafe( buffer( buffer = 0.5, max = 1.0, feedFM ))
    ),
    # output the SECOND STREAM audio to the icecast stream server in MP3 format
    output.icecast (
      start = true,
      id = stlID,
      protocol = stlProt,
      host = stlHost,
      port = stlPort,
      user = stlUser,
      password = stlPass,
      mount = stlMntHD2,
      url = stlURL,
      description = stlDesc,

      # (unlabeled) format:
      # internal_quality: Lame algorithms internal quality. A value
      # between 0 and 9, 0 being highest quality and 9 the worst
      # (default: 2).
      %mp3( samplerate = 48000, stereo = true, bitrate = 160, internal_quality = 1 ),

      # (unlabeled) source:
      # buffer is necessary when using the internal liquidsoap clock
      mksafe( buffer( buffer = 0.5, max = 1.0, feedHD2 ))
    )
###
###    # Example of a local OGG Vorbis archive of the audio signal:
###
###    # Create an archive file of the stream
###    #  "/audio/archive/%Y/%m/%d/%H-%M-%S.ogg"
###    #,
###    output.file (
###      id = "archive",
###      append = true,
###
###      # close, then re-open the file at the top of each hour
###      reopen_when = { 0m0s },
###
###      # (unlabeled) format
###      %vorbis( samplerate = 48000, channels = 2, quality = 0.25 ),
###
###      # (unlabeled) filename
###      "/audio/archive/%Y/%m/%d/%H-%M-%S.ogg",
###
###      # (unlabeled) source
###      amplify( 10.0, mksafe( buffer( feed )))
###    )
  ]
)

  • cat /usr/local/bin/pmw-encoder-jack-connect.sh
#!/bin/zsh

##############################################################################
##############################################################################
##############################################################################
###          DO NOT USE THIS SCRIPT WITHOUT MAKING MODIFICATIONS!          ###
##############################################################################
##############################################################################
##############################################################################
#
# NOTE:
# This script is COMPLETELY installation-specific! See the "BUG ALERT" below.
#

##############################################################################
##############################################################################
#
# BUG ALERT!!
#
# This script completely depends on the audio encoder
# configuration. As of the time of this writing, the encoder is
# liquidsoap, and the encoder configuration is in
# /etc/liquidsoap/10-icecast-encoder.liq.
#
##############################################################################
##############################################################################

zmodload zsh/regex

##############################################################################
#
# log STDOUT and STDERR of this script and all commands called by this
# script to separate files
#
exec 1> /var/tmp/${0##*/}.out
exec 2> /var/tmp/${0##*/}.err

# Get the name of the encoder port from the Liquidsoap script.
liquidsoapScript=${LIQUIDSOAP_SCRIPT:-${ROOT:-/}etc/liquidsoap/10-icecast-encoder.liq}
encoderID=$(fgrep -A 1 "input.jack" ${liquidsoapScript} | tail -n 1 | cut -d'"' -f2)

# Establish up the port basenames.
rivendellPlaybackBase=${RIVENDELL_PLAYBACK_BASE:-"rivendell_0:playout"}
encoderBase=${ENCODER_BASE:-"${encoderID}:in"}
allPorts=( ${rivendellPlaybackBase} ${encoderBase} )

# Attempt to wait until all the ports we are looking for are actually
# ready to be connected.
attemptLimit=10
thisAttempt=1
sleepDuration=0.50

# Initial pause while jackd sets things up.
sleep $(( sleepDuration * 2 ))

while (( thisAttempt < attemptLimit )) ; do
    jackPorts=( $(/usr/bin/jack_lsp) )
    x=0
    for need in ${allPorts} ; do
        found=0
        for port in ${jackPorts} ; do
            [[ ${port} =~ "${need}_.*" ]] && { ((x++)) ; found=1 ; }
        done
       
        (( found )) && break
    done
    if (( x == ${#allPorts} )) ; then
        : ports are up, we are good to go
        break
    fi
    sleep ${sleepDuration}
    (( thisAttempt++ ))
done

if (( thisAttempt < attemptLimit )) ; then
    # Connect Rivendell primary output (playout_0X) to the jack encoder
    jack_connect ${rivendellPlaybackBase}_0L ${encoderBase}_0
    jack_connect ${rivendellPlaybackBase}_0R ${encoderBase}_1

    exitValue=0
else
    echo "JACK ports were not ready after waiting $(( attemptLimit * sleepDuration )) seconds. Contact Support." 2>&1
    exitValue=1
fi

exit ${exitValue}

Note: This script has at least one flaw: we don't report which JACK ports are not available. Needs more work...
  • The final step is to use a JACK connection manager to connect all the ports that are created by the Liquidsoap instance and Rivendell.