From 58c91fcb0f0e32af58e6be20564bed1ba6e35ae6 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:35:23 +0200 Subject: [PATCH] playaback: use a fixed fMP4 part duration --- internal/playback/muxer.go | 3 +- internal/playback/muxer_fmp4.go | 96 ++++++++++++++----------------- internal/playback/on_get.go | 5 +- internal/playback/on_get_test.go | 44 ++++++-------- internal/playback/segment_fmp4.go | 22 +++---- 5 files changed, 69 insertions(+), 101 deletions(-) diff --git a/internal/playback/muxer.go b/internal/playback/muxer.go index 06c527d69c8..2582980be77 100644 --- a/internal/playback/muxer.go +++ b/internal/playback/muxer.go @@ -3,8 +3,7 @@ package playback type muxer interface { writeInit(init []byte) setTrack(trackID int) - writeSample(dts int64, ptsOffset int32, isNonSyncSample bool, payload []byte) + writeSample(dts int64, ptsOffset int32, isNonSyncSample bool, payload []byte) error writeFinalDTS(dts int64) flush() error - finalFlush() error } diff --git a/internal/playback/muxer_fmp4.go b/internal/playback/muxer_fmp4.go index e4d6bfa1df9..ae1c7c0bba9 100644 --- a/internal/playback/muxer_fmp4.go +++ b/internal/playback/muxer_fmp4.go @@ -2,15 +2,17 @@ package playback import ( "io" + "time" "github.com/bluenviron/mediacommon/pkg/formats/fmp4" "github.com/bluenviron/mediacommon/pkg/formats/fmp4/seekablebuffer" ) +var partSize = durationGoToMp4(1*time.Second, fmp4Timescale) + type muxerFMP4Track struct { - started bool id int - firstDTS uint64 + firstDTS int64 lastDTS int64 samples []*fmp4.PartSample } @@ -42,58 +44,27 @@ func (w *muxerFMP4) setTrack(trackID int) { w.curTrack = findTrack(w.tracks, trackID) if w.curTrack == nil { w.curTrack = &muxerFMP4Track{ - id: trackID, + id: trackID, + firstDTS: -1, } w.tracks = append(w.tracks, w.curTrack) } } -func (w *muxerFMP4) writeSample(dts int64, ptsOffset int32, isNonSyncSample bool, payload []byte) { - if !w.curTrack.started { - if dts >= 0 { - w.curTrack.started = true - w.curTrack.firstDTS = uint64(dts) +func (w *muxerFMP4) writeSample(dts int64, ptsOffset int32, isNonSyncSample bool, payload []byte) error { + if dts >= 0 { + if w.curTrack.firstDTS < 0 { + w.curTrack.firstDTS = dts + // reset GOP preceding the first frame if !isNonSyncSample { - w.curTrack.samples = []*fmp4.PartSample{{ - PTSOffset: ptsOffset, - IsNonSyncSample: isNonSyncSample, - Payload: payload, - }} - } else { - w.curTrack.samples = append(w.curTrack.samples, &fmp4.PartSample{ - PTSOffset: ptsOffset, - IsNonSyncSample: isNonSyncSample, - Payload: payload, - }) + w.curTrack.samples = nil } - w.curTrack.lastDTS = dts - } else { - ptsOffset = 0 - - if !isNonSyncSample { - w.curTrack.samples = []*fmp4.PartSample{{ - PTSOffset: ptsOffset, - IsNonSyncSample: isNonSyncSample, - Payload: payload, - }} - } else { - w.curTrack.samples = append(w.curTrack.samples, &fmp4.PartSample{ - PTSOffset: ptsOffset, - IsNonSyncSample: isNonSyncSample, - Payload: payload, - }) - } - } - } else { - if w.curTrack.samples == nil { - w.curTrack.firstDTS = uint64(dts) } else { diff := dts - w.curTrack.lastDTS if diff < 0 { diff = 0 } - w.curTrack.samples[len(w.curTrack.samples)-1].Duration = uint32(diff) } @@ -103,25 +74,48 @@ func (w *muxerFMP4) writeSample(dts int64, ptsOffset int32, isNonSyncSample bool Payload: payload, }) w.curTrack.lastDTS = dts + + if (w.curTrack.lastDTS - w.curTrack.firstDTS) > int64(partSize) { + err := w.innerFlush(false) + if err != nil { + return err + } + } + } else { + // store GOP preceding the first frame, with PTSOffset = 0 and Duration = 0 + if !isNonSyncSample { + w.curTrack.samples = []*fmp4.PartSample{{ + IsNonSyncSample: isNonSyncSample, + Payload: payload, + }} + } else { + w.curTrack.samples = append(w.curTrack.samples, &fmp4.PartSample{ + IsNonSyncSample: isNonSyncSample, + Payload: payload, + }) + } } + + return nil } func (w *muxerFMP4) writeFinalDTS(dts int64) { - if w.curTrack.started && w.curTrack.samples != nil { + if w.curTrack.firstDTS >= 0 { diff := dts - w.curTrack.lastDTS if diff < 0 { diff = 0 } - w.curTrack.samples[len(w.curTrack.samples)-1].Duration = uint32(diff) } } -func (w *muxerFMP4) flush2(final bool) error { +func (w *muxerFMP4) innerFlush(final bool) error { var part fmp4.Part for _, track := range w.tracks { - if track.started && (len(track.samples) > 1 || (final && len(track.samples) != 0)) { + if track.firstDTS >= 0 && (len(track.samples) > 1 || (final && len(track.samples) != 0)) { + // do not write the final sample + // in order to allow changing its duration to compensate NTP-DTS differences var samples []*fmp4.PartSample if !final { samples = track.samples[:len(track.samples)-1] @@ -131,15 +125,13 @@ func (w *muxerFMP4) flush2(final bool) error { part.Tracks = append(part.Tracks, &fmp4.PartTrack{ ID: track.id, - BaseTime: track.firstDTS, + BaseTime: uint64(track.firstDTS), Samples: samples, }) if !final { track.samples = track.samples[len(track.samples)-1:] - track.firstDTS = uint64(track.lastDTS) - } else { - track.samples = nil + track.firstDTS = track.lastDTS } } } @@ -173,9 +165,5 @@ func (w *muxerFMP4) flush2(final bool) error { } func (w *muxerFMP4) flush() error { - return w.flush2(false) -} - -func (w *muxerFMP4) finalFlush() error { - return w.flush2(true) + return w.innerFlush(true) } diff --git a/internal/playback/on_get.go b/internal/playback/on_get.go index a04c61522b9..6611c1be487 100644 --- a/internal/playback/on_get.go +++ b/internal/playback/on_get.go @@ -110,14 +110,13 @@ func seekAndMux( }() if err != nil { if errors.Is(err, errStopIteration) { - return nil + break } - return err } } - err = m.finalFlush() + err = m.flush() if err != nil { return err } diff --git a/internal/playback/on_get_test.go b/internal/playback/on_get_test.go index 67fc5f7aa6c..560d37c1a33 100644 --- a/internal/playback/on_get_test.go +++ b/internal/playback/on_get_test.go @@ -229,6 +229,7 @@ func TestOnGet(t *testing.T) { writeSegment1(t, filepath.Join(dir, "mypath", "2008-11-07_11-22-00-500000.mp4")) writeSegment2(t, filepath.Join(dir, "mypath", "2008-11-07_11-23-02-500000.mp4")) + writeSegment2(t, filepath.Join(dir, "mypath", "2008-11-07_11-23-04-500000.mp4")) s := &Server{ Address: "127.0.0.1:9996", @@ -252,7 +253,7 @@ func TestOnGet(t *testing.T) { v := url.Values{} v.Set("path", "mypath") v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano)) - v.Set("duration", "2") + v.Set("duration", "3") v.Set("format", "fmp4") u.RawQuery = v.Encode() @@ -283,36 +284,29 @@ func TestOnGet(t *testing.T) { Duration: 0, Payload: []byte{3, 4}, }, - }, - }, - }, - }, - { - SequenceNumber: 1, - Tracks: []*fmp4.PartTrack{ - { - ID: 1, - BaseTime: 0, - Samples: []*fmp4.PartSample{ { Duration: 90000, IsNonSyncSample: true, Payload: []byte{5, 6}, }, + { + Duration: 90000, + Payload: []byte{7, 8}, + }, }, }, }, }, { - SequenceNumber: 2, + SequenceNumber: 1, Tracks: []*fmp4.PartTrack{ { ID: 1, - BaseTime: 90000, + BaseTime: 180000, Samples: []*fmp4.PartSample{ { Duration: 90000, - Payload: []byte{7, 8}, + Payload: []byte{9, 10}, }, }, }, @@ -385,6 +379,11 @@ func TestOnGetDifferentInit(t *testing.T) { Duration: 0, Payload: []byte{3, 4}, }, + { + Duration: 90000, + IsNonSyncSample: true, + Payload: []byte{5, 6}, + }, }, }, }, @@ -456,17 +455,6 @@ func TestOnGetNTPCompensation(t *testing.T) { Duration: 0, Payload: []byte{3, 4}, }, - }, - }, - }, - }, - { - SequenceNumber: 1, - Tracks: []*fmp4.PartTrack{ - { - ID: 1, - BaseTime: 0, - Samples: []*fmp4.PartSample{ { Duration: 45000, // 90 - 45 IsNonSyncSample: true, @@ -481,11 +469,11 @@ func TestOnGetNTPCompensation(t *testing.T) { }, }, { - SequenceNumber: 2, + SequenceNumber: 1, Tracks: []*fmp4.PartTrack{ { ID: 1, - BaseTime: 135000, // 180 - 45 + BaseTime: 135000, Samples: []*fmp4.PartSample{ { Duration: 90000, diff --git a/internal/playback/segment_fmp4.go b/internal/playback/segment_fmp4.go index 6b4e75bb4f6..997b2159184 100644 --- a/internal/playback/segment_fmp4.go +++ b/internal/playback/segment_fmp4.go @@ -374,12 +374,15 @@ func segmentFMP4SeekAndMuxParts( return nil, err } - m.writeSample( + err = m.writeSample( muxerDTS, e.SampleCompositionTimeOffsetV1, (e.SampleFlags&sampleFlagIsNonSyncSample) != 0, payload, ) + if err != nil { + return nil, err + } muxerDTS += int64(e.SampleDuration) } @@ -389,12 +392,6 @@ func segmentFMP4SeekAndMuxParts( if muxerDTS > maxMuxerDTS { maxMuxerDTS = muxerDTS } - - case "mdat": - err := m.flush() - if err != nil { - return nil, err - } } return nil, nil }) @@ -474,12 +471,15 @@ func segmentFMP4WriteParts( return nil, err } - m.writeSample( + err = m.writeSample( muxerDTS, e.SampleCompositionTimeOffsetV1, (e.SampleFlags&sampleFlagIsNonSyncSample) != 0, payload, ) + if err != nil { + return nil, err + } muxerDTS += int64(e.SampleDuration) } @@ -489,12 +489,6 @@ func segmentFMP4WriteParts( if muxerDTS > maxMuxerDTS { maxMuxerDTS = muxerDTS } - - case "mdat": - err := m.flush() - if err != nil { - return nil, err - } } return nil, nil })