diff --git a/go.mod b/go.mod
index a6ebfc0a..eefd1698 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
github.com/alecthomas/kong v0.9.0
github.com/bluenviron/gohlslib v1.3.2
github.com/bluenviron/gortsplib/v4 v4.9.1-0.20240515082130-f283abc2e7cd
- github.com/bluenviron/mediacommon v1.10.0
+ github.com/bluenviron/mediacommon v1.10.1-0.20240518092051-bab50c4ba9c5
github.com/datarhei/gosrt v0.6.0
github.com/fsnotify/fsnotify v1.7.0
github.com/gin-gonic/gin v1.10.0
diff --git a/go.sum b/go.sum
index 1e3e40c8..694d9eda 100644
--- a/go.sum
+++ b/go.sum
@@ -24,8 +24,8 @@ github.com/bluenviron/gohlslib v1.3.2 h1:xRiPfMIeYCkspL6jYa7Qrl4pIY+1w7IvFjx49Cs
github.com/bluenviron/gohlslib v1.3.2/go.mod h1:1/m7A2o5IWyBdZeauXe2bViu2l1mL2l8DMQl9302A2U=
github.com/bluenviron/gortsplib/v4 v4.9.1-0.20240515082130-f283abc2e7cd h1:w1Uml4bXdixu7cArQ3JyiZTpaKzZ31eP9+bWoPPkWcY=
github.com/bluenviron/gortsplib/v4 v4.9.1-0.20240515082130-f283abc2e7cd/go.mod h1:iLJ1tmwGMbaN04ZYh/KRlAHsCbz9Rycn7cPAvdR+Vkc=
-github.com/bluenviron/mediacommon v1.10.0 h1:ffIWaS+1vYpPLV6QOt4VEvIlb/OKtodzagzsY6EDOnw=
-github.com/bluenviron/mediacommon v1.10.0/go.mod h1:HDyW2CzjvhYJXtdxstdFPio3G0qSocPhqkhUt/qffec=
+github.com/bluenviron/mediacommon v1.10.1-0.20240518092051-bab50c4ba9c5 h1:fZL+8Bz8wT0ljvt+ZyGGzirT1jQxH1wgqOiyRifAL60=
+github.com/bluenviron/mediacommon v1.10.1-0.20240518092051-bab50c4ba9c5/go.mod h1:HDyW2CzjvhYJXtdxstdFPio3G0qSocPhqkhUt/qffec=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
diff --git a/internal/playback/mp4/mp4_writer.go b/internal/playback/mp4/mp4_writer.go
deleted file mode 100644
index 098364e9..00000000
--- a/internal/playback/mp4/mp4_writer.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package mp4
-
-import (
- "io"
-
- "github.com/abema/go-mp4"
-)
-
-type mp4Writer struct {
- w *mp4.Writer
-}
-
-func newMP4Writer(w io.WriteSeeker) *mp4Writer {
- return &mp4Writer{
- w: mp4.NewWriter(w),
- }
-}
-
-func (w *mp4Writer) writeBoxStart(box mp4.IImmutableBox) (int, error) {
- bi := &mp4.BoxInfo{
- Type: box.GetType(),
- }
- var err error
- bi, err = w.w.StartBox(bi)
- if err != nil {
- return 0, err
- }
-
- _, err = mp4.Marshal(w.w, box, mp4.Context{})
- if err != nil {
- return 0, err
- }
-
- return int(bi.Offset), nil
-}
-
-func (w *mp4Writer) writeBoxEnd() error {
- _, err := w.w.EndBox()
- return err
-}
-
-func (w *mp4Writer) writeBox(box mp4.IImmutableBox) (int, error) {
- off, err := w.writeBoxStart(box)
- if err != nil {
- return 0, err
- }
-
- err = w.writeBoxEnd()
- if err != nil {
- return 0, err
- }
-
- return off, nil
-}
-
-func (w *mp4Writer) rewriteBox(off int, box mp4.IImmutableBox) error {
- prevOff, err := w.w.Seek(0, io.SeekCurrent)
- if err != nil {
- return err
- }
-
- _, err = w.w.Seek(int64(off), io.SeekStart)
- if err != nil {
- return err
- }
-
- _, err = w.writeBoxStart(box)
- if err != nil {
- return err
- }
-
- err = w.writeBoxEnd()
- if err != nil {
- return err
- }
-
- _, err = w.w.Seek(prevOff, io.SeekStart)
- if err != nil {
- return err
- }
-
- return nil
-}
diff --git a/internal/playback/mp4/presentation.go b/internal/playback/mp4/presentation.go
deleted file mode 100644
index 0f12bfe5..00000000
--- a/internal/playback/mp4/presentation.go
+++ /dev/null
@@ -1,209 +0,0 @@
-// Package mp4 contains a MP4 muxer.
-package mp4
-
-import (
- "io"
- "time"
-
- "github.com/abema/go-mp4"
- "github.com/bluenviron/mediacommon/pkg/formats/fmp4/seekablebuffer"
-)
-
-const (
- globalTimescale = 1000
-)
-
-func durationMp4ToGo(v int64, timeScale uint32) time.Duration {
- timeScale64 := int64(timeScale)
- secs := v / timeScale64
- dec := v % timeScale64
- return time.Duration(secs)*time.Second + time.Duration(dec)*time.Second/time.Duration(timeScale64)
-}
-
-// Presentation is timed sequence of video/audio samples.
-type Presentation struct {
- Tracks []*Track
-}
-
-// Marshal encodes a Presentation.
-func (p *Presentation) Marshal(w io.Writer) error {
- /*
- |ftyp|
- |moov|
- | |mvhd|
- | |trak|
- | |trak|
- | |....|
- |mdat|
- */
-
- dataSize, sortedSamples := p.sortSamples()
-
- err := p.marshalFtypAndMoov(w)
- if err != nil {
- return err
- }
-
- return p.marshalMdat(w, dataSize, sortedSamples)
-}
-
-func (p *Presentation) sortSamples() (uint32, []*Sample) {
- sampleCount := 0
- for _, track := range p.Tracks {
- sampleCount += len(track.Samples)
- }
-
- processedSamples := make([]int, len(p.Tracks))
- elapsed := make([]int64, len(p.Tracks))
- offset := uint32(0)
- sortedSamples := make([]*Sample, sampleCount)
- pos := 0
-
- for i, track := range p.Tracks {
- elapsed[i] = int64(track.TimeOffset)
- }
-
- for {
- bestTrack := -1
- var bestElapsed time.Duration
-
- for i, track := range p.Tracks {
- if processedSamples[i] < len(track.Samples) {
- elapsedGo := durationMp4ToGo(elapsed[i], track.TimeScale)
-
- if bestTrack == -1 || elapsedGo < bestElapsed {
- bestTrack = i
- bestElapsed = elapsedGo
- }
- }
- }
-
- if bestTrack == -1 {
- break
- }
-
- sample := p.Tracks[bestTrack].Samples[processedSamples[bestTrack]]
- sample.offset = offset
-
- processedSamples[bestTrack]++
- elapsed[bestTrack] += int64(sample.Duration)
- offset += sample.PayloadSize
- sortedSamples[pos] = sample
- pos++
- }
-
- return offset, sortedSamples
-}
-
-func (p *Presentation) marshalFtypAndMoov(w io.Writer) error {
- var outBuf seekablebuffer.Buffer
- mw := newMP4Writer(&outBuf)
-
- _, err := mw.writeBox(&mp4.Ftyp{ //
- MajorBrand: [4]byte{'i', 's', 'o', 'm'},
- MinorVersion: 1,
- CompatibleBrands: []mp4.CompatibleBrandElem{
- {CompatibleBrand: [4]byte{'i', 's', 'o', 'm'}},
- {CompatibleBrand: [4]byte{'i', 's', 'o', '2'}},
- {CompatibleBrand: [4]byte{'m', 'p', '4', '1'}},
- {CompatibleBrand: [4]byte{'m', 'p', '4', '2'}},
- },
- })
- if err != nil {
- return err
- }
-
- _, err = mw.writeBoxStart(&mp4.Moov{}) //
- if err != nil {
- return err
- }
-
- mvhd := &mp4.Mvhd{ //
- Timescale: globalTimescale,
- Rate: 65536,
- Volume: 256,
- Matrix: [9]int32{0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000},
- NextTrackID: uint32(len(p.Tracks) + 1),
- }
- mvhdOffset, err := mw.writeBox(mvhd)
- if err != nil {
- return err
- }
-
- stcos := make([]*mp4.Stco, len(p.Tracks))
- stcosOffsets := make([]int, len(p.Tracks))
-
- for i, track := range p.Tracks {
- var res *headerTrackMarshalResult
- res, err = track.marshal(mw)
- if err != nil {
- return err
- }
-
- stcos[i] = res.stco
- stcosOffsets[i] = res.stcoOffset
-
- if res.presentationDuration > mvhd.DurationV0 {
- mvhd.DurationV0 = res.presentationDuration
- }
- }
-
- err = mw.rewriteBox(mvhdOffset, mvhd)
- if err != nil {
- return err
- }
-
- err = mw.writeBoxEnd() //
- if err != nil {
- return err
- }
-
- moovEndOffset, err := outBuf.Seek(0, io.SeekCurrent)
- if err != nil {
- return err
- }
-
- dataOffset := moovEndOffset + 8
-
- for i := range p.Tracks {
- for j := range stcos[i].ChunkOffset {
- stcos[i].ChunkOffset[j] += uint32(dataOffset)
- }
-
- err = mw.rewriteBox(stcosOffsets[i], stcos[i])
- if err != nil {
- return err
- }
- }
-
- _, err = w.Write(outBuf.Bytes())
- return err
-}
-
-func (p *Presentation) marshalMdat(w io.Writer, dataSize uint32, sortedSamples []*Sample) error {
- mdatSize := uint32(8) + dataSize
-
- _, err := w.Write([]byte{byte(mdatSize >> 24), byte(mdatSize >> 16), byte(mdatSize >> 8), byte(mdatSize)})
- if err != nil {
- return err
- }
-
- _, err = w.Write([]byte{'m', 'd', 'a', 't'})
- if err != nil {
- return err
- }
-
- for _, sa := range sortedSamples {
- pl, err := sa.GetPayload()
- if err != nil {
- return err
- }
-
- _, err = w.Write(pl)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
diff --git a/internal/playback/mp4/presentation_test.go b/internal/playback/mp4/presentation_test.go
deleted file mode 100644
index 294f4d83..00000000
--- a/internal/playback/mp4/presentation_test.go
+++ /dev/null
@@ -1,165 +0,0 @@
-package mp4
-
-import (
- "bytes"
- "testing"
-
- "github.com/bluenviron/mediacommon/pkg/formats/fmp4"
- "github.com/bluenviron/mediamtx/internal/test"
- "github.com/stretchr/testify/require"
-)
-
-var casesPresentation = []struct {
- name string
- dec Presentation
- enc []byte
-}{
- {
- "standard",
- Presentation{
- Tracks: []*Track{
- {
- ID: 1,
- TimeScale: 90000,
- TimeOffset: -90000,
- Codec: &fmp4.CodecH264{
- SPS: test.FormatH264.SPS,
- PPS: test.FormatH264.PPS,
- },
- Samples: []*Sample{
- {
- Duration: 90000,
- PTSOffset: -45000,
- PayloadSize: 2,
- GetPayload: func() ([]byte, error) {
- return []byte{1, 2}, nil
- },
- },
- {
- Duration: 90000,
- PayloadSize: 2,
- GetPayload: func() ([]byte, error) {
- return []byte{3, 4}, nil
- },
- },
- {
- Duration: 90000,
- PTSOffset: -45000,
- PayloadSize: 2,
- GetPayload: func() ([]byte, error) {
- return []byte{5, 6}, nil
- },
- },
- },
- },
- },
- },
- []byte{
- 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70,
- 0x69, 0x73, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x01,
- 0x69, 0x73, 0x6f, 0x6d, 0x69, 0x73, 0x6f, 0x32,
- 0x6d, 0x70, 0x34, 0x31, 0x6d, 0x70, 0x34, 0x32,
- 0x00, 0x00, 0x02, 0xbf, 0x6d, 0x6f, 0x6f, 0x76,
- 0x00, 0x00, 0x00, 0x6c, 0x6d, 0x76, 0x68, 0x64,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
- 0x00, 0x00, 0x07, 0xd0, 0x00, 0x01, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x4b,
- 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c,
- 0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x03,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x07, 0xd0, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x07, 0x80, 0x00, 0x00, 0x04, 0x38, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73,
- 0x00, 0x00, 0x00, 0x1c, 0x65, 0x6c, 0x73, 0x74,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x0f, 0xa0, 0x00, 0x01, 0x5f, 0x90,
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc3,
- 0x6d, 0x64, 0x69, 0x61, 0x00, 0x00, 0x00, 0x20,
- 0x6d, 0x64, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x5f, 0x90, 0x00, 0x02, 0xbf, 0x20,
- 0x55, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d,
- 0x68, 0x64, 0x6c, 0x72, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x65,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x64, 0x65,
- 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72,
- 0x00, 0x00, 0x00, 0x01, 0x6e, 0x6d, 0x69, 0x6e,
- 0x66, 0x00, 0x00, 0x00, 0x14, 0x76, 0x6d, 0x68,
- 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x24, 0x64, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00,
- 0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x01, 0x2e, 0x73, 0x74, 0x62,
- 0x6c, 0x00, 0x00, 0x00, 0x96, 0x73, 0x74, 0x73,
- 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x86, 0x61, 0x76, 0x63,
- 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x07, 0x80, 0x04, 0x38, 0x00, 0x48, 0x00,
- 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x18, 0xff, 0xff, 0x00,
- 0x00, 0x00, 0x30, 0x61, 0x76, 0x63, 0x43, 0x01,
- 0x42, 0xc0, 0x28, 0x03, 0x01, 0x00, 0x19, 0x67,
- 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, 0x27,
- 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00,
- 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20,
- 0x01, 0x00, 0x04, 0x08, 0x06, 0x07, 0x08, 0x00,
- 0x00, 0x00, 0x18, 0x73, 0x74, 0x74, 0x73, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x03, 0x00, 0x01, 0x5f, 0x90, 0x00,
- 0x00, 0x00, 0x28, 0x63, 0x74, 0x74, 0x73, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
- 0x00, 0x00, 0x01, 0xff, 0xff, 0x50, 0x38, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0xff, 0xff, 0x50, 0x38, 0x00,
- 0x00, 0x00, 0x1c, 0x73, 0x74, 0x73, 0x63, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x73,
- 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
- 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00,
- 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x73,
- 0x74, 0x63, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xe7, 0x00,
- 0x00, 0x00, 0x0e, 0x6d, 0x64, 0x61, 0x74, 0x01,
- 0x02, 0x03, 0x04, 0x05, 0x06,
- },
- },
-}
-
-func TestPresentationMarshal(t *testing.T) {
- for _, ca := range casesPresentation {
- t.Run(ca.name, func(t *testing.T) {
- var buf bytes.Buffer
- err := ca.dec.Marshal(&buf)
- require.NoError(t, err)
- require.Equal(t, ca.enc, buf.Bytes())
- })
- }
-}
diff --git a/internal/playback/mp4/sample.go b/internal/playback/mp4/sample.go
deleted file mode 100644
index 58dbd66a..00000000
--- a/internal/playback/mp4/sample.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package mp4
-
-// Sample is a sample of a Track.
-type Sample struct {
- Duration uint32
- PTSOffset int32
- IsNonSyncSample bool
- PayloadSize uint32
- GetPayload func() ([]byte, error)
-
- offset uint32 // filled by sortSamples
-}
diff --git a/internal/playback/mp4/track.go b/internal/playback/mp4/track.go
deleted file mode 100644
index d21743e4..00000000
--- a/internal/playback/mp4/track.go
+++ /dev/null
@@ -1,1139 +0,0 @@
-package mp4
-
-import (
- "fmt"
-
- "github.com/abema/go-mp4"
- "github.com/bluenviron/mediacommon/pkg/codecs/av1"
- "github.com/bluenviron/mediacommon/pkg/codecs/h264"
- "github.com/bluenviron/mediacommon/pkg/codecs/h265"
- "github.com/bluenviron/mediacommon/pkg/formats/fmp4"
-)
-
-// Specification: ISO 14496-1, Table 5
-const (
- objectTypeIndicationVisualISO14496part2 = 0x20
- objectTypeIndicationAudioISO14496part3 = 0x40
- objectTypeIndicationVisualISO1318part2Main = 0x61
- objectTypeIndicationAudioISO11172part3 = 0x6B
- objectTypeIndicationVisualISO10918part1 = 0x6C
-)
-
-// Specification: ISO 14496-1, Table 6
-const (
- streamTypeVisualStream = 0x04
- streamTypeAudioStream = 0x05
-)
-
-func boolToUint8(v bool) uint8 {
- if v {
- return 1
- }
- return 0
-}
-
-func allSamplesAreSync(samples []*Sample) bool {
- for _, sa := range samples {
- if sa.IsNonSyncSample {
- return false
- }
- }
- return true
-}
-
-type headerTrackMarshalResult struct {
- stco *mp4.Stco
- stcoOffset int
- presentationDuration uint32
-}
-
-// Track is a track of a Presentation.
-type Track struct {
- ID int
- TimeScale uint32
- TimeOffset int32
- Codec fmp4.Codec
- Samples []*Sample
-}
-
-func (t *Track) marshal(w *mp4Writer) (*headerTrackMarshalResult, error) {
- /*
- |trak|
- | |tkhd|
- | |edts|
- | | |elst|
- | |mdia|
- | | |mdhd|
- | | |hdlr|
- | | |minf|
- | | | |vmhd| (video)
- | | | |smhd| (audio)
- | | | |dinf|
- | | | | |dref|
- | | | | | |url|
- | | | |stbl|
- | | | | |stsd|
- | | | | | |av01| (AV1)
- | | | | | | |av1C|
- | | | | | |vp09| (VP9)
- | | | | | | |vpcC|
- | | | | | |hev1| (H265)
- | | | | | | |hvcC|
- | | | | | |avc1| (H264)
- | | | | | | |avcC|
- | | | | | |mp4v| (MPEG-4/2/1 video, MJPEG)
- | | | | | | |esds|
- | | | | | |Opus| (Opus)
- | | | | | | |dOps|
- | | | | | |mp4a| (MPEG-4/1 audio)
- | | | | | | |esds|
- | | | | | |ac-3| (AC-3)
- | | | | | | |dac3|
- | | | | | |ipcm| (LPCM)
- | | | | | | |pcmC|
- | | | | |stts|
- | | | | |stss|
- | | | | |ctts|
- | | | | |stsc|
- | | | | |stsz|
- | | | | |stco|
- */
-
- _, err := w.writeBoxStart(&mp4.Trak{}) //
- if err != nil {
- return nil, err
- }
-
- var av1SequenceHeader *av1.SequenceHeader
- var h265SPS *h265.SPS
- var h264SPS *h264.SPS
-
- var width int
- var height int
-
- switch codec := t.Codec.(type) {
- case *fmp4.CodecAV1:
- av1SequenceHeader = &av1.SequenceHeader{}
- err = av1SequenceHeader.Unmarshal(codec.SequenceHeader)
- if err != nil {
- return nil, fmt.Errorf("unable to parse AV1 sequence header: %w", err)
- }
-
- width = av1SequenceHeader.Width()
- height = av1SequenceHeader.Height()
-
- case *fmp4.CodecVP9:
- if codec.Width == 0 {
- return nil, fmt.Errorf("VP9 parameters not provided")
- }
-
- width = codec.Width
- height = codec.Height
-
- case *fmp4.CodecH265:
- if len(codec.VPS) == 0 || len(codec.SPS) == 0 || len(codec.PPS) == 0 {
- return nil, fmt.Errorf("H265 parameters not provided")
- }
-
- h265SPS = &h265.SPS{}
- err = h265SPS.Unmarshal(codec.SPS)
- if err != nil {
- return nil, fmt.Errorf("unable to parse H265 SPS: %w", err)
- }
-
- width = h265SPS.Width()
- height = h265SPS.Height()
-
- case *fmp4.CodecH264:
- if len(codec.SPS) == 0 || len(codec.PPS) == 0 {
- return nil, fmt.Errorf("H264 parameters not provided")
- }
-
- h264SPS = &h264.SPS{}
- err = h264SPS.Unmarshal(codec.SPS)
- if err != nil {
- return nil, fmt.Errorf("unable to parse H264 SPS: %w", err)
- }
-
- width = h264SPS.Width()
- height = h264SPS.Height()
-
- case *fmp4.CodecMPEG4Video:
- if len(codec.Config) == 0 {
- return nil, fmt.Errorf("MPEG-4 Video config not provided")
- }
-
- // TODO: parse config and use real values
- width = 800
- height = 600
-
- case *fmp4.CodecMPEG1Video:
- if len(codec.Config) == 0 {
- return nil, fmt.Errorf("MPEG-1/2 Video config not provided")
- }
-
- // TODO: parse config and use real values
- width = 800
- height = 600
-
- case *fmp4.CodecMJPEG:
- if codec.Width == 0 {
- return nil, fmt.Errorf("M-JPEG parameters not provided")
- }
-
- width = codec.Width
- height = codec.Height
- }
-
- sampleDuration := uint32(0)
- for _, sa := range t.Samples {
- sampleDuration += sa.Duration
- }
-
- presentationDuration := uint32(((int64(sampleDuration) + int64(t.TimeOffset)) * globalTimescale) / int64(t.TimeScale))
-
- if t.Codec.IsVideo() {
- _, err = w.writeBox(&mp4.Tkhd{ //
- FullBox: mp4.FullBox{
- Flags: [3]byte{0, 0, 3},
- },
- TrackID: uint32(t.ID),
- DurationV0: presentationDuration,
- Width: uint32(width * 65536),
- Height: uint32(height * 65536),
- Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
- })
- if err != nil {
- return nil, err
- }
- } else {
- _, err = w.writeBox(&mp4.Tkhd{ //
- FullBox: mp4.FullBox{
- Flags: [3]byte{0, 0, 3},
- },
- TrackID: uint32(t.ID),
- DurationV0: presentationDuration,
- AlternateGroup: 1,
- Volume: 256,
- Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000},
- })
- if err != nil {
- return nil, err
- }
- }
-
- _, err = w.writeBoxStart(&mp4.Edts{}) //
- if err != nil {
- return nil, err
- }
-
- err = t.marshalELST(w, sampleDuration) //
- if err != nil {
- return nil, err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBoxStart(&mp4.Mdia{}) //
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.Mdhd{ //
- Timescale: t.TimeScale,
- DurationV0: uint32(int64(sampleDuration) + int64(t.TimeOffset)),
- Language: [3]byte{'u', 'n', 'd'},
- })
- if err != nil {
- return nil, err
- }
-
- if t.Codec.IsVideo() {
- _, err = w.writeBox(&mp4.Hdlr{ //
- HandlerType: [4]byte{'v', 'i', 'd', 'e'},
- Name: "VideoHandler",
- })
- if err != nil {
- return nil, err
- }
- } else {
- _, err = w.writeBox(&mp4.Hdlr{ //
- HandlerType: [4]byte{'s', 'o', 'u', 'n'},
- Name: "SoundHandler",
- })
- if err != nil {
- return nil, err
- }
- }
-
- _, err = w.writeBoxStart(&mp4.Minf{}) //
- if err != nil {
- return nil, err
- }
-
- if t.Codec.IsVideo() {
- _, err = w.writeBox(&mp4.Vmhd{ //
- FullBox: mp4.FullBox{
- Flags: [3]byte{0, 0, 1},
- },
- })
- if err != nil {
- return nil, err
- }
- } else {
- _, err = w.writeBox(&mp4.Smhd{}) //
- if err != nil {
- return nil, err
- }
- }
-
- _, err = w.writeBoxStart(&mp4.Dinf{}) //
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBoxStart(&mp4.Dref{ //
- EntryCount: 1,
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.Url{ //
- FullBox: mp4.FullBox{
- Flags: [3]byte{0, 0, 1},
- },
- })
- if err != nil {
- return nil, err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBoxStart(&mp4.Stbl{}) //
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBoxStart(&mp4.Stsd{ //
- EntryCount: 1,
- })
- if err != nil {
- return nil, err
- }
-
- switch codec := t.Codec.(type) {
- case *fmp4.CodecAV1:
- _, err = w.writeBoxStart(&mp4.VisualSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeAv01(),
- },
- DataReferenceIndex: 1,
- },
- Width: uint16(width),
- Height: uint16(height),
- Horizresolution: 4718592,
- Vertresolution: 4718592,
- FrameCount: 1,
- Depth: 24,
- PreDefined3: -1,
- })
- if err != nil {
- return nil, err
- }
-
- var bs []byte
- bs, err = av1.BitstreamMarshal([][]byte{codec.SequenceHeader})
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.Av1C{ //
- Marker: 1,
- Version: 1,
- SeqProfile: av1SequenceHeader.SeqProfile,
- SeqLevelIdx0: av1SequenceHeader.SeqLevelIdx[0],
- SeqTier0: boolToUint8(av1SequenceHeader.SeqTier[0]),
- HighBitdepth: boolToUint8(av1SequenceHeader.ColorConfig.HighBitDepth),
- TwelveBit: boolToUint8(av1SequenceHeader.ColorConfig.TwelveBit),
- Monochrome: boolToUint8(av1SequenceHeader.ColorConfig.MonoChrome),
- ChromaSubsamplingX: boolToUint8(av1SequenceHeader.ColorConfig.SubsamplingX),
- ChromaSubsamplingY: boolToUint8(av1SequenceHeader.ColorConfig.SubsamplingY),
- ChromaSamplePosition: uint8(av1SequenceHeader.ColorConfig.ChromaSamplePosition),
- ConfigOBUs: bs,
- })
- if err != nil {
- return nil, err
- }
-
- case *fmp4.CodecVP9:
- _, err = w.writeBoxStart(&mp4.VisualSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeVp09(),
- },
- DataReferenceIndex: 1,
- },
- Width: uint16(width),
- Height: uint16(height),
- Horizresolution: 4718592,
- Vertresolution: 4718592,
- FrameCount: 1,
- Depth: 24,
- PreDefined3: -1,
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.VpcC{ //
- FullBox: mp4.FullBox{
- Version: 1,
- },
- Profile: codec.Profile,
- Level: 10, // level 1
- BitDepth: codec.BitDepth,
- ChromaSubsampling: codec.ChromaSubsampling,
- VideoFullRangeFlag: boolToUint8(codec.ColorRange),
- })
- if err != nil {
- return nil, err
- }
-
- case *fmp4.CodecH265:
- _, err = w.writeBoxStart(&mp4.VisualSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeHev1(),
- },
- DataReferenceIndex: 1,
- },
- Width: uint16(width),
- Height: uint16(height),
- Horizresolution: 4718592,
- Vertresolution: 4718592,
- FrameCount: 1,
- Depth: 24,
- PreDefined3: -1,
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.HvcC{ //
- ConfigurationVersion: 1,
- GeneralProfileIdc: h265SPS.ProfileTierLevel.GeneralProfileIdc,
- GeneralProfileCompatibility: h265SPS.ProfileTierLevel.GeneralProfileCompatibilityFlag,
- GeneralConstraintIndicator: [6]uint8{
- codec.SPS[7], codec.SPS[8], codec.SPS[9],
- codec.SPS[10], codec.SPS[11], codec.SPS[12],
- },
- GeneralLevelIdc: h265SPS.ProfileTierLevel.GeneralLevelIdc,
- // MinSpatialSegmentationIdc
- // ParallelismType
- ChromaFormatIdc: uint8(h265SPS.ChromaFormatIdc),
- BitDepthLumaMinus8: uint8(h265SPS.BitDepthLumaMinus8),
- BitDepthChromaMinus8: uint8(h265SPS.BitDepthChromaMinus8),
- // AvgFrameRate
- // ConstantFrameRate
- NumTemporalLayers: 1,
- // TemporalIdNested
- LengthSizeMinusOne: 3,
- NumOfNaluArrays: 3,
- NaluArrays: []mp4.HEVCNaluArray{
- {
- NaluType: byte(h265.NALUType_VPS_NUT),
- NumNalus: 1,
- Nalus: []mp4.HEVCNalu{{
- Length: uint16(len(codec.VPS)),
- NALUnit: codec.VPS,
- }},
- },
- {
- NaluType: byte(h265.NALUType_SPS_NUT),
- NumNalus: 1,
- Nalus: []mp4.HEVCNalu{{
- Length: uint16(len(codec.SPS)),
- NALUnit: codec.SPS,
- }},
- },
- {
- NaluType: byte(h265.NALUType_PPS_NUT),
- NumNalus: 1,
- Nalus: []mp4.HEVCNalu{{
- Length: uint16(len(codec.PPS)),
- NALUnit: codec.PPS,
- }},
- },
- },
- })
- if err != nil {
- return nil, err
- }
-
- case *fmp4.CodecH264:
- _, err = w.writeBoxStart(&mp4.VisualSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeAvc1(),
- },
- DataReferenceIndex: 1,
- },
- Width: uint16(width),
- Height: uint16(height),
- Horizresolution: 4718592,
- Vertresolution: 4718592,
- FrameCount: 1,
- Depth: 24,
- PreDefined3: -1,
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.AVCDecoderConfiguration{ //
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeAvcC(),
- },
- ConfigurationVersion: 1,
- Profile: h264SPS.ProfileIdc,
- ProfileCompatibility: codec.SPS[2],
- Level: h264SPS.LevelIdc,
- LengthSizeMinusOne: 3,
- NumOfSequenceParameterSets: 1,
- SequenceParameterSets: []mp4.AVCParameterSet{
- {
- Length: uint16(len(codec.SPS)),
- NALUnit: codec.SPS,
- },
- },
- NumOfPictureParameterSets: 1,
- PictureParameterSets: []mp4.AVCParameterSet{
- {
- Length: uint16(len(codec.PPS)),
- NALUnit: codec.PPS,
- },
- },
- })
- if err != nil {
- return nil, err
- }
-
- case *fmp4.CodecMPEG4Video: //nolint:dupl
- _, err = w.writeBoxStart(&mp4.VisualSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeMp4v(),
- },
- DataReferenceIndex: 1,
- },
- Width: uint16(width),
- Height: uint16(height),
- Horizresolution: 4718592,
- Vertresolution: 4718592,
- FrameCount: 1,
- Depth: 24,
- PreDefined3: -1,
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.Esds{ //
- Descriptors: []mp4.Descriptor{
- {
- Tag: mp4.ESDescrTag,
- Size: 32 + uint32(len(codec.Config)),
- ESDescriptor: &mp4.ESDescriptor{
- ESID: uint16(t.ID),
- },
- },
- {
- Tag: mp4.DecoderConfigDescrTag,
- Size: 18 + uint32(len(codec.Config)),
- DecoderConfigDescriptor: &mp4.DecoderConfigDescriptor{
- ObjectTypeIndication: objectTypeIndicationVisualISO14496part2,
- StreamType: streamTypeVisualStream,
- Reserved: true,
- MaxBitrate: 1000000,
- AvgBitrate: 1000000,
- },
- },
- {
- Tag: mp4.DecSpecificInfoTag,
- Size: uint32(len(codec.Config)),
- Data: codec.Config,
- },
- {
- Tag: mp4.SLConfigDescrTag,
- Size: 1,
- Data: []byte{0x02},
- },
- },
- })
- if err != nil {
- return nil, err
- }
-
- case *fmp4.CodecMPEG1Video: //nolint:dupl
- _, err = w.writeBoxStart(&mp4.VisualSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeMp4v(),
- },
- DataReferenceIndex: 1,
- },
- Width: uint16(width),
- Height: uint16(height),
- Horizresolution: 4718592,
- Vertresolution: 4718592,
- FrameCount: 1,
- Depth: 24,
- PreDefined3: -1,
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.Esds{ //
- Descriptors: []mp4.Descriptor{
- {
- Tag: mp4.ESDescrTag,
- Size: 32 + uint32(len(codec.Config)),
- ESDescriptor: &mp4.ESDescriptor{
- ESID: uint16(t.ID),
- },
- },
- {
- Tag: mp4.DecoderConfigDescrTag,
- Size: 18 + uint32(len(codec.Config)),
- DecoderConfigDescriptor: &mp4.DecoderConfigDescriptor{
- ObjectTypeIndication: objectTypeIndicationVisualISO1318part2Main,
- StreamType: streamTypeVisualStream,
- Reserved: true,
- MaxBitrate: 1000000,
- AvgBitrate: 1000000,
- },
- },
- {
- Tag: mp4.DecSpecificInfoTag,
- Size: uint32(len(codec.Config)),
- Data: codec.Config,
- },
- {
- Tag: mp4.SLConfigDescrTag,
- Size: 1,
- Data: []byte{0x02},
- },
- },
- })
- if err != nil {
- return nil, err
- }
-
- case *fmp4.CodecMJPEG: //nolint:dupl
- _, err = w.writeBoxStart(&mp4.VisualSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeMp4v(),
- },
- DataReferenceIndex: 1,
- },
- Width: uint16(width),
- Height: uint16(height),
- Horizresolution: 4718592,
- Vertresolution: 4718592,
- FrameCount: 1,
- Depth: 24,
- PreDefined3: -1,
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.Esds{ //
- Descriptors: []mp4.Descriptor{
- {
- Tag: mp4.ESDescrTag,
- Size: 27,
- ESDescriptor: &mp4.ESDescriptor{
- ESID: uint16(t.ID),
- },
- },
- {
- Tag: mp4.DecoderConfigDescrTag,
- Size: 13,
- DecoderConfigDescriptor: &mp4.DecoderConfigDescriptor{
- ObjectTypeIndication: objectTypeIndicationVisualISO10918part1,
- StreamType: streamTypeVisualStream,
- Reserved: true,
- MaxBitrate: 1000000,
- AvgBitrate: 1000000,
- },
- },
- {
- Tag: mp4.SLConfigDescrTag,
- Size: 1,
- Data: []byte{0x02},
- },
- },
- })
- if err != nil {
- return nil, err
- }
-
- case *fmp4.CodecOpus:
- _, err = w.writeBoxStart(&mp4.AudioSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeOpus(),
- },
- DataReferenceIndex: 1,
- },
- ChannelCount: uint16(codec.ChannelCount),
- SampleSize: 16,
- SampleRate: 48000 * 65536,
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.DOps{ //
- OutputChannelCount: uint8(codec.ChannelCount),
- PreSkip: 312,
- InputSampleRate: 48000,
- })
- if err != nil {
- return nil, err
- }
-
- case *fmp4.CodecMPEG4Audio:
- _, err = w.writeBoxStart(&mp4.AudioSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeMp4a(),
- },
- DataReferenceIndex: 1,
- },
- ChannelCount: uint16(codec.ChannelCount),
- SampleSize: 16,
- SampleRate: uint32(codec.SampleRate * 65536),
- })
- if err != nil {
- return nil, err
- }
-
- enc, _ := codec.Config.Marshal()
-
- _, err = w.writeBox(&mp4.Esds{ //
- Descriptors: []mp4.Descriptor{
- {
- Tag: mp4.ESDescrTag,
- Size: 32 + uint32(len(enc)),
- ESDescriptor: &mp4.ESDescriptor{
- ESID: uint16(t.ID),
- },
- },
- {
- Tag: mp4.DecoderConfigDescrTag,
- Size: 18 + uint32(len(enc)),
- DecoderConfigDescriptor: &mp4.DecoderConfigDescriptor{
- ObjectTypeIndication: objectTypeIndicationAudioISO14496part3,
- StreamType: streamTypeAudioStream,
- Reserved: true,
- MaxBitrate: 128825,
- AvgBitrate: 128825,
- },
- },
- {
- Tag: mp4.DecSpecificInfoTag,
- Size: uint32(len(enc)),
- Data: enc,
- },
- {
- Tag: mp4.SLConfigDescrTag,
- Size: 1,
- Data: []byte{0x02},
- },
- },
- })
- if err != nil {
- return nil, err
- }
-
- case *fmp4.CodecMPEG1Audio:
- _, err = w.writeBoxStart(&mp4.AudioSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeMp4a(),
- },
- DataReferenceIndex: 1,
- },
- ChannelCount: uint16(codec.ChannelCount),
- SampleSize: 16,
- SampleRate: uint32(codec.SampleRate * 65536),
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.Esds{ //
- Descriptors: []mp4.Descriptor{
- {
- Tag: mp4.ESDescrTag,
- Size: 27,
- ESDescriptor: &mp4.ESDescriptor{
- ESID: uint16(t.ID),
- },
- },
- {
- Tag: mp4.DecoderConfigDescrTag,
- Size: 13,
- DecoderConfigDescriptor: &mp4.DecoderConfigDescriptor{
- ObjectTypeIndication: objectTypeIndicationAudioISO11172part3,
- StreamType: streamTypeAudioStream,
- Reserved: true,
- MaxBitrate: 128825,
- AvgBitrate: 128825,
- },
- },
- {
- Tag: mp4.SLConfigDescrTag,
- Size: 1,
- Data: []byte{0x02},
- },
- },
- })
- if err != nil {
- return nil, err
- }
-
- case *fmp4.CodecAC3:
- _, err = w.writeBoxStart(&mp4.AudioSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeAC3(),
- },
- DataReferenceIndex: 1,
- },
- ChannelCount: uint16(codec.ChannelCount),
- SampleSize: 16,
- SampleRate: uint32(codec.SampleRate * 65536),
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.Dac3{ //
- Fscod: codec.Fscod,
- Bsid: codec.Bsid,
- Bsmod: codec.Bsmod,
- Acmod: codec.Acmod,
- LfeOn: func() uint8 {
- if codec.LfeOn {
- return 1
- }
- return 0
- }(),
- BitRateCode: codec.BitRateCode,
- })
- if err != nil {
- return nil, err
- }
-
- case *fmp4.CodecLPCM:
- _, err = w.writeBoxStart(&mp4.AudioSampleEntry{ //
- SampleEntry: mp4.SampleEntry{
- AnyTypeBox: mp4.AnyTypeBox{
- Type: mp4.BoxTypeIpcm(),
- },
- DataReferenceIndex: 1,
- },
- ChannelCount: uint16(codec.ChannelCount),
- SampleSize: uint16(codec.BitDepth), // FFmpeg leaves this to 16 instead of using real bit depth
- SampleRate: uint32(codec.SampleRate * 65536),
- })
- if err != nil {
- return nil, err
- }
-
- _, err = w.writeBox(&mp4.PcmC{ //
- FormatFlags: func() uint8 {
- if codec.LittleEndian {
- return 1
- }
- return 0
- }(),
- PCMSampleSize: uint8(codec.BitDepth),
- })
- if err != nil {
- return nil, err
- }
- }
-
- err = w.writeBoxEnd() // *>
- if err != nil {
- return nil, err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, err
- }
-
- err = t.marshalSTTS(w) //
- if err != nil {
- return nil, err
- }
-
- err = t.marshalSTSS(w) //
- if err != nil {
- return nil, err
- }
-
- err = t.marshalCTTS(w) //
- if err != nil {
- return nil, err
- }
-
- err = t.marshalSTSC(w) //
- if err != nil {
- return nil, err
- }
-
- err = t.marshalSTSZ(w) //
- if err != nil {
- return nil, err
- }
-
- stco, stcoOffset, err := t.marshalSTCO(w) //
- if err != nil {
- return nil, err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, err
- }
-
- err = w.writeBoxEnd() //
- if err != nil {
- return nil, err
- }
-
- return &headerTrackMarshalResult{
- stco: stco,
- stcoOffset: stcoOffset,
- presentationDuration: presentationDuration,
- }, nil
-}
-
-func (t *Track) marshalELST(w *mp4Writer, sampleDuration uint32) error {
- if t.TimeOffset > 0 {
- _, err := w.writeBox(&mp4.Elst{
- EntryCount: 2,
- Entries: []mp4.ElstEntry{
- { // pause
- SegmentDurationV0: uint32((uint64(t.TimeOffset) * globalTimescale) / uint64(t.TimeScale)),
- MediaTimeV0: -1,
- MediaRateInteger: 1,
- MediaRateFraction: 0,
- },
- { // presentation
- SegmentDurationV0: uint32((uint64(sampleDuration) * globalTimescale) / uint64(t.TimeScale)),
- MediaTimeV0: 0,
- MediaRateInteger: 1,
- MediaRateFraction: 0,
- },
- },
- })
- return err
- }
-
- _, err := w.writeBox(&mp4.Elst{
- EntryCount: 1,
- Entries: []mp4.ElstEntry{{
- SegmentDurationV0: uint32(((uint64(sampleDuration) +
- uint64(-t.TimeOffset)) * globalTimescale) / uint64(t.TimeScale)),
- MediaTimeV0: -t.TimeOffset,
- MediaRateInteger: 1,
- MediaRateFraction: 0,
- }},
- })
- return err
-}
-
-func (t *Track) marshalSTTS(w *mp4Writer) error {
- entries := []mp4.SttsEntry{{
- SampleCount: 1,
- SampleDelta: t.Samples[0].Duration,
- }}
-
- for _, sa := range t.Samples[1:] {
- if sa.Duration == entries[len(entries)-1].SampleDelta {
- entries[len(entries)-1].SampleCount++
- } else {
- entries = append(entries, mp4.SttsEntry{
- SampleCount: 1,
- SampleDelta: sa.Duration,
- })
- }
- }
-
- _, err := w.writeBox(&mp4.Stts{
- EntryCount: uint32(len(entries)),
- Entries: entries,
- })
- return err
-}
-
-func (t *Track) marshalSTSS(w *mp4Writer) error {
- if allSamplesAreSync(t.Samples) {
- return nil
- }
-
- var sampleNumbers []uint32
-
- for i, sa := range t.Samples {
- if !sa.IsNonSyncSample {
- sampleNumbers = append(sampleNumbers, uint32(i+1))
- }
- }
-
- _, err := w.writeBox(&mp4.Stss{
- EntryCount: uint32(len(sampleNumbers)),
- SampleNumber: sampleNumbers,
- })
- return err
-}
-
-func (t *Track) marshalCTTS(w *mp4Writer) error {
- entries := []mp4.CttsEntry{{
- SampleCount: 1,
- SampleOffsetV0: uint32(t.Samples[0].PTSOffset),
- }}
-
- for _, sa := range t.Samples[1:] {
- if uint32(sa.PTSOffset) == entries[len(entries)-1].SampleOffsetV0 {
- entries[len(entries)-1].SampleCount++
- } else {
- entries = append(entries, mp4.CttsEntry{
- SampleCount: 1,
- SampleOffsetV0: uint32(sa.PTSOffset),
- })
- }
- }
-
- _, err := w.writeBox(&mp4.Ctts{
- FullBox: mp4.FullBox{
- Version: 0,
- },
- EntryCount: uint32(len(entries)),
- Entries: entries,
- })
- return err
-}
-
-func (t *Track) marshalSTSC(w *mp4Writer) error {
- entries := []mp4.StscEntry{{
- FirstChunk: 1,
- SamplesPerChunk: 1,
- SampleDescriptionIndex: 1,
- }}
-
- firstSample := t.Samples[0]
- off := firstSample.offset + firstSample.PayloadSize
-
- for _, sa := range t.Samples[1:] {
- if sa.offset == off {
- entries[len(entries)-1].SamplesPerChunk++
- } else {
- entries = append(entries, mp4.StscEntry{
- FirstChunk: uint32(len(entries) + 1),
- SamplesPerChunk: 1,
- SampleDescriptionIndex: 1,
- })
- }
-
- off = sa.offset + sa.PayloadSize
- }
-
- // further compression
- for i := len(entries) - 1; i >= 1; i-- {
- if entries[i].SamplesPerChunk == entries[i-1].SamplesPerChunk {
- for j := i; j < len(entries)-1; j++ {
- entries[j] = entries[j+1]
- }
- entries = entries[:len(entries)-1]
- }
- }
-
- _, err := w.writeBox(&mp4.Stsc{
- EntryCount: uint32(len(entries)),
- Entries: entries,
- })
- return err
-}
-
-func (t *Track) marshalSTSZ(w *mp4Writer) error {
- sampleSizes := make([]uint32, len(t.Samples))
-
- for i, sa := range t.Samples {
- sampleSizes[i] = sa.PayloadSize
- }
-
- _, err := w.writeBox(&mp4.Stsz{
- SampleSize: 0,
- SampleCount: uint32(len(sampleSizes)),
- EntrySize: sampleSizes,
- })
- return err
-}
-
-func (t *Track) marshalSTCO(w *mp4Writer) (*mp4.Stco, int, error) {
- firstSample := t.Samples[0]
- off := firstSample.offset + firstSample.PayloadSize
-
- entries := []uint32{firstSample.offset}
-
- for _, sa := range t.Samples[1:] {
- if sa.offset != off {
- entries = append(entries, sa.offset)
- }
- off = sa.offset + sa.PayloadSize
- }
-
- stco := &mp4.Stco{
- EntryCount: uint32(len(entries)),
- ChunkOffset: entries,
- }
-
- offset, err := w.writeBox(stco)
- if err != nil {
- return nil, 0, err
- }
-
- return stco, offset, err
-}
diff --git a/internal/playback/muxer_mp4.go b/internal/playback/muxer_mp4.go
index af905c0b..d7338b22 100644
--- a/internal/playback/muxer_mp4.go
+++ b/internal/playback/muxer_mp4.go
@@ -4,11 +4,11 @@ import (
"io"
"github.com/bluenviron/mediacommon/pkg/formats/fmp4"
- "github.com/bluenviron/mediamtx/internal/playback/mp4"
+ "github.com/bluenviron/mediacommon/pkg/formats/pmp4"
)
type muxerMP4Track struct {
- mp4.Track
+ pmp4.Track
lastDTS int64
}
@@ -33,7 +33,7 @@ func (w *muxerMP4) writeInit(init *fmp4.Init) {
for i, track := range init.Tracks {
w.tracks[i] = &muxerMP4Track{
- Track: mp4.Track{
+ Track: pmp4.Track{
ID: track.ID,
TimeScale: track.TimeScale,
Codec: track.Codec,
@@ -73,7 +73,7 @@ func (w *muxerMP4) writeSample(
ptsOffset = 0
}
- w.curTrack.Samples = append(w.curTrack.Samples, &mp4.Sample{
+ w.curTrack.Samples = append(w.curTrack.Samples, &pmp4.Sample{
PTSOffset: ptsOffset,
IsNonSyncSample: isNonSyncSample,
PayloadSize: payloadSize,
@@ -93,8 +93,8 @@ func (w *muxerMP4) writeFinalDTS(dts int64) {
}
func (w *muxerMP4) flush() error {
- h := mp4.Presentation{
- Tracks: make([]*mp4.Track, len(w.tracks)),
+ h := pmp4.Presentation{
+ Tracks: make([]*pmp4.Track, len(w.tracks)),
}
for i, track := range w.tracks {