-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathencode.go
More file actions
286 lines (267 loc) · 8 KB
/
Copy pathencode.go
File metadata and controls
286 lines (267 loc) · 8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
package locmaf
import (
"fmt"
"sort"
"github.com/Eyevinn/locmaf/vi64"
"github.com/Eyevinn/mp4ff/mp4"
)
// EncodeCanonical encodes one CMAF chunk as a canonical LOCMAF Object:
// the genBox elements, one full or delta header, and the mdat payload.
//
// The header type follows the canonical rule: a full header when prev
// holds no in-group reference (the first chunk of a group) or when the
// source BMDT diverges from the delta derivation (a timeline
// discontinuity re-anchors with a full header); a delta header
// otherwise. prev is mutated to reflect the just-emitted chunk; it must
// not be nil.
func EncodeCanonical(genBoxes []GenBox, moof *mp4.MoofBox, mdatPayload []byte,
prev *State, moov *mp4.MoovBox) ([]byte, error) {
if prev == nil {
return nil, fmt.Errorf("prev state must not be nil: %w", ErrBadSource)
}
sv, err := extractSourceValues(moof, moov)
if err != nil {
return nil, err
}
// The receiver derives sizes from the mdat-payload length (the last
// listed size, the n×size check, the n == 1 rule), so a source whose
// sample sizes do not cover the payload exactly cannot round-trip.
var sizeSum uint64
for _, s := range sv.sizes {
sizeSum += uint64(s)
}
if sizeSum != uint64(len(mdatPayload)) {
return nil, fmt.Errorf("source sample sizes sum to %d but the mdat payload is %d bytes: %w",
sizeSum, len(mdatPayload), ErrBadSource)
}
cf, err := emitFields(sv, moov)
if err != nil {
return nil, err
}
full := !prev.hasAny
if !full {
derived, ok := prev.deriveNextBMDT(moov.Mvex.Trex.DefaultSampleDuration)
if !ok || derived != sv.bmdt {
full = true // timeline discontinuity: re-anchor with a full header
}
}
var elementType uint64
var props []byte
if full {
elementType = ElementTypeFullHeader
props = encodeFullProperties(cf)
} else {
elementType = ElementTypeDeltaHeader
props = encodeDeltaProperties(cf, prev)
}
out := make([]byte, 0, len(props)+len(mdatPayload)+64)
for _, gb := range genBoxes {
if len(gb.Name) != 4 {
return nil, fmt.Errorf("genBox name %q is not a FourCC: %w", gb.Name, ErrBadSource)
}
boxSize := uint64(4 + len(gb.Payload))
if boxSize > 0xFFFFFFFB {
return nil, fmt.Errorf("genBox %q payload too large: %w", gb.Name, ErrBadSource)
}
out = vi64.Append(out, ElementTypeGenBox)
out = vi64.Append(out, boxSize)
out = append(out, gb.Name...)
out = append(out, gb.Payload...)
}
out = vi64.Append(out, elementType)
out = vi64.Append(out, uint64(len(props)))
out = append(out, props...)
out = append(out, mdatPayload...)
prev.store(cf)
return out, nil
}
// EncodeRaw encodes complete ISO BMFF boxes, carried verbatim, as a
// rawBoxes LOCMAF Object — the escape from the moof-header model for
// content LOCMAF does not otherwise carry: an in-band CMAF Header
// (ftyp + moov) in self-framed carriage, or a chunk whose moof falls
// outside the LOCMAF field model. boxes must be one or more complete
// boxes, each carrying its actual size in the 32-bit size field (the
// ISO size escapes 0 and 1 are not allowed). The element carries no
// length of its own — the Object length delimits it. A rawBoxes Object
// resets the in-group delta chain, so prev is Reset and the next
// EncodeCanonical chunk in the group emits a full header; prev must not
// be nil.
func EncodeRaw(boxes []byte, prev *State) ([]byte, error) {
if prev == nil {
return nil, fmt.Errorf("prev state must not be nil: %w", ErrBadSource)
}
if err := validateRawBoxes(boxes, ErrBadSource); err != nil {
return nil, err
}
out := make([]byte, 0, len(boxes)+1)
out = vi64.Append(out, ElementTypeRawBoxes)
out = append(out, boxes...)
prev.Reset()
return out, nil
}
// propertyEntry is one (field_id, payload) tuple ready for framing.
type propertyEntry struct {
id fieldID
payload []byte
}
// frameProperties serializes entries in ascending field-ID order with
// the parity-rule framing (scalars bare, odd IDs length-prefixed).
func frameProperties(entries []propertyEntry) []byte {
sort.Slice(entries, func(i, j int) bool { return entries[i].id < entries[j].id })
var out []byte
for _, e := range entries {
out = vi64.Append(out, uint64(e.id))
if e.id.isList() {
out = vi64.Append(out, uint64(len(e.payload)))
}
out = append(out, e.payload...)
}
return out
}
// encodeFullProperties emits every represented field with its absolute
// value encoding.
func encodeFullProperties(cf *chunkFields) []byte {
var entries []propertyEntry
for id, v := range cf.scalars {
entries = append(entries, propertyEntry{id, vi64.Append(nil, v)})
}
for id, list := range cf.lists {
var payload []byte
for _, v := range list {
payload = vi64.Append(payload, v)
}
entries = append(entries, propertyEntry{id, payload})
}
for id, list := range cf.signedLists {
var payload []byte
for _, v := range list {
payload = vi64.AppendZigzag(payload, v)
}
entries = append(entries, propertyEntry{id, payload})
}
for id, blob := range cf.rawBlobs {
entries = append(entries, propertyEntry{id, append([]byte(nil), blob...)})
}
return frameProperties(entries)
}
// encodeDeltaProperties emits exactly the fields whose represented
// values changed from the in-group reference, plus the deletion marker
// for fields that left it. Scalar and list elements are zigzag deltas
// (missing previous entries count as 0); raw bytes overwrite.
func encodeDeltaProperties(cf *chunkFields, prev *State) []byte {
var entries []propertyEntry
for id, v := range cf.scalars {
prevV, prevHas := prev.scalars[id]
if prevHas && prevV == v {
continue
}
if id == idTfdtBaseMediaDecodeTime {
// Full-header-only: a delta chunk's BMDT is always derived.
// EncodeCanonical only reaches here when the derivation
// matches, so there is never a delta to emit.
continue
}
entries = append(entries, propertyEntry{id, vi64.AppendZigzag(nil, int64(v)-int64(prevV))})
}
for id, list := range cf.lists {
prevList := prev.lists[id]
if equalU64(list, prevList) {
continue
}
var payload []byte
for i, v := range list {
var p uint64
if i < len(prevList) {
p = prevList[i]
}
payload = vi64.AppendZigzag(payload, int64(v)-int64(p))
}
entries = append(entries, propertyEntry{id, payload})
}
for id, list := range cf.signedLists {
prevList := prev.signedLists[id]
if equalI64(list, prevList) {
continue
}
var payload []byte
for i, v := range list {
var p int64
if i < len(prevList) {
p = prevList[i]
}
payload = vi64.AppendZigzag(payload, v-p)
}
entries = append(entries, propertyEntry{id, payload})
}
for id, blob := range cf.rawBlobs {
if equalByteSlices(blob, prev.rawBlobs[id]) {
continue
}
entries = append(entries, propertyEntry{id, append([]byte(nil), blob...)})
}
// Deletion marker: fields present in prev but absent now, as plain
// unsigned vi64 values in ascending order.
var deleted []uint64
for id := range prev.scalars {
if _, ok := cf.scalars[id]; !ok {
deleted = append(deleted, uint64(id))
}
}
for id := range prev.lists {
if _, ok := cf.lists[id]; !ok {
deleted = append(deleted, uint64(id))
}
}
for id := range prev.signedLists {
if _, ok := cf.signedLists[id]; !ok {
deleted = append(deleted, uint64(id))
}
}
for id := range prev.rawBlobs {
if _, ok := cf.rawBlobs[id]; !ok {
deleted = append(deleted, uint64(id))
}
}
if len(deleted) > 0 {
sort.Slice(deleted, func(i, j int) bool { return deleted[i] < deleted[j] })
var payload []byte
for _, id := range deleted {
payload = vi64.Append(payload, id)
}
entries = append(entries, propertyEntry{idDeltaDeletedLocmafIDs, payload})
}
return frameProperties(entries)
}
func equalU64(a, b []uint64) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func equalI64(a, b []int64) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func equalByteSlices(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}