From e146df9ed7ef67a30ff75d77a302c43709c6b7ae Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sun, 14 Aug 2022 12:44:01 +0200 Subject: [PATCH] hls: move fMP4 generator into dedicated folder --- internal/hls/fmp4/audiosample.go | 17 + .../init.go} | 19 +- .../{mp4/writer.go => hls/fmp4/mp4writer.go} | 22 +- internal/hls/fmp4/part.go | 291 +++++++++++++++++ internal/hls/fmp4/videosample.go | 24 ++ internal/hls/muxer_variant_fmp4.go | 31 +- internal/hls/muxer_variant_fmp4_part.go | 299 +----------------- internal/hls/muxer_variant_fmp4_segment.go | 16 +- internal/hls/muxer_variant_fmp4_segmenter.go | 72 +++-- 9 files changed, 409 insertions(+), 382 deletions(-) create mode 100644 internal/hls/fmp4/audiosample.go rename internal/hls/{muxer_variant_fmp4_init.go => fmp4/init.go} (94%) rename internal/{mp4/writer.go => hls/fmp4/mp4writer.go} (73%) create mode 100644 internal/hls/fmp4/part.go create mode 100644 internal/hls/fmp4/videosample.go diff --git a/internal/hls/fmp4/audiosample.go b/internal/hls/fmp4/audiosample.go new file mode 100644 index 00000000..323683bd --- /dev/null +++ b/internal/hls/fmp4/audiosample.go @@ -0,0 +1,17 @@ +package fmp4 + +import ( + "time" +) + +// AudioSample is an audio sample. +type AudioSample struct { + AU []byte + PTS time.Duration + Next *AudioSample +} + +// Duration returns the sample duration. +func (s AudioSample) Duration() time.Duration { + return s.Next.PTS - s.PTS +} diff --git a/internal/hls/muxer_variant_fmp4_init.go b/internal/hls/fmp4/init.go similarity index 94% rename from internal/hls/muxer_variant_fmp4_init.go rename to internal/hls/fmp4/init.go index 4edaa3f0..0be6502a 100644 --- a/internal/hls/muxer_variant_fmp4_init.go +++ b/internal/hls/fmp4/init.go @@ -1,11 +1,9 @@ -package hls +package fmp4 import ( gomp4 "github.com/abema/go-mp4" "github.com/aler9/gortsplib" "github.com/aler9/gortsplib/pkg/h264" - - "github.com/aler9/rtsp-simple-server/internal/mp4" ) type myEsds struct { @@ -21,7 +19,7 @@ func init() { //nolint:gochecknoinits gomp4.AddBoxDef(&myEsds{}, 0) } -func mp4InitGenerateVideoTrack(w *mp4.Writer, trackID int, videoTrack *gortsplib.TrackH264) error { +func generateInitVideoTrack(w *mp4Writer, trackID int, videoTrack *gortsplib.TrackH264) error { /* trak - tkhd @@ -81,7 +79,7 @@ func mp4InitGenerateVideoTrack(w *mp4.Writer, trackID int, videoTrack *gortsplib } _, err = w.WriteBox(&gomp4.Mdhd{ // - Timescale: fmp4VideoTimescale, // the number of time units that pass per second + Timescale: videoTimescale, // the number of time units that pass per second Language: [3]byte{'u', 'n', 'd'}, }) if err != nil { @@ -265,7 +263,7 @@ func mp4InitGenerateVideoTrack(w *mp4.Writer, trackID int, videoTrack *gortsplib return nil } -func mp4InitGenerateAudioTrack(w *mp4.Writer, trackID int, audioTrack *gortsplib.TrackMPEG4Audio) error { +func generateInitAudioTrack(w *mp4Writer, trackID int, audioTrack *gortsplib.TrackMPEG4Audio) error { /* trak - tkhd @@ -511,7 +509,8 @@ func mp4InitGenerateAudioTrack(w *mp4.Writer, trackID int, audioTrack *gortsplib return nil } -func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.TrackMPEG4Audio) ([]byte, error) { +// GenerateInit generates a FMP4 initialization file. +func GenerateInit(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.TrackMPEG4Audio) ([]byte, error) { /* - ftyp - moov @@ -523,7 +522,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac - trex (audio) */ - w := mp4.NewWriter() + w := newMP4Writer() _, err := w.WriteBox(&gomp4.Ftyp{ // MajorBrand: [4]byte{'m', 'p', '4', '2'}, @@ -558,7 +557,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac trackID := 1 if videoTrack != nil { - err := mp4InitGenerateVideoTrack(w, trackID, videoTrack) + err := generateInitVideoTrack(w, trackID, videoTrack) if err != nil { return nil, err } @@ -567,7 +566,7 @@ func mp4InitGenerate(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.Trac } if audioTrack != nil { - err := mp4InitGenerateAudioTrack(w, trackID, audioTrack) + err := generateInitAudioTrack(w, trackID, audioTrack) if err != nil { return nil, err } diff --git a/internal/mp4/writer.go b/internal/hls/fmp4/mp4writer.go similarity index 73% rename from internal/mp4/writer.go rename to internal/hls/fmp4/mp4writer.go index 906d78d1..bc07a152 100644 --- a/internal/mp4/writer.go +++ b/internal/hls/fmp4/mp4writer.go @@ -1,4 +1,4 @@ -package mp4 +package fmp4 import ( "io" @@ -7,15 +7,15 @@ import ( "github.com/orcaman/writerseeker" ) -// Writer is a MP4 writer. -type Writer struct { +// mp4Writer is a MP4 writer. +type mp4Writer struct { buf *writerseeker.WriterSeeker w *gomp4.Writer } -// NewWriter allocates a Writer. -func NewWriter() *Writer { - w := &Writer{ +// newMP4Writer allocates a mp4Writer. +func newMP4Writer() *mp4Writer { + w := &mp4Writer{ buf: &writerseeker.WriterSeeker{}, } @@ -25,7 +25,7 @@ func NewWriter() *Writer { } // WriteBoxStart writes a box start. -func (w *Writer) WriteBoxStart(box gomp4.IImmutableBox) (int, error) { +func (w *mp4Writer) WriteBoxStart(box gomp4.IImmutableBox) (int, error) { bi := &gomp4.BoxInfo{ Type: box.GetType(), } @@ -44,13 +44,13 @@ func (w *Writer) WriteBoxStart(box gomp4.IImmutableBox) (int, error) { } // WriteBoxEnd writes a box end. -func (w *Writer) WriteBoxEnd() error { +func (w *mp4Writer) WriteBoxEnd() error { _, err := w.w.EndBox() return err } // WriteBox writes a self-closing box. -func (w *Writer) WriteBox(box gomp4.IImmutableBox) (int, error) { +func (w *mp4Writer) WriteBox(box gomp4.IImmutableBox) (int, error) { off, err := w.WriteBoxStart(box) if err != nil { return 0, err @@ -65,7 +65,7 @@ func (w *Writer) WriteBox(box gomp4.IImmutableBox) (int, error) { } // RewriteBox rewrites a box. -func (w *Writer) RewriteBox(off int, box gomp4.IImmutableBox) error { +func (w *mp4Writer) RewriteBox(off int, box gomp4.IImmutableBox) error { prevOff, err := w.w.Seek(0, io.SeekCurrent) if err != nil { return err @@ -95,6 +95,6 @@ func (w *Writer) RewriteBox(off int, box gomp4.IImmutableBox) error { } // Bytes returns the MP4 content. -func (w *Writer) Bytes() []byte { +func (w *mp4Writer) Bytes() []byte { return w.buf.Bytes() } diff --git a/internal/hls/fmp4/part.go b/internal/hls/fmp4/part.go new file mode 100644 index 00000000..7ff3de59 --- /dev/null +++ b/internal/hls/fmp4/part.go @@ -0,0 +1,291 @@ +package fmp4 + +import ( + "math" + "time" + + gomp4 "github.com/abema/go-mp4" + "github.com/aler9/gortsplib" +) + +func durationGoToMp4(v time.Duration, timescale time.Duration) int64 { + return int64(math.Round(float64(v*timescale) / float64(time.Second))) +} + +func generatePartVideoTraf( + w *mp4Writer, + trackID int, + videoSamples []*VideoSample, +) (*gomp4.Trun, int, error) { + /* + traf + - tfhd + - tfdt + - trun + */ + + _, err := w.WriteBoxStart(&gomp4.Traf{}) // + if err != nil { + return nil, 0, err + } + + flags := 0 + + _, err = w.WriteBox(&gomp4.Tfhd{ // + FullBox: gomp4.FullBox{ + Flags: [3]byte{2, byte(flags >> 8), byte(flags)}, + }, + TrackID: uint32(trackID), + }) + if err != nil { + return nil, 0, err + } + + _, err = w.WriteBox(&gomp4.Tfdt{ // + FullBox: gomp4.FullBox{ + Version: 1, + }, + // sum of decode durations of all earlier samples + BaseMediaDecodeTimeV1: uint64(durationGoToMp4(videoSamples[0].DTS, videoTimescale)), + }) + if err != nil { + return nil, 0, err + } + + flags = 0 + flags |= 0x01 // data offset present + flags |= 0x100 // sample duration present + flags |= 0x200 // sample size present + flags |= 0x400 // sample flags present + flags |= 0x800 // sample composition time offset present or v1 + + trun := &gomp4.Trun{ // + FullBox: gomp4.FullBox{ + Version: 1, + Flags: [3]byte{0, byte(flags >> 8), byte(flags)}, + }, + SampleCount: uint32(len(videoSamples)), + } + + for _, e := range videoSamples { + off := e.PTS - e.DTS + + flags := uint32(0) + if !e.IDRPresent { + flags |= 1 << 16 // sample_is_non_sync_sample + } + + trun.Entries = append(trun.Entries, gomp4.TrunEntry{ + SampleDuration: uint32(durationGoToMp4(e.Duration(), videoTimescale)), + SampleSize: uint32(len(e.AVCC)), + SampleFlags: flags, + SampleCompositionTimeOffsetV1: int32(durationGoToMp4(off, videoTimescale)), + }) + } + + trunOffset, err := w.WriteBox(trun) + if err != nil { + return nil, 0, err + } + + err = w.WriteBoxEnd() // + if err != nil { + return nil, 0, err + } + + return trun, trunOffset, nil +} + +func generatePartAudioTraf( + w *mp4Writer, + trackID int, + audioTrack *gortsplib.TrackMPEG4Audio, + audioSamples []*AudioSample, +) (*gomp4.Trun, int, error) { + /* + traf + - tfhd + - tfdt + - trun + */ + + if len(audioSamples) == 0 { + return nil, 0, nil + } + + _, err := w.WriteBoxStart(&gomp4.Traf{}) // + if err != nil { + return nil, 0, err + } + + flags := 0 + + _, err = w.WriteBox(&gomp4.Tfhd{ // + FullBox: gomp4.FullBox{ + Flags: [3]byte{2, byte(flags >> 8), byte(flags)}, + }, + TrackID: uint32(trackID), + }) + if err != nil { + return nil, 0, err + } + + _, err = w.WriteBox(&gomp4.Tfdt{ // + FullBox: gomp4.FullBox{ + Version: 1, + }, + // sum of decode durations of all earlier samples + BaseMediaDecodeTimeV1: uint64(durationGoToMp4(audioSamples[0].PTS, time.Duration(audioTrack.ClockRate()))), + }) + if err != nil { + return nil, 0, err + } + + flags = 0 + flags |= 0x01 // data offset present + flags |= 0x100 // sample duration present + flags |= 0x200 // sample size present + + trun := &gomp4.Trun{ // + FullBox: gomp4.FullBox{ + Version: 0, + Flags: [3]byte{0, byte(flags >> 8), byte(flags)}, + }, + SampleCount: uint32(len(audioSamples)), + } + + for _, e := range audioSamples { + trun.Entries = append(trun.Entries, gomp4.TrunEntry{ + SampleDuration: uint32(durationGoToMp4(e.Duration(), time.Duration(audioTrack.ClockRate()))), + SampleSize: uint32(len(e.AU)), + }) + } + + trunOffset, err := w.WriteBox(trun) + if err != nil { + return nil, 0, err + } + + err = w.WriteBoxEnd() // + if err != nil { + return nil, 0, err + } + + return trun, trunOffset, nil +} + +// GeneratePart generates a FMP4 part file. +func GeneratePart( + videoTrack *gortsplib.TrackH264, + audioTrack *gortsplib.TrackMPEG4Audio, + videoSamples []*VideoSample, + audioSamples []*AudioSample, +) ([]byte, error) { + /* + moof + - mfhd + - traf (video) + - traf (audio) + mdat + */ + + w := newMP4Writer() + + moofOffset, err := w.WriteBoxStart(&gomp4.Moof{}) // + if err != nil { + return nil, err + } + + _, err = w.WriteBox(&gomp4.Mfhd{ // + SequenceNumber: 0, + }) + if err != nil { + return nil, err + } + + trackID := 1 + + var videoTrun *gomp4.Trun + var videoTrunOffset int + if videoTrack != nil { + var err error + videoTrun, videoTrunOffset, err = generatePartVideoTraf( + w, trackID, videoSamples) + if err != nil { + return nil, err + } + + trackID++ + } + + var audioTrun *gomp4.Trun + var audioTrunOffset int + if audioTrack != nil { + var err error + audioTrun, audioTrunOffset, err = generatePartAudioTraf(w, trackID, audioTrack, audioSamples) + if err != nil { + return nil, err + } + } + + err = w.WriteBoxEnd() // + if err != nil { + return nil, err + } + + mdat := &gomp4.Mdat{} // + + dataSize := 0 + videoDataSize := 0 + + if videoTrack != nil { + for _, e := range videoSamples { + dataSize += len(e.AVCC) + } + videoDataSize = dataSize + } + + if audioTrack != nil { + for _, e := range audioSamples { + dataSize += len(e.AU) + } + } + + mdat.Data = make([]byte, dataSize) + pos := 0 + + if videoTrack != nil { + for _, e := range videoSamples { + pos += copy(mdat.Data[pos:], e.AVCC) + } + } + + if audioTrack != nil { + for _, e := range audioSamples { + pos += copy(mdat.Data[pos:], e.AU) + } + } + + mdatOffset, err := w.WriteBox(mdat) + if err != nil { + return nil, err + } + + if videoTrack != nil { + videoTrun.DataOffset = int32(mdatOffset - moofOffset + 8) + err = w.RewriteBox(videoTrunOffset, videoTrun) + if err != nil { + return nil, err + } + } + + if audioTrack != nil && audioTrun != nil { + audioTrun.DataOffset = int32(videoDataSize + mdatOffset - moofOffset + 8) + err = w.RewriteBox(audioTrunOffset, audioTrun) + if err != nil { + return nil, err + } + } + + return w.Bytes(), nil +} diff --git a/internal/hls/fmp4/videosample.go b/internal/hls/fmp4/videosample.go new file mode 100644 index 00000000..a28e0205 --- /dev/null +++ b/internal/hls/fmp4/videosample.go @@ -0,0 +1,24 @@ +package fmp4 + +import ( + "time" +) + +const ( + videoTimescale = 90000 +) + +// VideoSample is a video sample. +type VideoSample struct { + NALUs [][]byte + PTS time.Duration + DTS time.Duration + AVCC []byte + IDRPresent bool + Next *VideoSample +} + +// Duration returns the sample duration. +func (s VideoSample) Duration() time.Duration { + return s.Next.DTS - s.DTS +} diff --git a/internal/hls/muxer_variant_fmp4.go b/internal/hls/muxer_variant_fmp4.go index dcdaee2d..c762683b 100644 --- a/internal/hls/muxer_variant_fmp4.go +++ b/internal/hls/muxer_variant_fmp4.go @@ -7,35 +7,10 @@ import ( "time" "github.com/aler9/gortsplib" + + "github.com/aler9/rtsp-simple-server/internal/hls/fmp4" ) -const ( - fmp4VideoTimescale = 90000 -) - -type fmp4VideoSample struct { - nalus [][]byte - pts time.Duration - dts time.Duration - avcc []byte - idrPresent bool - next *fmp4VideoSample -} - -func (s fmp4VideoSample) duration() time.Duration { - return s.next.dts - s.dts -} - -type fmp4AudioSample struct { - au []byte - pts time.Duration - next *fmp4AudioSample -} - -func (s fmp4AudioSample) duration() time.Duration { - return s.next.pts - s.pts -} - type muxerVariantFMP4 struct { playlist *muxerVariantFMP4Playlist segmenter *muxerVariantFMP4Segmenter @@ -110,7 +85,7 @@ func (v *muxerVariantFMP4) file(name string, msn string, part string, skip strin if v.initContent == nil || (v.videoTrack != nil && (!bytes.Equal(v.videoLastSPS, sps) || !bytes.Equal(v.videoLastPPS, pps))) { - initContent, err := mp4InitGenerate(v.videoTrack, v.audioTrack) + initContent, err := fmp4.GenerateInit(v.videoTrack, v.audioTrack) if err != nil { return &MuxerFileResponse{Status: http.StatusInternalServerError} } diff --git a/internal/hls/muxer_variant_fmp4_part.go b/internal/hls/muxer_variant_fmp4_part.go index 1d319d1b..63b402f8 100644 --- a/internal/hls/muxer_variant_fmp4_part.go +++ b/internal/hls/muxer_variant_fmp4_part.go @@ -3,298 +3,15 @@ package hls import ( "bytes" "io" - "math" "strconv" "time" - gomp4 "github.com/abema/go-mp4" "github.com/aler9/gortsplib" "github.com/aler9/gortsplib/pkg/mpeg4audio" - "github.com/aler9/rtsp-simple-server/internal/mp4" + "github.com/aler9/rtsp-simple-server/internal/hls/fmp4" ) -func durationGoToMp4(v time.Duration, timescale time.Duration) int64 { - return int64(math.Round(float64(v*timescale) / float64(time.Second))) -} - -func mp4PartGenerateVideoTraf( - w *mp4.Writer, - trackID int, - videoSamples []*fmp4VideoSample, -) (*gomp4.Trun, int, error) { - /* - traf - - tfhd - - tfdt - - trun - */ - - _, err := w.WriteBoxStart(&gomp4.Traf{}) // - if err != nil { - return nil, 0, err - } - - flags := 0 - - _, err = w.WriteBox(&gomp4.Tfhd{ // - FullBox: gomp4.FullBox{ - Flags: [3]byte{2, byte(flags >> 8), byte(flags)}, - }, - TrackID: uint32(trackID), - }) - if err != nil { - return nil, 0, err - } - - _, err = w.WriteBox(&gomp4.Tfdt{ // - FullBox: gomp4.FullBox{ - Version: 1, - }, - // sum of decode durations of all earlier samples - BaseMediaDecodeTimeV1: uint64(durationGoToMp4(videoSamples[0].dts, fmp4VideoTimescale)), - }) - if err != nil { - return nil, 0, err - } - - flags = 0 - flags |= 0x01 // data offset present - flags |= 0x100 // sample duration present - flags |= 0x200 // sample size present - flags |= 0x400 // sample flags present - flags |= 0x800 // sample composition time offset present or v1 - - trun := &gomp4.Trun{ // - FullBox: gomp4.FullBox{ - Version: 1, - Flags: [3]byte{0, byte(flags >> 8), byte(flags)}, - }, - SampleCount: uint32(len(videoSamples)), - } - - for _, e := range videoSamples { - off := e.pts - e.dts - - flags := uint32(0) - if !e.idrPresent { - flags |= 1 << 16 // sample_is_non_sync_sample - } - - trun.Entries = append(trun.Entries, gomp4.TrunEntry{ - SampleDuration: uint32(durationGoToMp4(e.duration(), fmp4VideoTimescale)), - SampleSize: uint32(len(e.avcc)), - SampleFlags: flags, - SampleCompositionTimeOffsetV1: int32(durationGoToMp4(off, fmp4VideoTimescale)), - }) - } - - trunOffset, err := w.WriteBox(trun) - if err != nil { - return nil, 0, err - } - - err = w.WriteBoxEnd() // - if err != nil { - return nil, 0, err - } - - return trun, trunOffset, nil -} - -func mp4PartGenerateAudioTraf( - w *mp4.Writer, - trackID int, - audioTrack *gortsplib.TrackMPEG4Audio, - audioSamples []*fmp4AudioSample, -) (*gomp4.Trun, int, error) { - /* - traf - - tfhd - - tfdt - - trun - */ - - if len(audioSamples) == 0 { - return nil, 0, nil - } - - _, err := w.WriteBoxStart(&gomp4.Traf{}) // - if err != nil { - return nil, 0, err - } - - flags := 0 - - _, err = w.WriteBox(&gomp4.Tfhd{ // - FullBox: gomp4.FullBox{ - Flags: [3]byte{2, byte(flags >> 8), byte(flags)}, - }, - TrackID: uint32(trackID), - }) - if err != nil { - return nil, 0, err - } - - _, err = w.WriteBox(&gomp4.Tfdt{ // - FullBox: gomp4.FullBox{ - Version: 1, - }, - // sum of decode durations of all earlier samples - BaseMediaDecodeTimeV1: uint64(durationGoToMp4(audioSamples[0].pts, time.Duration(audioTrack.ClockRate()))), - }) - if err != nil { - return nil, 0, err - } - - flags = 0 - flags |= 0x01 // data offset present - flags |= 0x100 // sample duration present - flags |= 0x200 // sample size present - - trun := &gomp4.Trun{ // - FullBox: gomp4.FullBox{ - Version: 0, - Flags: [3]byte{0, byte(flags >> 8), byte(flags)}, - }, - SampleCount: uint32(len(audioSamples)), - } - - for _, e := range audioSamples { - trun.Entries = append(trun.Entries, gomp4.TrunEntry{ - SampleDuration: uint32(durationGoToMp4(e.duration(), time.Duration(audioTrack.ClockRate()))), - SampleSize: uint32(len(e.au)), - }) - } - - trunOffset, err := w.WriteBox(trun) - if err != nil { - return nil, 0, err - } - - err = w.WriteBoxEnd() // - if err != nil { - return nil, 0, err - } - - return trun, trunOffset, nil -} - -func mp4PartGenerate( - videoTrack *gortsplib.TrackH264, - audioTrack *gortsplib.TrackMPEG4Audio, - videoSamples []*fmp4VideoSample, - audioSamples []*fmp4AudioSample, -) ([]byte, error) { - /* - moof - - mfhd - - traf (video) - - traf (audio) - mdat - */ - - w := mp4.NewWriter() - - moofOffset, err := w.WriteBoxStart(&gomp4.Moof{}) // - if err != nil { - return nil, err - } - - _, err = w.WriteBox(&gomp4.Mfhd{ // - SequenceNumber: 0, - }) - if err != nil { - return nil, err - } - - trackID := 1 - - var videoTrun *gomp4.Trun - var videoTrunOffset int - if videoTrack != nil { - var err error - videoTrun, videoTrunOffset, err = mp4PartGenerateVideoTraf( - w, trackID, videoSamples) - if err != nil { - return nil, err - } - - trackID++ - } - - var audioTrun *gomp4.Trun - var audioTrunOffset int - if audioTrack != nil { - var err error - audioTrun, audioTrunOffset, err = mp4PartGenerateAudioTraf(w, trackID, audioTrack, audioSamples) - if err != nil { - return nil, err - } - } - - err = w.WriteBoxEnd() // - if err != nil { - return nil, err - } - - mdat := &gomp4.Mdat{} // - - dataSize := 0 - videoDataSize := 0 - - if videoTrack != nil { - for _, e := range videoSamples { - dataSize += len(e.avcc) - } - videoDataSize = dataSize - } - - if audioTrack != nil { - for _, e := range audioSamples { - dataSize += len(e.au) - } - } - - mdat.Data = make([]byte, dataSize) - pos := 0 - - if videoTrack != nil { - for _, e := range videoSamples { - pos += copy(mdat.Data[pos:], e.avcc) - } - } - - if audioTrack != nil { - for _, e := range audioSamples { - pos += copy(mdat.Data[pos:], e.au) - } - } - - mdatOffset, err := w.WriteBox(mdat) - if err != nil { - return nil, err - } - - if videoTrack != nil { - videoTrun.DataOffset = int32(mdatOffset - moofOffset + 8) - err = w.RewriteBox(videoTrunOffset, videoTrun) - if err != nil { - return nil, err - } - } - - if audioTrack != nil && audioTrun != nil { - audioTrun.DataOffset = int32(videoDataSize + mdatOffset - moofOffset + 8) - err = w.RewriteBox(audioTrunOffset, audioTrun) - if err != nil { - return nil, err - } - } - - return w.Bytes(), nil -} - func fmp4PartName(id uint64) string { return "part" + strconv.FormatUint(id, 10) } @@ -305,8 +22,8 @@ type muxerVariantFMP4Part struct { id uint64 isIndependent bool - videoSamples []*fmp4VideoSample - audioSamples []*fmp4AudioSample + videoSamples []*fmp4.VideoSample + audioSamples []*fmp4.AudioSample renderedContent []byte renderedDuration time.Duration } @@ -341,7 +58,7 @@ func (p *muxerVariantFMP4Part) duration() time.Duration { if p.videoTrack != nil { ret := time.Duration(0) for _, e := range p.videoSamples { - ret += e.duration() + ret += e.Duration() } return ret } @@ -356,7 +73,7 @@ func (p *muxerVariantFMP4Part) duration() time.Duration { func (p *muxerVariantFMP4Part) finalize() error { if len(p.videoSamples) > 0 || len(p.audioSamples) > 0 { var err error - p.renderedContent, err = mp4PartGenerate( + p.renderedContent, err = fmp4.GeneratePart( p.videoTrack, p.audioTrack, p.videoSamples, @@ -374,13 +91,13 @@ func (p *muxerVariantFMP4Part) finalize() error { return nil } -func (p *muxerVariantFMP4Part) writeH264(sample *fmp4VideoSample) { - if sample.idrPresent { +func (p *muxerVariantFMP4Part) writeH264(sample *fmp4.VideoSample) { + if sample.IDRPresent { p.isIndependent = true } p.videoSamples = append(p.videoSamples, sample) } -func (p *muxerVariantFMP4Part) writeAAC(sample *fmp4AudioSample) { +func (p *muxerVariantFMP4Part) writeAAC(sample *fmp4.AudioSample) { p.audioSamples = append(p.audioSamples, sample) } diff --git a/internal/hls/muxer_variant_fmp4_segment.go b/internal/hls/muxer_variant_fmp4_segment.go index 44db0941..c53baa63 100644 --- a/internal/hls/muxer_variant_fmp4_segment.go +++ b/internal/hls/muxer_variant_fmp4_segment.go @@ -7,6 +7,8 @@ import ( "time" "github.com/aler9/gortsplib" + + "github.com/aler9/rtsp-simple-server/internal/hls/fmp4" ) type partsReader struct { @@ -101,8 +103,8 @@ func (s *muxerVariantFMP4Segment) getRenderedDuration() time.Duration { } func (s *muxerVariantFMP4Segment) finalize( - nextVideoSample *fmp4VideoSample, - nextAudioSample *fmp4AudioSample, + nextVideoSample *fmp4.VideoSample, + nextAudioSample *fmp4.AudioSample, ) error { err := s.currentPart.finalize() if err != nil { @@ -117,7 +119,7 @@ func (s *muxerVariantFMP4Segment) finalize( s.currentPart = nil if s.videoTrack != nil { - s.renderedDuration = nextVideoSample.dts - s.startDTS + s.renderedDuration = nextVideoSample.DTS - s.startDTS } else { s.renderedDuration = 0 for _, pa := range s.parts { @@ -128,8 +130,8 @@ func (s *muxerVariantFMP4Segment) finalize( return nil } -func (s *muxerVariantFMP4Segment) writeH264(sample *fmp4VideoSample, adjustedPartDuration time.Duration) error { - size := uint64(len(sample.avcc)) +func (s *muxerVariantFMP4Segment) writeH264(sample *fmp4.VideoSample, adjustedPartDuration time.Duration) error { + size := uint64(len(sample.AVCC)) if (s.size + size) > s.segmentMaxSize { return fmt.Errorf("reached maximum segment size") @@ -160,8 +162,8 @@ func (s *muxerVariantFMP4Segment) writeH264(sample *fmp4VideoSample, adjustedPar return nil } -func (s *muxerVariantFMP4Segment) writeAAC(sample *fmp4AudioSample, adjustedPartDuration time.Duration) error { - size := uint64(len(sample.au)) +func (s *muxerVariantFMP4Segment) writeAAC(sample *fmp4.AudioSample, adjustedPartDuration time.Duration) error { + size := uint64(len(sample.AU)) if (s.size + size) > s.segmentMaxSize { return fmt.Errorf("reached maximum segment size") diff --git a/internal/hls/muxer_variant_fmp4_segmenter.go b/internal/hls/muxer_variant_fmp4_segmenter.go index 6f671fd4..67c8975c 100644 --- a/internal/hls/muxer_variant_fmp4_segmenter.go +++ b/internal/hls/muxer_variant_fmp4_segmenter.go @@ -6,6 +6,8 @@ import ( "github.com/aler9/gortsplib" "github.com/aler9/gortsplib/pkg/h264" + + "github.com/aler9/rtsp-simple-server/internal/hls/fmp4" ) func partDurationIsCompatible(partDuration time.Duration, sampleDuration time.Duration) bool { @@ -60,8 +62,8 @@ type muxerVariantFMP4Segmenter struct { currentSegment *muxerVariantFMP4Segment nextSegmentID uint64 nextPartID uint64 - nextVideoSample *fmp4VideoSample - nextAudioSample *fmp4AudioSample + nextVideoSample *fmp4.VideoSample + nextAudioSample *fmp4.AudioSample firstSegmentFinalized bool sampleDurations map[time.Duration]struct{} adjustedPartDuration time.Duration @@ -144,18 +146,18 @@ func (m *muxerVariantFMP4Segmenter) writeH264(pts time.Duration, nalus [][]byte) return err } - return m.writeH264Entry(&fmp4VideoSample{ - pts: pts, - nalus: nalus, - avcc: avcc, - idrPresent: idrPresent, + return m.writeH264Entry(&fmp4.VideoSample{ + PTS: pts, + NALUs: nalus, + AVCC: avcc, + IDRPresent: idrPresent, }) } -func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) error { +func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4.VideoSample) error { if !m.videoFirstIDRReceived { // skip sample silently until we find one with an IDR - if !sample.idrPresent { + if !sample.IDRPresent { return nil } @@ -164,25 +166,25 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro m.videoSPS = append([]byte(nil), m.videoTrack.SafeSPS()...) var err error - sample.dts, err = m.videoDTSExtractor.Extract(sample.nalus, sample.pts) + sample.DTS, err = m.videoDTSExtractor.Extract(sample.NALUs, sample.PTS) if err != nil { return err } - sample.nalus = nil + sample.NALUs = nil - m.startDTS = sample.dts - sample.dts = 0 - sample.pts -= m.startDTS + m.startDTS = sample.DTS + sample.DTS = 0 + sample.PTS -= m.startDTS } else { var err error - sample.dts, err = m.videoDTSExtractor.Extract(sample.nalus, sample.pts) + sample.DTS, err = m.videoDTSExtractor.Extract(sample.NALUs, sample.PTS) if err != nil { return err } - sample.nalus = nil + sample.NALUs = nil - sample.dts -= m.startDTS - sample.pts -= m.startDTS + sample.DTS -= m.startDTS + sample.PTS -= m.startDTS } // put samples into a queue in order to @@ -192,7 +194,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro if sample == nil { return nil } - sample.next = m.nextVideoSample + sample.Next = m.nextVideoSample now := time.Now() @@ -202,7 +204,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro m.lowLatency, m.genSegmentID(), now, - sample.dts, + sample.DTS, m.segmentMaxSize, m.videoTrack, m.audioTrack, @@ -211,7 +213,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro ) } - m.adjustPartDuration(sample.duration()) + m.adjustPartDuration(sample.Duration()) err := m.currentSegment.writeH264(sample, m.adjustedPartDuration) if err != nil { @@ -219,13 +221,13 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro } // switch segment - if sample.next.idrPresent { + if sample.Next.IDRPresent { sps := m.videoTrack.SafeSPS() spsChanged := !bytes.Equal(m.videoSPS, sps) - if (sample.next.dts-m.currentSegment.startDTS) >= m.segmentDuration || + if (sample.Next.DTS-m.currentSegment.startDTS) >= m.segmentDuration || spsChanged { - err := m.currentSegment.finalize(sample.next, nil) + err := m.currentSegment.finalize(sample.Next, nil) if err != nil { return err } @@ -237,7 +239,7 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro m.lowLatency, m.genSegmentID(), now, - sample.next.dts, + sample.Next.DTS, m.segmentMaxSize, m.videoTrack, m.audioTrack, @@ -258,20 +260,20 @@ func (m *muxerVariantFMP4Segmenter) writeH264Entry(sample *fmp4VideoSample) erro } func (m *muxerVariantFMP4Segmenter) writeAAC(pts time.Duration, au []byte) error { - return m.writeAACEntry(&fmp4AudioSample{ - pts: pts, - au: au, + return m.writeAACEntry(&fmp4.AudioSample{ + PTS: pts, + AU: au, }) } -func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error { +func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4.AudioSample) error { if m.videoTrack != nil { // wait for the video track if !m.videoFirstIDRReceived { return nil } - sample.pts -= m.startDTS + sample.PTS -= m.startDTS } // put samples into a queue in order to @@ -280,7 +282,7 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error if sample == nil { return nil } - sample.next = m.nextAudioSample + sample.Next = m.nextAudioSample now := time.Now() @@ -291,7 +293,7 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error m.lowLatency, m.genSegmentID(), now, - sample.pts, + sample.PTS, m.segmentMaxSize, m.videoTrack, m.audioTrack, @@ -313,8 +315,8 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error // switch segment if m.videoTrack == nil && - (sample.next.pts-m.currentSegment.startDTS) >= m.segmentDuration { - err := m.currentSegment.finalize(nil, sample.next) + (sample.Next.PTS-m.currentSegment.startDTS) >= m.segmentDuration { + err := m.currentSegment.finalize(nil, sample.Next) if err != nil { return err } @@ -326,7 +328,7 @@ func (m *muxerVariantFMP4Segmenter) writeAACEntry(sample *fmp4AudioSample) error m.lowLatency, m.genSegmentID(), now, - sample.next.pts, + sample.Next.PTS, m.segmentMaxSize, m.videoTrack, m.audioTrack,