uupaa.adts.js
Version:
ADTS parser.
236 lines (205 loc) • 9.31 kB
JavaScript
(function moduleExporter(name, closure) {
"use strict";
var entity = GLOBAL["WebModule"]["exports"](name, closure);
if (typeof module !== "undefined") {
module["exports"] = entity;
}
return entity;
})("ADTS", function moduleClosure(global, WebModule, VERIFY, VERBOSE) {
"use strict";
// --- technical terms / data structure --------------------
/*
- TechnicalTerms
- https://github.com/uupaa/ADTS.js/wiki/TechnicalTerms
- AudioMetaDataObject
- https://github.com/uupaa/ADTS.js/wiki/AudioMetaDataObject
- ADTSHeaderObject
- https://github.com/uupaa/ADTS.js/wiki/ADTSHeaderObject
*/
// --- dependency modules ----------------------------------
//var HexDump = global["WebModule"]["HexDump"];
var Hash = global["WebModule"]["Hash"];
var CRC16 = Hash["CRC16"] || null;
// --- import / local extract functions --------------------
// --- define / local variables ----------------------------
var SAMPLING_RATE = {
0: 96000, // `0000` - 96000 Hz
1: 88200, // `0001` - 88200 Hz
2: 64000, // `0010` - 64000 Hz
3: 48000, // `0011` - 48000 Hz
4: 44100, // `0100` - 44100 Hz
5: 32000, // `0101` - 32000 Hz
6: 24000, // `0110` - 24000 Hz
7: 22050, // `0111` - 22050 Hz
8: 16000, // `1000` - 16000 Hz
9: 12000, // `1001` - 12000 Hz
10: 11025, // `1010` - 11025 Hz
11: 8000, // `1011` - 8000 Hz
12: 7350, // `1100` - 7350 Hz
13: 0, // `1101` - Reserved
14: 0, // `1110` - Reserved
15: 0, // `1111` - other
};
var CHANNELS = {
0: 0.0,
1: 1.0, // 1 ch - (Front: center)
2: 2.0, // 2 ch - (Front: left, right)
3: 3.0, // 3 ch - (Front: left, center, right)
4: 4.0, // 4 ch - (Front: left, center, right) (Rear: center)
5: 5.0, // 5 ch - (Front: left, center, right) (Rear: left, right)
6: 5.1, // 5.1 ch - (Front: left, center, right) (Rear: left, right, subwoofer)
7: 7.1, // 7.1 ch - (Front: left, center, right)(Side: left, right)(Rear: left, right, subwoofer)
};
var PROFILES = {
0: "Main",
1: "LC",
2: "SSR",
3: "",
};
var SOUNDLESS = new Uint8Array([
// --- ADTS Header ---
0xff, 0xf1, 0x50, 0x80, 0x17, 0x02, 0xa4, // MPEG-4, AAC-LC, 44100Hz, L/R, FrameLength = 184byte
// --- CRC ---
0x21, 0x11,
// ---- Raw Data Block ---
0x45, 0x00, 0x14, 0x50, 0x01, 0x46, 0xf9, 0x91,
0x0a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e,
]);
// --- class / interfaces ----------------------------------
var ADTS = {
"VERBOSE": VERBOSE,
"parse": ADTS_parse, // ADTS.parse(source:Uint8Array, cursor:UINT32 = 0):AudioMetaDataObject
"toBlob": ADTS_toBlob, // ADTS.toBlob(source:Uint8Array, preBufferLength:UINT8 = 0, postBufferLength:UINT8 = 0):Blob
"repository": "https://github.com/uupaa/ADTS.js",
};
// --- implements ------------------------------------------
function ADTS_parse(source, // Uint8Array - ADTS+AAC Stream (raw level byte stream).
cursor) { // @arg UINT32 = 0 - source offset.
// @options.parseOnly Boolean = false - true is parse only (AACObject.data is empty)
// @ret AudioMetaDataObject
//{@dev
if (VERIFY) {
$valid($type(source, "Uint8Array"), ADTS_parse, "source");
$valid($type(cursor, "UINT32|omit"), ADTS_parse, "cursor");
}
//}@dev
var view = { source: source, cursor: cursor || 0 };
var sourceLimit = view.source.length - 7; // 7 = minimum buffer length
var version = 0; // AudioMetaDataObject.version
var profile = ""; // AudioMetaDataObject.profile
var channels = 0.0; // AudioMetaDataObject.channels
var duration = 0; // AudioMetaDataObject.duration
var frameCount = 0; // AudioMetaDataObject.frameCount
var samplingRate = 0; // AudioMetaDataObject.samplingRate
var header = []; // AudioMetaDataObject.header
var bufferRange = []; // AudioMetaDataObject.bufferRange - [<begin, end>, ...]
var totalBufferLength = 0; // AudioMetaDataObject.totalBufferLength
var a = 0x00;
var b = 0x00;
var c = 0x00;
var d = 0x00;
var e = 0x00;
var f = 0x00;
while (view.cursor < sourceLimit) {
a = view.source[view.cursor];
b = view.source[view.cursor + 1];
c = view.source[view.cursor + 2];
d = view.source[view.cursor + 3];
e = view.source[view.cursor + 4];
f = view.source[view.cursor + 5];
var syncWord = (a === 0xFF) && ((b & 0xF0) === 0xF0); // find sync word.
if (syncWord) {
var frameLength = (d & 0x03) << 11 | // |------HH| | |
e << 3 | // | |HHHHHHHH| |
(f & 0xe0) >> 5; // | | |HHH-----|
var headerLength = 7;
var bufferLength = frameLength - headerLength;
var preCalcCRC = 0;
if (b & 0x01) { // CRC protection
headerLength = 9;
bufferLength = frameLength - headerLength;
preCalcCRC = ((view.source[view.cursor + 7] << 8) |
view.source[view.cursor + 8]) >>> 0;
}
if (frameCount++ === 0) { // first frame -> pickup ADTS header
version = (b & 0x08) === 1 ? 2 : 4; // A: `0` = MPEG4, `1` = MPEG2
profile = PROFILES[(c & 0xC0) >> 6]; // C: `00` = Main, `01` = LC, `10` = SSR
samplingRate = SAMPLING_RATE[(c & 0x3C) >> 2]; // D:
channels = CHANNELS[((c & 0x1) << 2 | d & 0xC0) >> 6]; // F: 1 - 7.1
}
header.push({
"headerLength": headerLength,
"bufferLength": bufferLength,
});
var begin = view.cursor + headerLength;
var end = view.cursor + frameLength;
if (VERIFY) {
if (preCalcCRC && CRC16) {
var crcVerify = CRC16( view.source.subarray( view.cursor, view.cursor + frameLength ) ); // CRC includes ADTS header.
if (preCalcCRC !== crcVerify) {
if (ADTS["VERBOSE"]) {
console.error("CRC unmatched", preCalcCRC, crcVerify);
}
}
}
}
bufferRange.push(begin, end);
totalBufferLength += end - begin;
view.cursor += frameLength;
} else {
view.cursor++; // skip unknown byte
}
}
duration = (1 / samplingRate) * 1024 * frameCount;
var result = {
"version": version, // MPEG Version. 2 (MPEG-2) or 4 (MPEG-4)
"profile": profile, // "Main" or "LC" or "SSR"
"channels": channels, // 1 / 2 / 3 / 4 / 5 / 5.1 / 7.1
"duration": duration, // duration
"frameCount": frameCount, //
"samplingRate": samplingRate, // 7359 - 96000
"header": header, // ADTS header. [ ADTSHeaderObject, ... ]
"bufferRange": bufferRange, // AAC binary data range. [<begin, end>, ... ]
"totalBufferLength": totalBufferLength,
};
if (ADTS["VERBOSE"]) {
console.dir(result);
}
return result;
}
function ADTS_toBlob(source, // Uint8Array - ADTS+AAC Stream
preBufferLength, // UINT8 = 0 - pre-extend soundless buffer length.
postBufferLength) { // @arg UINT8 = 0 - post-extend soundless buffer length.
// @ret Blob
var buffer = [];
for (var i = 0, iz = preBufferLength; i < iz; ++i) {
buffer.push(SOUNDLESS);
}
buffer.push(source);
for (i = 0, iz = postBufferLength; i < iz; ++i) {
buffer.push(SOUNDLESS);
}
return new Blob(buffer, { type: "audio/aac" });
}
return ADTS; // return entity
});