From f5a5ac006af5bda2d82e8f7d3ccfebc7efe090eb Mon Sep 17 00:00:00 2001 From: Lerk Date: Tue, 15 Mar 2022 08:55:06 +0100 Subject: [PATCH] Add videotoolbox codec (#1771) * add videotoolbox codec * add -realtime flag for medium and below quality * add quality level to extra arguments * use variant flags instead of extra arguments * add videotoolbox test * fix test --- core/transcoder/codecs.go | 79 ++++++++++++++++++- .../transcoder_videotoolbox_test.go | 50 ++++++++++++ 2 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 core/transcoder/transcoder_videotoolbox_test.go diff --git a/core/transcoder/codecs.go b/core/transcoder/codecs.go index c8c1feb67..aa53ca562 100644 --- a/core/transcoder/codecs.go +++ b/core/transcoder/codecs.go @@ -23,10 +23,11 @@ type Codec interface { } var supportedCodecs = map[string]string{ - (&Libx264Codec{}).Name(): "libx264", - (&OmxCodec{}).Name(): "omx", - (&VaapiCodec{}).Name(): "vaapi", - (&NvencCodec{}).Name(): "NVIDIA nvenc", + (&Libx264Codec{}).Name(): "libx264", + (&OmxCodec{}).Name(): "omx", + (&VaapiCodec{}).Name(): "vaapi", + (&NvencCodec{}).Name(): "NVIDIA nvenc", + (&VideoToolboxCodec{}).Name(): "videotoolbox", } // Libx264Codec represents an instance of the Libx264 Codec. @@ -381,6 +382,74 @@ func (c *Video4Linux) GetPresetForLevel(l int) string { return presetMapping[l] } +// VideoToolboxCodec represents an instance of the VideoToolbox codec. +type VideoToolboxCodec struct { +} + +// Name returns the codec name. +func (c *VideoToolboxCodec) Name() string { + return "h264_videotoolbox" +} + +// DisplayName returns the human readable name of the codec. +func (c *VideoToolboxCodec) DisplayName() string { + return "VideoToolbox" +} + +// GlobalFlags are the global flags used with this codec in the transcoder. +func (c *VideoToolboxCodec) GlobalFlags() string { + var flags []string + + return strings.Join(flags, " ") +} + +// PixelFormat is the pixel format required for this codec. +func (c *VideoToolboxCodec) PixelFormat() string { + return "nv12" +} + +// ExtraFilters are the extra filters required for this codec in the transcoder. +func (c *VideoToolboxCodec) ExtraFilters() string { + return "" +} + +// ExtraArguments are the extra arguments used with this codec in the transcoder. +func (c *VideoToolboxCodec) ExtraArguments() string { + return "" +} + +// VariantFlags returns a string representing a single variant processed by this codec. +func (c *VideoToolboxCodec) VariantFlags(v *HLSVariant) string { + arguments := []string{ + "-realtime true", + "-realtime true", + "-realtime true", + } + + if v.cpuUsageLevel >= len(arguments) { + return "" + } + + return arguments[v.cpuUsageLevel] +} + +// GetPresetForLevel returns the string preset for this codec given an integer level. +func (c *VideoToolboxCodec) GetPresetForLevel(l int) string { + presetMapping := []string{ + "ultrafast", + "superfast", + "veryfast", + "faster", + "fast", + } + + if l >= len(presetMapping) { + return "superfast" + } + + return presetMapping[l] +} + // GetCodecs will return the supported codecs available on the system. func GetCodecs(ffmpegPath string) []string { codecs := make([]string, 0) @@ -419,6 +488,8 @@ func getCodec(name string) Codec { return &OmxCodec{} case (&Video4Linux{}).Name(): return &Video4Linux{} + case (&VideoToolboxCodec{}).Name(): + return &VideoToolboxCodec{} default: return &Libx264Codec{} } diff --git a/core/transcoder/transcoder_videotoolbox_test.go b/core/transcoder/transcoder_videotoolbox_test.go new file mode 100644 index 000000000..f500a0397 --- /dev/null +++ b/core/transcoder/transcoder_videotoolbox_test.go @@ -0,0 +1,50 @@ +package transcoder + +import ( + "path/filepath" + "testing" + + "github.com/owncast/owncast/models" +) + +func TestFFmpegVideoToolboxCommand(t *testing.T) { + latencyLevel := models.GetLatencyLevel(2) + codec := VideoToolboxCodec{} + + transcoder := new(Transcoder) + transcoder.ffmpegPath = filepath.Join("fake", "path", "ffmpeg") + transcoder.SetInput("fakecontent.flv") + transcoder.SetOutputPath("fakeOutput") + transcoder.SetIdentifier("jdFsdfzGg") + transcoder.SetInternalHTTPPort("8123") + transcoder.SetCodec(codec.Name()) + transcoder.currentLatencyLevel = latencyLevel + + variant := HLSVariant{} + variant.videoBitrate = 1200 + variant.isAudioPassthrough = true + variant.SetVideoFramerate(30) + variant.SetCPUUsageLevel(2) + transcoder.AddVariant(variant) + + variant2 := HLSVariant{} + variant2.videoBitrate = 3500 + variant2.isAudioPassthrough = true + variant2.SetVideoFramerate(24) + variant2.SetCPUUsageLevel(4) + transcoder.AddVariant(variant2) + + variant3 := HLSVariant{} + variant3.isAudioPassthrough = true + variant3.isVideoPassthrough = true + transcoder.AddVariant(variant3) + + cmd := transcoder.getString() + + expectedLogPath := filepath.Join("data", "logs", "transcoder.log") + expected := `FFREPORT=file="` + expectedLogPath + `":level=32 ` + transcoder.ffmpegPath + ` -hide_banner -loglevel warning -fflags +genpts -i fakecontent.flv -map v:0 -c:v:0 h264_videotoolbox -b:v:0 1008k -maxrate:v:0 1088k -g:v:0 90 -keyint_min:v:0 90 -r:v:0 30 -realtime true -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_videotoolbox -b:v:1 3308k -maxrate:v:1 3572k -g:v:1 72 -keyint_min:v:1 72 -r:v:1 24 -map a:0? -c:a:1 copy -preset fast -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset ultrafast -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 10 -hls_flags program_date_time+independent_segments+omit_endlist -segment_format_options mpegts_flags=mpegts_copyts=1 -pix_fmt nv12 -sc_threshold 0 -master_pl_name stream.m3u8 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg-%d.ts -max_muxing_queue_size 400 -method PUT http://127.0.0.1:8123/%v/stream.m3u8` + + if cmd != expected { + t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected) + } +}