Skip to content

Instantly share code, notes, and snippets.

@fizzyade
Last active February 27, 2024 22:43
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fizzyade/00a4ea6e171b4d1e8c33d9dd8f10784c to your computer and use it in GitHub Desktop.
Save fizzyade/00a4ea6e171b4d1e8c33d9dd8f10784c to your computer and use it in GitHub Desktop.
tvheadend container + nvidia ffmpeg under Unraid

NVIDIA+TVHEADEND+UNRAID+ACCELERATED FFMPEG

I recently had a need to get hardware accelerated ffmpeg running on tvheaded under unraid, I eventually achieved this with a few tricks, here are my notes on what I did to achieve this.

First up, Unraid NVIDIA support

Install the Nvidia plugin under Unraid, this installs a custom kernel complete with the nidia drivers and docker runtime to support thiese.

Next install a hardware accelerated FFMPEG

Create a new docker container named "ffmpeg", we will clone the following repo:

jrottenberg/ffmpeg:4.1-nvidia

I chose this version as it is linked against a version of the nvidia drivers that is compatible with the Unraid Nvidia plugin.

Go into advanced settings on the docker container and set the "Extra Parameters" to the following:

-it --entrypoint bash --runtime=nvidia

The container as default simply runs an instance of ffmpeg and then exits. Modifying the entrypoint allows us to spawn a container containing the FFMPEG binary and keep it running so we don't have to create a container on the fly every time.

Setting the runtime to nvidia is important, without it hardware acceleration won't work.

Now we have a container set up which is capable of running a hardware accelerated FFMPEG.

Modifying the tvheadend container

We need to make some changes to the tvheadend container to allow it to use our ffmpeg, you could make these changes by cloning the docker file and then creating your own modified image, but I have gone with a second option of overlaying some modified files over the existing installation.

In the appdata folder for your tvheadend, create the following files with the following content:

Filename: run (for example /mnt/user/appdata/tvheadend/run)

#!/usr/bin/with-contenv bash

IFS=" " read -r -a RUN_ARRAY <<< "$RUN_OPTS"

apk add docker
chmod o+rw /var/run/docker.sock

exec \
        s6-setuidgid abc /usr/bin/tvheadend -C -c /config "${RUN_ARRAY[@]}"

I have added the lines to install docker and change the permissions on docker.sock. This allows us to access the containers running on the host, the permission change is required as the tvheadend runs as an unprivileged user in the container and with the default permissions cannot access the docker.sock socket.

Ensure the file has execute permissions (chmod +x /mnt/user/appdata/tvheadend/run on the host)

Filename: ffmpeg.hw (for example /mnt/user/appdata/tvheadend/ffmpeg.hw)

#!/bin/sh

docker exec -i ffmpeg /usr/local/bin/ffmpeg $@

(If you called your container something other than ffmpeg then you will need to change the "-i ffmpeg" to match the name of your container.)

Ensure the file has execute permissions (chmod +x /mnt/user/appdata/tvheadend/ffmpeg.hw on the host)

Modifying the tvheadend container

We need to add the following paths to the tvheadend container:

Host Path: /var/run/docker.sock
Container Path: /var/run/docker.sock

Host Path: /mnt/user/appdata/tvheadend/run
Container Path: /etc/services.d/tvheadend/run

Host Path: /mnt/user/appdata/tvheadend/run
Container Path: /etc/services.d/tvheadend/run

Host Path: /mnt/user/appdata/tvheadend/ffmpeg.hw
Container Path: /usr/local/bin/ffmpeg.hw

(I have noticed tvheadend doing some weird things with the run script, sometimes a copy appears in /root, so I have added that as well, no idea if it's actually required, but it doesn't hurt so....)

Configuring tvheadend

Ensure that the ffmpeg container is started and restart the tvheadend container, you should now be able to add a custom stream format in tvheaded with a command like:

Command Line:

/usr/local/bin/ffmpeg.hw -hwaccel cuvid -c:v mpeg2_cuvid -i pipe:0 -c:a copy -c:v h264_nvenc -b:v 5M -f matroska pipe:1

Mime Type:

video/h264

When you select that stream profile you should find that it uses the hardware accelerated ffmpeg, you can check this by running nvidia-smi on the host, you should see ffmpeg running.

@m0ngr31
Copy link

m0ngr31 commented Nov 26, 2019

How do you create the custom stream format? I don't see anything about commands or mime types

@fizzyade
Copy link
Author

How do you create the custom stream format? I don't see anything about commands or mime types

In tvheadend configuration, go to the stream tab, click add and select MPEG-TS Spawn/built in.

Then you will get a window with command line and mime type. You then need to select this stream either as default or make sure it's selected in the m3u file.

@m0ngr31
Copy link

m0ngr31 commented Nov 26, 2019

Okay, I got that, but nvidia-smi doesn't show any activity when I'm watching. Does it only do it when it starts buffering or something?

@fizzyade
Copy link
Author

it’s definitely using the ffmpeg from the docker container? (i.e the stub script file which does the docker exec command).

@fizzyade
Copy link
Author

and you’ve definitely set up the ffmpeg container to use the nvidia runtime?

@m0ngr31
Copy link

m0ngr31 commented Nov 26, 2019

Yeah, I setup everything per the instructions.

Only thing I can think of is the stream shows as htsp profile:

2019-11-26 09:42:47.004 [ INFO] subscription: 0005: "172.19.37.70 [ | Kodi Media Center ]" subscribing on channel "OUTHD", weight: 150, adapter: "HDHomeRun ATSC-C Tuner #1 (172.19.37.191)", network: "ATSC-C", mux: "777MHz", profile="htsp", hostname="172.19.37.70", username="172.19.37.70", client="Kodi Media Center"```

@fizzyade
Copy link
Author

oh, you’re using kodi and a hdhomerun. Are you sure that the hdhomerun isn’t being used directly?

@m0ngr31
Copy link

m0ngr31 commented Nov 26, 2019

I'm using the TVHeadEnd Kodi PVR addon, but it only does HTSP I guess. Might be my issue then.

@fizzyade
Copy link
Author

I'm using the TVHeadEnd Kodi PVR addon, but it only does HTSP I guess. Might be my issue then.

Sorry I can’t be of more help, I don’t use Kodi so don’t know how to make use of it.

When using http you pass in the profile to use as a GET parameter.

@psycmos
Copy link

psycmos commented Oct 10, 2021

I did you tutorial but the WEBPAGE of docker of TVH with this path not appear.
Docker starts but no TVH panel

In your tutorial you repeat it 2times:
Host Path: /mnt/user/appdata/tvheadend/run
Container Path: /etc/services.d/tvheadend/run

Host Path: /mnt/user/appdata/tvheadend/run
Container Path: /etc/services.d/tvheadend/run

You have any idea what are causing it?
I'm using the latest nvidia plugin with unraind 6.10RC1 and TVH

@bartaadalbert
Copy link

Install NVIDIA Container Toolkit: To enable GPU support in Docker containers, you need to install the NVIDIA Container Toolkit. You can follow the official instructions here: https://github.com/NVIDIA/nvidia-docker

On a Linux system, you can typically install it using the following commands:

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update && sudo apt-get install -y nvidia-docker2
sudo systemctl restart docker

Create an external Docker network named 'tvff'

docker network create tvff

Now, create a Docker Compose configuration file (docker-compose.yml) with the following content:

  version: '3.8'
  services:
    tvheadend:
      image: lscr.io/linuxserver/tvheadend:latest
      container_name: tvheadend
      hostname: tvheadend
      environment:
        - PUID=1000
        - PGID=1000
        - TZ=Etc/UTC
        - RUN_OPTS=
      volumes:
        - /path/to/data:/config
        - /path/to/recordings:/recordings
        - /var/run/docker.sock:/var/run/docker.sock  # Mapping the Docker socket
        - /mnt/user/appdata/tvheadend/run:/etc/services.d/tvheadend/run  # Mapping configuration directory
        - /mnt/user/appdata/tvheadend/ffmpeg.hw:/usr/local/bin/ffmpeg.hw # Mapping FFmpeg binary
      ports:
        - 9981:9981
        - 9982:9982
      devices:
        - /dev/dri:/dev/dri
        - /dev/dvb:/dev/dvb
      restart: unless-stopped
  
    ffmpeg:
      image: jrottenberg/ffmpeg:4.1-nvidia
      container_name: ffmpeg
      hostname: ffmpeg
      entrypoint: /usr/local/bin/start_ffmpeg.sh
      devices:
        - /dev/dri:/dev/dri
      runtime: nvidia
      volumes:
        - /path/to/recordings:/recordings
        - ./start_ffmpeg.sh:/usr/local/bin/start_ffmpeg.sh
  
  networks: 
    default: 
      external: 
        name: tvff

@bockbilbo
Copy link

@bartaadalbert do you mind sharing the content of "start_ffmpeg.sh"? I am having problems keeping the ffmpeg container up. I tried with entrypoint: bash, but it is not working. Thanks!

@bockbilbo
Copy link

Made it work with the following:

#!/bin/bash
tail -f /dev/null

@bockbilbo
Copy link

bockbilbo commented Jan 24, 2024

Also, if anyone is interested, sharing the ffmpeg CLI commands used for different profiles, respecting the input's number of audio, video and subtitle channel counts.

nvidia-h264-aac-matroska

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

nvidia-h264-aac-matroska-1080p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -vf "scale=-1:'min(1080,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

nvidia-h264-aac-matroska-720p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -vf "scale=-1:'min(720,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

nvidia-h264-aac-mp4

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -movflags frag_keyframe+empty_moov -f mp4 pipe:1 

nvidia-h264-aac-mp4-1080p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -vf "scale=-1:'min(1080,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -movflags frag_keyframe+empty_moov -f mp4 pipe:1 

nvidia-h264-aac-mp4-720p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -vf "scale=-1:'min(720,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -movflags frag_keyframe+empty_moov -f mp4 pipe:1 

nvidia-hevc-aac-matroska

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v hevc_nvenc -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

nvidia-hevc-aac-matroska-1080p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v hevc_nvenc -vf "scale=-1:'min(1080,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

nvidia-hevc-aac-matroska-720p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v hevc_nvenc -vf "scale=-1:'min(720,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

@bartaadalbert
Copy link

bartaadalbert commented Jan 24, 2024

Start FFmpeg Script (start_ffmpeg.sh)

Here's the content for the start_ffmpeg.sh script. Place this script in your Docker container to keep FFmpeg running continuously.

#!/bin/bash
while true; do
  /usr/local/bin/ffmpeg "$@"
  sleep 1  # Add a small delay before restarting FFmpeg
done

Ensure that this script is executable and properly mounted in your Docker container. Set this script as the entrypoint in your Docker configuration.


Docker Compose Configuration

When running FFmpeg and Tvheadend in Docker containers, managing log file sizes is crucial to avoid consuming excessive disk space. Below is the Docker Compose configuration, with log rotation settings for both services.

version: '3.8'
services:
  tvheadend:
    image: lscr.io/linuxserver/tvheadend:latest
    container_name: tvheadend
    hostname: tvheadend
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Etc/UTC
      - RUN_OPTS=
    volumes:
      - /path/to/data:/config
      - /path/to/recordings:/recordings
      - /var/run/docker.sock:/var/run/docker.sock  # Mapping the Docker socket
      - /mnt/user/appdata/tvheadend/run:/etc/services.d/tvheadend/run  # Mapping configuration directory
      - /mnt/user/appdata/tvheadend/ffmpeg.hw:/usr/local/bin/ffmpeg.hw # Mapping FFmpeg binary
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "3"
    ports:
      - 9981:9981
      - 9982:9982
    devices:
      - /dev/dri:/dev/dri
      - /dev/dvb:/dev/dvb
    restart: unless-stopped

  ffmpeg:
    image: jrottenberg/ffmpeg:4.1-nvidia
    container_name: ffmpeg
    hostname: ffmpeg
    entrypoint: /usr/local/bin/start_ffmpeg.sh
    devices:
      - /dev/dri:/dev/dri
    runtime: nvidia
    volumes:
      - /path/to/recordings:/recordings
      - ./start_ffmpeg.sh:/usr/local/bin/start_ffmpeg.sh
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "3"

networks: 
  default: 
    external: 
      name: tvff

@bockbilbo
Copy link

bockbilbo commented Jan 25, 2024

Thanks @bartaadalbert. I think it's best to use my start_ffmpeg.sh script. Fffmpeg is not a service, and the purpose of the script is to ensure the container is active so that we can use its ffmpeg command with nvidia support. For that purpose, tailing /dev/null should be less resource intensive that an infinite loop loading ffmpeg.

On your compose file, you might want to add restart:always to ffmpeg and add the dependency to ffmpeg on tvheadend:
services:

  tvheadend:
    [...]
    depends_on:
      - ffmpeg
  ffmpeg:
    [...]
    restart: always

On a separate note, the ffmpeg.hw script by @fizzyade should have the arguments quoted to avoid problems if passing arguments with spaces to ffmpeg.

#!/bin/sh

docker exec -i ffmpeg /usr/local/bin/ffmpeg "$@"

@isaacolsen94
Copy link

Also, if anyone is interested, sharing the ffmpeg CLI commands used for different profiles, respecting the input's number of audio, video and subtitle channel counts.

nvidia-h264-aac-matroska

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

nvidia-h264-aac-matroska-1080p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -vf "scale=-1:'min(1080,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

nvidia-h264-aac-matroska-720p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -vf "scale=-1:'min(720,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

nvidia-h264-aac-mp4

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -movflags frag_keyframe+empty_moov -f mp4 pipe:1 

nvidia-h264-aac-mp4-1080p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -vf "scale=-1:'min(1080,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -movflags frag_keyframe+empty_moov -f mp4 pipe:1 

nvidia-h264-aac-mp4-720p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v h264_nvenc -vf "scale=-1:'min(720,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -movflags frag_keyframe+empty_moov -f mp4 pipe:1 

nvidia-hevc-aac-matroska

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v hevc_nvenc -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

nvidia-hevc-aac-matroska-1080p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v hevc_nvenc -vf "scale=-1:'min(1080,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

nvidia-hevc-aac-matroska-720p

/usr/local/bin/ffmpeg.hw -hwaccel cuda -i pipe:0 -map 0:v? -map 0:a? -map 0:s? -c:a aac -c:v hevc_nvenc -vf "scale=-1:'min(720,ih)'" -c:s copy -disposition:s:0 0 -default_mode infer_no_subs -bufsize 12000k -f matroska pipe:1 

Hey what "Mime type:" did you use for these? Thank you for sharing!

@isaacolsen94
Copy link

isaacolsen94 commented Feb 27, 2024

@fizzyade thank you for this awesome write up! Any way you'd be willing to do an updated version? I followed the tutorial for Unraid (I'm on 6.12.8), ffmpeg-4.2 docker and the LSIO tvheaded docker. But it doesn't seem to be working. Maybe a new guide isn't needed and I just may need some guidance on what I'm doing wrong

@isaacolsen94
Copy link

How did you guys implement this with a recording profile? Mine currently seem to fail if I use of one these presets

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment