summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabe Kangas <gabek@real-ity.com>2020-08-06 12:19:35 -0700
committerGitHub <noreply@github.com>2020-08-06 12:19:35 -0700
commitd21f13612093556e0915389c9c35e1f483e590ac (patch)
tree95f62dde944970c0cf06638a8a0bdab8baf1b7b8
parentdf04af0f38583120d5ba5c620a848e12c933331d (diff)
Transcoder updates for bitrate validator warnings (#106)v0.0.1
* Transcoder updates for validator warnings (gop, max-bitrates) for #50 * Make the variant settings easier to set/read in code * Update test to reflect update to transcoder * Reduce the buffer to give it a little more room to improve the quality. It feels a bit crunched right now. * Fix test due to changed bitrate buffer ratios * Tweak buffer size one more time to get errors in the validator to zero * Saw errors w/ different bitrates, so tweaking it again
-rw-r--r--config/config.go9
-rw-r--r--config/defaults.go1
-rw-r--r--core/ffmpeg/transcoder.go60
-rw-r--r--core/ffmpeg/transcoder_test.go4
4 files changed, 45 insertions, 29 deletions
diff --git a/config/config.go b/config/config.go
index 3451e2860..2ed4abe66 100644
--- a/config/config.go
+++ b/config/config.go
@@ -197,6 +197,15 @@ func (c *config) GetVideoStreamQualities() []StreamQuality {
return _default.VideoSettings.StreamQualities
}
+// GetFramerate returns the framerate or default
+func (q *StreamQuality) GetFramerate() int {
+ if q.Framerate > 0 {
+ return q.Framerate
+ }
+
+ return _default.VideoSettings.StreamQualities[0].Framerate
+}
+
//Load tries to load the configuration file
func Load(filePath string, versionInfo string) error {
Config = new(config)
diff --git a/config/defaults.go b/config/defaults.go
index ded829273..0c89e5353 100644
--- a/config/defaults.go
+++ b/config/defaults.go
@@ -21,6 +21,7 @@ func getDefaults() config {
IsAudioPassthrough: true,
VideoBitrate: 1200,
EncoderPreset: "veryfast",
+ Framerate: 24,
}
defaults.VideoSettings.StreamQualities = []StreamQuality{defaultQuality}
diff --git a/core/ffmpeg/transcoder.go b/core/ffmpeg/transcoder.go
index 68d83a39f..8172376ba 100644
--- a/core/ffmpeg/transcoder.go
+++ b/core/ffmpeg/transcoder.go
@@ -33,7 +33,7 @@ type HLSVariant struct {
videoSize VideoSize // Resizes the video via scaling
framerate int // The output framerate
- videoBitrate string // The output bitrate
+ videoBitrate int // The output bitrate
isVideoPassthrough bool // Override all settings and just copy the video stream
audioBitrate string // The audio bitrate
@@ -164,11 +164,11 @@ func getVariantFromConfigQuality(quality config.StreamQuality, index int) HLSVar
variant.encoderPreset = "veryfast"
}
- variant.SetVideoBitrate(strconv.Itoa(quality.VideoBitrate) + "k")
+ variant.SetVideoBitrate(quality.VideoBitrate)
variant.SetAudioBitrate(strconv.Itoa(quality.AudioBitrate) + "k")
variant.SetVideoScalingWidth(quality.ScaledWidth)
variant.SetVideoScalingHeight(quality.ScaledHeight)
- variant.SetVideoFramerate(quality.Framerate)
+ variant.SetVideoFramerate(quality.GetFramerate())
return variant
}
@@ -196,13 +196,6 @@ func NewTranscoder() Transcoder {
transcoder.segmentLengthSeconds = config.Config.GetVideoSegmentSecondsLength()
qualities := config.Config.VideoSettings.StreamQualities
- if len(qualities) == 0 {
- defaultQuality := config.StreamQuality{}
- defaultQuality.VideoBitrate = 1000
- defaultQuality.EncoderPreset = "superfast"
- qualities = append(qualities, defaultQuality)
- }
-
for index, quality := range qualities {
variant := getVariantFromConfigQuality(quality, index)
transcoder.AddVariant(variant)
@@ -212,9 +205,9 @@ func NewTranscoder() Transcoder {
}
// Uses `map` https://www.ffmpeg.org/ffmpeg-all.html#Stream-specifiers-1 https://www.ffmpeg.org/ffmpeg-all.html#Advanced-options
-func (v *HLSVariant) getVariantString() string {
+func (v *HLSVariant) getVariantString(t *Transcoder) string {
variantEncoderCommands := []string{
- v.getVideoQualityString(),
+ v.getVideoQualityString(t),
v.getAudioQualityString(),
}
@@ -222,17 +215,7 @@ func (v *HLSVariant) getVariantString() string {
variantEncoderCommands = append(variantEncoderCommands, v.getScalingString())
}
- if v.framerate == 0 {
- v.framerate = 30
- }
-
- if v.framerate != 0 {
- variantEncoderCommands = append(variantEncoderCommands, fmt.Sprintf("-r %d", v.framerate))
- // Insert a keyframe every 2 seconds.
- // Multiply your output frame rate * 2. For example, if your input is -framerate 30, then use -g 60
- variantEncoderCommands = append(variantEncoderCommands, "-g "+strconv.Itoa(v.framerate*2))
- variantEncoderCommands = append(variantEncoderCommands, "-keyint_min "+strconv.Itoa(v.framerate*2))
- }
+ variantEncoderCommands = append(variantEncoderCommands, fmt.Sprintf("-r %d", v.framerate))
if v.encoderPreset != "" {
variantEncoderCommands = append(variantEncoderCommands, fmt.Sprintf("-preset %s", v.encoderPreset))
@@ -247,7 +230,7 @@ func (t *Transcoder) getVariantsString() string {
var variantsStreamMaps = " -var_stream_map \""
for _, variant := range t.variants {
- variantsCommandFlags = variantsCommandFlags + " " + variant.getVariantString()
+ variantsCommandFlags = variantsCommandFlags + " " + variant.getVariantString(t)
variantsStreamMaps = variantsStreamMaps + fmt.Sprintf("v:%d,a:%d ", variant.index, variant.index)
}
variantsCommandFlags = variantsCommandFlags + " " + variantsStreamMaps + "\""
@@ -278,17 +261,40 @@ func (v *HLSVariant) getScalingString() string {
// Video Quality
// SetVideoBitrate will set the output bitrate of this variant's video
-func (v *HLSVariant) SetVideoBitrate(bitrate string) {
+func (v *HLSVariant) SetVideoBitrate(bitrate int) {
v.videoBitrate = bitrate
}
-func (v *HLSVariant) getVideoQualityString() string {
+func (v *HLSVariant) getVideoQualityString(t *Transcoder) string {
if v.isVideoPassthrough {
return fmt.Sprintf("-map v:0 -c:v:%d copy", v.index)
}
encoderCodec := "libx264"
- return fmt.Sprintf("-map v:0 -c:v:%d %s -b:v:%d %s", v.index, encoderCodec, v.index, v.videoBitrate)
+
+ // -1 to work around segments being generated slightly larger than expected.
+ // https://trac.ffmpeg.org/ticket/6915?replyto=58#comment:57
+ gop := (t.segmentLengthSeconds * v.framerate) - 1
+
+ // For limiting the output bitrate
+ // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate
+ // https://developer.apple.com/documentation/http_live_streaming/about_apple_s_http_live_streaming_tools
+ // Adjust the max & buffer size until the output bitrate doesn't exceed the ~+10% that Apple's media validator
+ // complains about.
+ maxBitrate := int(float64(v.videoBitrate) * 1.06) // Max is a ~+10% over specified bitrate.
+ bufferSize := int(float64(v.videoBitrate) * 1.2) // How often it checks the bitrate of encoded segments to see if it's too high/low.
+
+ cmd := []string{
+ "-map v:0",
+ fmt.Sprintf("-c:v:%d %s", v.index, encoderCodec), // Video codec used for this variant
+ fmt.Sprintf("-b:v:%d %dk", v.index, v.videoBitrate), // The average bitrate for this variant
+ fmt.Sprintf("-maxrate:v:%d %dk", v.index, maxBitrate), // The max bitrate allowed for this variant
+ fmt.Sprintf("-bufsize:v:%d %dk", v.index, bufferSize), // How often the encoder checks the bitrate in order to meet average/max values
+ fmt.Sprintf("-g:v:%d %d", v.index, gop), // How often i-frames are encoded into the segments
+ fmt.Sprintf("-x264-params:v:%d \"scenecut=0:open_gop=0:min-keyint=%d:keyint=%d\"", v.index, gop, gop), // How often i-frames are encoded into the segments
+ }
+
+ return strings.Join(cmd, " ")
}
// SetVideoFramerate will set the output framerate of this variant's video
diff --git a/core/ffmpeg/transcoder_test.go b/core/ffmpeg/transcoder_test.go
index 8488aa0de..58e0f7cbb 100644
--- a/core/ffmpeg/transcoder_test.go
+++ b/core/ffmpeg/transcoder_test.go
@@ -13,7 +13,7 @@ func TestFFmpegCommand(t *testing.T) {
transcoder.SetHLSPlaylistLength(10)
variant := HLSVariant{}
- variant.videoBitrate = "1200k"
+ variant.videoBitrate = 1200
variant.isAudioPassthrough = true
variant.encoderPreset = "veryfast"
variant.SetVideoFramerate(30)
@@ -22,7 +22,7 @@ func TestFFmpegCommand(t *testing.T) {
cmd := transcoder.getString()
- expected := "cat fakecontent.flv | /fake/path/ffmpeg -hide_banner -i pipe: -map v:0 -c:v:0 libx264 -b:v:0 1200k -map a:0 -c:a:0 copy -r 30 -g 60 -keyint_min 60 -preset veryfast -var_stream_map \"v:0,a:0 \" -f hls -hls_time 4 -hls_list_size 10 -hls_delete_threshold 10 -hls_flags delete_segments+program_date_time+temp_file -tune zerolatency -sc_threshold 0 -master_pl_name stream.m3u8 -strftime 1 -hls_segment_filename fakeOutput/%v/stream-%s.ts -max_muxing_queue_size 400 fakeOutput/%v/stream.m3u8 2> transcoder.log"
+ expected := `cat fakecontent.flv | /fake/path/ffmpeg -hide_banner -i pipe: -map v:0 -c:v:0 libx264 -b:v:0 1200k -maxrate:v:0 1272k -bufsize:v:0 1440k -g:v:0 119 -x264-params:v:0 "scenecut=0:open_gop=0:min-keyint=119:keyint=119" -map a:0 -c:a:0 copy -r 30 -preset veryfast -var_stream_map "v:0,a:0 " -f hls -hls_time 4 -hls_list_size 10 -hls_delete_threshold 10 -hls_flags delete_segments+program_date_time+temp_file -tune zerolatency -sc_threshold 0 -master_pl_name stream.m3u8 -strftime 1 -hls_segment_filename fakeOutput/%v/stream-%s.ts -max_muxing_queue_size 400 fakeOutput/%v/stream.m3u8 2> transcoder.log`
if cmd != expected {
t.Errorf("ffmpeg command does not match expected. Got %s, want: %s", cmd, expected)