diff --git a/internal/ffmpeg/ffmpeg.go b/internal/ffmpeg/ffmpeg.go index aacf66cb..db5890d0 100644 --- a/internal/ffmpeg/ffmpeg.go +++ b/internal/ffmpeg/ffmpeg.go @@ -49,7 +49,7 @@ var defaults = map[string]string{ "global": "-hide_banner", // inputs - "file": "-re -readrate_initial_burst 0.001 -i {input}", + "file": "-re -i {input}", "http": "-fflags nobuffer -flags low_delay -i {input}", "rtsp": "-fflags nobuffer -flags low_delay -timeout 5000000 -user_agent go2rtc/ffmpeg -rtsp_flags prefer_tcp -i {input}", @@ -151,9 +151,10 @@ func inputTemplate(name, s string, query url.Values) string { func parseArgs(s string) *ffmpeg.Args { // init FFmpeg arguments args := &ffmpeg.Args{ - Bin: defaults["bin"], - Global: defaults["global"], - Output: defaults["output"], + Bin: defaults["bin"], + Global: defaults["global"], + Output: defaults["output"], + Version: verAV, } var query url.Values diff --git a/internal/ffmpeg/ffmpeg_test.go b/internal/ffmpeg/ffmpeg_test.go index d5a39284..3fc5d208 100644 --- a/internal/ffmpeg/ffmpeg_test.go +++ b/internal/ffmpeg/ffmpeg_test.go @@ -3,6 +3,7 @@ package ffmpeg import ( "testing" + "github.com/AlexxIT/go2rtc/pkg/ffmpeg" "github.com/stretchr/testify/require" ) @@ -292,3 +293,23 @@ func TestDrawText(t *testing.T) { }) } } + +func TestVersion(t *testing.T) { + verAV = ffmpeg.Version61 + tests := []struct { + name string + source string + expect string + }{ + { + source: "/media/bbb.mp4", + expect: `ffmpeg -hide_banner -readrate_initial_burst 0.001 -re -i /media/bbb.mp4 -c copy -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}`, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + args := parseArgs(test.source) + require.Equal(t, test.expect, args.String()) + }) + } +} diff --git a/internal/ffmpeg/version.go b/internal/ffmpeg/version.go index 74028c54..976c92d0 100644 --- a/internal/ffmpeg/version.go +++ b/internal/ffmpeg/version.go @@ -1,68 +1,47 @@ package ffmpeg import ( - "bytes" "errors" "os/exec" - "strings" "sync" + "github.com/AlexxIT/go2rtc/pkg/ffmpeg" "github.com/rs/zerolog/log" ) -var checkMu sync.Mutex -var checkErr error -var checkVer string - -const ( - FFmpeg50 = "59. 16" - FFmpeg51 = "59. 27" - FFmpeg60 = "60. 3" - FFmpeg61 = "60. 16" - FFmpeg70 = "61. 1" -) +var verMu sync.Mutex +var verErr error +var verFF string +var verAV string func Version() (string, error) { - checkMu.Lock() - defer checkMu.Unlock() + verMu.Lock() + defer verMu.Unlock() - if checkVer != "" { - return checkVer, checkErr + if verFF != "" { + return verFF, verErr } cmd := exec.Command(defaults["bin"], "-version") b, err := cmd.Output() if err != nil { - checkVer = "-" - checkErr = err - return checkVer, checkErr + verFF = "-" + verErr = err + return verFF, verErr } - if len(b) < 100 { - checkVer = "?" - return checkVer, nil + verFF, verAV = ffmpeg.ParseVersion(b) + + if verFF == "" { + verFF = "?" } - // ffmpeg version n7.0-30-g8b0fe91754-20240520 Copyright (c) 2000-2024 the FFmpeg developers - b = b[15:] - if i := bytes.IndexByte(b, ' '); i > 0 { - checkVer = string(b[:i]) + // better to compare libavformat, because nightly/master builds + if verAV != "" && verAV < ffmpeg.Version50 { + verErr = errors.New("ffmpeg: unsupported version: " + verFF) } - // libavformat 60. 16.100 / 60. 16.100 - if i := strings.Index(string(b), "libavformat"); i > 0 { - // better to compare libavformat, because nightly/master builds - libav := string(b[i+15 : i+25]) - if libav < FFmpeg50 { - checkErr = errors.New("ffmpeg: unsupported version: " + checkVer) - return checkVer, checkErr - } - if libav < FFmpeg61 && strings.Contains(defaults["file"], "readrate_initial_burst") { - defaults["file"] = "-re -i {input}" - } - } + log.Debug().Str("version", verFF).Str("libavformat", verAV).Msgf("[ffmpeg] bin") - log.Debug().Str("version", checkVer).Msgf("[ffmpeg] bin") - - return checkVer, nil + return verFF, verErr } diff --git a/internal/ffmpeg/virtual/virtual.go b/internal/ffmpeg/virtual/virtual.go index 836fc37b..4dc3b025 100644 --- a/internal/ffmpeg/virtual/virtual.go +++ b/internal/ffmpeg/virtual/virtual.go @@ -67,7 +67,7 @@ func GetInputTTS(src string) string { return "" } - input := `-re -readrate_initial_burst 0.001 -f lavfi -i "flite=text='` + query.Get("text") + `'` + input := `-re -f lavfi -i "flite=text='` + query.Get("text") + `'` // ffmpeg -f lavfi -i flite=list_voices=1 // awb, kal, kal16, rms, slt diff --git a/pkg/ffmpeg/ffmpeg.go b/pkg/ffmpeg/ffmpeg.go index 912e7684..a7ca71c7 100644 --- a/pkg/ffmpeg/ffmpeg.go +++ b/pkg/ffmpeg/ffmpeg.go @@ -6,6 +6,15 @@ import ( "strings" ) +// correlation of libavformat versions with ffmpeg versions +const ( + Version50 = "59. 16" + Version51 = "59. 27" + Version60 = "60. 3" + Version61 = "60. 16" + Version70 = "61. 1" +) + type Args struct { Bin string // ffmpeg Global string // -hide_banner -v error @@ -13,6 +22,7 @@ type Args struct { Codecs []string // -c:v libx264 -g:v 30 -preset:v ultrafast -tune:v zerolatency Filters []string // scale=1920:1080 Output string // -f rtsp {output} + Version string // libavformat version, it's more reliable than the ffmpeg version Video, Audio int // count of Video and Audio params } @@ -52,6 +62,11 @@ func (a *Args) String() string { } b.WriteByte(' ') + // starting from FFmpeg 6.1 readrate=1 has default initial bust 0.5 sec + // it might make us miss the first couple seconds of the file + if strings.HasPrefix(a.Input, "-re ") && a.Version >= Version61 { + b.WriteString("-readrate_initial_burst 0.001 ") + } b.WriteString(a.Input) multimode := a.Video > 1 || a.Audio > 1 @@ -91,3 +106,18 @@ func (a *Args) String() string { return b.String() } + +func ParseVersion(b []byte) (ffmpeg string, libavformat string) { + if len(b) > 100 { + // ffmpeg version n7.0-30-g8b0fe91754-20240520 Copyright (c) 2000-2024 the FFmpeg developers + if i := bytes.IndexByte(b[15:], ' '); i > 0 { + ffmpeg = string(b[15 : 15+i]) + } + + // libavformat 60. 16.100 / 60. 16.100 + if i := strings.Index(string(b), "libavformat"); i > 0 { + libavformat = string(b[i+15 : i+25]) + } + } + return +}