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"
"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

View File

@ -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++ {