Skip to content

Commit

Permalink
sort samples by DTS
Browse files Browse the repository at this point in the history
  • Loading branch information
aler9 committed Apr 9, 2024
1 parent 22a661d commit 9c22059
Show file tree
Hide file tree
Showing 10 changed files with 1,478 additions and 95 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/alecthomas/kong v0.9.0
github.com/bluenviron/gohlslib v1.3.0
github.com/bluenviron/gortsplib/v4 v4.8.0
github.com/bluenviron/mediacommon v1.9.3-0.20240408205450-b2b6c85fb0e0
github.com/bluenviron/mediacommon v1.9.2
github.com/datarhei/gosrt v0.6.0
github.com/fsnotify/fsnotify v1.7.0
github.com/gin-gonic/gin v1.9.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ github.com/bluenviron/gohlslib v1.3.0 h1:I9t1Nba6VJKg5rLoXSzQFPkZZYBUwBqCU2Divp0
github.com/bluenviron/gohlslib v1.3.0/go.mod h1:wD8ysO6HB90d17sxoIQXGHINo2KYj/mZirMnPtKLJZQ=
github.com/bluenviron/gortsplib/v4 v4.8.0 h1:nvFp6rHALcSep3G9uBFI0uogS9stVZLNq/92TzGZdQg=
github.com/bluenviron/gortsplib/v4 v4.8.0/go.mod h1:+d+veuyvhvikUNp0GRQkk6fEbd/DtcXNidMRm7FQRaA=
github.com/bluenviron/mediacommon v1.9.3-0.20240408205450-b2b6c85fb0e0 h1:GkvT15WaWrShOkl+XkYtBWoHaf7hacT/h8qQBh0UFQg=
github.com/bluenviron/mediacommon v1.9.3-0.20240408205450-b2b6c85fb0e0/go.mod h1:0z/KHiSTlaAB8FoyW+mYulZRG70Kupcy/0yZZkgLe5M=
github.com/bluenviron/mediacommon v1.9.2 h1:EHcvoC5YMXRcFE010bTNf07ZiSlB/e/AdZyG7GsEYN0=
github.com/bluenviron/mediacommon v1.9.2/go.mod h1:lt8V+wMyPw8C69HAqDWV5tsAwzN9u2Z+ca8B6C//+n0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
Expand Down
83 changes: 83 additions & 0 deletions internal/playback/mp4/mp4_writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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
}
208 changes: 208 additions & 0 deletions internal/playback/mp4/presentation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// 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{ // <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{}) // <moov>
if err != nil {
return err
}

mvhd := &mp4.Mvhd{ // <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 {
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() // </moov>
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
}
12 changes: 12 additions & 0 deletions internal/playback/mp4/sample.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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
}
Loading

0 comments on commit 9c22059

Please sign in to comment.