@audio/decode-mp4 1.2.0 → 1.3.0
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-mp4.js +40 -10
- package/package.json +3 -3
package/decode-mp4.js
CHANGED
|
@@ -238,16 +238,17 @@ function parseStsd(buf, off, end, cb) {
|
|
|
238
238
|
|
|
239
239
|
/** Parse the first audio track out of a complete moov. Returns null while moov is incomplete or absent. */
|
|
240
240
|
function parseTrack(buf) {
|
|
241
|
-
let traks = [], t = null, moov = false, trex = new Map()
|
|
241
|
+
let traks = [], t = null, moov = false, trex = new Map(), movie = {}
|
|
242
242
|
walk(buf, 0, buf.length, (type, data, off) => {
|
|
243
243
|
if (type === 'moov') moov = true
|
|
244
|
+
else if (type === 'mvhd' || type === 'ilst' && !t) movie[type] = data // movie timescale; iTunes tags
|
|
244
245
|
else if (type === 'trak') traks.push(t = { children: {} })
|
|
245
246
|
else if (type === '/trak') t = null
|
|
246
247
|
else if (type === 'trex') trex.set(r32(data, 4), { size: r32(data, 16) })
|
|
247
248
|
else if (!t) return
|
|
248
249
|
else if (type === 'tkhd') t.id = r32(data, data[0] === 1 ? 20 : 12)
|
|
249
250
|
else if (type === 'hdlr') t.handler = str4(data, 8)
|
|
250
|
-
else if (type === 'mdhd') t.timescale = r32(data, data[0] === 1 ? 20 : 12)
|
|
251
|
+
else if (type === 'mdhd') { t.timescale = r32(data, data[0] === 1 ? 20 : 12); t.children.mdhd = data }
|
|
251
252
|
else if (type === 'entry') t.entry ??= data
|
|
252
253
|
else if (type === 'stsz') t.sizes = parseStsz(data)
|
|
253
254
|
else if (type === 'stco') t.stco = parseStco(data)
|
|
@@ -262,6 +263,7 @@ function parseTrack(buf) {
|
|
|
262
263
|
if (trex.size && !track.sizes?.length) { track.fragmented = true; track.trex = trex.get(track.id) || {} }
|
|
263
264
|
else if (!track.sizes || !track.stco?.length) throw Error('Audio track has no sample tables')
|
|
264
265
|
if (!track.entry.sampleRate && track.timescale) track.entry.sampleRate = track.timescale
|
|
266
|
+
track.movie = movie
|
|
265
267
|
return track
|
|
266
268
|
}
|
|
267
269
|
|
|
@@ -316,25 +318,27 @@ const MP3_OTI = new Set([0x69, 0x6B]), AAC_OTI = new Set([0x40, 0x66, 0x67, 0x68
|
|
|
316
318
|
const UNSUPPORTED = { 0xE1: 'QCELP' }
|
|
317
319
|
const DTS_TYPES = new Set(['dtsc', 'dtsh', 'dtsl', 'dtse']) // DTS-HD variants carry a decodable core
|
|
318
320
|
|
|
319
|
-
async function createCodec({ entry, children }) {
|
|
321
|
+
async function createCodec({ entry, children, movie }) {
|
|
320
322
|
let { type } = entry
|
|
323
|
+
// AAC and ALAC trim the encoder's priming and padding by the track's edit list or iTunSMPB, MP3 by the edit list (gapless)
|
|
324
|
+
let boxes = { elst: children.elst, mdhd: children.mdhd, stts: children.stts, mvhd: movie?.mvhd, ilst: movie?.ilst ?? children.ilst }
|
|
321
325
|
if (type === 'mp4a') {
|
|
322
326
|
let { oti, dsi } = children.esds ? parseEsds(children.esds) : {}
|
|
323
|
-
if (MP3_OTI.has(oti)) return
|
|
327
|
+
if (MP3_OTI.has(oti)) return mp3(boxes)
|
|
324
328
|
if (oti === 0xA5) return frames(import('@audio/decode-ac3'))
|
|
325
329
|
if (oti === 0xA9) return frames(import('@audio/decode-dts'))
|
|
326
330
|
if (oti === 0xA6) return frames(import('@audio/decode-eac3'))
|
|
327
331
|
if (!oti || AAC_OTI.has(oti)) {
|
|
328
332
|
if (!dsi) throw Error('MP4 AAC track has no AudioSpecificConfig')
|
|
329
|
-
return aac({ asc: dsi })
|
|
333
|
+
return aac({ asc: dsi }, boxes)
|
|
330
334
|
}
|
|
331
335
|
throw unsupported(UNSUPPORTED[oti] || 'esds object type 0x' + oti.toString(16))
|
|
332
336
|
}
|
|
333
337
|
if (type === 'alac') {
|
|
334
338
|
if (!children.alac) throw Error('MP4 ALAC track has no magic cookie')
|
|
335
|
-
return aac({ alac: children.alac })
|
|
339
|
+
return aac({ alac: children.alac }, boxes)
|
|
336
340
|
}
|
|
337
|
-
if (type === '.mp3') return
|
|
341
|
+
if (type === '.mp3') return mp3(boxes)
|
|
338
342
|
if (type === 'fLaC') {
|
|
339
343
|
if (!children.dfLa) throw Error('MP4 FLAC track has no dfLa box')
|
|
340
344
|
return flac(children.dfLa.subarray(4))
|
|
@@ -383,8 +387,9 @@ function pcmFormat(e, ch) {
|
|
|
383
387
|
|
|
384
388
|
// --- adapters: { feed(frames: Uint8Array[]), flush(), free() } ---
|
|
385
389
|
|
|
386
|
-
async function aac(opts) {
|
|
387
|
-
let
|
|
390
|
+
async function aac(opts, boxes) {
|
|
391
|
+
let { decoder, gapless } = await import('@audio/decode-aac')
|
|
392
|
+
let dec = await decoder({ ...opts, gapless: gapless?.({ ...boxes, ...opts }) })
|
|
388
393
|
return { feed: frames => dec.decode(frames), flush: () => dec.flush(), free: () => dec.free() }
|
|
389
394
|
}
|
|
390
395
|
|
|
@@ -394,6 +399,26 @@ async function frames(load) {
|
|
|
394
399
|
return { feed: frames => dec.decode(concat(frames)), flush: () => dec.flush?.() ?? EMPTY, free: () => dec.free() }
|
|
395
400
|
}
|
|
396
401
|
|
|
402
|
+
// MP3 samples carry no LAME tag, so mpg123 decodes from the media's first sample: the edit list's window drops the
|
|
403
|
+
// encoder and decoder delay (1105 for LAME, as ffmpeg writes it) and the padding. decode-aac reads the window.
|
|
404
|
+
async function mp3(boxes) {
|
|
405
|
+
let [codec, { gapless }] = await Promise.all([frames(import('@audio/decode-mp3')), import('@audio/decode-aac')])
|
|
406
|
+
let g = gapless?.(boxes)
|
|
407
|
+
if (!g) return codec
|
|
408
|
+
let skip = null, left = null
|
|
409
|
+
return {
|
|
410
|
+
feed(frames) {
|
|
411
|
+
let r = codec.feed(frames), n = r.channelData?.[0]?.length
|
|
412
|
+
if (!n) return r
|
|
413
|
+
skip ??= Math.round(g.start * r.sampleRate); left ??= g.duration == null ? Infinity : Math.round(g.duration * r.sampleRate)
|
|
414
|
+
let a = Math.min(n, skip), b = Math.min(n, a + left)
|
|
415
|
+
skip -= a; left -= b - a
|
|
416
|
+
return b <= a ? EMPTY : a || b < n ? { ...r, channelData: r.channelData.map(c => c.subarray(a, b)) } : r
|
|
417
|
+
},
|
|
418
|
+
flush: codec.flush, free: codec.free
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
397
422
|
async function flac(blocks) {
|
|
398
423
|
let dec = await (await import('@audio/decode-flac')).decoder()
|
|
399
424
|
let head = concat([new Uint8Array([0x66, 0x4C, 0x61, 0x43]), blocks]) // "fLaC" + METADATA_BLOCKs
|
|
@@ -405,12 +430,17 @@ async function flac(blocks) {
|
|
|
405
430
|
}
|
|
406
431
|
}
|
|
407
432
|
|
|
433
|
+
// Mapping family 1 orders 3–8 channels as Vorbis does (RFC 7845 §5.1.1.2): out in SMPTE/WAV order (L R C LFE Ls Rs …)
|
|
434
|
+
// as decode-opus reorders Ogg Opus: output channel j takes the coded channel of Vorbis position OPUS_ORDER[n][j]
|
|
435
|
+
const OPUS_ORDER = { 3: [0, 2, 1], 5: [0, 2, 1, 3, 4], 6: [0, 2, 1, 5, 3, 4], 7: [0, 2, 1, 6, 5, 3, 4], 8: [0, 2, 1, 7, 5, 6, 3, 4] }
|
|
436
|
+
|
|
408
437
|
async function opus(dOps) {
|
|
409
438
|
let channels = dOps[1]
|
|
410
439
|
let opts = { channels, sampleRate: 48000, preSkip: r16(dOps, 2), outputGain: (r16(dOps, 8) << 16) >> 16 }
|
|
411
440
|
if (dOps[10] > 0) {
|
|
412
441
|
opts.streamCount = dOps[11]; opts.coupledStreamCount = dOps[12]
|
|
413
|
-
|
|
442
|
+
let table = Array.from(dOps.subarray(13, 13 + channels))
|
|
443
|
+
opts.channelMappingTable = dOps[10] === 1 && OPUS_ORDER[channels] ? OPUS_ORDER[channels].map(i => table[i]) : table
|
|
414
444
|
}
|
|
415
445
|
let dec = await (await import('@audio/decode-opus/core')).createOpusDecoder()
|
|
416
446
|
dec.configure(opts)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@audio/decode-mp4",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Decode the audio track of MP4, MOV, M4A, M4V and 3GP files (AAC, ALAC, MP3, FLAC, Opus, AMR, PCM) to PCM samples",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "decode-mp4.js",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"LICENSE"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@audio/decode-aac": "^1.
|
|
30
|
+
"@audio/decode-aac": "^1.6.0",
|
|
31
31
|
"@audio/decode-ac3": "^1.0.0",
|
|
32
32
|
"@audio/decode-amr": "^1.3.0",
|
|
33
33
|
"@audio/decode-dts": "^1.0.0",
|
|
34
34
|
"@audio/decode-flac": "^1.3.0",
|
|
35
35
|
"@audio/decode-mp3": "^1.3.0",
|
|
36
|
-
"@audio/decode-opus": "^1.3.
|
|
36
|
+
"@audio/decode-opus": "^1.3.3",
|
|
37
37
|
"@audio/decode-eac3": "^1.0.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|