Make fragments of ~6 seconds instead of a fragment per keyframe

This commit is contained in:
Zoe Roux 2024-07-01 16:38:42 +00:00
parent 0579afe02b
commit f6dab80a98
No known key found for this signature in database
2 changed files with 17 additions and 11 deletions

View File

@ -4,12 +4,16 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"log" "log"
"math"
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
"sync" "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 { type Keyframe struct {
Sha string Sha string
Keyframes []float64 Keyframes []float64
@ -97,7 +101,7 @@ func GetKeyframes(sha string, path string) *Keyframe {
} }
func getKeyframes(path string, kf *Keyframe, sha string) error { 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. // 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 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 // 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) ret := make([]float64, 0, 1000)
max := 100 max := 100
last_frame := math.Inf(-1)
done := 0 done := 0
// sometimes, videos can start at a timing greater than 0:00. We need to take that into account // 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 // 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 return err
} }
// Before, we wanted to only save keyframes with at least 3s betweens // The -f hls encoder decides to not cut at every keyframes (even if we ask it to) when they are too close by.
// to prevent segments of 0.2s but sometimes, the -f segment muxer discards // Instead we can cut every X seconds at the next keyframe, we'll use that as a marker.
// the segment time and decide to cut at a random keyframe. Having every keyframe if fpts < (last_frame+OptimalFragmentDuration) {
// handled as a segment prevents that. continue
}
ret = append(ret, fpts) ret = append(ret, fpts)
last_frame = fpts
if len(ret) == max { if len(ret) == max {
kf.add(ret) kf.add(ret)
@ -190,7 +197,7 @@ func getKeyframes(path string, kf *Keyframe, sha string) error {
} }
func getDummyKeyframes(path string, sha string) ([]float64, error) { func getDummyKeyframes(path string, sha string) ([]float64, error) {
dummyKeyframeDuration := float64(2) dummyKeyframeDuration := OptimalFragmentDuration
info, err := GetInfo(path, sha) info, err := GetInfo(path, sha)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -267,8 +267,7 @@ func (ts *Stream) run(start int32) error {
args = append(args, ts.handle.getTranscodeArgs(toSegmentStr(segments))...) args = append(args, ts.handle.getTranscodeArgs(toSegmentStr(segments))...)
args = append(args, args = append(args,
"-f", "hls", "-f", "hls",
// Cut at every keyframes. "-hls_time", fmt.Sprint(OptimalFragmentDuration),
"-hls_time", "0",
"-start_number", fmt.Sprint(start_segment), "-start_number", fmt.Sprint(start_segment),
"-hls_segment_type", "fmp4", "-hls_segment_type", "fmp4",
"-hls_fmp4_init_filename", fmt.Sprintf("%s/init.mp4", outpath), "-hls_fmp4_init_filename", fmt.Sprintf("%s/init.mp4", outpath),
@ -361,7 +360,7 @@ func (ts *Stream) run(start int32) error {
go func() { go func() {
err := cmd.Wait() 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) log.Printf("ffmpeg %d was killed by us", encoder_id)
} else if err != nil { } else if err != nil {
log.Printf("ffmpeg %d occurred an error: %v: %s", encoder_id, err, stderr.String()) 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-VERSION:7
#EXT-X-PLAYLIST-TYPE:EVENT #EXT-X-PLAYLIST-TYPE:EVENT
#EXT-X-START:TIME-OFFSET=0 #EXT-X-START:TIME-OFFSET=0
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0 #EXT-X-MEDIA-SEQUENCE:0
#EXT-X-INDEPENDENT-SEGMENTS #EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MAP:URI="init.mp4" #EXT-X-MAP:URI="init.mp4"
` `
index += fmt.Sprintf("#EXT-X-TARGETDURATION:%d\n", int(OptimalFragmentDuration))
length, is_done := ts.file.Keyframes.Length() length, is_done := ts.file.Keyframes.Length()
for segment := int32(0); segment < length-1; segment++ { for segment := int32(0); segment < length-1; segment++ {