-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
241 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters