aac
Version:
An AAC decoder for Aurora.js
438 lines (350 loc) • 15.9 kB
JavaScript
/*
* AAC.js - Advanced Audio Coding decoder in JavaScript
* Created by Devon Govett
* Copyright (c) 2012, Official.fm Labs
*
* AAC.js is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* AAC.js is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
* Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
* If not, see <http://www.gnu.org/licenses/>.
*/
var AV = require('av');
var ADTSDemuxer = require('./adts_demuxer');
var ICStream = require('./ics');
var CPEElement = require('./cpe');
var CCEElement = require('./cce');
var FilterBank = require('./filter_bank');
var tables = require('./tables');
var AACDecoder = AV.Decoder.extend(function() {
AV.Decoder.register('mp4a', this);
AV.Decoder.register('aac ', this);
// AAC profiles
const AOT_AAC_MAIN = 1, // no
AOT_AAC_LC = 2, // yes
AOT_AAC_LTP = 4, // no
AOT_ESCAPE = 31;
// Channel configurations
const CHANNEL_CONFIG_NONE = 0,
CHANNEL_CONFIG_MONO = 1,
CHANNEL_CONFIG_STEREO = 2,
CHANNEL_CONFIG_STEREO_PLUS_CENTER = 3,
CHANNEL_CONFIG_STEREO_PLUS_CENTER_PLUS_REAR_MONO = 4,
CHANNEL_CONFIG_FIVE = 5,
CHANNEL_CONFIG_FIVE_PLUS_ONE = 6,
CHANNEL_CONFIG_SEVEN_PLUS_ONE = 8;
this.prototype.init = function() {
this.format.floatingPoint = true;
}
this.prototype.setCookie = function(buffer) {
var data = AV.Stream.fromBuffer(buffer),
stream = new AV.Bitstream(data);
this.config = {};
this.config.profile = stream.read(5);
if (this.config.profile === AOT_ESCAPE)
this.config.profile = 32 + stream.read(6);
this.config.sampleIndex = stream.read(4);
if (this.config.sampleIndex === 0x0f) {
this.config.sampleRate = stream.read(24);
for (var i = 0; i < tables.SAMPLE_RATES.length; i++) {
if (tables.SAMPLE_RATES[i] === this.config.sampleRate) {
this.config.sampleIndex = i;
break;
}
}
} else {
this.config.sampleRate = tables.SAMPLE_RATES[this.config.sampleIndex];
}
this.config.chanConfig = stream.read(4);
this.format.channelsPerFrame = this.config.chanConfig; // sometimes m4a files encode this wrong
switch (this.config.profile) {
case AOT_AAC_MAIN:
case AOT_AAC_LC:
case AOT_AAC_LTP:
if (stream.read(1)) // frameLengthFlag
throw new Error('frameLengthFlag not supported');
this.config.frameLength = 1024;
if (stream.read(1)) // dependsOnCoreCoder
stream.advance(14); // coreCoderDelay
if (stream.read(1)) { // extensionFlag
if (this.config.profile > 16) { // error resiliant profile
this.config.sectionDataResilience = stream.read(1);
this.config.scalefactorResilience = stream.read(1);
this.config.spectralDataResilience = stream.read(1);
}
stream.advance(1);
}
if (this.config.chanConfig === CHANNEL_CONFIG_NONE) {
stream.advance(4) // element_instance_tag
throw new Error('PCE unimplemented');
}
break;
default:
throw new Error('AAC profile ' + this.config.profile + ' not supported.');
}
this.filter_bank = new FilterBank(false, this.config.chanConfig);
};
const SCE_ELEMENT = 0,
CPE_ELEMENT = 1,
CCE_ELEMENT = 2,
LFE_ELEMENT = 3,
DSE_ELEMENT = 4,
PCE_ELEMENT = 5,
FIL_ELEMENT = 6,
END_ELEMENT = 7;
// The main decoding function.
this.prototype.readChunk = function() {
var stream = this.bitstream;
// check if there is an ADTS header, and read it if so
if (stream.peek(12) === 0xfff)
ADTSDemuxer.readHeader(stream);
this.cces = [];
var elements = [],
config = this.config,
frameLength = config.frameLength,
elementType = null;
while ((elementType = stream.read(3)) !== END_ELEMENT) {
var id = stream.read(4);
switch (elementType) {
// single channel and low frequency elements
case SCE_ELEMENT:
case LFE_ELEMENT:
var ics = new ICStream(this.config);
ics.id = id;
elements.push(ics);
ics.decode(stream, config, false);
break;
// channel pair element
case CPE_ELEMENT:
var cpe = new CPEElement(this.config);
cpe.id = id;
elements.push(cpe);
cpe.decode(stream, config);
break;
// channel coupling element
case CCE_ELEMENT:
var cce = new CCEElement(this.config);
this.cces.push(cce);
cce.decode(stream, config);
break;
// data-stream element
case DSE_ELEMENT:
var align = stream.read(1),
count = stream.read(8);
if (count === 255)
count += stream.read(8);
if (align)
stream.align();
// skip for now...
stream.advance(count * 8);
break;
// program configuration element
case PCE_ELEMENT:
throw new Error("TODO: PCE_ELEMENT")
break;
// filler element
case FIL_ELEMENT:
if (id === 15)
id += stream.read(8) - 1;
// skip for now...
stream.advance(id * 8);
break;
default:
throw new Error('Unknown element')
}
}
stream.align();
this.process(elements);
// Interleave channels
var data = this.data,
channels = data.length,
output = new Float32Array(frameLength * channels),
j = 0;
for (var k = 0; k < frameLength; k++) {
for (var i = 0; i < channels; i++) {
output[j++] = data[i][k] / 32768;
}
}
return output;
};
this.prototype.process = function(elements) {
var channels = this.config.chanConfig;
// if (channels === 1 && psPresent)
// TODO: sbrPresent (2)
var mult = 1;
var len = mult * this.config.frameLength;
var data = this.data = [];
// Initialize channels
for (var i = 0; i < channels; i++) {
data[i] = new Float32Array(len);
}
var channel = 0;
for (var i = 0; i < elements.length && channel < channels; i++) {
var e = elements[i];
if (e instanceof ICStream) { // SCE or LFE element
channel += this.processSingle(e, channel);
} else if (e instanceof CPEElement) {
this.processPair(e, channel);
channel += 2;
} else if (e instanceof CCEElement) {
channel++;
} else {
throw new Error("Unknown element found.")
}
}
};
this.prototype.processSingle = function(element, channel) {
var profile = this.config.profile,
info = element.info,
data = element.data;
if (profile === AOT_AAC_MAIN)
throw new Error("Main prediction unimplemented");
if (profile === AOT_AAC_LTP)
throw new Error("LTP prediction unimplemented");
this.applyChannelCoupling(element, CCEElement.BEFORE_TNS, data, null);
if (element.tnsPresent)
element.tns.process(element, data, false);
this.applyChannelCoupling(element, CCEElement.AFTER_TNS, data, null);
// filterbank
this.filter_bank.process(info, data, this.data[channel], channel);
if (profile === AOT_AAC_LTP)
throw new Error("LTP prediction unimplemented");
this.applyChannelCoupling(element, CCEElement.AFTER_IMDCT, this.data[channel], null);
if (element.gainPresent)
throw new Error("Gain control not implemented");
if (this.sbrPresent)
throw new Error("SBR not implemented");
return 1;
};
this.prototype.processPair = function(element, channel) {
var profile = this.config.profile,
left = element.left,
right = element.right,
l_info = left.info,
r_info = right.info,
l_data = left.data,
r_data = right.data;
// Mid-side stereo
if (element.commonWindow && element.maskPresent)
this.processMS(element, l_data, r_data);
if (profile === AOT_AAC_MAIN)
throw new Error("Main prediction unimplemented");
// Intensity stereo
this.processIS(element, l_data, r_data);
if (profile === AOT_AAC_LTP)
throw new Error("LTP prediction unimplemented");
this.applyChannelCoupling(element, CCEElement.BEFORE_TNS, l_data, r_data);
if (left.tnsPresent)
left.tns.process(left, l_data, false);
if (right.tnsPresent)
right.tns.process(right, r_data, false);
this.applyChannelCoupling(element, CCEElement.AFTER_TNS, l_data, r_data);
// filterbank
this.filter_bank.process(l_info, l_data, this.data[channel], channel);
this.filter_bank.process(r_info, r_data, this.data[channel + 1], channel + 1);
if (profile === AOT_AAC_LTP)
throw new Error("LTP prediction unimplemented");
this.applyChannelCoupling(element, CCEElement.AFTER_IMDCT, this.data[channel], this.data[channel + 1]);
if (left.gainPresent)
throw new Error("Gain control not implemented");
if (right.gainPresent)
throw new Error("Gain control not implemented");
if (this.sbrPresent)
throw new Error("SBR not implemented");
};
// Intensity stereo
this.prototype.processIS = function(element, left, right) {
var ics = element.right,
info = ics.info,
offsets = info.swbOffsets,
windowGroups = info.groupCount,
maxSFB = info.maxSFB,
bandTypes = ics.bandTypes,
sectEnd = ics.sectEnd,
scaleFactors = ics.scaleFactors;
var idx = 0, groupOff = 0;
for (var g = 0; g < windowGroups; g++) {
for (var i = 0; i < maxSFB;) {
var end = sectEnd[idx];
if (bandTypes[idx] === ICStream.INTENSITY_BT || bandTypes[idx] === ICStream.INTENSITY_BT2) {
for (; i < end; i++, idx++) {
var c = bandTypes[idx] === ICStream.INTENSITY_BT ? 1 : -1;
if (element.maskPresent)
c *= element.ms_used[idx] ? -1 : 1;
var scale = c * scaleFactors[idx];
for (var w = 0; w < info.groupLength[g]; w++) {
var off = groupOff + w * 128 + offsets[i],
len = offsets[i + 1] - offsets[i];
for (var j = 0; j < len; j++) {
right[off + j] = left[off + j] * scale;
}
}
}
} else {
idx += end - i;
i = end;
}
}
groupOff += info.groupLength[g] * 128;
}
};
// Mid-side stereo
this.prototype.processMS = function(element, left, right) {
var ics = element.left,
info = ics.info,
offsets = info.swbOffsets,
windowGroups = info.groupCount,
maxSFB = info.maxSFB,
sfbCBl = ics.bandTypes,
sfbCBr = element.right.bandTypes;
var groupOff = 0, idx = 0;
for (var g = 0; g < windowGroups; g++) {
for (var i = 0; i < maxSFB; i++, idx++) {
if (element.ms_used[idx] && sfbCBl[idx] < ICStream.NOISE_BT && sfbCBr[idx] < ICStream.NOISE_BT) {
for (var w = 0; w < info.groupLength[g]; w++) {
var off = groupOff + w * 128 + offsets[i];
for (var j = 0; j < offsets[i + 1] - offsets[i]; j++) {
var t = left[off + j] - right[off + j];
left[off + j] += right[off + j];
right[off + j] = t;
}
}
}
}
groupOff += info.groupLength[g] * 128;
}
};
this.prototype.applyChannelCoupling = function(element, couplingPoint, data1, data2) {
var cces = this.cces,
isChannelPair = element instanceof CPEElement,
applyCoupling = couplingPoint === CCEElement.AFTER_IMDCT ? 'applyIndependentCoupling' : 'applyDependentCoupling';
for (var i = 0; i < cces.length; i++) {
var cce = cces[i],
index = 0;
if (cce.couplingPoint === couplingPoint) {
for (var c = 0; c < cce.coupledCount; c++) {
var chSelect = cce.chSelect[c];
if (cce.channelPair[c] === isChannelPair && cce.idSelect[c] === element.id) {
if (chSelect !== 1) {
cce[applyCoupling](index, data1);
if (chSelect) index++;
}
if (chSelect !== 2)
cce[applyCoupling](index++, data2);
} else {
index += 1 + (chSelect === 3 ? 1 : 0);
}
}
}
}
};
});
module.exports = AACDecoder;