• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Eyevinn / mp4ff / 11690195733

05 Nov 2024 06:19PM UTC coverage: 75.914% (+9.0%) from 66.885%
11690195733

Pull #380

github

tobbee
test: add more unit tests to avc package
Pull Request #380: Refactor and much better test coverage

501 of 623 new or added lines in 19 files covered. (80.42%)

250 existing lines in 25 files now uncovered.

14826 of 19530 relevant lines covered (75.91%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.5
/examples/segmenter/segmenter.go
1
package main
2

3
import (
4
        "fmt"
5
        "io"
6

7
        "github.com/Eyevinn/mp4ff/mp4"
8
)
9

10
// Segmenter - segment the progressive inFIle
11
type Segmenter struct {
12
        inFile *mp4.File
13
        tracks []*Track
14
        nrSegs int //  target number of segments
15
}
16

17
// Track - media track defined by inTrak
18
type Track struct {
19
        trackType string
20
        inTrak    *mp4.TrakBox
21
        timeScale uint32
22
        trackID   uint32 // trackID in segmented output
23
        lang      string
24
        segments  []sampleInterval
25
}
26

27
// NewSegmenter - create a Segmenter from inFile and fill in track information
28
func NewSegmenter(inFile *mp4.File) (*Segmenter, error) {
1✔
29
        if inFile.IsFragmented() {
1✔
NEW
30
                return nil, fmt.Errorf("segmented input file not supported")
×
31
        }
×
32
        s := Segmenter{inFile: inFile}
1✔
33
        traks := inFile.Moov.Traks
1✔
34
        for _, trak := range traks {
2✔
35
                track := &Track{trackType: "", lang: ""}
1✔
36
                switch hdlrType := trak.Mdia.Hdlr.HandlerType; hdlrType {
1✔
37
                case "vide":
1✔
38
                        track.trackType = "video"
1✔
39
                case "soun":
1✔
40
                        track.trackType = "audio"
1✔
41
                default:
×
42
                        return nil, fmt.Errorf("hdlr typpe %q not supported", hdlrType)
×
43
                }
44
                track.lang = trak.Mdia.Mdhd.GetLanguage()
1✔
45
                if trak.Mdia.Elng != nil {
1✔
46
                        track.lang = trak.Mdia.Elng.Language
×
47
                }
×
48
                track.inTrak = trak
1✔
49
                track.timeScale = trak.Mdia.Mdhd.Timescale
1✔
50
                s.tracks = append(s.tracks, track)
1✔
51
        }
52
        return &s, nil
1✔
53
}
54

55
// SetTargetSegmentation - set segment start points
56
func (s *Segmenter) SetTargetSegmentation(syncTimescale uint32, segStarts []syncPoint) error {
1✔
57
        var err error
1✔
58
        for i := range s.tracks {
2✔
59
                s.tracks[i].segments, err = getSegmentIntervals(syncTimescale, segStarts, s.tracks[i].inTrak)
1✔
60
                if err != nil {
1✔
61
                        return err
×
62
                }
×
63
        }
64
        s.nrSegs = len(segStarts)
1✔
65
        return nil
1✔
66
}
67

68
// MakeInitSegments - initialized and return init segments for all the tracks
69
func (s *Segmenter) MakeInitSegments() ([]*mp4.InitSegment, error) {
1✔
70
        var inits []*mp4.InitSegment
1✔
71
        for _, tr := range s.tracks {
2✔
72
                init := mp4.CreateEmptyInit()
1✔
73
                init.Moov.Mvhd.Timescale = s.inFile.Moov.Mvhd.Timescale
1✔
74
                inMovieDuration := s.inFile.Moov.Mvhd.Duration
1✔
75
                init.Moov.Mvex.AddChild(&mp4.MehdBox{FragmentDuration: int64(inMovieDuration)})
1✔
76
                init.AddEmptyTrack(tr.timeScale, tr.trackType, tr.lang)
1✔
77
                outTrak := init.Moov.Trak
1✔
78
                tr.trackID = outTrak.Tkhd.TrackID
1✔
79

1✔
80
                inStsd := tr.inTrak.Mdia.Minf.Stbl.Stsd
1✔
81
                outStsd := outTrak.Mdia.Minf.Stbl.Stsd
1✔
82
                switch tr.trackType {
1✔
83
                case "audio":
1✔
84
                        if inStsd.Mp4a != nil {
2✔
85
                                outStsd.AddChild(inStsd.Mp4a)
1✔
86
                        } else if inStsd.AC3 != nil {
1✔
87
                                outStsd.AddChild(inStsd.AC3)
×
88
                        } else if inStsd.EC3 != nil {
×
89
                                outStsd.AddChild(inStsd.EC3)
×
90
                        }
×
91
                case "video":
1✔
92
                        if inStsd.AvcX != nil {
2✔
93
                                outStsd.AddChild(inStsd.AvcX)
1✔
94
                        } else if inStsd.HvcX != nil {
1✔
95
                                outStsd.AddChild(inStsd.HvcX)
×
96
                        }
×
97
                default:
×
NEW
98
                        return nil, fmt.Errorf("unsupported tracktype: %s", tr.trackType)
×
99
                }
100
                inits = append(inits, init)
1✔
101
        }
102
        return inits, nil
1✔
103
}
104

105
// MakeMuxedInitSegment - initialized and return one init segments for all the tracks
106
func (s *Segmenter) MakeMuxedInitSegment() (*mp4.InitSegment, error) {
1✔
107
        init := mp4.CreateEmptyInit()
1✔
108
        inMovieDuration := s.inFile.Moov.Mvhd.Duration
1✔
109
        init.Moov.Mvex.AddChild(&mp4.MehdBox{FragmentDuration: int64(inMovieDuration)})
1✔
110
        for _, tr := range s.tracks {
2✔
111
                init.AddEmptyTrack(tr.timeScale, tr.trackType, tr.lang)
1✔
112
                outTrak := init.Moov.Traks[len(init.Moov.Traks)-1]
1✔
113
                tr.trackID = outTrak.Tkhd.TrackID
1✔
114
                inStsd := tr.inTrak.Mdia.Minf.Stbl.Stsd
1✔
115
                outStsd := outTrak.Mdia.Minf.Stbl.Stsd
1✔
116
                switch tr.trackType {
1✔
117
                case "audio":
1✔
118
                        if inStsd.Mp4a != nil {
2✔
119
                                outStsd.AddChild(inStsd.Mp4a)
1✔
120
                        } else if inStsd.AC3 != nil {
1✔
121
                                outStsd.AddChild(inStsd.AC3)
×
122
                        } else if inStsd.EC3 != nil {
×
123
                                outStsd.AddChild(inStsd.EC3)
×
124
                        }
×
125
                case "video":
1✔
126
                        if inStsd.AvcX != nil {
2✔
127
                                outStsd.AddChild(inStsd.AvcX)
1✔
128
                        } else if inStsd.HvcX != nil {
1✔
129
                                outStsd.AddChild(inStsd.HvcX)
×
130
                        }
×
131
                default:
×
NEW
132
                        return nil, fmt.Errorf("unsupported tracktype: %s", tr.trackType)
×
133
                }
134
        }
135

136
        return init, nil
1✔
137
}
138

139
// GetFullSamplesForInterval - get slice of fullsamples with numbers startSampleNr to endSampleNr (inclusive)
140
func (s *Segmenter) GetFullSamplesForInterval(mp4f *mp4.File, tr *Track, startSampleNr, endSampleNr uint32, rs io.ReadSeeker) ([]mp4.FullSample, error) {
1✔
141
        stbl := tr.inTrak.Mdia.Minf.Stbl
1✔
142
        samples := make([]mp4.FullSample, 0, endSampleNr-startSampleNr+1)
1✔
143
        mdat := mp4f.Mdat
1✔
144
        mdatPayloadStart := mdat.PayloadAbsoluteOffset()
1✔
145
        for sampleNr := startSampleNr; sampleNr <= endSampleNr; sampleNr++ {
2✔
146
                chunkNr, sampleNrAtChunkStart, err := stbl.Stsc.ChunkNrFromSampleNr(int(sampleNr))
1✔
147
                if err != nil {
1✔
148
                        return nil, err
×
149
                }
×
150
                var offset int64
1✔
151
                if stbl.Stco != nil {
2✔
152
                        offset = int64(stbl.Stco.ChunkOffset[chunkNr-1])
1✔
153
                } else if stbl.Co64 != nil {
1✔
154
                        offset = int64(stbl.Co64.ChunkOffset[chunkNr-1])
×
155
                }
×
156

157
                for sNr := sampleNrAtChunkStart; sNr < int(sampleNr); sNr++ {
2✔
158
                        offset += int64(stbl.Stsz.GetSampleSize(sNr))
1✔
159
                }
1✔
160
                size := stbl.Stsz.GetSampleSize(int(sampleNr))
1✔
161
                decTime, dur := stbl.Stts.GetDecodeTime(sampleNr)
1✔
162
                var cto int32 = 0
1✔
163
                if stbl.Ctts != nil {
2✔
164
                        cto = stbl.Ctts.GetCompositionTimeOffset(sampleNr)
1✔
165
                }
1✔
166
                var sampleData []byte
1✔
167
                // Next find bytes as slice in mdat
1✔
168
                if mdat.GetLazyDataSize() > 0 {
1✔
169
                        _, err := rs.Seek(offset, io.SeekStart)
×
170
                        if err != nil {
×
171
                                return nil, err
×
172
                        }
×
173
                        sampleData = make([]byte, size)
×
174
                        _, err = io.ReadFull(rs, sampleData)
×
175
                        if err != nil {
×
176
                                return nil, err
×
177
                        }
×
178
                } else {
1✔
179
                        offsetInMdatData := uint64(offset) - mdatPayloadStart
1✔
180
                        sampleData = mdat.Data[offsetInMdatData : offsetInMdatData+uint64(size)]
1✔
181
                }
1✔
182

183
                //presTime := uint64(int64(decTime) + int64(cto))
184
                //One can either segment on presentationTime or DecodeTime
185
                //presTimeMs := presTime * 1000 / uint64(tr.timeScale)
186
                sc := mp4.FullSample{
1✔
187
                        Sample: mp4.Sample{
1✔
188
                                Flags:                 TranslateSampleFlagsForFragment(stbl, sampleNr),
1✔
189
                                Size:                  size,
1✔
190
                                Dur:                   dur,
1✔
191
                                CompositionTimeOffset: cto,
1✔
192
                        },
1✔
193
                        DecodeTime: decTime,
1✔
194
                        Data:       sampleData,
1✔
195
                }
1✔
196

1✔
197
                //fmt.Printf("Sample %d times %d %d, sync %v, offset %d, size %d\n", sampleNr, decTime, cto, isSync, offset, size)
1✔
198
                samples = append(samples, sc)
1✔
199
        }
200
        return samples, nil
1✔
201
}
202

203
// GetSamplesForInterval - get slice of samples with numbers startSampleNr to endSampleNr (inclusive)
204
func (s *Segmenter) GetSamplesForInterval(mp4f *mp4.File, trak *mp4.TrakBox, startSampleNr, endSampleNr uint32) ([]mp4.Sample, error) {
1✔
205
        stbl := trak.Mdia.Minf.Stbl
1✔
206
        samples := make([]mp4.Sample, 0, endSampleNr-startSampleNr+1)
1✔
207
        for sampleNr := startSampleNr; sampleNr <= endSampleNr; sampleNr++ {
2✔
208
                size := stbl.Stsz.GetSampleSize(int(sampleNr))
1✔
209
                dur := stbl.Stts.GetDur(sampleNr)
1✔
210
                var cto int32 = 0
1✔
211
                if stbl.Ctts != nil {
2✔
212
                        cto = stbl.Ctts.GetCompositionTimeOffset(sampleNr)
1✔
213
                }
1✔
214

215
                //presTime := uint64(int64(decTime) + int64(cto))
216
                //One can either segment on presentationTime or DecodeTime
217
                //presTimeMs := presTime * 1000 / uint64(trak.timeScale)
218
                sc := mp4.Sample{
1✔
219
                        Flags:                 TranslateSampleFlagsForFragment(stbl, sampleNr),
1✔
220
                        Size:                  size,
1✔
221
                        Dur:                   dur,
1✔
222
                        CompositionTimeOffset: cto,
1✔
223
                }
1✔
224

1✔
225
                //fmt.Printf("Sample %d times %d %d, sync %v, offset %d, size %d\n", sampleNr, decTime, cto, isSync, offset, size)
1✔
226
                samples = append(samples, sc)
1✔
227
        }
228
        return samples, nil
1✔
229
}
230

231
// TranslateSampleFlagsForFragment - translate sample flags from stss and sdtp to what is needed in trun
232
func TranslateSampleFlagsForFragment(stbl *mp4.StblBox, sampleNr uint32) (flags uint32) {
1✔
233
        var sampleFlags mp4.SampleFlags
1✔
234
        if stbl.Stss != nil {
2✔
235
                isSync := stbl.Stss.IsSyncSample(uint32(sampleNr))
1✔
236
                sampleFlags.SampleIsNonSync = !isSync
1✔
237
                if isSync {
2✔
238
                        sampleFlags.SampleDependsOn = 2 //2 == does not depend on others (I-picture). May be overridden by sdtp entry
1✔
239
                }
1✔
240
        }
241
        if stbl.Sdtp != nil {
1✔
242
                entry := stbl.Sdtp.Entries[uint32(sampleNr)-1] // table starts at 0, but sampleNr is one-based
×
243
                sampleFlags.IsLeading = entry.IsLeading()
×
244
                sampleFlags.SampleDependsOn = entry.SampleDependsOn()
×
245
                sampleFlags.SampleHasRedundancy = entry.SampleHasRedundancy()
×
246
                sampleFlags.SampleIsDependedOn = entry.SampleIsDependedOn()
×
247
        }
×
248
        return sampleFlags.Encode()
1✔
249
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc