Browse Source
* Query for installed codecs * Start modeling out codecs * Can now specify a codec and get the correct settings returned from the model * Return codecs in admin/serverconfig * Start handling transcoding errors and return messages to user * filter available codecs against a whitelist * Fix merge * Codecs are working * Switching between codecs work * Add apis for setting a custom video codec * Cleanup the logging of transcoder errors * Add v4l codec * Add fetching v4l * Add support for per-codec presets * Use updated nvenc encoding parameters * Update log message * Some more codec WIP * Turn off v4l. It is a mess. * Try to make the lowest latency level a bit more playable * Use a human redable display name in console messages * Turn on transcoder persistent connections * Add more codec-related user-facing error messages * Give the initial offline state transcoder an id * Force a minimum segment count of 3 * Disable qsv for now. set x264 specific params in VariantFlags * Close body in case * Ignore vbv underflow message, it is not actionable * Determine a dynamic gop value based on the length of segments * Add codec-specific tests * Cleanup * Ignore goconst lint warnings in codec file * Troubleshoot omx * Add more codec tests * Remove no longer accurate comment * Bundle admin from codec branch * Revert back to old setting * Cleanup list of codecs a bit * Remove old references to the encoder preset * Commit updated API documentation * Update admin bundle * Commit updated API documentation * Add codec setting to api spec * Commit updated API documentation Co-authored-by: Owncast <owncast@owncast.online>pull/941/head
21 changed files with 845 additions and 180 deletions
@ -0,0 +1,373 @@
@@ -0,0 +1,373 @@
|
||||
//nolint:goconst
|
||||
|
||||
package transcoder |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os/exec" |
||||
"strings" |
||||
|
||||
log "github.com/sirupsen/logrus" |
||||
) |
||||
|
||||
// Codec represents a supported codec on the system.
|
||||
type Codec interface { |
||||
Name() string |
||||
DisplayName() string |
||||
GlobalFlags() string |
||||
PixelFormat() string |
||||
ExtraArguments() string |
||||
ExtraFilters() string |
||||
VariantFlags(v *HLSVariant) string |
||||
GetPresetForLevel(l int) string |
||||
} |
||||
|
||||
var supportedCodecs = map[string]string{ |
||||
(&Libx264Codec{}).Name(): "libx264", |
||||
(&OmxCodec{}).Name(): "omx", |
||||
(&VaapiCodec{}).Name(): "vaapi", |
||||
(&NvencCodec{}).Name(): "NVIDIA nvenc", |
||||
} |
||||
|
||||
type Libx264Codec struct { |
||||
} |
||||
|
||||
func (c *Libx264Codec) Name() string { |
||||
return "libx264" |
||||
} |
||||
|
||||
func (c *Libx264Codec) DisplayName() string { |
||||
return "x264" |
||||
} |
||||
|
||||
func (c *Libx264Codec) GlobalFlags() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *Libx264Codec) PixelFormat() string { |
||||
return "yuv420p" |
||||
} |
||||
|
||||
func (c *Libx264Codec) ExtraArguments() string { |
||||
return strings.Join([]string{ |
||||
"-tune", "zerolatency", // Option used for good for fast encoding and low-latency streaming (always includes iframes in each segment)
|
||||
}, " ") |
||||
} |
||||
|
||||
func (c *Libx264Codec) ExtraFilters() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *Libx264Codec) VariantFlags(v *HLSVariant) string { |
||||
bufferSize := int(float64(v.videoBitrate) * 1.2) // How often it checks the bitrate of encoded segments to see if it's too high/low.
|
||||
|
||||
return strings.Join([]string{ |
||||
fmt.Sprintf("-x264-params:v:%d \"scenecut=0:open_gop=0\"", v.index), // How often the encoder checks the bitrate in order to meet average/max values
|
||||
fmt.Sprintf("-bufsize:v:%d %dk", v.index, bufferSize), |
||||
fmt.Sprintf("-profile:v:%d %s", v.index, "high"), // Encoding profile
|
||||
}, " ") |
||||
} |
||||
|
||||
func (c *Libx264Codec) GetPresetForLevel(l int) string { |
||||
presetMapping := []string{ |
||||
"ultrafast", |
||||
"superfast", |
||||
"veryfast", |
||||
"faster", |
||||
"fast", |
||||
} |
||||
|
||||
if l >= len(presetMapping) { |
||||
return "superfast" |
||||
} |
||||
|
||||
return presetMapping[l] |
||||
} |
||||
|
||||
type OmxCodec struct { |
||||
} |
||||
|
||||
func (c *OmxCodec) Name() string { |
||||
return "h264_omx" |
||||
} |
||||
|
||||
func (c *OmxCodec) DisplayName() string { |
||||
return "OpenMAX (omx)" |
||||
} |
||||
|
||||
func (c *OmxCodec) GlobalFlags() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *OmxCodec) PixelFormat() string { |
||||
return "yuv420p" |
||||
} |
||||
|
||||
func (c *OmxCodec) ExtraArguments() string { |
||||
return strings.Join([]string{ |
||||
"-tune", "zerolatency", // Option used for good for fast encoding and low-latency streaming (always includes iframes in each segment)
|
||||
}, " ") |
||||
} |
||||
|
||||
func (c *OmxCodec) ExtraFilters() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *OmxCodec) VariantFlags(v *HLSVariant) string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *OmxCodec) GetPresetForLevel(l int) string { |
||||
presetMapping := []string{ |
||||
"ultrafast", |
||||
"superfast", |
||||
"veryfast", |
||||
"faster", |
||||
"fast", |
||||
} |
||||
|
||||
if l >= len(presetMapping) { |
||||
return "superfast" |
||||
} |
||||
|
||||
return presetMapping[l] |
||||
} |
||||
|
||||
type VaapiCodec struct { |
||||
} |
||||
|
||||
func (c *VaapiCodec) Name() string { |
||||
return "h264_vaapi" |
||||
} |
||||
|
||||
func (c *VaapiCodec) DisplayName() string { |
||||
return "VA-API" |
||||
} |
||||
|
||||
func (c *VaapiCodec) GlobalFlags() string { |
||||
flags := []string{ |
||||
"-vaapi_device", "/dev/dri/renderD128", |
||||
} |
||||
|
||||
return strings.Join(flags, " ") |
||||
} |
||||
|
||||
func (c *VaapiCodec) PixelFormat() string { |
||||
return "vaapi_vld" |
||||
} |
||||
|
||||
func (c *VaapiCodec) ExtraFilters() string { |
||||
return "format=nv12,hwupload" |
||||
} |
||||
|
||||
func (c *VaapiCodec) ExtraArguments() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *VaapiCodec) VariantFlags(v *HLSVariant) string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *VaapiCodec) GetPresetForLevel(l int) string { |
||||
presetMapping := []string{ |
||||
"ultrafast", |
||||
"superfast", |
||||
"veryfast", |
||||
"faster", |
||||
"fast", |
||||
} |
||||
|
||||
if l >= len(presetMapping) { |
||||
return "superfast" |
||||
} |
||||
|
||||
return presetMapping[l] |
||||
} |
||||
|
||||
type NvencCodec struct { |
||||
} |
||||
|
||||
func (c *NvencCodec) Name() string { |
||||
return "h264_nvenc" |
||||
} |
||||
|
||||
func (c *NvencCodec) DisplayName() string { |
||||
return "nvidia nvenc" |
||||
} |
||||
|
||||
func (c *NvencCodec) GlobalFlags() string { |
||||
flags := []string{ |
||||
"-hwaccel cuda", |
||||
} |
||||
|
||||
return strings.Join(flags, " ") |
||||
} |
||||
|
||||
func (c *NvencCodec) PixelFormat() string { |
||||
return "yuv420p" |
||||
} |
||||
|
||||
func (c *NvencCodec) ExtraArguments() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *NvencCodec) ExtraFilters() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *NvencCodec) VariantFlags(v *HLSVariant) string { |
||||
tuning := "ll" // low latency
|
||||
return fmt.Sprintf("-tune:v:%d %s", v.index, tuning) |
||||
} |
||||
|
||||
func (c *NvencCodec) GetPresetForLevel(l int) string { |
||||
presetMapping := []string{ |
||||
"p1", |
||||
"p2", |
||||
"p3", |
||||
"p4", |
||||
"p5", |
||||
} |
||||
|
||||
if l >= len(presetMapping) { |
||||
return "p3" |
||||
} |
||||
|
||||
return presetMapping[l] |
||||
} |
||||
|
||||
type QuicksyncCodec struct { |
||||
} |
||||
|
||||
func (c *QuicksyncCodec) Name() string { |
||||
return "h264_qsv" |
||||
} |
||||
|
||||
func (c *QuicksyncCodec) DisplayName() string { |
||||
return "Intel QuickSync" |
||||
} |
||||
|
||||
func (c *QuicksyncCodec) GlobalFlags() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *QuicksyncCodec) PixelFormat() string { |
||||
return "nv12" |
||||
} |
||||
|
||||
func (c *QuicksyncCodec) ExtraArguments() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *QuicksyncCodec) ExtraFilters() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *QuicksyncCodec) VariantFlags(v *HLSVariant) string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *QuicksyncCodec) GetPresetForLevel(l int) string { |
||||
presetMapping := []string{ |
||||
"ultrafast", |
||||
"superfast", |
||||
"veryfast", |
||||
"faster", |
||||
"fast", |
||||
} |
||||
|
||||
if l >= len(presetMapping) { |
||||
return "superfast" |
||||
} |
||||
|
||||
return presetMapping[l] |
||||
} |
||||
|
||||
type Video4Linux struct{} |
||||
|
||||
func (c *Video4Linux) Name() string { |
||||
return "h264_v4l2m2m" |
||||
} |
||||
|
||||
func (c *Video4Linux) DisplayName() string { |
||||
return "Video4Linux" |
||||
} |
||||
|
||||
func (c *Video4Linux) GlobalFlags() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *Video4Linux) PixelFormat() string { |
||||
return "nv21" |
||||
} |
||||
|
||||
func (c *Video4Linux) ExtraArguments() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *Video4Linux) ExtraFilters() string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *Video4Linux) VariantFlags(v *HLSVariant) string { |
||||
return "" |
||||
} |
||||
|
||||
func (c *Video4Linux) 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) |
||||
|
||||
cmd := exec.Command(ffmpegPath, "-encoders") |
||||
out, err := cmd.CombinedOutput() |
||||
if err != nil { |
||||
log.Errorln(err) |
||||
return codecs |
||||
} |
||||
|
||||
response := string(out) |
||||
lines := strings.Split(response, "\n") |
||||
for _, line := range lines { |
||||
if strings.Contains(line, "H.264") { |
||||
fields := strings.Fields(line) |
||||
codec := fields[1] |
||||
if _, supported := supportedCodecs[codec]; supported { |
||||
codecs = append(codecs, codec) |
||||
} |
||||
} |
||||
} |
||||
|
||||
return codecs |
||||
} |
||||
|
||||
func getCodec(name string) Codec { |
||||
switch name { |
||||
case (&NvencCodec{}).Name(): |
||||
return &NvencCodec{} |
||||
case (&VaapiCodec{}).Name(): |
||||
return &VaapiCodec{} |
||||
case (&QuicksyncCodec{}).Name(): |
||||
return &QuicksyncCodec{} |
||||
case (&OmxCodec{}).Name(): |
||||
return &OmxCodec{} |
||||
case (&Video4Linux{}).Name(): |
||||
return &Video4Linux{} |
||||
default: |
||||
return &Libx264Codec{} |
||||
} |
||||
} |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
package transcoder |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/owncast/owncast/models" |
||||
) |
||||
|
||||
func TestFFmpegNvencCommand(t *testing.T) { |
||||
latencyLevel := models.GetLatencyLevel(3) |
||||
codec := NvencCodec{} |
||||
|
||||
transcoder := new(Transcoder) |
||||
transcoder.ffmpegPath = "/fake/path/ffmpeg" |
||||
transcoder.SetInput("fakecontent.flv") |
||||
transcoder.SetOutputPath("fakeOutput") |
||||
transcoder.SetIdentifier("jdoieGg") |
||||
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() |
||||
|
||||
expected := `FFREPORT=file="transcoder.log":level=32 /fake/path/ffmpeg -hide_banner -loglevel warning -hwaccel cuda -fflags +genpts -i fakecontent.flv -map v:0 -c:v:0 h264_nvenc -b:v:0 1200k -maxrate:v:0 1272k -g:v:0 60 -keyint_min:v:0 60 -r:v:0 30 -tune:v:0 ll -map a:0? -c:a:0 copy -preset p3 -map v:0 -c:v:1 h264_nvenc -b:v:1 3500k -maxrate:v:1 3710k -g:v:1 48 -keyint_min:v:1 48 -r:v:1 24 -tune:v:1 ll -map a:0? -c:a:1 copy -preset p5 -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -preset p1 -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 2 -hls_list_size 3 -segment_format_options mpegts_flags=+initial_discontinuity:mpegts_copyts=1 -pix_fmt yuv420p -sc_threshold 0 -master_pl_name stream.m3u8 -strftime 1 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdoieGg%s.ts -max_muxing_queue_size 400 -method PUT -http_persistent 0 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) |
||||
} |
||||
} |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
package transcoder |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/owncast/owncast/models" |
||||
) |
||||
|
||||
func TestFFmpegOmxCommand(t *testing.T) { |
||||
latencyLevel := models.GetLatencyLevel(3) |
||||
codec := OmxCodec{} |
||||
|
||||
transcoder := new(Transcoder) |
||||
transcoder.ffmpegPath = "/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() |
||||
|
||||
expected := `FFREPORT=file="transcoder.log":level=32 /fake/path/ffmpeg -hide_banner -loglevel warning -fflags +genpts -i fakecontent.flv -map v:0 -c:v:0 h264_omx -b:v:0 1200k -maxrate:v:0 1272k -g:v:0 60 -keyint_min:v:0 60 -r:v:0 30 -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 h264_omx -b:v:1 3500k -maxrate:v:1 3710k -g:v:1 48 -keyint_min:v:1 48 -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 2 -hls_list_size 3 -segment_format_options mpegts_flags=+initial_discontinuity:mpegts_copyts=1 -tune zerolatency -pix_fmt yuv420p -sc_threshold 0 -master_pl_name stream.m3u8 -strftime 1 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdFsdfzGg%s.ts -max_muxing_queue_size 400 -method PUT -http_persistent 0 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) |
||||
} |
||||
} |
@ -1,46 +0,0 @@
@@ -1,46 +0,0 @@
|
||||
package transcoder |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/owncast/owncast/models" |
||||
) |
||||
|
||||
func TestFFmpegCommand(t *testing.T) { |
||||
latencyLevel := models.GetLatencyLevel(3) |
||||
|
||||
transcoder := new(Transcoder) |
||||
transcoder.ffmpegPath = "/fake/path/ffmpeg" |
||||
transcoder.SetInput("fakecontent.flv") |
||||
transcoder.SetOutputPath("fakeOutput") |
||||
transcoder.SetIdentifier("jdofFGg") |
||||
transcoder.SetInternalHTTPPort("8123") |
||||
transcoder.currentLatencyLevel = latencyLevel |
||||
|
||||
variant := HLSVariant{} |
||||
variant.videoBitrate = 1200 |
||||
variant.isAudioPassthrough = true |
||||
variant.encoderPreset = "veryfast" |
||||
variant.SetVideoFramerate(30) |
||||
transcoder.AddVariant(variant) |
||||
|
||||
variant2 := HLSVariant{} |
||||
variant2.videoBitrate = 3500 |
||||
variant2.isAudioPassthrough = true |
||||
variant2.encoderPreset = "faster" |
||||
variant2.SetVideoFramerate(24) |
||||
transcoder.AddVariant(variant2) |
||||
|
||||
variant3 := HLSVariant{} |
||||
variant3.isAudioPassthrough = true |
||||
variant3.isVideoPassthrough = true |
||||
transcoder.AddVariant(variant3) |
||||
|
||||
cmd := transcoder.getString() |
||||
|
||||
expected := `/fake/path/ffmpeg -hide_banner -loglevel warning -i fakecontent.flv -map v:0 -c:v:0 libx264 -b:v:0 1200k -maxrate:v:0 1272k -bufsize:v:0 1440k -g:v:0 89 -profile:v:0 high -r:v:0 30 -x264-params:v:0 "scenecut=0:open_gop=0:min-keyint=89:keyint=89" -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 libx264 -b:v:1 3500k -maxrate:v:1 3710k -bufsize:v:1 4200k -g:v:1 71 -profile:v:1 high -r:v:1 24 -x264-params:v:1 "scenecut=0:open_gop=0:min-keyint=71:keyint=71" -map a:0? -c:a:1 copy -preset faster -map v:0 -c:v:2 copy -map a:0? -c:a:2 copy -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 " -f hls -hls_time 3 -hls_list_size 3 -hls_delete_threshold 10 -tune zerolatency -pix_fmt yuv420p -sc_threshold 0 -master_pl_name stream.m3u8 -strftime 1 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg%s.ts -max_muxing_queue_size 400 -method PUT -http_persistent 0 -fflags +genpts http://127.0.0.1:8123/%v/stream.m3u8 2> transcoder.log` |
||||
|
||||
if cmd != expected { |
||||
t.Errorf("ffmpeg command does not match expected.\nGot %s\n, want: %s", cmd, expected) |
||||
} |
||||
} |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
package transcoder |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/owncast/owncast/models" |
||||
) |
||||
|
||||
func TestFFmpegVaapiCommand(t *testing.T) { |
||||
latencyLevel := models.GetLatencyLevel(3) |
||||
codec := VaapiCodec{} |
||||
|
||||
transcoder := new(Transcoder) |
||||
transcoder.ffmpegPath = "/fake/path/ffmpeg" |
||||
transcoder.SetInput("fakecontent.flv") |
||||
transcoder.SetOutputPath("fakeOutput") |
||||
transcoder.SetIdentifier("jdofFGg") |
||||
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() |
||||
|
||||
expected := `FFREPORT=file="transcoder.log":level=32 /fake/path/ffmpeg -hide_banner -loglevel warning -vaapi_device /dev/dri/renderD128 -fflags +genpts -i fakecontent.flv -map v:0 -c:v:0 h264_vaapi -b:v:0 1200k -maxrate:v:0 1272k -g:v:0 60 -keyint_min:v:0 60 -r:v:0 30 -map a:0? -c:a:0 copy -filter:v:0 "format=nv12,hwupload" -preset veryfast -map v:0 -c:v:1 h264_vaapi -b:v:1 3500k -maxrate:v:1 3710k -g:v:1 48 -keyint_min:v:1 48 -r:v:1 24 -map a:0? -c:a:1 copy -filter:v:1 "format=nv12,hwupload" -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 2 -hls_list_size 3 -segment_format_options mpegts_flags=+initial_discontinuity:mpegts_copyts=1 -pix_fmt vaapi_vld -sc_threshold 0 -master_pl_name stream.m3u8 -strftime 1 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg%s.ts -max_muxing_queue_size 400 -method PUT -http_persistent 0 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) |
||||
} |
||||
} |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
package transcoder |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/owncast/owncast/models" |
||||
) |
||||
|
||||
func TestFFmpegx264Command(t *testing.T) { |
||||
latencyLevel := models.GetLatencyLevel(3) |
||||
codec := Libx264Codec{} |
||||
|
||||
transcoder := new(Transcoder) |
||||
transcoder.ffmpegPath = "/fake/path/ffmpeg" |
||||
transcoder.SetInput("fakecontent.flv") |
||||
transcoder.SetOutputPath("fakeOutput") |
||||
transcoder.SetIdentifier("jdofFGg") |
||||
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() |
||||
|
||||
expected := `FFREPORT=file="transcoder.log":level=32 /fake/path/ffmpeg -hide_banner -loglevel warning -fflags +genpts -i fakecontent.flv -map v:0 -c:v:0 libx264 -b:v:0 1200k -maxrate:v:0 1272k -g:v:0 60 -keyint_min:v:0 60 -r:v:0 30 -x264-params:v:0 "scenecut=0:open_gop=0" -bufsize:v:0 1440k -profile:v:0 high -map a:0? -c:a:0 copy -preset veryfast -map v:0 -c:v:1 libx264 -b:v:1 3500k -maxrate:v:1 3710k -g:v:1 48 -keyint_min:v:1 48 -r:v:1 24 -x264-params:v:1 "scenecut=0:open_gop=0" -bufsize:v:1 4200k -profile:v:1 high -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 2 -hls_list_size 3 -segment_format_options mpegts_flags=+initial_discontinuity:mpegts_copyts=1 -tune zerolatency -pix_fmt yuv420p -sc_threshold 0 -master_pl_name stream.m3u8 -strftime 1 -hls_segment_filename http://127.0.0.1:8123/%v/stream-jdofFGg%s.ts -max_muxing_queue_size 400 -method PUT -http_persistent 0 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) |
||||
} |
||||
} |
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
package transcoder |
||||
|
||||
import ( |
||||
"strings" |
||||
"sync" |
||||
|
||||
log "github.com/sirupsen/logrus" |
||||
) |
||||
|
||||
var _lastTranscoderLogMessage = "" |
||||
var l = &sync.RWMutex{} |
||||
|
||||
var errorMap = map[string]string{ |
||||
"Unrecognized option 'vaapi_device'": "you are likely trying to utilize a vaapi codec, but your version of ffmpeg or your hardware doesn't support it. change your codec to libx264 and restart your stream", |
||||
"unable to open display": "your copy of ffmpeg is likely installed via snap packages. please uninstall and re-install via a non-snap method. https://owncast.online/docs/troubleshooting/#misc-video-issues", |
||||
"Failed to open file 'http://127.0.0.1": "error transcoding. make sure your version of ffmpeg is compatible with your selected codec or is recent enough https://owncast.online/docs/troubleshooting/#codecs", |
||||
"can't configure encoder": "error with codec. if your copy of ffmpeg or your hardware does not support your selected codec you may need to select another", |
||||
"Unable to parse option value": "you are likely trying to utilize a specific codec, but your version of ffmpeg or your hardware doesn't support it. either fix your ffmpeg install or try changing your codec to libx264 and restart your stream", |
||||
"OpenEncodeSessionEx failed: out of memory": "your NVIDIA gpu is limiting the number of concurrent stream qualities you can support. remove a stream output variant and try again.", |
||||
"Cannot use rename on non file protocol, this may lead to races and temporary partial files": "", |
||||
"No VA display found for device": "vaapi not enabled. either your copy of ffmpeg does not support it, your hardware does not support it, or you need to install additional drivers for your hardware.", |
||||
"Could not find a valid device": "your codec is either not supported or not configured properly", |
||||
"H.264 bitstream error": "transcoding content error playback issues may arise. you may want to use the default codec if you are not already.", |
||||
|
||||
`Unknown encoder 'h264_qsv'`: "your copy of ffmpeg does not have support for Intel QuickSync encoding (h264_qsv). change the selected codec in your video settings", |
||||
`Unknown encoder 'h264_vaapi'`: "your copy of ffmpeg does not have support for VA-API encoding (h264_vaapi). change the selected codec in your video settings", |
||||
`Unknown encoder 'h264_nvenc'`: "your copy of ffmpeg does not have support for NVIDIA hardware encoding (h264_nvenc). change the selected codec in your video settings", |
||||
`Unknown encoder 'h264_x264'`: "your copy of ffmpeg does not have support for the default x264 codec (h264_x264). download a version of ffmpeg that supports this.", |
||||
`Unrecognized option 'x264-params`: "your copy of ffmpeg does not have support for the default libx264 codec (h264_x264). download a version of ffmpeg that supports this.", |
||||
|
||||
// Generic error for a codec
|
||||
"Unrecognized option": "error with codec. if your copy of ffmpeg or your hardware does not support your selected codec you may need to select another", |
||||
} |
||||
|
||||
var ignoredErrors = []string{ |
||||
"Duplicated segment filename detected", |
||||
"Error while opening encoder for output stream", |
||||
"Unable to parse option value", |
||||
"Last message repeated", |
||||
"Option not found", |
||||
"use of closed network connection", |
||||
"URL read error: End of file", |
||||
"upload playlist failed, will retry with a new http session", |
||||
"VBV underflow", |
||||
"Cannot use rename on non file protocol", |
||||
} |
||||
|
||||
func handleTranscoderMessage(message string) { |
||||
log.Debugln(message) |
||||
|
||||
l.Lock() |
||||
defer l.Unlock() |
||||
|
||||
// Ignore certain messages that we don't care about.
|
||||
for _, error := range ignoredErrors { |
||||
if strings.Contains(message, error) { |
||||
return |
||||
} |
||||
} |
||||
|
||||
// Convert specific transcoding messages to human-readable messages.
|
||||
for error, displayMessage := range errorMap { |
||||
if strings.Contains(message, error) { |
||||
message = displayMessage |
||||
break |
||||
} |
||||
} |
||||
|
||||
if message == "" { |
||||
return |
||||
} |
||||
|
||||
// No good comes from a flood of repeated messages.
|
||||
if message == _lastTranscoderLogMessage { |
||||
return |
||||
} |
||||
|
||||
log.Error(message) |
||||
|
||||
_lastTranscoderLogMessage = message |
||||
} |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue