mediamtx/internal/hls/muxer_variant_fmp4_segment.go
Alessandro Ros e5ab731d14
Improve HLS client (#1179)
* hls source: support fMP4s video streams

* hls source: start reading live streams from (end of playlist - starting point)

* hls client: wait processing of current fMP4 segment before downloading another one

* hls client: support fmp4 trun boxes with default sample duration, flags and size

* hls client: merge fmp4 init file reader and writer

* hls client: merge fmp4 part reader and writer

* hls client: improve precision of go <-> mp4 time conversion

* hls client: fix esds generation in go-mp4

* hls client: support audio in separate playlist

* hls client: support an arbitrary number of tracks in fmp4 init files

* hls client: support EXT-X-BYTERANGE

* hls client: support fmp4 segments with multiple parts at once

* hls client: support an arbitrary number of mpeg-ts tracks

* hls client: synchronize tracks around a primary track

* update go-mp4

* hls: synchronize track reproduction around a leading one

* hls client: reset stream if playback is too late

* hls client: add limit on DTS-RTC difference

* hls client: support again streams that don't provide codecs in master playlist
2022-10-23 14:04:33 +02:00

187 lines
3.8 KiB
Go

package hls
import (
"fmt"
"io"
"strconv"
"time"
"github.com/aler9/gortsplib"
)
type partsReader struct {
parts []*muxerVariantFMP4Part
curPart int
curPos int
}
func (mbr *partsReader) Read(p []byte) (int, error) {
n := 0
lenp := len(p)
for {
if mbr.curPart >= len(mbr.parts) {
return n, io.EOF
}
copied := copy(p[n:], mbr.parts[mbr.curPart].content[mbr.curPos:])
mbr.curPos += copied
n += copied
if mbr.curPos == len(mbr.parts[mbr.curPart].content) {
mbr.curPart++
mbr.curPos = 0
}
if n == lenp {
return n, nil
}
}
}
type muxerVariantFMP4Segment struct {
lowLatency bool
id uint64
startTime time.Time
startDTS time.Duration
segmentMaxSize uint64
videoTrack *gortsplib.TrackH264
audioTrack *gortsplib.TrackMPEG4Audio
genPartID func() uint64
onPartFinalized func(*muxerVariantFMP4Part)
name string
size uint64
parts []*muxerVariantFMP4Part
currentPart *muxerVariantFMP4Part
renderedDuration time.Duration
}
func newMuxerVariantFMP4Segment(
lowLatency bool,
id uint64,
startTime time.Time,
startDTS time.Duration,
segmentMaxSize uint64,
videoTrack *gortsplib.TrackH264,
audioTrack *gortsplib.TrackMPEG4Audio,
genPartID func() uint64,
onPartFinalized func(*muxerVariantFMP4Part),
) *muxerVariantFMP4Segment {
s := &muxerVariantFMP4Segment{
lowLatency: lowLatency,
id: id,
startTime: startTime,
startDTS: startDTS,
segmentMaxSize: segmentMaxSize,
videoTrack: videoTrack,
audioTrack: audioTrack,
genPartID: genPartID,
onPartFinalized: onPartFinalized,
name: "seg" + strconv.FormatUint(id, 10),
}
s.currentPart = newMuxerVariantFMP4Part(
s.videoTrack,
s.audioTrack,
s.genPartID(),
)
return s
}
func (s *muxerVariantFMP4Segment) reader() io.Reader {
return &partsReader{parts: s.parts}
}
func (s *muxerVariantFMP4Segment) getRenderedDuration() time.Duration {
return s.renderedDuration
}
func (s *muxerVariantFMP4Segment) finalize(
nextVideoSampleDTS time.Duration,
) error {
err := s.currentPart.finalize()
if err != nil {
return err
}
if s.currentPart.content != nil {
s.onPartFinalized(s.currentPart)
s.parts = append(s.parts, s.currentPart)
}
s.currentPart = nil
if s.videoTrack != nil {
s.renderedDuration = nextVideoSampleDTS - s.startDTS
} else {
s.renderedDuration = 0
for _, pa := range s.parts {
s.renderedDuration += pa.renderedDuration
}
}
return nil
}
func (s *muxerVariantFMP4Segment) writeH264(sample *augmentedVideoSample, adjustedPartDuration time.Duration) error {
size := uint64(len(sample.Payload))
if (s.size + size) > s.segmentMaxSize {
return fmt.Errorf("reached maximum segment size")
}
s.size += size
s.currentPart.writeH264(sample)
// switch part
if s.lowLatency &&
s.currentPart.duration() >= adjustedPartDuration {
err := s.currentPart.finalize()
if err != nil {
return err
}
s.parts = append(s.parts, s.currentPart)
s.onPartFinalized(s.currentPart)
s.currentPart = newMuxerVariantFMP4Part(
s.videoTrack,
s.audioTrack,
s.genPartID(),
)
}
return nil
}
func (s *muxerVariantFMP4Segment) writeAAC(sample *augmentedAudioSample, adjustedPartDuration time.Duration) error {
size := uint64(len(sample.Payload))
if (s.size + size) > s.segmentMaxSize {
return fmt.Errorf("reached maximum segment size")
}
s.size += size
s.currentPart.writeAAC(sample)
// switch part
if s.lowLatency && s.videoTrack == nil &&
s.currentPart.duration() >= adjustedPartDuration {
err := s.currentPart.finalize()
if err != nil {
return err
}
s.parts = append(s.parts, s.currentPart)
s.onPartFinalized(s.currentPart)
s.currentPart = newMuxerVariantFMP4Part(
s.videoTrack,
s.audioTrack,
s.genPartID(),
)
}
return nil
}