Difference between revisions of "Recording from HDMI"

From MythTV Official Wiki
Jump to: navigation, search
(HDMI capture device: I got rid of the unnecessary audio transcoding step)
(mythexternrecorder configuration file: I got rid of the unnecessary transcoding step)
 
Line 581: Line 581:
 
{{Code box|mythexternrecorder1.conf|
 
{{Code box|mythexternrecorder1.conf|
 
<pre>[RECORDER]
 
<pre>[RECORDER]
command="/usr/bin/ffmpeg -hide_banner -nostats -loglevel panic -i udp://192.168.1.60:17001 -vcodec copy -filter_complex "asetpts=PTS-0.3/TB" -codec:a aac -b:a 128k -f mpegts pipe:1"
+
command="/usr/bin/ffmpeg -hide_banner -nostats -loglevel panic -i udp://192.168.1.60:17001 -vcodec copy -filter_complex "asetpts=PTS-0.3/TB" -f mpegts pipe:1"
 
cleanup="/bin/bash /home/mythtv/cleanup_command hdmicap1.local"
 
cleanup="/bin/bash /home/mythtv/cleanup_command hdmicap1.local"
 
desc="hdmicap1 %CHANNUM%&nbsp;%CHANNAME%&nbsp;%CALLSIGN%"
 
desc="hdmicap1 %CHANNUM%&nbsp;%CHANNAME%&nbsp;%CALLSIGN%"

Latest revision as of 15:55, 4 March 2023

This article is a case study of the adoption of HDMI capture devices for the purpose of recording video and audio using MythTV. It also touches on infrared channel changing and the use of system events.

Context

For many years I have been recording analogue video and audio from set-top boxes using Hauppauge HVR-1900 capture devices (served in Linux by the pvrusb2 module). Whereas this worked OK there were several disadvantages, including the fact that only SD video can be captured this way, which is also letterboxed.

So I wanted to move to a fully-digital capture pipeline, at reasonable cost.

I already had access to a number of unencrypted DVB-C channels, which I was able to record using an HDHR, but the remaining channels can only be accessed through set-top boxes provided by the cable company. Their only digital output is HDMI, so the problem is focused on capturing audio and video from HDMI.

Video and audio capture pipeline

Each of the pipelines consists of a set-top box, an HDMI capture device and an IR blaster for controlling the set-top box.

HDMI capture device

Based on the information on this page and the further resources it links to, I decided to purchase LKV373A senders. These devices are intended to transport HDMI audio/video over gigabit ethernet, and so they are normally sold as sets of a sender and a receiver. However, for our purposes only the sender is needed, and it is possible to purchase only the senders from some online sellers.

Note that these devices can only be used for recording if the HDMI signal is HDCP-free. The topic of dealing with HDCP if present is not discussed and please don’t ask about it here.

These devices capture HDMI video and audio, and output it as an H.264 stream. Audio is limited to 2-channel MP2 at 192 kbit/s.

Several hardware revisions of this device exist; what is needed is V3 hardware, and the firmware recommended on the above page. Don’t buy unless you are sure that you’ll be getting V3 hardware.

I assigned each capture device a fixed IP address in my DHCP servers and gave them each a DNS hostname.The HDMI capture device needs to be configured to send its H.264 stream to the IP address of the MythTV backend server. This is done by means of an HTTP GET command as described on Juul’s page above. In my environment the URL that causes the capture device to start streaming is as follows:

http://192.168.1.69/dev/info.cgi?action=streaminfo&rtp=on&multicast=on&mcastaddr=192.168.1.60

In this URL, 192.168.1.69 is the IP address of the capture device, while 192.168.1.60 is the IP address of the MythTV backend server.

Unfortunately, the stream is always sent to UDP port 5004, so that if you have more than one HDMI capture device in use at the same time, a UDP socket listening to port 5004 on the backend server would receive intermingled data from all capture devices, which is useless. So either we need to be able to instruct the application that receives the streams to create separate sockets based not only on the destination port number but also on the source address, or we need to separate the streams onto different destination UDP ports. I went for the latter solution.

Specifically, I created nftables rules that redirect the video streams from port 5004 to ports 17001, 17002 and 17003 based on whether the stream comes from the 1st, 2nd or 3rd HDMI capture device.


Script.png /etc/nftables.conf

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
        chain input {
                type filter hook input priority 0;
        }
        chain forward {
                type filter hook forward priority 0;
        }
        chain output {
                type filter hook output priority 0;
        }
}

table inet nat {
        chain prerouting {
                type nat hook prerouting priority dstnat;
                iif enp3s0 ip saddr { 192.168.1.69 } udp dport 5004 dnat 192.168.1.60:17001;
                iif enp3s0 ip saddr { 192.168.1.74 } udp dport 5004 dnat 192.168.1.60:17002;
                iif enp3s0 ip saddr { 192.168.1.75 } udp dport 5004 dnat 192.168.1.60:17003;
        }
}

In the above file, 192.168.1.{69,74,75} are the IP addresses of the HDMI capture devices, while 192.168.1.60 is the IP address of the MythTV backend, and enp3s0 is the name of the Ethernet interface on which the streams arrive on the backend server.

On the backend server, the video/audio stream can be captured using ffmpeg as follows. The example is for the 1st HDMI capture device; substitute the port number for the other devices as needed:

$ /usr/bin/ffmpeg -hide_banner -nostats -loglevel panic -i udp://192.168.1.60:17001 -vcodec copy -filter_complex "asetpts=PTS-0.3/TB" -f mpegts pipe:1

Some explanation on this incantation:

  • -hide_banner, -nostats and -loglevel panic are needed in order to prevent ffmpeg outputting anything other than video/audio, since mythexternrecorder expects this command to output the stream on its standard output (stdout).
  • -i udp://192.168.1.60:17001 tells ffmpeg from where to acquire the stream.
  • -vcodec copy tells ffmpeg to apply the copy codec to the video content of the stream, meaning that video is transparently copied from the input to the output stream without any processing.
  • -filter_complex "asetpts=PTS-0.3/TB" improves lipsync by telling ffmpeg to subtract 300ms from the presentation time stamps of audio packets.
  • -f mpegts tells ffmpeg to encapsulate the stream as an MPEG transport stream.
  • pipe:1 tells ffmpeg to output the stream on stdout.

ffmpeg is used to receive the stream because minor tweaks are needed for it to be usable by MythTV. These tweaks do not take a lot of CPU so they can be done safely in real time. Another option would have been to record the raw stream and have MythTV transcode it later. However, such an approach would prevent live TV from working.

IR blaster

In my previous setup I had only been using two set-top boxes and had been controlling them using LIRC with two IR blaster LEDs connected to an MCE USB IR receiver/sender. However, I wanted to adopt a different IR blaster methodology for the following reasons:

  • Firstly, the LIRC software project carries a lot of technical debt, which results in LIRC being quite difficult to configure and use.
  • Secondly, I wanted to move to a setup involving 3 capture pipelines, meaning that I have 3 STBs to control rather than 2, so that in any event I needed additional IR blaster hardware.


Whereas many other options exist, I decided to adopt Arduino Nano hardware with GirsLite firmware, as described on this page. The Arduino Nanos I use only have the blaster LEDs equipped (called IR sending diodes on the above page); the visible-light LEDs and IR receivers have not been equipped because they are not needed.

I placed each Arduino Nano in a small plastic enclosure and drilled a small hole such that I can precisely point the blaster at the STB it is meant to control without interfering with the other STBs.

I created udev rules in order to ensure that each Arduino Nano is reliably assigned a persistent name upon each boot of the backend server.

I used IrScrutinizer (a Java program described and available here) and a fully-equipped Arduino Nano to capture the IR signals emitted by the STB remote. My STBs use the TDC-38 protocol, and, for example, the following command line sends digit 1 to the STB:

$ harchardware --arduino --device /dev/arduino1 --transmit --protocol TDC-38 --names D=18,F=2,S=10

The harchardware tool is included in the IrScrutinizer package.

In order to send multiple signals in one harchardware invocation, use the --file option as follows:

Create a file containing the signals to be sent, e.g.

$ cat arguments.txt
--protocol TDC-38 --names D=18,F=2,S=10
--protocol TDC-38 --names D=18,F=5,S=10
--protocol TDC-38 --names D=18,F=2,S=10

Then send them all in one go as follows:

$ harchardware --arduino --device /dev/arduino1 transmit --file arguments.txt

This is needed because harchardware is also a Java application, and upon each invocation the JVM has to start which (depending on your server hardware) can take several seconds, which is too long given the inter-digit timeout STBs typically have when receiving a multi-digit channel number. This problem occurs even on my backend which sports a 4GHz Intel CPU, fast RAM and SSD.

Note that the --file option to harchardware is, as of this writing, available only in snapshot releases; it will be included in a future general release.

Alternative method

Having said all that, I am actually using a different method for issuing blaster commands to the Arduinos, not involving harchardware. The reason is that I had implemented this method before Bengt Martensson, the author of IrScrutinizer and harchardware, had developed the --file option.

My method is based on using the GirsLite firmware directly using an expect script. I wrote a bash script that generates an expect script such as the following:

#!/usr/bin/expect -f
set portID [open /dev/arduino1 r+]
set baud 115200
fconfigure $portID -mode "115200,n,8,1"
fconfigure $portID -blocking 0 -buffering none
spawn -open $portID
set timeout 2
send -- "\r"
expect "OK\r\n"
# 1
send -- "send 1 38000 0 28 0 315 315 315 630 315 315 630 630 315 315 630 630 630 630 315 315 315 315 315 315 315 315 315 315 630 630 315 89000\r"
expect "OK\r\n"
# 2
send -- "send 1 38000 0 26 0 315 315 315 630 315 315 630 630 315 315 630 630 630 630 315 315 315 315 315 315 315 315 630 630 630 89315\r"
expect "OK\r\n"
# 1
send -- "send 1 38000 0 28 0 315 315 315 630 315 315 630 630 315 315 630 630 630 630 315 315 315 315 315 315 315 315 315 315 630 630 315 89000\r"
expect "OK\r\n"

That script is then executed as follows:

$ /usr/bin/expect -f filename

This is very fast (no JVM startup delay). The send commands were obtained from IrScrutinizer’s logfile.

One additional complication is that the STBs didn’t reliably receive two of the same infrared signals one after the other, such as repeating digits in channel numbers. For this reason I modified the bash script to insert delays between identical signals in the expect script.

I didn't know this at the time, but with hindsight only my alternative method is able to deal with the repeating digit problem: the --file option to harchardware does not support the insertion of delays.

Saving power

With a view to enabling power savings, I have connected the power supply of each pipeline to a USB-controlled power strip, which enables me to switch each outlet on and off individually.

I went for this radical approach for the following reasons:

  • Among the various devices that make up a pipeline, only the STB natively supports being switched on and off, so simply using infrared to turn the STB on and off would still leave the other devices running and consuming power 24/7.
  • The STB can be configured to either go into standby when turned off by means of the infrared signal, or to go into a deep sleep mode. Only in the latter mode does the STB actually conserve a meaningful amount of power, because in standby it continues to be synchronised to the cable signal. But waking from deep sleep takes around 90 seconds, which is very similar to the amount of time it takes the STB to boot from power-on. There is therefore very little reason not to switch the STB off completely when not in use.

The challenge is to power the pipeline up before recordings start, making sure that we don’t try and tune to the desired channel until sufficient time has passed for the STB to fully boot to a state in which it can receive and act on infrared signals.

A simple approach would be to power the pipeline up from the REC_PENDING system event, and be done with it. This event is triggered repeatedly before the beginning of scheduled recordings: at 120s, 90s, 60s, and 30s before the recording is due to start. So if we power the pipeline up 120s before the recording starts, the channel change command that is triggered by the beginning of the recording will be successful.

However, we can’t be sure that the REC_PENDING event will indeed be triggered that long ahead of time:

  • At the time of writing this, there is a MythTV bug that causes the REC_PENDING event only to begin being triggered 60s before the recording starts, rather than 120s. That bug was resolved in fixes/33 and master before this article was published.
  • If a recording rule is created late then that is another reason why the 120s notice period would not be able to be respected.
  • Live TV is essentially a recording without any notice at all.

So what we need to do is tell the channel changing script how long ago the pipeline was powered on, so that it knows whether and how long it needs to wait before sending infrared signals to the STB. This means that channel changes may be delayed by a few (tens of) seconds from the beginning of recordings, but that is better than the channel change not happening at all. This period of uncertainty needs to be mitigated by having recording rules start the recording sufficiently early relative to the start time provided by the guide data.

Integration scripts

blast_arduino.sh

This script sends IR signals to the set-top box. The signals can be a channel number (specified as either a multi-digit number such as 121 or a series of single digits such as 1 2 1) or any other signal name for which an entry exists in the sendcmds.txt file. Once again: that file was constructed based on IrScrutinizer log output.

The script generates an expect script and then executes that.


Script.png blast_arduino.sh

#!/bin/bash
# Optional: --dry-run
# $1: transmitter
# $2 and following: channel number (1 or more digits)

dryrun=0

logger --id=$PPID -p local7.info "$0 called with parameters $* by PID $PPID" -s 2>> /home/mythtv/hdmicap.log

if [ "$1" == "--dry-run" ] ; then
  dryrun=1
  shift
fi

case $1 in
  1 | hdmicap1 | hdmicap1.example.com)
    export device=/dev/arduino1
    export outfile=/tmp/expect1
    export switchno=1
    ;;
  2 | hdmicap2 | hdmicap2.example.com)
    export device=/dev/arduino2
    export outfile=/tmp/expect2
    export switchno=2
    ;;
  3 | hdmicap3 | hdmicap3.example.com)
    export device=/dev/arduino3
    export outfile=/tmp/expect3
    export switchno=3
    ;;
  *)
    exit 1
    ;;
esac

cat << EOF > ${outfile}
#!/usr/bin/expect -f
set portID [open ${device} r+]
set baud 115200
fconfigure \$portID -mode "115200,n,8,1"
fconfigure \$portID -blocking 0 -buffering none
spawn -open \$portID
set timeout 2
send -- "\r"
expect "OK\r\n"
EOF

shift
prevcmd=none

while (( "$#" )); do
  if [ -n "$1" ] && [ "$1" -eq "$1" ] 2>/dev/null; then # this is a number
    numstr=$1
    for (( i=0; i<${#numstr}; i++ )); do
      cmd=`fgrep ${numstr:$i:1}: /home/mythtv/sendcmds.txt | awk '{$1=""; print $0}'`
      if [ "$cmd" == "$prevcmd" ]; then
        echo sleep 1 >> ${outfile}
      fi
      echo \# ${numstr:$i:1} >> ${outfile}
      echo send -- \"send 1 38000 ${cmd}\\r\" >> ${outfile}
      echo expect \"OK\\r\\n\" >> ${outfile}
      prevcmd=$cmd
    done
  else  # this is not a number
    cmd=`fgrep $1: /home/mythtv/sendcmds.txt | awk '{$1=""; print $0}'`
    if [ "$cmd" == "$prevcmd" ]; then
      echo sleep 1 >> ${outfile}
    fi
    echo \# $1 >> ${outfile}
    echo send -- \"send 1 38000 ${cmd}\\r\" >> ${outfile}
    echo expect \"OK\\r\\n\" >> ${outfile}
    prevcmd=$cmd
  fi
  shift
done

if [ "$dryrun" == "0" ] ; then
  ### Make sure that the STB is fully ready before changing channels
  /home/mythtv/hcpow.sh $switchno wait

  /usr/bin/expect -f ${outfile}
fi

rm ${outfile}

exit 0

start_streaming

This helper script instructs the HDMI capture device to start streaming to the MythTV backend.


Script.png start_streaming

#!/bin/bash
myIPaddr=$(hostname -I)
sourceIPaddr="$(host -t A ${1} | awk '/has.*address/{print $NF; exit}')"
if [ "`/bin/ping -c 1 $sourceIPaddr`" ] ; then
  URL="http://$sourceIPaddr/dev/info.cgi?action=streaminfo&rtp=on&multicast=on&mcastaddr=$myIPaddr"
  curl --silent --output /dev/null $URL
else
  exit 1
fi
exit 0

stop_streaming

I haven’t yet found a good method for telling the device to stop streaming. For now this script merely resets the HDMI capture device. However, since the pipeline is also being switched off at the end of recordings it doesn't really matter.


Script.png stop_streaming

#!/bin/bash
if [ "`/bin/ping -c1 $1`" ]; then
  nc $1 9999 -i 1 <<EOS
reboot
EOS
else
  #### Deal with a crashed HDMI capture device
  ### Code omitted from this example
fi
exit 0


have_video_lock

This script asks the HDMI capture device whether an HDMI signal is available on its input.


Script.png have_video_lock

#!/bin/bash

if [ "`/bin/ping -c 1 $1`" ] ; then
nc $1 9999 -i 1 > /tmp/have_video_lock_$1.out <<EOS
get_video_lock
exit
EOS

  result=$(cat /tmp/have_video_lock_$1.out  | head -n 5 | tail -n 1)
  result=$(echo $result|tr -d '"\r\n')

  case $result in
    "Lock")
      nc $1 9999 -i 1 > /tmp/have_video_lock_$1.out <<EOS
get_hdcp
exit
EOS
      result=$(cat /tmp/have_video_lock_$1.out  | head -n 5 | tail -n 1)
      result=$(echo $result|tr -d '"\r\n')

      case $result in
        "Off")
          retval=0
	  ;;
        *)
          retval=2
	  ;;
      esac
      ;;
    "Unlock")
      retval=1
      ;;
    *)
      retval=2
      ;;
  esac
else
  retval=3
fi

exit $retval

tuner_command

This script is invoked by the MythTV external recorder as specified in the |mythexternrecorder configuration file. It performs some sanity checking, then tells the STB to start streaming and tunes the STB to the correct channel.


Script.png tuner_command

#!/bin/bash
#
# $1: FQDN of the streaming device
# $2: channel number

# https://stegard.net/2022/05/locking-critical-sections-in-shell-scripts/
lock_acquire() {
    # Open a file descriptor to lock file
    exec {LOCKFD}>$lockfile || return 1

    # Block until an exclusive lock can be obtained on the file descriptor
    flock -x $LOCKFD
}

lock_release() {
    test "$LOCKFD" || return 1
    
    # Close lock file descriptor, thereby releasing exclusive lock
    exec {LOCKFD}>&- && unset LOCKFD
}

case $1 in
  hdmicap1.example.com | hdmicap1)
    export switchno=1
    ;;
  hdmicap2.example.com | hdmicap2)
    export switchno=2
    ;;
  hdmicap3.example.com | hdmicap3)
    export switchno=3
    ;;
  *)
    exit 1
esac

export lockfile=/tmp/tuner_command$switchno
lock_acquire || { echo "$0[$$]: Failed to acquire lock (held by $(cat $lockfile))" ; exit 0; }
echo $$ > $lockfile

# BEGIN CRITICAL SECTION

/home/mythtv/have_video_lock $1
if [ $? -ne 0 ]; then
  elapsed=0
  until /home/mythtv/have_video_lock $1; do
    if [ $elapsed -ge 20 ]; then
      ## Power-cycling code omitted
    elif [ $elapsed -ge 60 ]; then
      lock_release
      exit 1
    fi
    elapsed=$(($elapsed+2))
    sleep 2s
  done
fi

### Start streaming
/home/mythtv/start_streaming $1

### Tune the STB to the required channel
/home/mythtv/blast_arduino.sh $switchno $2

# END CRITICAL SECTION

lock_release
exit 0

cleanup_command

This script is invoked by mythexternrecorder.


Script.png cleanup_command

#!/bin/bash

logger --id=$PPID -p local7.info "$0 called with parameters $* by PID $PPID" -s 2>> /home/mythtv/hdmicap.log

/home/mythtv/stop_streaming $1

# Also switch the STB off
case $1 in
  hdmicap1.example.com | hdmicap1)
    export switchno=1
    ;;
  hdmicap2.example.com | hdmicap2)
    export switchno=2
    ;;
  hdmicap3.example.com | hdmicap3)
    export switchno=3
    ;;
  *)
    exit 1
esac

/home/mythtv/hcpow.sh $switchno off

system-events/recording-pending

This script is an event handler for the MythTV recording pending system event with %CARDID% as its sole parameter.

I have configured each pipeline with two virtual tuners, and the first instances of the three pipelines have CARDID values 32-34, and their second instances have CARDID values 35-37.


Script.png system-events/recording-pending

#!/bin/bash
logger --id=$PPID -p local7.info "$0 called with parameters $* by PID $PPID" -s 2>> /home/mythtv/hdmicap.log
devid=$1
case $devid in
  32|33|34)
    /home/mythtv/hcpow.sh $(expr $devid - 31) on
    ;;
  35|36|37)
    /home/mythtv/hcpow.sh $(expr $devid - 34) on
    ;;
esac

hcpow.sh

This script is a finite state machine that governs the powering of the pipelines.

The program that actually turns the USB-controlled power strip on and off is sispmctl.


Script.png hcpow.sh

#!/bin/bash
#
# Usage: hcpow.sh <pipeline> <command>
#
#   <pipeline> is a number (1-3)
#
#   <command> is one of:
#      status: emits the power state of the given pipeline on stdout
#      on: power the given pipeline on
#      off: power the given pipeline off
#      wait: wait for the given pipeline to transition from the poweringup
#         state to the on state.
#      MAGIC: intended only for recursive use by hcpow itself. Requires
#         an additional parameter: PID of the caller
#
#   Each pipeline is in one of the following states:
#
#      off: The pipeline is powered down, and there is no state file.
#
#      poweringup: The pipeline has been instructed to power up but isn't
#         fully ready yet. There is a state file having a name of the form
#         hcpow.<pipeline>.poweringup.<PID>
#
#      on: The pipeline is powered up and fully operational. There is a
#         state file having a name of the form hcpow.<pipeline>.on
#
# Returns a nonzero value in case of errors. Error messages are emitted on
# sterr. There is normally no output on stdout, except in response to the
# status command.

stateroot=/tmp/hcpow

pipeline=$1
cmd=$2

case ${cmd} in
  status)
    if [ -f $stateroot.$pipeline.poweringup.* ] ; then
      echo poweringup
    elif [ -f $stateroot.$pipeline.on ]; then
      echo on
    else
      echo off
    fi
    ;;

  on)
    if [ -f $stateroot.$pipeline.* ] ; then
      # Do nothing: the pipeline is either already on or is powering up
      :
    else
      touch $stateroot.$pipeline.poweringup.$$
      sispmctl -o $pipeline
      # Can't schedule the rename using at because it doesn't support
      # timespecs expressed in seconds. So just sleep in the background.
      $0 $pipeline MAGIC $$ &
    fi
    ;;
  MAGIC)
    # Called recursively, and as a background task, by the on command
    sleep 90s
    # Note that this use of mv is robust in case the pipeline gets powered
    # down again during the 90s period, because if that happens the state
    # file will have been deleted such that the mv is a no-op.
    # Furthermore if the pipeline also gets powered on again during that
    # period, then the PID of the script handling the on command will be
    # different from the one the MAGIC command was invoked by, so the
    # mv is matched to the correct invocation.
    mv $stateroot.$pipeline.poweringup.$3 $stateroot.$pipeline.on
    ;;

  off)
    rm $stateroot.$pipeline.*
    sispmctl -f $pipeline
    ;;

  wait)
    hadtowait=0
    if [ -f $stateroot.$pipeline.poweringup.* ]; then
      logger --id=$PPID -p local7.info "$0 $*: waiting for pipeline $pipeline to boot" -s 2>> /home/mythtv/hdmicap.log
      hadtowait=1
    fi
    while [ ! -f $stateroot.$pipeline.on ] ; do
      # Detect whether the pipeline has been powered down (state file has
      # disappeared). Exit if so.
      if [ ! -f $stateroot.$pipeline.* ] ; then
        break
      fi
      sleep 1s
    done
    if [ "$hadtowait" == "1" ] ; then
      logger --id=$PPID -p local7.info "$0 $*: done waiting for pipeline $pipeline" -s 2>> /home/mythtv/hdmicap.log
    fi
    ;;

  *)
    echo $0: unknown command $cmd >&2
    exit 1
    ;;
esac

exit 0

MythTV configuration

MythTV’s mythexternrecorder is used as the vehicle for ingesting the captured streams into MythTV. It is briefly discussed on the ExternalRecorder page, where it is referred to as the Generic External Recorder.

mythexternrecorder configuration file

Each capture pipeline requires a separate instance of mythexternrecorder, and therefore a separate configuration file. The one shown here is for the first pipeline; the differences relative to the other pipelines are trivial.


Script.png mythexternrecorder1.conf

[RECORDER]
command="/usr/bin/ffmpeg -hide_banner -nostats -loglevel panic -i udp://192.168.1.60:17001 -vcodec copy -filter_complex "asetpts=PTS-0.3/TB" -f mpegts pipe:1"
cleanup="/bin/bash /home/mythtv/cleanup_command hdmicap1.local"
desc="hdmicap1 %CHANNUM% %CHANNAME% %CALLSIGN%"

[TUNER]
channels=/home/mythtv/hdmicap_channels.conf
command="/bin/bash /home/mythtv/tuner_command hdmicap1.local %CHANNUM%"
newepisodecommand="/bin/bash /home/mythtv/blast_arduino.sh 1 %CHANNUM%"
timeout=80000

mythtv-setup

In mythtv-setup, each capture pipeline has a Capture Card entry as follows:

Card Type = External (black box) Recorder ->

Command Path = /usr/bin/mythexternrecorder --conf /home/mythtv/mythexternrecorder1.conf

Tuning timeout = 120000

Each capture card is also connected to a Video Source as follows:

Input Name = MPEG2TS

Display Name = <pick something you like, but the last two characters have to be globally unique. I went for HDMI1, HDMI2 and HDMI3>

Video Source = select an appropriate video source

External Channel Change Command = <blank>