mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-10-05 08:16:55 +08:00
Rework FFmpeg hardware support
This commit is contained in:
@@ -9,7 +9,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
func Init(bin string) {
|
||||||
|
Bin = bin
|
||||||
|
|
||||||
api.HandleFunc("api/ffmpeg/devices", apiDevices)
|
api.HandleFunc("api/ffmpeg/devices", apiDevices)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
package ffmpeg
|
package ffmpeg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/AlexxIT/go2rtc/internal/app"
|
"github.com/AlexxIT/go2rtc/internal/app"
|
||||||
"github.com/AlexxIT/go2rtc/internal/exec"
|
"github.com/AlexxIT/go2rtc/internal/exec"
|
||||||
"github.com/AlexxIT/go2rtc/internal/ffmpeg/device"
|
"github.com/AlexxIT/go2rtc/internal/ffmpeg/device"
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/ffmpeg/hardware"
|
||||||
"github.com/AlexxIT/go2rtc/internal/rtsp"
|
"github.com/AlexxIT/go2rtc/internal/rtsp"
|
||||||
"github.com/AlexxIT/go2rtc/internal/streams"
|
"github.com/AlexxIT/go2rtc/internal/streams"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/ffmpeg"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,8 +35,8 @@ func Init() {
|
|||||||
return exec.Handle("exec:" + args.String())
|
return exec.Handle("exec:" + args.String())
|
||||||
})
|
})
|
||||||
|
|
||||||
device.Bin = defaults["bin"]
|
device.Init(defaults["bin"])
|
||||||
device.Init()
|
hardware.Init(defaults["bin"])
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaults = map[string]string{
|
var defaults = map[string]string{
|
||||||
@@ -116,19 +116,19 @@ func inputTemplate(name, s string, query url.Values) string {
|
|||||||
return strings.Replace(template, "{input}", s, 1)
|
return strings.Replace(template, "{input}", s, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseArgs(s string) *Args {
|
func parseArgs(s string) *ffmpeg.Args {
|
||||||
// init FFmpeg arguments
|
// init FFmpeg arguments
|
||||||
args := &Args{
|
args := &ffmpeg.Args{
|
||||||
bin: defaults["bin"],
|
Bin: defaults["bin"],
|
||||||
global: defaults["global"],
|
Global: defaults["global"],
|
||||||
output: defaults["output"],
|
Output: defaults["output"],
|
||||||
}
|
}
|
||||||
|
|
||||||
var query url.Values
|
var query url.Values
|
||||||
if i := strings.IndexByte(s, '#'); i > 0 {
|
if i := strings.IndexByte(s, '#'); i > 0 {
|
||||||
query = parseQuery(s[i+1:])
|
query = parseQuery(s[i+1:])
|
||||||
args.video = len(query["video"])
|
args.Video = len(query["video"])
|
||||||
args.audio = len(query["audio"])
|
args.Audio = len(query["audio"])
|
||||||
s = s[:i]
|
s = s[:i]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,46 +139,46 @@ func parseArgs(s string) *Args {
|
|||||||
if i := strings.Index(s, "://"); i > 0 {
|
if i := strings.Index(s, "://"); i > 0 {
|
||||||
switch s[:i] {
|
switch s[:i] {
|
||||||
case "http", "https", "rtmp":
|
case "http", "https", "rtmp":
|
||||||
args.input = inputTemplate("http", s, query)
|
args.Input = inputTemplate("http", s, query)
|
||||||
case "rtsp", "rtsps":
|
case "rtsp", "rtsps":
|
||||||
// https://ffmpeg.org/ffmpeg-protocols.html#rtsp
|
// https://ffmpeg.org/ffmpeg-protocols.html#rtsp
|
||||||
// skip unnecessary input tracks
|
// skip unnecessary input tracks
|
||||||
switch {
|
switch {
|
||||||
case (args.video > 0 && args.audio > 0) || (args.video == 0 && args.audio == 0):
|
case (args.Video > 0 && args.Audio > 0) || (args.Video == 0 && args.Audio == 0):
|
||||||
args.input = "-allowed_media_types video+audio "
|
args.Input = "-allowed_media_types video+audio "
|
||||||
case args.video > 0:
|
case args.Video > 0:
|
||||||
args.input = "-allowed_media_types video "
|
args.Input = "-allowed_media_types video "
|
||||||
case args.audio > 0:
|
case args.Audio > 0:
|
||||||
args.input = "-allowed_media_types audio "
|
args.Input = "-allowed_media_types audio "
|
||||||
}
|
}
|
||||||
|
|
||||||
args.input += inputTemplate("rtsp", s, query)
|
args.Input += inputTemplate("rtsp", s, query)
|
||||||
default:
|
default:
|
||||||
args.input = "-i " + s
|
args.Input = "-i " + s
|
||||||
}
|
}
|
||||||
} else if streams.Get(s) != nil {
|
} else if streams.Get(s) != nil {
|
||||||
s = "rtsp://127.0.0.1:" + rtsp.Port + "/" + s
|
s = "rtsp://127.0.0.1:" + rtsp.Port + "/" + s
|
||||||
switch {
|
switch {
|
||||||
case args.video > 0 && args.audio == 0:
|
case args.Video > 0 && args.Audio == 0:
|
||||||
s += "?video"
|
s += "?video"
|
||||||
case args.audio > 0 && args.video == 0:
|
case args.Audio > 0 && args.Video == 0:
|
||||||
s += "?audio"
|
s += "?audio"
|
||||||
default:
|
default:
|
||||||
s += "?video&audio"
|
s += "?video&audio"
|
||||||
}
|
}
|
||||||
args.input = inputTemplate("rtsp", s, query)
|
args.Input = inputTemplate("rtsp", s, query)
|
||||||
} else if strings.HasPrefix(s, "device?") {
|
} else if strings.HasPrefix(s, "device?") {
|
||||||
var err error
|
var err error
|
||||||
args.input, err = device.GetInput(s)
|
args.Input, err = device.GetInput(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
args.input = inputTemplate("file", s, query)
|
args.Input = inputTemplate("file", s, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
if query["async"] != nil {
|
if query["async"] != nil {
|
||||||
args.input = "-use_wallclock_as_timestamps 1 -async 1 " + args.input
|
args.Input = "-use_wallclock_as_timestamps 1 -async 1 " + args.Input
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse query params:
|
// Parse query params:
|
||||||
@@ -226,7 +226,7 @@ func parseArgs(s string) *Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Process video codecs
|
// 3. Process video codecs
|
||||||
if args.video > 0 {
|
if args.Video > 0 {
|
||||||
for _, video := range query["video"] {
|
for _, video := range query["video"] {
|
||||||
if video != "copy" {
|
if video != "copy" {
|
||||||
if codec := defaults[video]; codec != "" {
|
if codec := defaults[video]; codec != "" {
|
||||||
@@ -243,7 +243,7 @@ func parseArgs(s string) *Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. Process audio codecs
|
// 4. Process audio codecs
|
||||||
if args.audio > 0 {
|
if args.Audio > 0 {
|
||||||
for _, audio := range query["audio"] {
|
for _, audio := range query["audio"] {
|
||||||
if audio != "copy" {
|
if audio != "copy" {
|
||||||
if codec := defaults[audio]; codec != "" {
|
if codec := defaults[audio]; codec != "" {
|
||||||
@@ -260,11 +260,11 @@ func parseArgs(s string) *Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if query["hardware"] != nil {
|
if query["hardware"] != nil {
|
||||||
MakeHardware(args, query["hardware"][0])
|
hardware.MakeHardware(args, query["hardware"][0], defaults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.codecs == nil {
|
if args.Codecs == nil {
|
||||||
args.AddCodec("-c copy")
|
args.AddCodec("-c copy")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,76 +283,3 @@ func parseQuery(s string) map[string][]string {
|
|||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
type Args struct {
|
|
||||||
bin string // ffmpeg
|
|
||||||
global string // -hide_banner -v error
|
|
||||||
input string // -re -stream_loop -1 -i /media/bunny.mp4
|
|
||||||
codecs []string // -c:v libx264 -g:v 30 -preset:v ultrafast -tune:v zerolatency
|
|
||||||
filters []string // scale=1920:1080
|
|
||||||
output string // -f rtsp {output}
|
|
||||||
|
|
||||||
video, audio int // count of video and audio params
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Args) AddCodec(codec string) {
|
|
||||||
a.codecs = append(a.codecs, codec)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Args) AddFilter(filter string) {
|
|
||||||
a.filters = append(a.filters, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Args) InsertFilter(filter string) {
|
|
||||||
a.filters = append([]string{filter}, a.filters...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Args) String() string {
|
|
||||||
b := bytes.NewBuffer(make([]byte, 0, 512))
|
|
||||||
|
|
||||||
b.WriteString(a.bin)
|
|
||||||
|
|
||||||
if a.global != "" {
|
|
||||||
b.WriteByte(' ')
|
|
||||||
b.WriteString(a.global)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteByte(' ')
|
|
||||||
b.WriteString(a.input)
|
|
||||||
|
|
||||||
multimode := a.video > 1 || a.audio > 1
|
|
||||||
var iv, ia int
|
|
||||||
|
|
||||||
for _, codec := range a.codecs {
|
|
||||||
// support multiple video and/or audio codecs
|
|
||||||
if multimode && len(codec) >= 5 {
|
|
||||||
switch codec[:5] {
|
|
||||||
case "-c:v ":
|
|
||||||
codec = "-map 0:v:0? " + strings.ReplaceAll(codec, ":v ", ":v:"+strconv.Itoa(iv)+" ")
|
|
||||||
iv++
|
|
||||||
case "-c:a ":
|
|
||||||
codec = "-map 0:a:0? " + strings.ReplaceAll(codec, ":a ", ":a:"+strconv.Itoa(ia)+" ")
|
|
||||||
ia++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteByte(' ')
|
|
||||||
b.WriteString(codec)
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.filters != nil {
|
|
||||||
for i, filter := range a.filters {
|
|
||||||
if i == 0 {
|
|
||||||
b.WriteString(" -vf ")
|
|
||||||
} else {
|
|
||||||
b.WriteByte(',')
|
|
||||||
}
|
|
||||||
b.WriteString(filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteByte(' ')
|
|
||||||
b.WriteString(a.output)
|
|
||||||
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
package ffmpeg
|
package hardware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/api"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/ffmpeg"
|
||||||
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -16,12 +19,16 @@ const (
|
|||||||
EngineVideoToolbox = "videotoolbox" // macOS
|
EngineVideoToolbox = "videotoolbox" // macOS
|
||||||
)
|
)
|
||||||
|
|
||||||
var cache = map[string]string{}
|
func Init(bin string) {
|
||||||
|
api.HandleFunc("api/ffmpeg/hardware", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
api.ResponseStreams(w, ProbeAll(bin))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// MakeHardware converts software FFmpeg args to hardware args
|
// MakeHardware converts software FFmpeg args to hardware args
|
||||||
// empty engine for autoselect
|
// empty engine for autoselect
|
||||||
func MakeHardware(args *Args, engine string) {
|
func MakeHardware(args *ffmpeg.Args, engine string, defaults map[string]string) {
|
||||||
for i, codec := range args.codecs {
|
for i, codec := range args.Codecs {
|
||||||
if len(codec) < 12 {
|
if len(codec) < 12 {
|
||||||
continue // skip short line (-c:v libx264...)
|
continue // skip short line (-c:v libx264...)
|
||||||
}
|
}
|
||||||
@@ -41,25 +48,25 @@ func MakeHardware(args *Args, engine string) {
|
|||||||
// temporary disable probe for H265 and MJPEG
|
// temporary disable probe for H265 and MJPEG
|
||||||
if engine == "" && name == "h264" {
|
if engine == "" && name == "h264" {
|
||||||
if engine = cache[name]; engine == "" {
|
if engine = cache[name]; engine == "" {
|
||||||
engine = ProbeHardware(name)
|
engine = ProbeHardware(args.Bin, name)
|
||||||
cache[name] = engine
|
cache[name] = engine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch engine {
|
switch engine {
|
||||||
case EngineVAAPI:
|
case EngineVAAPI:
|
||||||
args.input = "-hwaccel vaapi -hwaccel_output_format vaapi " + args.input
|
args.Input = "-hwaccel vaapi -hwaccel_output_format vaapi " + args.Input
|
||||||
args.codecs[i] = defaults[name+"/"+engine]
|
args.Codecs[i] = defaults[name+"/"+engine]
|
||||||
|
|
||||||
for i, filter := range args.filters {
|
for i, filter := range args.Filters {
|
||||||
if strings.HasPrefix(filter, "scale=") {
|
if strings.HasPrefix(filter, "scale=") {
|
||||||
args.filters[i] = "scale_vaapi=" + filter[6:]
|
args.Filters[i] = "scale_vaapi=" + filter[6:]
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(filter, "transpose=") {
|
if strings.HasPrefix(filter, "transpose=") {
|
||||||
if filter == "transpose=1,transpose=1" { // 180 degrees half-turn
|
if filter == "transpose=1,transpose=1" { // 180 degrees half-turn
|
||||||
args.filters[i] = "transpose_vaapi=4" // reversal
|
args.Filters[i] = "transpose_vaapi=4" // reversal
|
||||||
} else {
|
} else {
|
||||||
args.filters[i] = "transpose_vaapi=" + filter[10:]
|
args.Filters[i] = "transpose_vaapi=" + filter[10:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,43 +75,53 @@ func MakeHardware(args *Args, engine string) {
|
|||||||
args.InsertFilter("format=vaapi|nv12,hwupload")
|
args.InsertFilter("format=vaapi|nv12,hwupload")
|
||||||
|
|
||||||
case EngineCUDA:
|
case EngineCUDA:
|
||||||
args.input = "-hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 2 " + args.input
|
args.Input = "-hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 2 " + args.Input
|
||||||
args.codecs[i] = defaults[name+"/"+engine]
|
args.Codecs[i] = defaults[name+"/"+engine]
|
||||||
|
|
||||||
for i, filter := range args.filters {
|
for i, filter := range args.Filters {
|
||||||
if strings.HasPrefix(filter, "scale=") {
|
if strings.HasPrefix(filter, "scale=") {
|
||||||
args.filters[i] = "scale_cuda=" + filter[6:]
|
args.Filters[i] = "scale_cuda=" + filter[6:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case EngineDXVA2:
|
case EngineDXVA2:
|
||||||
args.input = "-hwaccel dxva2 -hwaccel_output_format dxva2_vld " + args.input
|
args.Input = "-hwaccel dxva2 -hwaccel_output_format dxva2_vld " + args.Input
|
||||||
args.codecs[i] = defaults[name+"/"+engine]
|
args.Codecs[i] = defaults[name+"/"+engine]
|
||||||
|
|
||||||
for i, filter := range args.filters {
|
for i, filter := range args.Filters {
|
||||||
if strings.HasPrefix(filter, "scale=") {
|
if strings.HasPrefix(filter, "scale=") {
|
||||||
args.filters[i] = "scale_qsv=" + filter[6:]
|
args.Filters[i] = "scale_qsv=" + filter[6:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
args.InsertFilter("hwmap=derive_device=qsv,format=qsv")
|
args.InsertFilter("hwmap=derive_device=qsv,format=qsv")
|
||||||
|
|
||||||
case EngineVideoToolbox:
|
case EngineVideoToolbox:
|
||||||
args.input = "-hwaccel videotoolbox -hwaccel_output_format videotoolbox_vld " + args.input
|
args.Input = "-hwaccel videotoolbox -hwaccel_output_format videotoolbox_vld " + args.Input
|
||||||
args.codecs[i] = defaults[name+"/"+engine]
|
args.Codecs[i] = defaults[name+"/"+engine]
|
||||||
|
|
||||||
case EngineV4L2M2M:
|
case EngineV4L2M2M:
|
||||||
args.codecs[i] = defaults[name+"/"+engine]
|
args.Codecs[i] = defaults[name+"/"+engine]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(arg ...string) bool {
|
var cache = map[string]string{}
|
||||||
err := exec.Command(defaults["bin"], arg...).Run()
|
|
||||||
log.Printf("%v %v", arg, err)
|
func run(bin string, args string) bool {
|
||||||
|
err := exec.Command(bin, strings.Split(args, " ")...).Run()
|
||||||
|
log.Printf("%v %v", args, err)
|
||||||
return err == nil
|
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 {
|
func cut(s string, sep byte, pos int) string {
|
||||||
for n := 0; n < pos; n++ {
|
for n := 0; n < pos; n++ {
|
||||||
if i := strings.IndexByte(s, sep); i > 0 {
|
if i := strings.IndexByte(s, sep); i > 0 {
|
37
internal/ffmpeg/hardware/hardware_darwin.go
Normal file
37
internal/ffmpeg/hardware/hardware_darwin.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package hardware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ProbeVideoToolboxH264 = "-f lavfi -i testsrc2 -t 1 -c h264_videotoolbox -f null -"
|
||||||
|
const ProbeVideoToolboxH265 = "-f lavfi -i testsrc2 -t 1 -c hevc_videotoolbox -f null -"
|
||||||
|
|
||||||
|
func ProbeAll(bin string) []api.Stream {
|
||||||
|
return []api.Stream{
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
94
internal/ffmpeg/hardware/hardware_linux.go
Normal file
94
internal/ffmpeg/hardware/hardware_linux.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package hardware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/api"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ProbeV4L2M2MH264 = "-f lavfi -i testsrc2 -t 1 -c h264_v4l2m2m -f null -"
|
||||||
|
const ProbeV4L2M2MH265 = "-f lavfi -i testsrc2 -t 1 -c hevc_v4l2m2m -f null -"
|
||||||
|
const ProbeVAAPIH264 = "-init_hw_device vaapi -f lavfi -i testsrc2 -t 1 -vf format=nv12,hwupload -c h264_vaapi -f null -"
|
||||||
|
const ProbeVAAPIH265 = "-init_hw_device vaapi -f lavfi -i testsrc2 -t 1 -vf format=nv12,hwupload -c hevc_vaapi -f null -"
|
||||||
|
const ProbeVAAPIJPEG = "-init_hw_device vaapi -f lavfi -i testsrc2 -t 1 -vf format=nv12,hwupload -c mjpeg_vaapi -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.Stream {
|
||||||
|
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
|
||||||
|
return []api.Stream{
|
||||||
|
{
|
||||||
|
Name: runToString(bin, ProbeV4L2M2MH264),
|
||||||
|
URL: "ffmpeg:...#video=h264#hardware=" + EngineV4L2M2M,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: runToString(bin, ProbeV4L2M2MH265),
|
||||||
|
URL: "ffmpeg:...#video=h265#hardware=" + EngineV4L2M2M,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []api.Stream{
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
case "h265":
|
||||||
|
if run(bin, ProbeV4L2M2MH265) {
|
||||||
|
return EngineV4L2M2M
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
61
internal/ffmpeg/hardware/hardware_windows.go
Normal file
61
internal/ffmpeg/hardware/hardware_windows.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
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.Stream {
|
||||||
|
return []api.Stream{
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
@@ -1,21 +0,0 @@
|
|||||||
package ffmpeg
|
|
||||||
|
|
||||||
func ProbeHardware(name string) string {
|
|
||||||
switch name {
|
|
||||||
case "h264":
|
|
||||||
if run(
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-c", "h264_videotoolbox", "-f", "null", "-") {
|
|
||||||
return EngineVideoToolbox
|
|
||||||
}
|
|
||||||
|
|
||||||
case "h265":
|
|
||||||
if run(
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-c", "hevc_videotoolbox", "-f", "null", "-") {
|
|
||||||
return EngineVideoToolbox
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return EngineSoftware
|
|
||||||
}
|
|
@@ -1,67 +0,0 @@
|
|||||||
package ffmpeg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ProbeHardware(name string) string {
|
|
||||||
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
|
|
||||||
switch name {
|
|
||||||
case "h264":
|
|
||||||
if run(
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-c", "h264_v4l2m2m", "-f", "null", "-") {
|
|
||||||
return EngineV4L2M2M
|
|
||||||
}
|
|
||||||
|
|
||||||
case "h265":
|
|
||||||
if run(
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-c", "hevc_v4l2m2m", "-f", "null", "-") {
|
|
||||||
return EngineV4L2M2M
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return EngineSoftware
|
|
||||||
}
|
|
||||||
|
|
||||||
switch name {
|
|
||||||
case "h264":
|
|
||||||
if run("-init_hw_device", "cuda",
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-c", "h264_nvenc", "-f", "null", "-") {
|
|
||||||
return EngineCUDA
|
|
||||||
}
|
|
||||||
|
|
||||||
if run("-init_hw_device", "vaapi",
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-vf", "format=nv12,hwupload",
|
|
||||||
"-c", "h264_vaapi", "-f", "null", "-") {
|
|
||||||
return EngineVAAPI
|
|
||||||
}
|
|
||||||
|
|
||||||
case "h265":
|
|
||||||
if run("-init_hw_device", "cuda",
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-c", "hevc_nvenc", "-f", "null", "-") {
|
|
||||||
return EngineCUDA
|
|
||||||
}
|
|
||||||
|
|
||||||
if run("-init_hw_device", "vaapi",
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-vf", "format=nv12,hwupload",
|
|
||||||
"-c", "hevc_vaapi", "-f", "null", "-") {
|
|
||||||
return EngineVAAPI
|
|
||||||
}
|
|
||||||
|
|
||||||
case "mjpeg":
|
|
||||||
if run("-init_hw_device", "vaapi",
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-vf", "format=nv12,hwupload",
|
|
||||||
"-c", "mjpeg_vaapi", "-f", "null", "-") {
|
|
||||||
return EngineVAAPI
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return EngineSoftware
|
|
||||||
}
|
|
@@ -1,40 +0,0 @@
|
|||||||
package ffmpeg
|
|
||||||
|
|
||||||
func ProbeHardware(name string) string {
|
|
||||||
switch name {
|
|
||||||
case "h264":
|
|
||||||
if run("-init_hw_device", "cuda",
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-c", "h264_nvenc", "-f", "null", "-") {
|
|
||||||
return EngineCUDA
|
|
||||||
}
|
|
||||||
|
|
||||||
if run("-init_hw_device", "dxva2",
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-c", "h264_qsv", "-f", "null", "-") {
|
|
||||||
return EngineDXVA2
|
|
||||||
}
|
|
||||||
|
|
||||||
case "h265":
|
|
||||||
if run("-init_hw_device", "cuda",
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-c", "hevc_nvenc", "-f", "null", "-") {
|
|
||||||
return EngineCUDA
|
|
||||||
}
|
|
||||||
|
|
||||||
if run("-init_hw_device", "dxva2",
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-c", "hevc_qsv", "-f", "null", "-") {
|
|
||||||
return EngineDXVA2
|
|
||||||
}
|
|
||||||
|
|
||||||
case "mjpeg":
|
|
||||||
if run("-init_hw_device", "dxva2",
|
|
||||||
"-f", "lavfi", "-i", "testsrc2", "-t", "1",
|
|
||||||
"-c", "mjpeg_qsv", "-f", "null", "-") {
|
|
||||||
return EngineDXVA2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return EngineSoftware
|
|
||||||
}
|
|
80
pkg/ffmpeg/ffmpeg.go
Normal file
80
pkg/ffmpeg/ffmpeg.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Args struct {
|
||||||
|
Bin string // ffmpeg
|
||||||
|
Global string // -hide_banner -v error
|
||||||
|
Input string // -re -stream_loop -1 -i /media/bunny.mp4
|
||||||
|
Codecs []string // -c:v libx264 -g:v 30 -preset:v ultrafast -tune:v zerolatency
|
||||||
|
Filters []string // scale=1920:1080
|
||||||
|
Output string // -f rtsp {output}
|
||||||
|
|
||||||
|
Video, Audio int // count of Video and Audio params
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Args) AddCodec(codec string) {
|
||||||
|
a.Codecs = append(a.Codecs, codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Args) AddFilter(filter string) {
|
||||||
|
a.Filters = append(a.Filters, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Args) InsertFilter(filter string) {
|
||||||
|
a.Filters = append([]string{filter}, a.Filters...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Args) String() string {
|
||||||
|
b := bytes.NewBuffer(make([]byte, 0, 512))
|
||||||
|
|
||||||
|
b.WriteString(a.Bin)
|
||||||
|
|
||||||
|
if a.Global != "" {
|
||||||
|
b.WriteByte(' ')
|
||||||
|
b.WriteString(a.Global)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteByte(' ')
|
||||||
|
b.WriteString(a.Input)
|
||||||
|
|
||||||
|
multimode := a.Video > 1 || a.Audio > 1
|
||||||
|
var iv, ia int
|
||||||
|
|
||||||
|
for _, codec := range a.Codecs {
|
||||||
|
// support multiple video and/or audio codecs
|
||||||
|
if multimode && len(codec) >= 5 {
|
||||||
|
switch codec[:5] {
|
||||||
|
case "-c:v ":
|
||||||
|
codec = "-map 0:v:0? " + strings.ReplaceAll(codec, ":v ", ":v:"+strconv.Itoa(iv)+" ")
|
||||||
|
iv++
|
||||||
|
case "-c:a ":
|
||||||
|
codec = "-map 0:a:0? " + strings.ReplaceAll(codec, ":a ", ":a:"+strconv.Itoa(ia)+" ")
|
||||||
|
ia++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteByte(' ')
|
||||||
|
b.WriteString(codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Filters != nil {
|
||||||
|
for i, filter := range a.Filters {
|
||||||
|
if i == 0 {
|
||||||
|
b.WriteString(" -vf ")
|
||||||
|
} else {
|
||||||
|
b.WriteByte(',')
|
||||||
|
}
|
||||||
|
b.WriteString(filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteByte(' ')
|
||||||
|
b.WriteString(a.Output)
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
13
www/add.html
13
www/add.html
@@ -184,6 +184,19 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<button id="hardware">FFmpeg Hardware</button>
|
||||||
|
<div class="module">
|
||||||
|
<table id="hardware-table">
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('hardware').addEventListener('click', async ev => {
|
||||||
|
ev.target.nextElementSibling.style.display = 'block'
|
||||||
|
await getStreams('api/ffmpeg/hardware', 'hardware-table')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<button id="hass">Home Assistant</button>
|
<button id="hass">Home Assistant</button>
|
||||||
<div class="module">
|
<div class="module">
|
||||||
<table id="hass-table"></table>
|
<table id="hass-table"></table>
|
||||||
|
Reference in New Issue
Block a user