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 {