Skip to content

Commit

Permalink
wip: add and update sidx
Browse files Browse the repository at this point in the history
  • Loading branch information
tobbee committed Feb 13, 2024
1 parent 011bf5d commit 7da3674
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 0 deletions.
202 changes: 202 additions & 0 deletions examples/add-sidx/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// add-sidx adds a top-level sidx box describing the segments of a fragmented files.
//
// Segments are identified by styp boxes if they exist, otherwise by
// the start of moof or emsg boxes.
package main

import (
"flag"
"fmt"
"log"
"os"

"github.com/Eyevinn/mp4ff/mp4"
)

func main() {

usePTO := flag.Bool("nzept", false, "Use non-zero earliestPresentationTime")
flag.Parse()
args := flag.Args()
if len(args) != 2 {
fmt.Println("Usage: add-sidx <input.mp4> <output.mp4>")
return
}

err := run(args[0], args[1], *usePTO)
if err != nil {
log.Fatal(err)
}
}

func run(inPath, outPath string, nonZeroEPT bool) error {
inFile, err := mp4.ReadMP4File(inPath)
if err != nil {
return err
}

err = updateSidx(inFile, nonZeroEPT)
if err != nil {
return fmt.Errorf("addSidx failed: %w", err)
}

w, err := os.Create(outPath)
if err != nil {
return fmt.Errorf("cannot create output file: %w", err)
}
defer w.Close()
err = inFile.Encode(w)
if err != nil {
return fmt.Errorf("failed to encode output file: %w", err)
}
return nil
}

type segData struct {
startPos uint64
presentationTime uint64
baseDecodeTime uint64
dur uint32
size uint32
}

// updateSidx updates or adds a top-level sidx box to a fragmented mp4 file.
func updateSidx(inFile *mp4.File, nonZeroEPT bool) error {

if !inFile.IsFragmented() {
return fmt.Errorf("input file is not fragmented")
}

initSeg := inFile.Init
if initSeg == nil {
return fmt.Errorf("input file does not have an init segment")
}

segs := inFile.Segments
if len(segs) == 0 {
return fmt.Errorf("input file does not have any media segments")
}
update := inFile.Sidx != nil

refTrak := findReferenceTrak(initSeg)
trex, ok := initSeg.Moov.Mvex.GetTrex(refTrak.Tkhd.TrackID)
if !ok {
return fmt.Errorf("no trex box found for track %d", refTrak.Tkhd.TrackID)
}
segDatas, err := findSegmentData(segs, refTrak, trex)
if err != nil {
return fmt.Errorf("failed to find segment data: %w", err)
}

var sidx *mp4.SidxBox
if update {
sidx = inFile.Sidx
} else {
sidx = &mp4.SidxBox{}
}
fillSidx(sidx, refTrak, segDatas, nonZeroEPT)
if !update {
err = insertSidx(inFile, segDatas, sidx)
if err != nil {
return fmt.Errorf("failed to insert sidx box: %w", err)
}
}
return nil
}

func findReferenceTrak(initSeg *mp4.InitSegment) *mp4.TrakBox {
var trak *mp4.TrakBox
for _, trak = range initSeg.Moov.Traks {
if trak.Mdia.Hdlr.HandlerType == "vide" {
return trak
}
}
for _, trak = range initSeg.Moov.Traks {
if trak.Mdia.Hdlr.HandlerType == "soun" {
return trak
}
}
return initSeg.Moov.Traks[0]
}

func findSegmentData(segs []*mp4.MediaSegment, refTrak *mp4.TrakBox, trex *mp4.TrexBox) ([]segData, error) {
segDatas := make([]segData, 0, len(segs))
for _, seg := range segs {
frag := seg.Fragments[0]
for _, traf := range frag.Moof.Trafs {
tfhd := traf.Tfhd
if tfhd.TrackID == refTrak.Tkhd.TrackID {
// Found the track that the sidx should be based on
baseTime := traf.Tfdt.BaseMediaDecodeTime()
dur := uint32(0)
var firstCompositionTimeOffest int64
for i, trun := range traf.Truns {
trun.AddSampleDefaultValues(tfhd, trex)
samples := trun.GetSamples()
for j, sample := range samples {
if i == 0 && j == 0 {
firstCompositionTimeOffest = int64(sample.CompositionTimeOffset)
}
dur += sample.Dur
}
}
sd := segData{
startPos: seg.StartPos,
presentationTime: uint64(int64(baseTime) + firstCompositionTimeOffest),
baseDecodeTime: baseTime,
dur: dur,
size: uint32(seg.Size()),
}
segDatas = append(segDatas, sd)
break
}
}
}
return segDatas, nil
}

func fillSidx(sidx *mp4.SidxBox, refTrak *mp4.TrakBox, segDatas []segData, nonZeroEPT bool) {
ept := uint64(0)
if nonZeroEPT {
ept = segDatas[0].presentationTime
}
sidx.Version = 1
sidx.Timescale = refTrak.Mdia.Mdhd.Timescale
sidx.ReferenceID = 1
sidx.EarliestPresentationTime = ept
sidx.FirstOffset = 0
sidx.SidxRefs = make([]mp4.SidxRef, 0, len(segDatas))

for _, segData := range segDatas {
size := segData.size
sidx.SidxRefs = append(sidx.SidxRefs, mp4.SidxRef{
ReferencedSize: size,
SubSegmentDuration: segData.dur,
StartsWithSAP: 1,
SAPType: 1,
})
}
}

func insertSidx(inFile *mp4.File, segDatas []segData, sidx *mp4.SidxBox) error {
// insert sidx box before first media segment
// TODO. Handle case where startPos is not reliable. Maybe first box of first segment
firstMediaBox, err := inFile.Segments[0].FirstBox()
if err != nil {
return fmt.Errorf("could not find position to insert sidx box: %w", err)
}
var mediaStartIdx = 0
for i, ch := range inFile.Children {
if ch == firstMediaBox {
mediaStartIdx = i
break
}
}
if mediaStartIdx == 0 {
return fmt.Errorf("could not find position to insert sidx box")
}
inFile.Children = append(inFile.Children[:mediaStartIdx], append([]mp4.Box{sidx}, inFile.Children[mediaStartIdx:]...)...)
inFile.Sidx = sidx
inFile.Sidxs = []*mp4.SidxBox{sidx}
return nil
}
23 changes: 23 additions & 0 deletions examples/add-sidx/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"testing"

"github.com/Eyevinn/mp4ff/mp4"
)

func TestAddSidx(t *testing.T) {
sidxOut := "testV300_sidx.mp4"
inPath := "../resegmenter/testdata/testV300.mp4"
err := run(inPath, sidxOut, false)
if err != nil {
t.Error(err)
}
reRead, err := mp4.ReadMP4File(sidxOut)
if err != nil {
t.Error(err)
}
if reRead.Sidx == nil {
t.Error("No sidx box added")
}
}
16 changes: 16 additions & 0 deletions mp4/mediasegment.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,19 @@ func (s *MediaSegment) CommonSampleDuration(trex *TrexBox) (uint32, error) {
}
return commonDur, nil
}

// FirstBox returns the first box in the segment, or an error if no boxes are found.
func (s *MediaSegment) FirstBox() (Box, error) {
if s.Styp != nil {
return s.Styp, nil
}
if len(s.Sidxs) > 0 {
return s.Sidxs[0], nil
}
if len(s.Fragments) > 0 {
if len(s.Fragments[0].Children) > 0 {
return s.Fragments[0].Children[0], nil
}
}
return nil, fmt.Errorf("no boxes in segment")
}

0 comments on commit 7da3674

Please sign in to comment.