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,