From ce47efc41596b914424a4b0c63298600d0077574 Mon Sep 17 00:00:00 2001 From: NodudeWasTaken <75137537+NodudeWasTaken@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:03:48 +0200 Subject: [PATCH] Add video codec profiles (#5154) --- pkg/ffmpeg/codec.go | 29 ++++++++++------ pkg/ffmpeg/codec_hardware.go | 54 ++++++++++++++++++----------- pkg/ffmpeg/stream_transcode.go | 32 +++++++++++------ pkg/ffmpeg/transcoder/screenshot.go | 6 ++-- pkg/ffmpeg/transcoder/splice.go | 8 ++--- 5 files changed, 80 insertions(+), 49 deletions(-) diff --git a/pkg/ffmpeg/codec.go b/pkg/ffmpeg/codec.go index 1195fdc3d..45fff9ffb 100644 --- a/pkg/ffmpeg/codec.go +++ b/pkg/ffmpeg/codec.go @@ -1,25 +1,32 @@ package ffmpeg -type VideoCodec string +type VideoCodec struct { + Name string // The full name of the codec including profile/quality + CodeName string // The core codec name without profile/quality suffix +} + +func makeVideoCodec(name string, codename string) VideoCodec { + return VideoCodec{name, codename} +} func (c VideoCodec) Args() []string { - if c == "" { + if c.CodeName == "" { return nil } - return []string{"-c:v", string(c)} + return []string{"-c:v", string(c.CodeName)} } var ( // Software codec's - VideoCodecLibX264 VideoCodec = "libx264" - VideoCodecLibWebP VideoCodec = "libwebp" - VideoCodecBMP VideoCodec = "bmp" - VideoCodecMJpeg VideoCodec = "mjpeg" - VideoCodecVP9 VideoCodec = "libvpx-vp9" - VideoCodecVPX VideoCodec = "libvpx" - VideoCodecLibX265 VideoCodec = "libx265" - VideoCodecCopy VideoCodec = "copy" + VideoCodecLibX264 = makeVideoCodec("x264", "libx264") + VideoCodecLibWebP = makeVideoCodec("WebP", "libwebp") + VideoCodecBMP = makeVideoCodec("BMP", "bmp") + VideoCodecMJpeg = makeVideoCodec("Jpeg", "mjpeg") + VideoCodecVP9 = makeVideoCodec("VPX-VP9", "libvpx-vp9") + VideoCodecVPX = makeVideoCodec("VPX-VP8", "libvpx") + VideoCodecLibX265 = makeVideoCodec("x265", "libx265") + VideoCodecCopy = makeVideoCodec("Copy", "copy") ) type AudioCodec string diff --git a/pkg/ffmpeg/codec_hardware.go b/pkg/ffmpeg/codec_hardware.go index e4797a84a..73d825706 100644 --- a/pkg/ffmpeg/codec_hardware.go +++ b/pkg/ffmpeg/codec_hardware.go @@ -15,16 +15,18 @@ import ( var ( // Hardware codec's - VideoCodecN264 VideoCodec = "h264_nvenc" - VideoCodecI264 VideoCodec = "h264_qsv" - VideoCodecA264 VideoCodec = "h264_amf" - VideoCodecM264 VideoCodec = "h264_videotoolbox" - VideoCodecV264 VideoCodec = "h264_vaapi" - VideoCodecR264 VideoCodec = "h264_v4l2m2m" - VideoCodecO264 VideoCodec = "h264_omx" - VideoCodecIVP9 VideoCodec = "vp9_qsv" - VideoCodecVVP9 VideoCodec = "vp9_vaapi" - VideoCodecVVPX VideoCodec = "vp8_vaapi" + VideoCodecN264 = makeVideoCodec("H264 NVENC", "h264_nvenc") + VideoCodecN264H = makeVideoCodec("H264 NVENC HQ profile", "h264_nvenc") + VideoCodecI264 = makeVideoCodec("H264 Intel Quick Sync Video (QSV)", "h264_qsv") + VideoCodecI264C = makeVideoCodec("H264 Intel Quick Sync Video (QSV) Compatibility profile", "h264_qsv") + VideoCodecA264 = makeVideoCodec("H264 Advanced Media Framework (AMF)", "h264_amf") + VideoCodecM264 = makeVideoCodec("H264 VideoToolbox", "h264_videotoolbox") + VideoCodecV264 = makeVideoCodec("H264 VAAPI", "h264_vaapi") + VideoCodecR264 = makeVideoCodec("H264 V4L2M2M", "h264_v4l2m2m") + VideoCodecO264 = makeVideoCodec("H264 OMX", "h264_omx") + VideoCodecIVP9 = makeVideoCodec("VP9 Intel Quick Sync Video (QSV)", "vp9_qsv") + VideoCodecVVP9 = makeVideoCodec("VP9 VAAPI", "vp9_vaapi") + VideoCodecVVPX = makeVideoCodec("VP8 VAAPI", "vp8_vaapi") ) const minHeight int = 480 @@ -33,9 +35,12 @@ const minHeight int = 480 func (f *FFMpeg) InitHWSupport(ctx context.Context) { var hwCodecSupport []VideoCodec + // Note that the first compatible codec is returned, so order is important for _, codec := range []VideoCodec{ + VideoCodecN264H, VideoCodecN264, VideoCodecI264, + VideoCodecI264C, VideoCodecV264, VideoCodecR264, VideoCodecIVP9, @@ -79,7 +84,7 @@ func (f *FFMpeg) InitHWSupport(ctx context.Context) { outstr := fmt.Sprintf("[InitHWSupport] Supported HW codecs [%d]:\n", len(hwCodecSupport)) for _, codec := range hwCodecSupport { - outstr += fmt.Sprintf("\t%s\n", codec) + outstr += fmt.Sprintf("\t%s - %s\n", codec.Name, codec.CodeName) } logger.Info(outstr) @@ -128,7 +133,8 @@ func (f *FFMpeg) hwCanFullHWTranscode(ctx context.Context, codec VideoCodec, vf // Prepend input for hardware encoding only func (f *FFMpeg) hwDeviceInit(args Args, toCodec VideoCodec, fullhw bool) Args { switch toCodec { - case VideoCodecN264: + case VideoCodecN264, + VideoCodecN264H: args = append(args, "-hwaccel_device") args = append(args, "0") if fullhw { @@ -150,6 +156,7 @@ func (f *FFMpeg) hwDeviceInit(args Args, toCodec VideoCodec, fullhw bool) Args { args = append(args, "vaapi") } case VideoCodecI264, + VideoCodecI264C, VideoCodecIVP9: if fullhw { args = append(args, "-hwaccel") @@ -187,12 +194,13 @@ func (f *FFMpeg) hwFilterInit(toCodec VideoCodec, fullhw bool) VideoFilter { videoFilter = videoFilter.Append("format=nv12") videoFilter = videoFilter.Append("hwupload") } - case VideoCodecN264: + case VideoCodecN264, VideoCodecN264H: if !fullhw { - videoFilter = videoFilter.Append("format=yuv420p") + videoFilter = videoFilter.Append("format=nv12") videoFilter = videoFilter.Append("hwupload_cuda") } case VideoCodecI264, + VideoCodecI264C, VideoCodecIVP9: if !fullhw { videoFilter = videoFilter.Append("hwupload=extra_hw_frames=64") @@ -268,7 +276,7 @@ func (f *FFMpeg) hwCodecFilter(args VideoFilter, codec VideoCodec, vf *models.Vi // Apply format switching if applicable func (f *FFMpeg) hwApplyFullHWFilter(args VideoFilter, codec VideoCodec, fullhw bool) VideoFilter { switch codec { - case VideoCodecN264: + case VideoCodecN264, VideoCodecN264H: if fullhw && f.version.Gteq(FFMpegVersion{major: 5}) { // Added in FFMpeg 5 args = args.Append("scale_cuda=format=yuv420p") } @@ -276,7 +284,7 @@ func (f *FFMpeg) hwApplyFullHWFilter(args VideoFilter, codec VideoCodec, fullhw if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 1}) { // Added in FFMpeg 3.1 args = args.Append("scale_vaapi=format=nv12") } - case VideoCodecI264, VideoCodecIVP9: + case VideoCodecI264, VideoCodecI264C, VideoCodecIVP9: if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 3}) { // Added in FFMpeg 3.3 args = args.Append("scale_qsv=format=nv12") } @@ -290,7 +298,7 @@ func (f *FFMpeg) hwApplyScaleTemplate(sargs string, codec VideoCodec, match []in var template string switch codec { - case VideoCodecN264: + case VideoCodecN264, VideoCodecN264H: template = "scale_cuda=$value" if fullhw && f.version.Gteq(FFMpegVersion{major: 5}) { // Added in FFMpeg 5 template += ":format=yuv420p" @@ -300,7 +308,7 @@ func (f *FFMpeg) hwApplyScaleTemplate(sargs string, codec VideoCodec, match []in if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 1}) { // Added in FFMpeg 3.1 template += ":format=nv12" } - case VideoCodecI264, VideoCodecIVP9: + case VideoCodecI264, VideoCodecI264C, VideoCodecIVP9: template = "scale_qsv=$value" if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 3}) { // Added in FFMpeg 3.3 template += ":format=nv12" @@ -312,7 +320,7 @@ func (f *FFMpeg) hwApplyScaleTemplate(sargs string, codec VideoCodec, match []in } // BUG: [scale_qsv]: Size values less than -1 are not acceptable. - isIntel := codec == VideoCodecI264 || codec == VideoCodecIVP9 + isIntel := codec == VideoCodecI264 || codec == VideoCodecI264C || codec == VideoCodecIVP9 // BUG: scale_vt doesn't call ff_scale_adjust_dimensions, thus cant accept negative size values isApple := codec == VideoCodecM264 return VideoFilter(templateReplaceScale(sargs, template, match, vf, isIntel || isApple)) @@ -322,7 +330,9 @@ func (f *FFMpeg) hwApplyScaleTemplate(sargs string, codec VideoCodec, match []in func (f *FFMpeg) hwCodecMaxRes(codec VideoCodec) (int, int) { switch codec { case VideoCodecN264, - VideoCodecI264: + VideoCodecN264H, + VideoCodecI264, + VideoCodecI264C: return 4096, 4096 } @@ -345,7 +355,9 @@ func (f *FFMpeg) hwCodecHLSCompatible() *VideoCodec { for _, element := range f.hwCodecSupport { switch element { case VideoCodecN264, + VideoCodecN264H, VideoCodecI264, + VideoCodecI264C, VideoCodecV264, VideoCodecR264, VideoCodecM264: // Note that the Apple encoder sucks at startup, thus HLS quality is crap @@ -360,7 +372,9 @@ func (f *FFMpeg) hwCodecMP4Compatible() *VideoCodec { for _, element := range f.hwCodecSupport { switch element { case VideoCodecN264, + VideoCodecN264H, VideoCodecI264, + VideoCodecI264C, VideoCodecM264: return &element } diff --git a/pkg/ffmpeg/stream_transcode.go b/pkg/ffmpeg/stream_transcode.go index 714652470..e0a30cdd9 100644 --- a/pkg/ffmpeg/stream_transcode.go +++ b/pkg/ffmpeg/stream_transcode.go @@ -45,12 +45,31 @@ func CodecInit(codec VideoCodec) (args Args) { "-rc", "vbr", "-cq", "15", ) - case VideoCodecI264: + case VideoCodecN264H: + args = append(args, + "-profile", "p7", + "-tune", "hq", + "-profile", "high", + "-rc", "vbr", + "-rc-lookahead", "60", + "-surfaces", "64", + "-spatial-aq", "1", + "-aq-strength", "15", + "-cq", "15", + "-coder", "cabac", + "-b_ref_mode", "middle", + ) + case VideoCodecI264, VideoCodecIVP9: args = append(args, "-global_quality", "20", "-preset", "faster", ) - case VideoCodecV264: + case VideoCodecI264C: + args = append(args, + "-q", "20", + "-preset", "faster", + ) + case VideoCodecV264, VideoCodecVVP9: args = append(args, "-qp", "20", ) @@ -67,15 +86,6 @@ func CodecInit(codec VideoCodec) (args Args) { "-preset", "superfast", "-crf", "25", ) - case VideoCodecIVP9: - args = append(args, - "-global_quality", "20", - "-preset", "faster", - ) - case VideoCodecVVP9: - args = append(args, - "-qp", "20", - ) } return args diff --git a/pkg/ffmpeg/transcoder/screenshot.go b/pkg/ffmpeg/transcoder/screenshot.go index a1ddef6b6..c3343d594 100644 --- a/pkg/ffmpeg/transcoder/screenshot.go +++ b/pkg/ffmpeg/transcoder/screenshot.go @@ -24,13 +24,13 @@ func (o *ScreenshotOptions) setDefaults() { } type ScreenshotOutputType struct { - codec ffmpeg.VideoCodec + codec *ffmpeg.VideoCodec format ffmpeg.Format } func (t ScreenshotOutputType) Args() []string { var ret []string - if t.codec != "" { + if t.codec != nil { ret = append(ret, t.codec.Args()...) } if t.format != "" { @@ -45,7 +45,7 @@ var ( format: "image2", } ScreenshotOutputTypeBMP = ScreenshotOutputType{ - codec: ffmpeg.VideoCodecBMP, + codec: &ffmpeg.VideoCodecBMP, format: "rawvideo", } ) diff --git a/pkg/ffmpeg/transcoder/splice.go b/pkg/ffmpeg/transcoder/splice.go index 7ae7e6c94..45d71332b 100644 --- a/pkg/ffmpeg/transcoder/splice.go +++ b/pkg/ffmpeg/transcoder/splice.go @@ -11,7 +11,7 @@ type SpliceOptions struct { OutputPath string Format ffmpeg.Format - VideoCodec ffmpeg.VideoCodec + VideoCodec *ffmpeg.VideoCodec VideoArgs ffmpeg.Args AudioCodec ffmpeg.AudioCodec @@ -45,11 +45,11 @@ func Splice(concatFile string, options SpliceOptions) ffmpeg.Args { args = args.Overwrite() // if video codec is not provided, then use copy - if options.VideoCodec == "" { - options.VideoCodec = ffmpeg.VideoCodecCopy + if options.VideoCodec == nil { + options.VideoCodec = &ffmpeg.VideoCodecCopy } - args = args.VideoCodec(options.VideoCodec) + args = args.VideoCodec(*options.VideoCodec) args = args.AppendArgs(options.VideoArgs) // if audio codec is not provided, then use copy