install go2rtc on bob

This commit is contained in:
2026-04-04 19:36:14 +02:00
parent f0b56e63d1
commit ccf88187b8
537 changed files with 69213 additions and 0 deletions
@@ -0,0 +1,106 @@
# Hardware
You **DON'T** need hardware acceleration if:
- you're not using the [FFmpeg source](../README.md)
- you're using only `#video=copy` for the FFmpeg source
- you're using only `#audio=...` (any audio) transcoding for the FFmpeg source
You **NEED** hardware acceleration if you're using `#video=h264`, `#video=h265`, `#video=mjpeg` (video) transcoding.
## Important
- Acceleration is disabled by default because it can be unstable (this may change in the future)
- go2rtc can automatically detect supported hardware acceleration if enabled
- go2rtc will enable hardware decoding only if hardware encoding is supported
- go2rtc will use the same GPU for decoder and encoder
- Intel and AMD will switch to a software decoder if the input codec isn't supported by the hardware decoder
- NVIDIA will fail if the input codec isn't supported by the hardware decoder
- Raspberry Pi always uses a software decoder
```yaml
streams:
# auto select hardware encoder
camera1_hw: ffmpeg:rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0#video=h264#hardware
# manual select hardware encoder (vaapi, cuda, v4l2m2m, dxva2, videotoolbox)
camera1_vaapi: ffmpeg:rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0#video=h264#hardware=vaapi
```
## Docker and Hass Addon
There are two versions of the Docker container and Hass Add-on:
- Latest (Alpine) supports hardware acceleration for Intel iGPU (CPU with graphics) and Raspberry Pi.
- Hardware (Debian 12) supports Intel iGPU, AMD GPU, NVIDIA GPU.
## Intel iGPU
**Supported on:** Windows binary, Linux binary, Docker, Hass Addon.
If you have an Intel Sandy Bridge (2011) CPU with graphics, you already have hardware decoding/encoding support for `AVC/H.264`.
If you have an Intel Skylake (2015) CPU with graphics, you already have hardware decoding/encoding support for `AVC/H.264`, `HEVC/H.265` and `MJPEG`.
Read more [here](https://en.wikipedia.org/wiki/Intel_Quick_Sync_Video#Hardware_decoding_and_encoding) and [here](https://en.wikipedia.org/wiki/Intel_Graphics_Technology#Capabilities_(GPU_video_acceleration)).
Linux and Docker:
- It may be important to have a recent OS and Linux kernel. For example, on my **Debian 10 (kernel 4.19)** it did not work, but after updating to **Debian 11 (kernel 5.10)** everything was fine.
- If you run into trouble, check that you have the `/dev/dri/` folder on your host.
Docker users should add the `--privileged` option to the container for access to the hardware.
**PS.** Supported via [VAAPI](https://trac.ffmpeg.org/wiki/Hardware/VAAPI) engine on Linux and [DXVA2+QSV](https://trac.ffmpeg.org/wiki/Hardware/QuickSync) engine on Windows.
## AMD GPU
*I don't have the hardware to test this!!!*
**Supported on:** Linux binary, Docker, Hass Addon.
Docker users should install: `alexxit/go2rtc:master-hardware`. Docker users should add the `--privileged` option to the container for access to the hardware.
Hass Addon users should install **go2rtc master hardware** version.
**PS.** Supported via [VAAPI](https://trac.ffmpeg.org/wiki/Hardware/VAAPI) engine.
## NVIDIA GPU
**Supported on:** Windows binary, Linux binary, Docker.
Docker users should install: `alexxit/go2rtc:master-hardware`.
Read more [here](https://docs.frigate.video/configuration/hardware_acceleration) and [here](https://jellyfin.org/docs/general/administration/hardware-acceleration/#nvidia-hardware-acceleration-on-docker-linux).
**PS.** Supported via [CUDA](https://trac.ffmpeg.org/wiki/HWAccelIntro#CUDANVENCNVDEC) engine.
## Raspberry Pi 3
**Supported on:** Linux binary, Docker, Hass Addon.
I don't recommend using transcoding on the Raspberry Pi 3. It's extremely slow, even with hardware acceleration. Also, it may fail when transcoding a 2K+ stream.
## Raspberry Pi 4
*I don't have the hardware to test this!!!*
**Supported on:** Linux binary, Docker, Hass Addon.
**PS.** Supported via [v4l2m2m](https://lalitm.com/hw-encoding-raspi/) engine.
## macOS
In my tests, transcoding is faster on the M1 CPU than on the M1 GPU. Transcoding time on the M1 CPU is better than any Intel iGPU and comparable to an NVIDIA RTX 2070.
**PS.** Supported via [videotoolbox](https://trac.ffmpeg.org/wiki/HWAccelIntro#VideoToolbox) engine.
## Rockchip
- It's important to use a custom FFmpeg build with Rockchip support from [@nyanmisaka](https://github.com/nyanmisaka/ffmpeg-rockchip)
- Static binaries from [@MarcA711](https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/)
- It's important to have Linux kernel 5.10 or 6.1
**Tested**
- [Orange Pi 3B](https://www.armbian.com/orangepi3b/) with Armbian 6.1, supports transcoding H.264, H.265, MJPEG
@@ -0,0 +1,210 @@
package hardware
import (
"net/http"
"os/exec"
"strings"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/pkg/ffmpeg"
)
const (
EngineSoftware = "software"
EngineVAAPI = "vaapi" // Intel iGPU and AMD GPU
EngineV4L2M2M = "v4l2m2m" // Raspberry Pi 3 and 4
EngineCUDA = "cuda" // NVidia on Windows and Linux
EngineDXVA2 = "dxva2" // Intel on Windows
EngineVideoToolbox = "videotoolbox" // macOS
EngineRKMPP = "rkmpp" // Rockchip
)
func Init(bin string) {
api.HandleFunc("api/ffmpeg/hardware", func(w http.ResponseWriter, r *http.Request) {
api.ResponseSources(w, ProbeAll(bin))
})
}
// MakeHardware converts software FFmpeg args to hardware args
// empty engine for autoselect
func MakeHardware(args *ffmpeg.Args, engine string, defaults map[string]string) {
for i, codec := range args.Codecs {
if len(codec) < 10 {
continue // skip short line (-c:v mjpeg...)
}
// get current codec name
name := cut(codec, ' ', 1)
switch name {
case "libx264":
name = "h264"
case "libx265":
name = "h265"
case "mjpeg":
default:
continue // skip unsupported codec
}
// temporary disable probe for H265
if engine == "" && name != "h265" {
if engine = cache[name]; engine == "" {
engine = ProbeHardware(args.Bin, name)
cache[name] = engine
}
}
switch engine {
case EngineVAAPI:
args.Codecs[i] = defaults[name+"/"+engine]
if !args.HasFilters("drawtext=") {
args.Input = "-hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_flags allow_profile_mismatch " + args.Input
if name == "h264" {
fixPixelFormat(args)
}
for i, filter := range args.Filters {
if strings.HasPrefix(filter, "scale=") {
args.Filters[i] = "scale_vaapi=" + filter[6:]
}
if strings.HasPrefix(filter, "transpose=") {
if filter == "transpose=1,transpose=1" { // 180 degrees half-turn
args.Filters[i] = "transpose_vaapi=4" // reversal
} else {
args.Filters[i] = "transpose_vaapi=" + filter[10:]
}
}
}
// fix if input doesn't support hwaccel, do nothing when support
// insert as first filter before hardware scale and transpose
args.InsertFilter("format=vaapi|nv12,hwupload")
} else {
// enable software pixel for drawtext, scale and transpose
args.Input = "-hwaccel vaapi -hwaccel_output_format nv12 -hwaccel_flags allow_profile_mismatch " + args.Input
args.AddFilter("hwupload")
}
case EngineCUDA:
args.Codecs[i] = defaults[name+"/"+engine]
// CUDA doesn't support hardware transpose
// https://github.com/AlexxIT/go2rtc/issues/389
if !args.HasFilters("drawtext=", "transpose=") {
args.Input = "-hwaccel cuda -hwaccel_output_format cuda " + args.Input
for i, filter := range args.Filters {
if strings.HasPrefix(filter, "scale=") {
args.Filters[i] = "scale_cuda=" + filter[6:]
}
}
} else {
args.Input = "-hwaccel cuda -hwaccel_output_format nv12 " + args.Input
args.AddFilter("hwupload")
}
case EngineDXVA2:
args.Input = "-hwaccel dxva2 -hwaccel_output_format dxva2_vld " + args.Input
args.Codecs[i] = defaults[name+"/"+engine]
for i, filter := range args.Filters {
if strings.HasPrefix(filter, "scale=") {
args.Filters[i] = "scale_qsv=" + filter[6:]
}
}
args.InsertFilter("hwmap=derive_device=qsv,format=qsv")
case EngineVideoToolbox:
args.Input = "-hwaccel videotoolbox -hwaccel_output_format videotoolbox_vld " + args.Input
args.Codecs[i] = defaults[name+"/"+engine]
case EngineV4L2M2M:
args.Codecs[i] = defaults[name+"/"+engine]
case EngineRKMPP:
args.Codecs[i] = defaults[name+"/"+engine]
if !args.HasFilters("drawtext=") {
args.Input = "-hwaccel rkmpp -hwaccel_output_format drm_prime -afbc rga " + args.Input
for i, filter := range args.Filters {
if strings.HasPrefix(filter, "scale=") {
args.Filters[i] = "scale_rkrga=" + filter[6:] + ":force_original_aspect_ratio=0"
}
if strings.HasPrefix(filter, "transpose=") {
if filter == "transpose=1,transpose=1" { // 180 degrees half-turn
args.Filters[i] = "vpp_rkrga=transpose=4" // reversal
} else {
args.Filters[i] = "vpp_rkrga=transpose=" + filter[10:]
}
}
}
if len(args.Filters) > 0 {
// fix if input doesn't support hwaccel, do nothing when support
// insert as first filter before hardware scale and transpose
args.InsertFilter("format=drm_prime|nv12,hwupload")
}
} else {
// enable software pixel for drawtext, scale and transpose
args.Input = "-hwaccel rkmpp -hwaccel_output_format nv12 -afbc rga " + args.Input
args.AddFilter("hwupload")
}
}
}
}
var cache = map[string]string{}
func run(bin string, args string) bool {
err := exec.Command(bin, strings.Split(args, " ")...).Run()
return err == nil
}
func runToString(bin string, args string) string {
if run(bin, args) {
return "OK"
} else {
return "ERROR"
}
}
func cut(s string, sep byte, pos int) string {
for n := 0; n < pos; n++ {
if i := strings.IndexByte(s, sep); i > 0 {
s = s[i+1:]
} else {
return ""
}
}
if i := strings.IndexByte(s, sep); i > 0 {
return s[:i]
}
return s
}
// fixPixelFormat:
// - good h264 pixel: yuv420p(tv, bt709) == yuv420p (mpeg/limited/tv)
// - bad h264 pixel: yuvj420p(pc, bt709) == yuvj420p (jpeg/full/pc)
// - bad jpeg pixel: yuvj422p(pc, bt470bg)
func fixPixelFormat(args *ffmpeg.Args) {
// in my tests this filters has same CPU/GPU load:
// - "hwupload"
// - "hwupload,scale_vaapi=out_color_matrix=bt709:out_range=tv"
// - "hwupload,scale_vaapi=out_color_matrix=bt709:out_range=tv:format=nv12"
const fixPixFmt = "out_color_matrix=bt709:out_range=tv:format=nv12"
for i, filter := range args.Filters {
if strings.HasPrefix(filter, "scale=") {
args.Filters[i] = filter + ":" + fixPixFmt
return
}
}
args.Filters = append(args.Filters, "scale="+fixPixFmt)
}
@@ -0,0 +1,62 @@
//go:build freebsd || netbsd || openbsd || dragonfly
package hardware
import (
"runtime"
"github.com/AlexxIT/go2rtc/internal/api"
)
const (
ProbeV4L2M2MH264 = "-f lavfi -i testsrc2 -t 1 -c h264_v4l2m2m -f null -"
ProbeV4L2M2MH265 = "-f lavfi -i testsrc2 -t 1 -c hevc_v4l2m2m -f null -"
ProbeRKMPPH264 = "-f lavfi -i testsrc2 -t 1 -c h264_rkmpp_encoder -f null -"
ProbeRKMPPH265 = "-f lavfi -i testsrc2 -t 1 -c hevc_rkmpp_encoder -f null -"
)
func ProbeAll(bin string) []*api.Source {
return []*api.Source{
{
Name: runToString(bin, ProbeV4L2M2MH264),
URL: "ffmpeg:...#video=h264#hardware=" + EngineV4L2M2M,
},
{
Name: runToString(bin, ProbeV4L2M2MH265),
URL: "ffmpeg:...#video=h265#hardware=" + EngineV4L2M2M,
},
{
Name: runToString(bin, ProbeRKMPPH264),
URL: "ffmpeg:...#video=h264#hardware=" + EngineRKMPP,
},
{
Name: runToString(bin, ProbeRKMPPH265),
URL: "ffmpeg:...#video=h265#hardware=" + EngineRKMPP,
},
}
}
func ProbeHardware(bin, name string) string {
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
switch name {
case "h264":
if run(bin, ProbeV4L2M2MH264) {
return EngineV4L2M2M
}
if run(bin, ProbeRKMPPH264) {
return EngineRKMPP
}
case "h265":
if run(bin, ProbeV4L2M2MH265) {
return EngineV4L2M2M
}
if run(bin, ProbeRKMPPH265) {
return EngineRKMPP
}
}
return EngineSoftware
}
return EngineSoftware
}
@@ -0,0 +1,39 @@
//go:build darwin || ios
package hardware
import (
"github.com/AlexxIT/go2rtc/internal/api"
)
const ProbeVideoToolboxH264 = "-f lavfi -i testsrc2=size=svga -t 1 -c h264_videotoolbox -f null -"
const ProbeVideoToolboxH265 = "-f lavfi -i testsrc2=size=svga -t 1 -c hevc_videotoolbox -f null -"
func ProbeAll(bin string) []*api.Source {
return []*api.Source{
{
Name: runToString(bin, ProbeVideoToolboxH264),
URL: "ffmpeg:...#video=h264#hardware=" + EngineVideoToolbox,
},
{
Name: runToString(bin, ProbeVideoToolboxH265),
URL: "ffmpeg:...#video=h265#hardware=" + EngineVideoToolbox,
},
}
}
func ProbeHardware(bin, name string) string {
switch name {
case "h264":
if run(bin, ProbeVideoToolboxH264) {
return EngineVideoToolbox
}
case "h265":
if run(bin, ProbeVideoToolboxH265) {
return EngineVideoToolbox
}
}
return EngineSoftware
}
@@ -0,0 +1,124 @@
//go:build unix && !darwin && !freebsd && !netbsd && !openbsd && !dragonfly
package hardware
import (
"runtime"
"github.com/AlexxIT/go2rtc/internal/api"
)
const (
ProbeV4L2M2MH264 = "-f lavfi -i testsrc2 -t 1 -c h264_v4l2m2m -f null -"
ProbeV4L2M2MH265 = "-f lavfi -i testsrc2 -t 1 -c hevc_v4l2m2m -f null -"
ProbeRKMPPH264 = "-f lavfi -i testsrc2 -t 1 -c h264_rkmpp -f null -"
ProbeRKMPPH265 = "-f lavfi -i testsrc2 -t 1 -c hevc_rkmpp -f null -"
ProbeRKMPPJPEG = "-f lavfi -i testsrc2 -t 1 -c mjpeg_rkmpp -f null -"
ProbeVAAPIH264 = "-init_hw_device vaapi -f lavfi -i testsrc2 -t 1 -vf format=nv12,hwupload -c h264_vaapi -f null -"
ProbeVAAPIH265 = "-init_hw_device vaapi -f lavfi -i testsrc2 -t 1 -vf format=nv12,hwupload -c hevc_vaapi -f null -"
ProbeVAAPIJPEG = "-init_hw_device vaapi -f lavfi -i testsrc2 -t 1 -vf format=nv12,hwupload -c mjpeg_vaapi -f null -"
ProbeCUDAH264 = "-init_hw_device cuda -f lavfi -i testsrc2 -t 1 -c h264_nvenc -f null -"
ProbeCUDAH265 = "-init_hw_device cuda -f lavfi -i testsrc2 -t 1 -c hevc_nvenc -f null -"
)
func ProbeAll(bin string) []*api.Source {
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
return []*api.Source{
{
Name: runToString(bin, ProbeV4L2M2MH264),
URL: "ffmpeg:...#video=h264#hardware=" + EngineV4L2M2M,
},
{
Name: runToString(bin, ProbeV4L2M2MH265),
URL: "ffmpeg:...#video=h265#hardware=" + EngineV4L2M2M,
},
{
Name: runToString(bin, ProbeRKMPPH264),
URL: "ffmpeg:...#video=h264#hardware=" + EngineRKMPP,
},
{
Name: runToString(bin, ProbeRKMPPH265),
URL: "ffmpeg:...#video=h265#hardware=" + EngineRKMPP,
},
{
Name: runToString(bin, ProbeRKMPPJPEG),
URL: "ffmpeg:...#video=mjpeg#hardware=" + EngineRKMPP,
},
}
}
return []*api.Source{
{
Name: runToString(bin, ProbeVAAPIH264),
URL: "ffmpeg:...#video=h264#hardware=" + EngineVAAPI,
},
{
Name: runToString(bin, ProbeVAAPIH265),
URL: "ffmpeg:...#video=h265#hardware=" + EngineVAAPI,
},
{
Name: runToString(bin, ProbeVAAPIJPEG),
URL: "ffmpeg:...#video=mjpeg#hardware=" + EngineVAAPI,
},
{
Name: runToString(bin, ProbeCUDAH264),
URL: "ffmpeg:...#video=h264#hardware=" + EngineCUDA,
},
{
Name: runToString(bin, ProbeCUDAH265),
URL: "ffmpeg:...#video=h265#hardware=" + EngineCUDA,
},
}
}
func ProbeHardware(bin, name string) string {
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
switch name {
case "h264":
if run(bin, ProbeV4L2M2MH264) {
return EngineV4L2M2M
}
if run(bin, ProbeRKMPPH264) {
return EngineRKMPP
}
case "h265":
if run(bin, ProbeV4L2M2MH265) {
return EngineV4L2M2M
}
if run(bin, ProbeRKMPPH265) {
return EngineRKMPP
}
case "mjpeg":
if run(bin, ProbeRKMPPJPEG) {
return EngineRKMPP
}
}
return EngineSoftware
}
switch name {
case "h264":
if run(bin, ProbeCUDAH264) {
return EngineCUDA
}
if run(bin, ProbeVAAPIH264) {
return EngineVAAPI
}
case "h265":
if run(bin, ProbeCUDAH265) {
return EngineCUDA
}
if run(bin, ProbeVAAPIH265) {
return EngineVAAPI
}
case "mjpeg":
if run(bin, ProbeVAAPIJPEG) {
return EngineVAAPI
}
}
return EngineSoftware
}
@@ -0,0 +1,63 @@
//go:build windows
package hardware
import "github.com/AlexxIT/go2rtc/internal/api"
const ProbeDXVA2H264 = "-init_hw_device dxva2 -f lavfi -i testsrc2 -t 1 -c h264_qsv -f null -"
const ProbeDXVA2H265 = "-init_hw_device dxva2 -f lavfi -i testsrc2 -t 1 -c hevc_qsv -f null -"
const ProbeDXVA2JPEG = "-init_hw_device dxva2 -f lavfi -i testsrc2 -t 1 -c mjpeg_qsv -f null -"
const ProbeCUDAH264 = "-init_hw_device cuda -f lavfi -i testsrc2 -t 1 -c h264_nvenc -f null -"
const ProbeCUDAH265 = "-init_hw_device cuda -f lavfi -i testsrc2 -t 1 -c hevc_nvenc -f null -"
func ProbeAll(bin string) []*api.Source {
return []*api.Source{
{
Name: runToString(bin, ProbeDXVA2H264),
URL: "ffmpeg:...#video=h264#hardware=" + EngineDXVA2,
},
{
Name: runToString(bin, ProbeDXVA2H265),
URL: "ffmpeg:...#video=h265#hardware=" + EngineDXVA2,
},
{
Name: runToString(bin, ProbeDXVA2JPEG),
URL: "ffmpeg:...#video=mjpeg#hardware=" + EngineDXVA2,
},
{
Name: runToString(bin, ProbeCUDAH264),
URL: "ffmpeg:...#video=h264#hardware=" + EngineCUDA,
},
{
Name: runToString(bin, ProbeCUDAH265),
URL: "ffmpeg:...#video=h265#hardware=" + EngineCUDA,
},
}
}
func ProbeHardware(bin, name string) string {
switch name {
case "h264":
if run(bin, ProbeCUDAH264) {
return EngineCUDA
}
if run(bin, ProbeDXVA2H264) {
return EngineDXVA2
}
case "h265":
if run(bin, ProbeCUDAH265) {
return EngineCUDA
}
if run(bin, ProbeDXVA2H265) {
return EngineDXVA2
}
case "mjpeg":
if run(bin, ProbeDXVA2JPEG) {
return EngineDXVA2
}
}
return EngineSoftware
}