@audio/decode-aac 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/decode-aac.js +78 -157
- package/package.json +1 -1
package/decode-aac.js
CHANGED
|
@@ -61,7 +61,9 @@ class AACDecoder {
|
|
|
61
61
|
this._ptr = 0
|
|
62
62
|
this._cap = 0
|
|
63
63
|
this._left = null
|
|
64
|
-
this.
|
|
64
|
+
this._fileOff = 0 // absolute file offset of _left[0] (M4A streaming)
|
|
65
|
+
this._skip = 0 // bytes to discard from incoming data (M4A streaming, when next frame is past _left)
|
|
66
|
+
this._m4a = null // M4A streaming iterator: { sizes, stco, stsc, idx, ci, sInC, spc, nextOff }
|
|
65
67
|
this._accum = null // Uint8Array[] — M4A header accumulator
|
|
66
68
|
this._accumLen = 0
|
|
67
69
|
}
|
|
@@ -72,36 +74,18 @@ class AACDecoder {
|
|
|
72
74
|
|
|
73
75
|
let buf = data instanceof Uint8Array ? data : new Uint8Array(data)
|
|
74
76
|
|
|
75
|
-
// M4A streming phase 2: extract frames by known sizes
|
|
76
77
|
if (this._m4a) return this._feedM4AData(buf)
|
|
77
|
-
|
|
78
|
-
// M4A accumulating: waiting for moov + mdat header
|
|
79
|
-
if (this._accum) {
|
|
80
|
-
this._accum.push(buf)
|
|
81
|
-
this._accumLen += buf.length
|
|
82
|
-
return this._tryM4AInit()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ADTS mode (already initialized)
|
|
86
78
|
if (this.h) return this._decodeADTS(buf)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
this.
|
|
91
|
-
this._accumLen = buf.length
|
|
79
|
+
// M4A: accumulating moov+mdat header, or first chunk starts with ftyp
|
|
80
|
+
if (this._accum || (buf.length > 8 && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70)) {
|
|
81
|
+
(this._accum ??= []).push(buf)
|
|
82
|
+
this._accumLen += buf.length
|
|
92
83
|
return this._tryM4AInit()
|
|
93
84
|
}
|
|
94
|
-
|
|
95
85
|
return this._decodeADTS(buf)
|
|
96
86
|
}
|
|
97
87
|
|
|
98
88
|
flush() {
|
|
99
|
-
if (this._accum?.length) {
|
|
100
|
-
// M4A moov-last: one-shot decode on accumulated data
|
|
101
|
-
let buf = this._catAccum()
|
|
102
|
-
this._accum = null; this._accumLen = 0
|
|
103
|
-
return this._decodeM4A(buf)
|
|
104
|
-
}
|
|
105
89
|
this._left = null
|
|
106
90
|
return EMPTY
|
|
107
91
|
}
|
|
@@ -121,6 +105,7 @@ class AACDecoder {
|
|
|
121
105
|
}
|
|
122
106
|
this._accum = null; this._accumLen = 0
|
|
123
107
|
this._m4a = null; this._left = null
|
|
108
|
+
this._fileOff = 0; this._skip = 0
|
|
124
109
|
}
|
|
125
110
|
|
|
126
111
|
_catAccum() {
|
|
@@ -133,18 +118,16 @@ class AACDecoder {
|
|
|
133
118
|
_tryM4AInit() {
|
|
134
119
|
let buf = this._catAccum()
|
|
135
120
|
let asc = null, stsz = null, stco = null, stsc = null
|
|
136
|
-
let mdatOff = 0, mdatLen = 0
|
|
137
121
|
|
|
138
|
-
parseBoxes(buf, 0, buf.length, (type, data
|
|
122
|
+
parseBoxes(buf, 0, buf.length, (type, data) => {
|
|
139
123
|
if (type === 'esds') asc = parseEsds(data)
|
|
140
124
|
else if (type === 'stsz') stsz = parseStsz(data)
|
|
141
125
|
else if (type === 'stco') stco = parseStco(data)
|
|
142
126
|
else if (type === 'co64') stco = parseCo64(data)
|
|
143
127
|
else if (type === 'stsc') stsc = parseStsc(data)
|
|
144
|
-
else if (type === 'mdat') { mdatOff = off; mdatLen = data.length }
|
|
145
128
|
})
|
|
146
129
|
|
|
147
|
-
if (!asc) return EMPTY // moov not
|
|
130
|
+
if (!asc || !stsz || !stco?.length) return EMPTY // moov/tables not ready
|
|
148
131
|
|
|
149
132
|
// Init WASM decoder with ASC
|
|
150
133
|
let m = this.m, h = m._aac_create()
|
|
@@ -158,47 +141,52 @@ class AACDecoder {
|
|
|
158
141
|
if (!this.ch) { m._aac_close(h); throw Error('M4A init: no channels in ASC') }
|
|
159
142
|
this.h = h
|
|
160
143
|
|
|
161
|
-
//
|
|
162
|
-
let frames = (stsz && stco)
|
|
163
|
-
? extractFrames(buf, stsz, stco, stsc)
|
|
164
|
-
: mdatLen ? scanMdat(buf, mdatOff, mdatLen) : []
|
|
165
|
-
|
|
166
|
-
let decodedIdx = frames.length
|
|
167
|
-
this._m4a = { sizes: stsz || [], idx: decodedIdx }
|
|
144
|
+
// Streaming: walk sample tables by absolute file offset so chunk boundaries are irrelevant.
|
|
168
145
|
this._accum = null; this._accumLen = 0
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
let dataStart = stco[0] + consumed
|
|
175
|
-
if (dataStart < buf.length) this._left = buf.subarray(dataStart).slice()
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (!frames.length) return EMPTY
|
|
179
|
-
return this._feedFrames(frames)
|
|
146
|
+
this._m4a = { sizes: stsz, stco, stsc, idx: 0, ci: 0, sInC: 0, spc: spcAt(0, stsc), nextOff: stco[0] }
|
|
147
|
+
this._left = buf
|
|
148
|
+
this._fileOff = 0
|
|
149
|
+
this._skip = 0
|
|
150
|
+
return this._extractM4A()
|
|
180
151
|
}
|
|
181
152
|
|
|
182
153
|
_feedM4AData(buf) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
buf =
|
|
154
|
+
if (this._skip > 0) {
|
|
155
|
+
let n = Math.min(this._skip, buf.length)
|
|
156
|
+
this._skip -= n
|
|
157
|
+
this._fileOff += n
|
|
158
|
+
buf = buf.subarray(n)
|
|
159
|
+
if (!buf.length) return EMPTY
|
|
188
160
|
}
|
|
161
|
+
this._left = append(this._left, buf)
|
|
162
|
+
return this._extractM4A()
|
|
163
|
+
}
|
|
189
164
|
|
|
190
|
-
|
|
165
|
+
_extractM4A() {
|
|
166
|
+
let st = this._m4a, frames = []
|
|
191
167
|
while (st.idx < st.sizes.length) {
|
|
192
|
-
let sz = st.sizes[st.idx]
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
st
|
|
168
|
+
let off = st.nextOff, sz = st.sizes[st.idx]
|
|
169
|
+
let bufOff = off - this._fileOff
|
|
170
|
+
if (bufOff + sz > this._left.length) break
|
|
171
|
+
if (bufOff >= 0) frames.push(this._left.subarray(bufOff, bufOff + sz))
|
|
172
|
+
advanceM4A(st)
|
|
197
173
|
}
|
|
198
174
|
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
175
|
+
if (st.idx < st.sizes.length) {
|
|
176
|
+
let nextOff = st.nextOff, end = this._fileOff + this._left.length
|
|
177
|
+
if (nextOff >= end) {
|
|
178
|
+
this._skip = nextOff - end
|
|
179
|
+
this._fileOff = end
|
|
180
|
+
this._left = null
|
|
181
|
+
} else if (nextOff > this._fileOff) {
|
|
182
|
+
this._left = this._left.subarray(nextOff - this._fileOff).slice()
|
|
183
|
+
this._fileOff = nextOff
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
this._left = null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return frames.length ? this._feedFrames(frames) : EMPTY
|
|
202
190
|
}
|
|
203
191
|
|
|
204
192
|
_alloc(len) {
|
|
@@ -213,14 +201,7 @@ class AACDecoder {
|
|
|
213
201
|
_decodeADTS(buf) {
|
|
214
202
|
let m = this.m
|
|
215
203
|
|
|
216
|
-
|
|
217
|
-
if (this._left) {
|
|
218
|
-
let merged = new Uint8Array(this._left.length + buf.length)
|
|
219
|
-
merged.set(this._left)
|
|
220
|
-
merged.set(buf, this._left.length)
|
|
221
|
-
buf = merged
|
|
222
|
-
this._left = null
|
|
223
|
-
}
|
|
204
|
+
if (this._left) { buf = append(this._left, buf); this._left = null }
|
|
224
205
|
|
|
225
206
|
if (!this.h) {
|
|
226
207
|
if (buf.length < 7) { this._left = buf.slice(); return EMPTY }
|
|
@@ -261,27 +242,6 @@ class AACDecoder {
|
|
|
261
242
|
return this._feedFrames(frames)
|
|
262
243
|
}
|
|
263
244
|
|
|
264
|
-
_decodeM4A(buf) {
|
|
265
|
-
let { asc, frames } = demuxM4A(buf)
|
|
266
|
-
if (!asc || !frames.length) return EMPTY
|
|
267
|
-
|
|
268
|
-
let m = this.m
|
|
269
|
-
let h = m._aac_create()
|
|
270
|
-
|
|
271
|
-
let srP = m._aac_sr_ptr(), chP = m._aac_ch_ptr()
|
|
272
|
-
let ptr = this._alloc(asc.length)
|
|
273
|
-
m.HEAPU8.set(asc, ptr)
|
|
274
|
-
let err = m._aac_init2(h, ptr, asc.length, srP, chP)
|
|
275
|
-
if (err < 0) { m._aac_close(h); throw Error('M4A init failed (code ' + err + ')') }
|
|
276
|
-
|
|
277
|
-
this.sr = m.getValue(srP, 'i32')
|
|
278
|
-
this.ch = m.getValue(chP, 'i8')
|
|
279
|
-
if (!this.ch) { m._aac_close(h); throw Error('M4A init: no channels in ASC') }
|
|
280
|
-
this.h = h
|
|
281
|
-
|
|
282
|
-
return this._feedFrames(frames)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
245
|
_feedFrames(frames) {
|
|
286
246
|
let m = this.m, h = this.h
|
|
287
247
|
let chunks = [], totalPerCh = 0, channels = this.ch, errors = 0
|
|
@@ -322,26 +282,11 @@ class AACDecoder {
|
|
|
322
282
|
|
|
323
283
|
// ===== M4A demuxer =====
|
|
324
284
|
|
|
325
|
-
function
|
|
326
|
-
|
|
327
|
-
let
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (type === 'esds') asc = parseEsds(data)
|
|
331
|
-
else if (type === 'stsz') stsz = parseStsz(data)
|
|
332
|
-
else if (type === 'stco') stco = parseStco(data)
|
|
333
|
-
else if (type === 'co64') stco = parseCo64(data)
|
|
334
|
-
else if (type === 'stsc') stsc = parseStsc(data)
|
|
335
|
-
else if (type === 'mdat') { mdatOff = off; mdatLen = data.length }
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
if (!asc) return { asc: null, frames: [] }
|
|
339
|
-
|
|
340
|
-
let frames = (stsz && stco)
|
|
341
|
-
? extractFrames(buf, stsz, stco, stsc)
|
|
342
|
-
: mdatLen ? scanMdat(buf, mdatOff, mdatLen) : []
|
|
343
|
-
|
|
344
|
-
return { asc, frames }
|
|
285
|
+
function append(left, buf) {
|
|
286
|
+
if (!left?.length) return buf.slice()
|
|
287
|
+
let merged = new Uint8Array(left.length + buf.length)
|
|
288
|
+
merged.set(left); merged.set(buf, left.length)
|
|
289
|
+
return merged
|
|
345
290
|
}
|
|
346
291
|
|
|
347
292
|
const CONTAINERS = new Set(['moov', 'trak', 'mdia', 'minf', 'stbl', 'udta', 'meta', 'edts', 'sinf'])
|
|
@@ -352,28 +297,21 @@ function parseBoxes(buf, start, end, cb) {
|
|
|
352
297
|
let size = r32(buf, off)
|
|
353
298
|
let type = String.fromCharCode(buf[off + 4], buf[off + 5], buf[off + 6], buf[off + 7])
|
|
354
299
|
|
|
355
|
-
if (size === 0)
|
|
356
|
-
|
|
357
|
-
} else if (size === 1 && off + 16 <= end) {
|
|
300
|
+
if (size === 0) size = end - off
|
|
301
|
+
else if (size === 1 && off + 16 <= end) {
|
|
358
302
|
size = r32(buf, off + 12)
|
|
359
303
|
if (size < 16) break
|
|
360
|
-
} else if (size < 8)
|
|
361
|
-
break
|
|
362
|
-
}
|
|
304
|
+
} else if (size < 8) break
|
|
363
305
|
|
|
364
|
-
|
|
365
|
-
|
|
306
|
+
// skip mdat fast — the raw frames don't interest us here
|
|
307
|
+
if (type === 'mdat') { off += size; continue }
|
|
308
|
+
// truncated non-mdat box: tables would be garbage — wait for more data
|
|
309
|
+
if (off + size > end) break
|
|
366
310
|
|
|
367
|
-
|
|
368
|
-
if (type === '
|
|
369
|
-
cb(type, buf.subarray(bodyOff, truncated ? end : off + size), bodyOff)
|
|
370
|
-
if (truncated) break
|
|
371
|
-
}
|
|
372
|
-
// any other box must be fully present before parsing — partial tables yield garbage
|
|
373
|
-
else if (truncated) break
|
|
374
|
-
else if (type === 'stsd') parseSampleDesc(buf, bodyOff, size - 8, cb)
|
|
311
|
+
let bodyOff = off + 8
|
|
312
|
+
if (type === 'stsd') parseSampleDesc(buf, bodyOff, size - 8, cb)
|
|
375
313
|
else if (CONTAINERS.has(type)) parseBoxes(buf, bodyOff + (type === 'meta' ? 4 : 0), off + size, cb)
|
|
376
|
-
else cb(type, buf.subarray(bodyOff, off + size)
|
|
314
|
+
else cb(type, buf.subarray(bodyOff, off + size))
|
|
377
315
|
|
|
378
316
|
off += size
|
|
379
317
|
}
|
|
@@ -428,41 +366,24 @@ function parseStsc(data) {
|
|
|
428
366
|
return e
|
|
429
367
|
}
|
|
430
368
|
|
|
431
|
-
function
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (stsc
|
|
436
|
-
|
|
437
|
-
for (let j = stsc.length - 1; j >= 0; j--)
|
|
438
|
-
if (cn >= stsc[j].first) { spc = stsc[j].spc; break }
|
|
439
|
-
}
|
|
440
|
-
let off = stco[ci]
|
|
441
|
-
for (let s = 0; s < spc && si < stsz.length; s++) {
|
|
442
|
-
let sz = stsz[si]
|
|
443
|
-
if (off + sz > buf.length) return frames // sequential: first missing → rest missing
|
|
444
|
-
frames.push(buf.subarray(off, off + sz))
|
|
445
|
-
si++
|
|
446
|
-
off += sz
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
return frames
|
|
369
|
+
function spcAt(ci, stsc) {
|
|
370
|
+
if (!stsc?.length) return 1
|
|
371
|
+
let spc = 1, cn = ci + 1
|
|
372
|
+
for (let j = stsc.length - 1; j >= 0; j--)
|
|
373
|
+
if (cn >= stsc[j].first) { spc = stsc[j].spc; break }
|
|
374
|
+
return spc
|
|
450
375
|
}
|
|
451
376
|
|
|
452
|
-
function
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
pos++
|
|
377
|
+
function advanceM4A(st) {
|
|
378
|
+
st.nextOff += st.sizes[st.idx]
|
|
379
|
+
st.idx++
|
|
380
|
+
st.sInC++
|
|
381
|
+
if (st.sInC >= st.spc && st.ci + 1 < st.stco.length) {
|
|
382
|
+
st.ci++
|
|
383
|
+
st.sInC = 0
|
|
384
|
+
st.spc = spcAt(st.ci, st.stsc)
|
|
385
|
+
st.nextOff = st.stco[st.ci]
|
|
464
386
|
}
|
|
465
|
-
return frames
|
|
466
387
|
}
|
|
467
388
|
|
|
468
389
|
function r32(buf, off) {
|