I’ve been using LGE webOS smart TV since 2014. It has been working seemingly well. Except for some nuisances like inability to play Classic FM. Raspberry Pi 4 with 8 GB RAM seems a tempting choice for a smart TV set-top box. But here is an issue: hardware accelerated video decoding only works in a 32-bit OS that can only operate 4GB RAM at most. It’s more than just trading precious RAM for CPU cycles, videos don’t play smoothly at all without hardware decoding.

Raspberry Pi 4 Model B

So here is how I set it up with very little to no compromise:

  • Install aarch64 Manjaro as a main OS to utilize the whole RAM
  • Setup a 32-bit Raspberry Pi OS container to run hardware-accelerated VLC, Kodi as well as other Raspberry Pi goodies like Wolfram Mathematica
  • Create a couple of bash scripts to simplify launching contained applications.

While it’s trivial to download and boot the Manjaro image, let me describe how to run raspios in more detail. First we need to download an image and extract its root file system into /var/lib/machines/raspios. The partition table doesn’t allow using losetup. So some manual calculation will be necessary using the following commands:

fdisk -l
mount -v -o loop,offset=$((512*start2)) -t ext4 raspios.img /mnt
mount -v -o loop,offset=$((512*start1)),sizelimit=$((512*size1)) -t vfat raspios.img /mnt/boot

Then the files could be just copied over the desired location:

sudo rsync -raP /mnt/* /var/lib/machines/raspios/

The following configuration files for systemd-nspawn need be created as recorded in the repository sakhnik/chbox:

./etc/polkit-1/rules.d/49-nopasswd_limited.rules
./etc/systemd/nspawn/raspios.nspawn
./etc/systemd/system/systemd-nspawn@raspios.service.d/override.conf

The machine could be started at this point with machinectl start raspios. The startup may take a bit of time because of slow dhcpcd service. It can be profiled and improved later with systemd-analyze blame. The machine could be configured to be started automatically: systemctl enable systemd-nspawn@raspios. The real magic happens in the script to execute programs from the container. Here we can define whatever is necessary in the container environment to allow accessing host graphics and audio:

pserver=/tmp/pulse-host/$USER

if ! (machinectl -q shell $USER@raspios /bin/mount | grep -q $pserver); then
    # Allow Arch accessing pulseaudio
    machinectl -q shell $USER@raspios /bin/mkdir -p $pserver
    machinectl bind --read-only raspios /run/user/$UID/pulse $pserver
fi

# Allow local connections from the container to the host X11 server
xhost +local: || true

machinectl -q shell \
    --setenv=DISPLAY=$DISPLAY \
    --setenv=PULSE_SERVER=unix:$pserver/native \
    $USER@raspios "$@"

Having defined a polkit rule earlier allows us executing some machinectl commands without elevation. And thus, the empowered $USER can obtain a shell in the container and will be able to launch GUI apps there. For instance, to launch VLC capable of hardware-accelerated decoding, we could put the following script into /usr/local/bin/vlc:

#!/bin/bash -x

# Quote parameters into one interpretable string.
input=
for i in "${@}"; do
    input="${input} ${i@Q}"
done

$HOME/bin/raspios.sh /bin/bash -xc "cd '$(pwd)'; vlc ${input}"

To take one step further, we can copy the vlc.desktop file from the container to the host, and modify the lines Exec= and TryExec= to point to /usr/local/bin/vlc.

VLC MMAL

Finally, we can create a script to watch the clipboard. When a new YouTube (or whatever other service youtube-dl supports) URL is noticed, it could be played automatically. Thus, the script yt-watch.sh was created. Now we can browse the playlist and just highlight the desired URL or copy it to the clipboard to have it played in a couple of seconds.