I created this document while installing a combo FE/BE so the instructions should be as accurate as possible.
The first part is common to both, so we will start there. We are going to build from source so the latest will always be available for the version we want to work with.
We will be installing 'mythtv-light' so the software is packaged as a .deb file that can be easily installed and upgraded.
You can find the version of v33 that you want from the link https://dl.orangedox.com/pCBmBm page for mythtv-light and you can use that. The latest are at: https://dl.orangedox.com/pCBmBm/1bO1JOe ... gB0yPsaTHe
Pick the Bookworm version,
I'll try to keep the v33 Bookworm folders up to date.
The hardware being used in this example is:
RPI4 4GB RAM
USB3 to SATA Adapter based on the ASMedia ASM1153E controller
1TB SATA SSD
USB keyboard
FHD HDMI monitor
SiliconDust HDHomeRun Quatro networked ATSC 1.0 tuner (4 tuners) (HDHR)
Ethernet connected to the same network as HDHR with Cat-5e, but WiFi can work if yours is a very good connection.
Using the Raspberry Pi Imager 1.7.5 or newer, select the Bookworm 64bit. Flash the image directly to the SATA SSD using the adapter.
The version of Bookworm that I installed had as default a locale that uses the ISO settings. Previous versions uses UTF-8. You may need to change this.
You need to download the mythtv-ight and install similar to the command below:
Code: Select all
sudo apt install ./mythtv-light_33.1-22-g26e76a3949_arm64_bookworm.deb
At this point the MythTV software is installed and if you only want a frontend then you are done.
If you want a combo FE/BE, then we have to setup the backend now.
There is a utility to make this easier so clone the following repository:
Code: Select all
cd
git clone https://github.com/MikeB2013/pi-utils.git
cd pi-utils
Make the following changes:
Code: Select all
mythtv_git_branch=fixes/33
php_version="8.2"
Code: Select all
./pi-mythbackend-helper.sh
reboot
This must be run as user 'mythtv'
Code: Select all
sudo su mythtv
cd
tv_grab_zz_sdjson_sqlite --manage-lineups --config-file $HOME/.mythtv/SD.xmltv
Then select the "Initialize/update the local database"
Take the defaults on the next several questions until it exits.
Code: Select all
tv_grab_zz_sdjson_sqlite --configure --config-file $HOME/.mythtv/SD.xmltv
Select the Lineup. If you only have one it will be listed and you can reply yes.
Take the defaults until the command exits.
Code: Select all
tv_grab_zz_sdjson_sqlite --days 0 --config-file $HOME/.mythtv/SD.xmltv
tv_grab_zz_sdjson_sqlite --manage-lineups --config-file $HOME/.mythtv/SD.xmltv
Confirm your lineup.
Choose which channels are selected. Walk thru the channels and select yes or no.
When you're done with the list of channels, just exit.
Exit back to user 'pi' with 'exit'
Now setup the backend with:
Code: Select all
mythtv-setup
Under Job Queue (Backend-Specific) I uncheck 'Allow Transcoding Jobs' and 'Allow Commerical Detection Jobs'. The RPI4 is not good at transcoding and Commerical detection also affects performance. Your mileage may vary.
You can make other changes if you want.
Under 2. Capture cards, select New Capture Card 4 times since we have a 4 tuner HDHomerun. The first select of New Capture Card takes some time comparied to the next 3, so wait.
At each New Capture Card entry select HDHomeRun Networked Tuner then press the right arrow and check the tuner you want to use and then right arrow on Recording Options and uncheck Use HDHomeRun for active EIT scan since we are using SchedulesDirect for EPG.
Repeat for the other tuner entries.
Under 4. Video Sources, select New Video Source. It will take a few seconds.Then select the new entry for New Video Source and then select Video Source Name and enter 'SD' which is what we used in our XMLTV setup. Note upper-case 'SD'
For Listings Grabber select Multinational (Schedules Direct JSON Web Service with SQLite D...
Then ESC and save and exit.
Under 5. Input connections select 'MPEG2TS' under Input Name and select 'SD' under Video Source.
Then only on the first Input Connections go to Scan for Channels and go to the bottom and select Scan.
Insert All
ESC and save. Then set the other Input conections up the same, but don't scan for them.
The rest of the default settings of Input connections should work and also allow multi-channel recording for subchannels like 5.1 and 5.2 on the same tuner.
Use 6. Channel Editor if you have some channels you don't want displayed.
Under 7. Storage Group you should fill out at least Default but can do more to suit your needs. Walk the folders for Default to /srv/mythtv/recordings. All of our storage directories in this case are under /srv/mythtv/.
reboot
Now we need to put in some delay mechanism to make sure the HDHomerun tuners are on the network before mythtv-backend.service starts up.
You are going to have to delay the startup of mythtv-backend until the HDHomerun tuners are discoverable.
You will need to install a utility to communicate with the HDHR tuners:
Code: Select all
sudo apt install hdhomerun-config
Code: Select all
sudo --login systemctl edit mythtv-backend.service
Code: Select all
[Service]
ExecStartPre=-/usr/local/bin/hdhomerun_check.py
can watch recordings etc.) If you want to prevent the backend from starting, remove the dash.
You'll need to put the hdhomerun_check.py in /usr/local/bin and make it executable, owner/group root.
hdhomerun_check.py
Code: Select all
#!/usr/bin/python3
# -*- coding: utf-8 -*-
""" See if the HD Homerun box(s) are accessible and running
Requires Python 3.6 or later.
For backends started by systemd, use:
sudo --preserve-env systemctl edit --force mythtv-backend.service
and enter or add as needed by your service:
[Service]
ExecStartPre=-/usr/local/bin/hdhomerun_check.py
Can be called with optional IP address(s) for users that
have multiple HDHRs that have STATIC addresses.
Use --help to see all options.
If run from the command line, then output will be to the screen.
Otherwise, a log file in /tmp named hdhr_discovery.log is made.
Changable with the --logfile switch.
Exit codes:
0 = success (for *ALL* HDHRs if multiple IPs were specified)
1 = no output from the hdhomerun_config discover command
2 = IPv4 and IPv6 addresses found, disable IPv6 on NIC
3 = logfile isn't writable, delete it and try again
4 = keyboard interrupt
5 x the number of HDHRs = HDHR is most likely not up
"""
__version__ = '1.28'
import argparse
import signal
import subprocess
import sys
from datetime import datetime
from os.path import basename
from os import _exit
from time import sleep
# pylint: disable=too-many-arguments,unused-argument
def keyboard_interrupt_handler(sigint, frame):
''' Handle all KeyboardInterrupts here. And, just leave. '''
_exit(4)
# pylint: enable=unused-argument
def get_program_arguments():
''' Process the command line. '''
parser = argparse.ArgumentParser(description='HDHR Access Test',
epilog='* Default values are in ()s')
parser.add_argument('HOSTS', type=str, default=None, nargs='*',
help='optional hostname(s)/IP(s) (%(default)s)')
parser.add_argument('--attempts', default=20, type=int, metavar='<num>',
help='number of tries to find HDHRs (%(default)i)')
parser.add_argument('--debug', action='store_true',
help='output additional information (%(default)s)')
parser.add_argument('--logfile', default='/tmp/hdhomerun_check.log',
type=str, metavar='<lf>',
help='optional path + name of log file (%(default)s)')
parser.add_argument('--sleep', default=1.5, type=float, metavar='<sec>',
help='seconds betweem attempts (%(default)s)')
parser.add_argument('--version', action='version',
version='%(prog)s ' + __version__)
return parser.parse_args()
def get_elapsed_time(start):
''' Calculate the time spent waiting for the HDHR to come up. '''
delta = datetime.utcnow() - start
rounded_delta = f'{delta.seconds + (delta.microseconds / 1000000):.3f}'
return rounded_delta
def log_or_print(loglevel, message, output):
''' Add timestamp, log level then print to the selected location. '''
print(datetime.now().strftime("%F %T.%f")[:-3], f'{loglevel:8}', message,
file=output)
def last_message(loglevel, result, host, start, attempt, output):
''' Common success or failure message text. '''
log_or_print(loglevel, f'{result} {"at " + host + " " if host else ""}'
f'in {get_elapsed_time(start)} seconds '
f'and {attempt} attempt{"s"[attempt == 1:]}\n', output)
def check_one_device(host, args, output):
''' Try to discover the HDHR(s). '''
attempt = 0
command = ['hdhomerun_config', 'discover']
start = datetime.utcnow()
if host:
command.append(host)
for attempt in range(1, args.attempts+1):
try:
discovery_response = subprocess.check_output(
command, text=True, stderr=subprocess.STDOUT).split()
except subprocess.CalledProcessError:
log_or_print('WARNING', f'{command[0]}: got no response, attempt: '
f'{attempt:2}', output)
sleep(args.sleep)
continue
if not discovery_response:
log_or_print('ERROR', f'No output from {command[0]}, aborting!',
output)
sys.exit(1)
if args.debug:
log_or_print('DEBUG', f'Got: {" ".join(discovery_response)}',
output)
if discovery_response.count('hdhomerun') > 1:
log_or_print('ERROR', f'{command[0]}: more than 1 IP, aborting!',
output)
sys.exit(2)
if discovery_response[0] != 'hdhomerun':
# Consider making this an ERROR and exiting not sleeping...
log_or_print('WARNING', f'{command[0]} got an unexpected response:'
f' {" ".join(discovery_response)}',
output)
sleep(args.sleep)
else:
last_message('INFO', f'Found HDHR {discovery_response[2]}', host,
start, attempt, output)
return 0
last_message('ERROR', 'No HDHR found', host, start, attempt, output)
return 5
def main(args, output=None):
''' Control checking of one or more devices. '''
log_or_print('INFO', f'Starting {basename(__file__)} v{__version__}, '
f'attempts={args.attempts}, sleep={args.sleep:.2f}', output)
if args.HOSTS:
return_value = 0
for host in args.HOSTS:
return_value += check_one_device(host, args, output)
else:
return_value = check_one_device(None, args, output)
return return_value
if __name__ == '__main__':
signal.signal(signal.SIGINT, keyboard_interrupt_handler)
ARGS = get_program_arguments()
if sys.stdin and sys.stdin.isatty():
RETURN_VALUE = main(ARGS)
else:
try:
with open(ARGS.logfile, encoding='ascii', mode='a') as file_obj:
RETURN_VALUE = main(ARGS, output=file_obj)
except PermissionError:
print(f'Can\'t write to {ARGS.logfile}, aborting!')
sys.exit(3)
sys.exit(RETURN_VALUE)
# vim: set expandtab tabstop=4 shiftwidth=4 smartindent colorcolumn=80:
You can run mythfrontend as on any system and configure it for the RPI4 as has been documented elsewhere on this forum catefory.
Now we can execute "mythfrontend" and setup and Audio and Video as below:
In Setup -> Audio -> Audio Output Device select "ALSA:hdmi:CARD=vc4hdmi0,DEV=0"
Setup Digital Audio and speakers base on your audio system.
In Setup -> Video -> Playback change what you want but the critical ones are:
Change the Current Video Playback Profile to OpenGL Normal and drill down to change Max CPUs to 4 and Deinterlacer Quality (single rate) to Low quality. Do the same for Deinterlacer quality (double rate).
Performance settings.
The RPI4 standard settings are all you need for a good picture. However, you can improve the FPS jitter statistics by overclocking. I have found that the RPI4 can be overclocked and still be reliable. Most all RPI4s can overclock to 2.0 Ghz. The unit in this case is the newest RPI4 stepping and by default on Bullseye runs at 1.85Ghz. More RPI4 overclocking information at https://magpi.raspberrypi.com/articles/ ... erry-pi-4
I overclocked it as follows:
edit /boot/config.txt
add these lines to the file:
Code: Select all
arm_freq=2147
over_voltage=6
gpu_freq=750
Since we are using a SSD we need to setup automatic trim once a week. The service that controls this is fstrim.service/timer.
Use 'lsusb' to find the vendor and product IDs for your USB3-SATA adapter. Mine is an ASMedia ASM1153E controller (Eluteng adapters) and "lsusb" shows:
Code: Select all
Bus 002 Device 002: ID 174c:55aa ASMedia Technology Inc. ASM1051E SATA 6Gb/s bridge, ASM1053E SATA 6Gb/s bridge...
Code: Select all
# ASMedia ASM1153E controller (Eluteng adapters)
ACTION=="add|change", ATTRS{idVendor}=="174c", ATTRS{idProduct}=="55aa", SUBSYSTEM=="scsi_disk", ATTR{provisioning_mode}="unmap"
Code: Select all
sudo systemctl enable --now fstrim.timer