From f6dab80a9875e7b4d9ba905a739ce380dc891776 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 1 Jul 2024 16:38:42 +0000 Subject: [PATCH] Make fragments of ~6 seconds instead of a fragment per keyframe --- transcoder/src/keyframes.go | 19 +++++++++++++------ transcoder/src/stream.go | 9 ++++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/transcoder/src/keyframes.go b/transcoder/src/keyframes.go index 96cbf6d6..ab902c55 100644 --- a/transcoder/src/keyframes.go +++ b/transcoder/src/keyframes.go @@ -4,12 +4,16 @@ import ( "bufio" "fmt" "log" + "math" "os/exec" "strconv" "strings" "sync" ) +// In seconds, the spec recomands 6 but since we don't control keyframes we go over more often than not. +const OptimalFragmentDuration = float64(5) + type Keyframe struct { Sha string Keyframes []float64 @@ -97,7 +101,7 @@ func GetKeyframes(sha string, path string) *Keyframe { } func getKeyframes(path string, kf *Keyframe, sha string) error { - defer printExecTime("ffprobe analysis for %s", path)() + defer printExecTime("keyframe extraction for %s", path)() // run ffprobe to return all IFrames, IFrames are points where we can split the video in segments. // We ask ffprobe to return the time of each frame and it's flags // We could ask it to return only i-frames (keyframes) with the -skip_frame nokey but using it is extremly slow @@ -123,6 +127,7 @@ func getKeyframes(path string, kf *Keyframe, sha string) error { ret := make([]float64, 0, 1000) max := 100 + last_frame := math.Inf(-1) done := 0 // sometimes, videos can start at a timing greater than 0:00. We need to take that into account // and only list keyframes that come after the start of the video (without that, our segments count @@ -154,12 +159,14 @@ func getKeyframes(path string, kf *Keyframe, sha string) error { return err } - // Before, we wanted to only save keyframes with at least 3s betweens - // to prevent segments of 0.2s but sometimes, the -f segment muxer discards - // the segment time and decide to cut at a random keyframe. Having every keyframe - // handled as a segment prevents that. + // The -f hls encoder decides to not cut at every keyframes (even if we ask it to) when they are too close by. + // Instead we can cut every X seconds at the next keyframe, we'll use that as a marker. + if fpts < (last_frame+OptimalFragmentDuration) { + continue + } ret = append(ret, fpts) + last_frame = fpts if len(ret) == max { kf.add(ret) @@ -190,7 +197,7 @@ func getKeyframes(path string, kf *Keyframe, sha string) error { } func getDummyKeyframes(path string, sha string) ([]float64, error) { - dummyKeyframeDuration := float64(2) + dummyKeyframeDuration := OptimalFragmentDuration info, err := GetInfo(path, sha) if err != nil { return nil, err diff --git a/transcoder/src/stream.go b/transcoder/src/stream.go index 43761c1c..7cdcd013 100644 --- a/transcoder/src/stream.go +++ b/transcoder/src/stream.go @@ -267,8 +267,7 @@ func (ts *Stream) run(start int32) error { args = append(args, ts.handle.getTranscodeArgs(toSegmentStr(segments))...) args = append(args, "-f", "hls", - // Cut at every keyframes. - "-hls_time", "0", + "-hls_time", fmt.Sprint(OptimalFragmentDuration), "-start_number", fmt.Sprint(start_segment), "-hls_segment_type", "fmp4", "-hls_fmp4_init_filename", fmt.Sprintf("%s/init.mp4", outpath), @@ -302,7 +301,7 @@ func (ts *Stream) run(start int32) error { scanner := bufio.NewScanner(stdout) format := ts.handle.getSegmentName() should_stop := false - is_init_ready:= false + is_init_ready := false for scanner.Scan() { line := scanner.Text() @@ -361,7 +360,7 @@ func (ts *Stream) run(start int32) error { go func() { err := cmd.Wait() - if exiterr, ok := err.(*exec.ExitError); ok && exiterr.ExitCode() == 255 { + if exiterr, ok := err.(*exec.ExitError); ok && (exiterr.ExitCode() == 255 || exiterr.ExitCode() == -1) { log.Printf("ffmpeg %d was killed by us", encoder_id) } else if err != nil { log.Printf("ffmpeg %d occurred an error: %v: %s", encoder_id, err, stderr.String()) @@ -385,11 +384,11 @@ func (ts *Stream) GetIndex() (string, error) { #EXT-X-VERSION:7 #EXT-X-PLAYLIST-TYPE:EVENT #EXT-X-START:TIME-OFFSET=0 -#EXT-X-TARGETDURATION:4 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-INDEPENDENT-SEGMENTS #EXT-X-MAP:URI="init.mp4" ` + index += fmt.Sprintf("#EXT-X-TARGETDURATION:%d\n", int(OptimalFragmentDuration)) length, is_done := ts.file.Keyframes.Length() for segment := int32(0); segment < length-1; segment++ {