@flomon-ui/jsmpeg
Version:
Flomon for jsmpeg
1,521 lines (1,467 loc) • 198 kB
JavaScript
'use strict';
/* eslint class-methods-use-this: ["error", { "exceptMethods": ["resume"] }] */
class AjaxSource {
constructor(url, options) {
this.url = url;
this.destination = null;
this.request = null;
this.streaming = false;
this.completed = false;
this.established = false;
this.progress = 0;
this.onEstablishedCallback = options.onSourceEstablished;
this.onCompletedCallback = options.onSourceCompleted;
if (options.hookOnEstablished) {
this.hookOnEstablished = options.hookOnEstablished;
}
}
connect(destination) {
this.destination = destination;
}
start() {
this.request = new XMLHttpRequest();
// eslint-disable-next-line func-names
this.request.onreadystatechange = function () {
if (this.request.readyState === this.request.DONE && this.request.status === 200) {
this.onLoad(this.request.response);
}
}.bind(this);
this.request.onprogress = this.onProgress.bind(this);
this.request.open('GET', this.url);
this.request.responseType = 'arraybuffer';
this.request.send();
}
resume() {
// Nothing to do here
}
destroy() {
this.request.abort();
}
onProgress(ev) {
this.progress = ev.loaded / ev.total;
}
onLoad(data) {
this.established = true;
this.completed = true;
this.progress = 1;
if (this.hookOnEstablished) {
this.hookOnEstablished();
}
if (this.onEstablishedCallback) {
this.onEstablishedCallback(this);
}
if (this.onCompletedCallback) {
this.onCompletedCallback(this);
}
if (this.destination) {
this.destination.write(data);
}
}
}
function styleInject(css, ref) {
if ( ref === void 0 ) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css_248z = ".canvas,.playButton,.poster,.unmuteButton{height:100%;left:0;position:absolute;top:0;width:100%;z-index:1}.playButton{-ms-flex-pack:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:center;justify-content:center}.canvas,.poster{display:block}.poster.hidden{display:none}.playButton,.unmuteButton{-webkit-tap-highlight-color:rgba(255,0,0,0);cursor:pointer;opacity:.7}.hidden.playButton,.hidden.unmuteButton{display:none}.playButton{z-index:10}.playButton>svg{fill:#fff;height:12vw;max-height:60px;max-width:60px;width:12vw}.unmuteButton{-ms-flex-pack:end;-ms-flex-align:end;-webkit-align-items:flex-end;align-items:flex-end;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:flex-end;justify-content:flex-end;z-index:10}.unmuteButton>svg{fill:#fff;height:9vw;margin:0 15px 15px 0;max-height:40px;max-width:40px;width:9vw}";
styleInject(css_248z);
/* eslint class-methods-use-this: ["error", { "exceptMethods": ["destroy"] }] */
class CanvasRenderer {
constructor(options) {
this.canvas = options.canvas || document.createElement('canvas');
this.width = this.canvas.width;
this.height = this.canvas.height;
this.enabled = true;
this.context = this.canvas.getContext('2d');
}
destroy() {
// Nothing to do here
}
resize(width, height) {
this.width = width | 0;
this.height = height | 0;
this.canvas.width = this.width;
this.canvas.height = this.height;
this.imageData = this.context.getImageData(0, 0, this.width, this.height);
Fill(this.imageData.data, 255);
}
renderProgress(progress) {
const w = this.canvas.width;
const h = this.canvas.height;
const ctx = this.context;
ctx.fillStyle = '#222';
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = '#fff';
ctx.fillRect(0, h - h * progress, w, h * progress);
}
render(y, cb, cr) {
this.YCbCrToRGBA(y, cb, cr, this.imageData.data);
this.context.putImageData(this.imageData, 0, 0);
}
YCbCrToRGBA(y, cb, cr, rgba) {
if (!this.enabled) {
return;
}
// Chroma values are the same for each block of 4 pixels, so we proccess
// 2 lines at a time, 2 neighboring pixels each.
// I wish we could use 32bit writes to the RGBA buffer instead of writing
// each byte separately, but we need the automatic clamping of the RGBA
// buffer.
const w = this.width + 15 >> 4 << 4;
const w2 = w >> 1;
let yIndex1 = 0;
let yIndex2 = w;
const yNext2Lines = w + (w - this.width);
let cIndex = 0;
const cNextLine = w2 - (this.width >> 1);
let rgbaIndex1 = 0;
let rgbaIndex2 = this.width * 4;
const rgbaNext2Lines = this.width * 4;
const cols = this.width >> 1;
const rows = this.height >> 1;
let ccb;
let ccr;
let r;
let g;
let b;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
ccb = cb[cIndex];
ccr = cr[cIndex];
cIndex++;
r = ccb + (ccb * 103 >> 8) - 179;
g = (ccr * 88 >> 8) - 44 + (ccb * 183 >> 8) - 91;
b = ccr + (ccr * 198 >> 8) - 227;
// Line 1
const y1 = y[yIndex1++];
const y2 = y[yIndex1++];
rgba[rgbaIndex1] = y1 + r;
rgba[rgbaIndex1 + 1] = y1 - g;
rgba[rgbaIndex1 + 2] = y1 + b;
rgba[rgbaIndex1 + 4] = y2 + r;
rgba[rgbaIndex1 + 5] = y2 - g;
rgba[rgbaIndex1 + 6] = y2 + b;
rgbaIndex1 += 8;
// Line 2
const y3 = y[yIndex2++];
const y4 = y[yIndex2++];
rgba[rgbaIndex2] = y3 + r;
rgba[rgbaIndex2 + 1] = y3 - g;
rgba[rgbaIndex2 + 2] = y3 + b;
rgba[rgbaIndex2 + 4] = y4 + r;
rgba[rgbaIndex2 + 5] = y4 - g;
rgba[rgbaIndex2 + 6] = y4 + b;
rgbaIndex2 += 8;
}
yIndex1 += yNext2Lines;
yIndex2 += yNext2Lines;
rgbaIndex1 += rgbaNext2Lines;
rgbaIndex2 += rgbaNext2Lines;
cIndex += cNextLine;
}
}
}
/* eslint class-methods-use-this: ["error", { "exceptMethods": ["destroy"] }] */
class BaseDecoder {
constructor(options) {
this.destination = null;
this.canPlay = false;
this.collectTimestamps = !options.streaming;
this.bytesWritten = 0;
this.timestamps = [];
this.timestampIndex = 0;
this.startTime = 0;
this.decodedTime = 0;
Object.defineProperty(this, 'currentTime', {
get: this.getCurrentTime
});
}
destroy() {}
connect(destination) {
this.destination = destination;
}
bufferGetIndex() {
return this.bits.index;
}
bufferSetIndex(index) {
this.bits.index = index;
}
bufferWrite(buffers) {
return this.bits.write(buffers);
}
write(pts, buffers) {
if (this.collectTimestamps) {
if (this.timestamps.length === 0) {
this.startTime = pts;
this.decodedTime = pts;
}
this.timestamps.push({
index: this.bytesWritten << 3,
time: pts
});
}
this.bytesWritten += this.bufferWrite(buffers);
this.canPlay = true;
}
seek(time) {
if (!this.collectTimestamps) {
return;
}
this.timestampIndex = 0;
for (let i = 0; i < this.timestamps.length; i++) {
if (this.timestamps[i].time > time) {
break;
}
this.timestampIndex = i;
}
const ts = this.timestamps[this.timestampIndex];
if (ts) {
this.bufferSetIndex(ts.index);
this.decodedTime = ts.time;
} else {
this.bits.index = 0;
this.decodedTime = this.startTime;
}
}
decode() {
this.advanceDecodedTime(0);
}
advanceDecodedTime(seconds) {
if (this.collectTimestamps) {
let newTimestampIndex = -1;
const currentIndex = this.bufferGetIndex();
for (let i = this.timestampIndex; i < this.timestamps.length; i++) {
if (this.timestamps[i].index > currentIndex) {
break;
}
newTimestampIndex = i;
}
// Did we find a new PTS, different from the last? If so, we don't have
// to advance the decoded time manually and can instead sync it exactly
// to the PTS.
if (newTimestampIndex !== -1 && newTimestampIndex !== this.timestampIndex) {
this.timestampIndex = newTimestampIndex;
this.decodedTime = this.timestamps[this.timestampIndex].time;
return;
}
}
this.decodedTime += seconds;
}
getCurrentTime() {
return this.decodedTime;
}
}
class BitBuffer {
constructor(bufferOrLength, mode) {
if (typeof bufferOrLength === 'object') {
this.bytes = bufferOrLength instanceof Uint8Array ? bufferOrLength : new Uint8Array(bufferOrLength);
this.byteLength = this.bytes.length;
} else {
this.bytes = new Uint8Array(bufferOrLength || 1024 * 1024);
this.byteLength = 0;
}
this.mode = mode || BitBuffer.MODE.EXPAND;
this.index = 0;
}
resize(size) {
const newBytes = new Uint8Array(size);
if (this.byteLength !== 0) {
this.byteLength = Math.min(this.byteLength, size);
newBytes.set(this.bytes, 0, this.byteLength);
}
this.bytes = newBytes;
this.index = Math.min(this.index, this.byteLength << 3);
}
evict(sizeNeeded) {
const bytePos = this.index >> 3;
const available = this.bytes.length - this.byteLength;
// If the current index is the write position, we can simply reset both
// to 0. Also reset (and throw away yet unread data) if we won't be able
// to fit the new data in even after a normal eviction.
if (this.index === this.byteLength << 3 || sizeNeeded > available + bytePos // emergency evac
) {
this.byteLength = 0;
this.index = 0;
return;
} else if (bytePos === 0) {
// Nothing read yet - we can't evict anything
return;
}
// Some browsers don't support copyWithin() yet - we may have to do
// it manually using set and a subarray
if (this.bytes.copyWithin) {
this.bytes.copyWithin(0, bytePos, this.byteLength);
} else {
this.bytes.set(this.bytes.subarray(bytePos, this.byteLength));
}
this.byteLength -= bytePos;
this.index -= bytePos << 3;
}
write(buffers) {
const isArrayOfBuffers = typeof buffers[0] === 'object';
let totalLength = 0;
const available = this.bytes.length - this.byteLength;
// Calculate total byte length
if (isArrayOfBuffers) {
totalLength = 0;
for (let i = 0; i < buffers.length; i++) {
totalLength += buffers[i].byteLength;
}
} else {
totalLength = buffers.byteLength;
}
// Do we need to resize or evict?
if (totalLength > available) {
if (this.mode === BitBuffer.MODE.EXPAND) {
const newSize = Math.max(this.bytes.length * 2, totalLength - available);
this.resize(newSize);
} else {
this.evict(totalLength);
}
}
if (isArrayOfBuffers) {
for (let i = 0; i < buffers.length; i++) {
this.appendSingleBuffer(buffers[i]);
}
} else {
this.appendSingleBuffer(buffers);
}
return totalLength;
}
appendSingleBuffer(buffer) {
buffer = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
this.bytes.set(buffer, this.byteLength);
this.byteLength += buffer.length;
}
findNextStartCode() {
for (let i = this.index + 7 >> 3; i < this.byteLength; i++) {
if (this.bytes[i] === 0x00 && this.bytes[i + 1] === 0x00 && this.bytes[i + 2] === 0x01) {
this.index = i + 4 << 3;
return this.bytes[i + 3];
}
}
this.index = this.byteLength << 3;
return -1;
}
findStartCode(code) {
const current = this.findNextStartCode();
if (current === code || current === -1) {
return current;
}
return -1;
}
nextBytesAreStartCode() {
const i = this.index + 7 >> 3;
return i >= this.byteLength || this.bytes[i] === 0x00 && this.bytes[i + 1] === 0x00 && this.bytes[i + 2] === 0x01;
}
peek(count) {
let offset = this.index;
let value = 0;
while (count) {
const currentByte = this.bytes[offset >> 3];
const remaining = 8 - (offset & 7); // remaining bits in byte
const read = remaining < count ? remaining : count; // bits in this run
const shift = remaining - read;
const mask = 0xff >> 8 - read;
value = value << read | (currentByte & mask << shift) >> shift;
offset += read;
count -= read;
}
return value;
}
read(count) {
const value = this.peek(count);
this.index += count;
return value;
}
skip(count) {
return this.index += count;
}
rewind(count) {
this.index = Math.max(this.index - count, 0);
}
has(count) {
return (this.byteLength << 3) - this.index >= count;
}
}
BitBuffer.MODE = {
EVICT: 1,
EXPAND: 2
};
// Based on kjmp2 by Martin J. Fiedler
class MP2 extends BaseDecoder {
constructor(options) {
super(options);
this.onDecodeCallback = options.onAudioDecode;
const bufferSize = options.audioBufferSize || 128 * 1024;
const bufferMode = options.streaming ? BitBuffer.MODE.EVICT : BitBuffer.MODE.EXPAND;
this.bits = new BitBuffer(bufferSize, bufferMode);
this.left = new Float32Array(1152);
this.right = new Float32Array(1152);
this.sampleRate = 44100;
this.D = new Float32Array(1024);
this.D.set(MP2.SYNTHESIS_WINDOW, 0);
this.D.set(MP2.SYNTHESIS_WINDOW, 512);
this.V = [new Float32Array(1024), new Float32Array(1024)];
this.U = new Int32Array(32);
this.VPos = 0;
this.allocation = [new Array(32), new Array(32)];
this.scaleFactorInfo = [new Uint8Array(32), new Uint8Array(32)];
this.scaleFactor = [new Array(32), new Array(32)];
this.sample = [new Array(32), new Array(32)];
for (let j = 0; j < 2; j++) {
for (let i = 0; i < 32; i++) {
this.scaleFactor[j][i] = [0, 0, 0];
this.sample[j][i] = [0, 0, 0];
}
}
}
decode() {
const startTime = Now();
const pos = this.bits.index >> 3;
if (pos >= this.bits.byteLength) {
return false;
}
const decoded = this.decodeFrame(this.left, this.right);
this.bits.index = pos + decoded << 3;
if (!decoded) {
return false;
}
if (this.destination) {
this.destination.play(this.sampleRate, this.left, this.right);
}
this.advanceDecodedTime(this.left.length / this.sampleRate);
const elapsedTime = Now() - startTime;
if (this.onDecodeCallback) {
this.onDecodeCallback(this, elapsedTime);
}
return true;
}
getCurrentTime() {
const enqueuedTime = this.destination ? this.destination.enqueuedTime : 0;
return this.decodedTime - enqueuedTime;
}
decodeFrame(left, right) {
// Check for valid header: syncword OK, MPEG-Audio Layer 2
const sync = this.bits.read(11);
const version = this.bits.read(2);
const layer = this.bits.read(2);
const hasCRC = !this.bits.read(1);
if (sync !== MP2.FRAME_SYNC || version !== MP2.VERSION.MPEG_1 || layer !== MP2.LAYER.II) {
// Invalid header or unsupported version
return 0;
}
let bitrateIndex = this.bits.read(4) - 1;
if (bitrateIndex > 13) {
// Invalid bit rate or 'free format'
return 0;
}
let sampleRateIndex = this.bits.read(2);
let sampleRate = MP2.SAMPLE_RATE[sampleRateIndex];
if (sampleRateIndex === 3) {
// Invalid sample rate
return 0;
}
if (version === MP2.VERSION.MPEG_2) {
sampleRateIndex += 4;
bitrateIndex += 14;
}
const padding = this.bits.read(1);
// eslint-disable-next-line no-unused-vars
this.bits.read(1);
const mode = this.bits.read(2);
// Parse the mode_extension, set up the stereo bound
let bound = 0;
if (mode === MP2.MODE.JOINT_STEREO) {
bound = this.bits.read(2) + 1 << 2;
} else {
this.bits.skip(2);
bound = mode === MP2.MODE.MONO ? 0 : 32;
}
// Discard the last 4 bits of the header and the CRC value, if present
this.bits.skip(4);
if (hasCRC) {
this.bits.skip(16);
}
// Compute the frame size
const bitrate = MP2.BIT_RATE[bitrateIndex];
sampleRate = MP2.SAMPLE_RATE[sampleRateIndex];
const frameSize = 144000 * bitrate / sampleRate + padding | 0;
// Prepare the quantizer table lookups
let tab3 = 0;
let sblimit = 0;
if (version === MP2.VERSION.MPEG_2) {
// MPEG-2 (LSR)
tab3 = 2;
sblimit = 30;
} else {
// MPEG-1
const tab1 = mode === MP2.MODE.MONO ? 0 : 1;
const tab2 = MP2.QUANT_LUT_STEP_1[tab1][bitrateIndex];
tab3 = MP2.QUANT_LUT_STEP_2[tab2][sampleRateIndex];
sblimit = tab3 & 63;
tab3 >>= 6;
}
if (bound > sblimit) {
bound = sblimit;
}
// Read the allocation information
for (let sb = 0; sb < bound; sb++) {
this.allocation[0][sb] = this.readAllocation(sb, tab3);
this.allocation[1][sb] = this.readAllocation(sb, tab3);
}
for (let sb = bound; sb < sblimit; sb++) {
this.allocation[0][sb] = this.allocation[1][sb] = this.readAllocation(sb, tab3);
}
// Read scale factor selector information
const channels = mode === MP2.MODE.MONO ? 1 : 2;
for (let sb = 0; sb < sblimit; sb++) {
for (let ch = 0; ch < channels; ch++) {
if (this.allocation[ch][sb]) {
this.scaleFactorInfo[ch][sb] = this.bits.read(2);
}
}
if (mode === MP2.MODE.MONO) {
this.scaleFactorInfo[1][sb] = this.scaleFactorInfo[0][sb];
}
}
// Read scale factors
for (let sb = 0; sb < sblimit; sb++) {
for (let ch = 0; ch < channels; ch++) {
if (this.allocation[ch][sb]) {
const sf = this.scaleFactor[ch][sb];
switch (this.scaleFactorInfo[ch][sb]) {
case 0:
sf[0] = this.bits.read(6);
sf[1] = this.bits.read(6);
sf[2] = this.bits.read(6);
break;
case 1:
sf[0] = sf[1] = this.bits.read(6);
sf[2] = this.bits.read(6);
break;
case 2:
sf[0] = sf[1] = sf[2] = this.bits.read(6);
break;
case 3:
sf[0] = this.bits.read(6);
sf[1] = sf[2] = this.bits.read(6);
break;
}
}
}
if (mode === MP2.MODE.MONO) {
// eslint-disable-next-line prefer-destructuring
this.scaleFactor[1][sb][0] = this.scaleFactor[0][sb][0];
// eslint-disable-next-line prefer-destructuring
this.scaleFactor[1][sb][1] = this.scaleFactor[0][sb][1];
// eslint-disable-next-line prefer-destructuring
this.scaleFactor[1][sb][2] = this.scaleFactor[0][sb][2];
}
}
// Coefficient input and reconstruction
let outPos = 0;
for (let part = 0; part < 3; part++) {
for (let granule = 0; granule < 4; granule++) {
// Read the samples
for (let sb = 0; sb < bound; sb++) {
this.readSamples(0, sb, part);
this.readSamples(1, sb, part);
}
for (let sb = bound; sb < sblimit; sb++) {
this.readSamples(0, sb, part);
// eslint-disable-next-line prefer-destructuring
this.sample[1][sb][0] = this.sample[0][sb][0];
// eslint-disable-next-line prefer-destructuring
this.sample[1][sb][1] = this.sample[0][sb][1];
// eslint-disable-next-line prefer-destructuring
this.sample[1][sb][2] = this.sample[0][sb][2];
}
for (let sb = sblimit; sb < 32; sb++) {
this.sample[0][sb][0] = 0;
this.sample[0][sb][1] = 0;
this.sample[0][sb][2] = 0;
this.sample[1][sb][0] = 0;
this.sample[1][sb][1] = 0;
this.sample[1][sb][2] = 0;
}
// Synthesis loop
for (let p = 0; p < 3; p++) {
// Shifting step
this.VPos = this.VPos - 64 & 1023;
for (let ch = 0; ch < 2; ch++) {
MP2.MatrixTransform(this.sample[ch], p, this.V[ch], this.VPos);
// Build U, windowing, calculate output
Fill(this.U, 0);
let dIndex = 512 - (this.VPos >> 1);
let vIndex = this.VPos % 128 >> 1;
while (vIndex < 1024) {
for (let i = 0; i < 32; ++i) {
this.U[i] += this.D[dIndex++] * this.V[ch][vIndex++];
}
vIndex += 128 - 32;
dIndex += 64 - 32;
}
vIndex = 128 - 32 + 1024 - vIndex;
dIndex -= 512 - 32;
while (vIndex < 1024) {
for (let i = 0; i < 32; ++i) {
this.U[i] += this.D[dIndex++] * this.V[ch][vIndex++];
}
vIndex += 128 - 32;
dIndex += 64 - 32;
}
// Output samples
const outChannel = ch === 0 ? left : right;
for (let j = 0; j < 32; j++) {
outChannel[outPos + j] = this.U[j] / 2147418112;
}
} // End of synthesis channel loop
outPos += 32;
} // End of synthesis sub-block loop
} // Decoding of the granule finished
}
this.sampleRate = sampleRate;
return frameSize;
}
readAllocation(sb, tab3) {
const tab4 = MP2.QUANT_LUT_STEP_3[tab3][sb];
const qtab = MP2.QUANT_LUT_STEP4[tab4 & 15][this.bits.read(tab4 >> 4)];
return qtab ? MP2.QUANT_TAB[qtab - 1] : 0;
}
readSamples(ch, sb, part) {
const q = this.allocation[ch][sb];
let sf = this.scaleFactor[ch][sb][part];
const sample = this.sample[ch][sb];
let val = 0;
if (!q) {
// No bits allocated for this subband
sample[0] = sample[1] = sample[2] = 0;
return;
}
// Resolve scalefactor
if (sf === 63) {
sf = 0;
} else {
const shift = sf / 3 | 0;
sf = MP2.SCALEFACTOR_BASE[sf % 3] + (1 << shift >> 1) >> shift;
}
// Decode samples
let adj = q.levels;
if (q.group) {
// Decode grouped samples
val = this.bits.read(q.bits);
sample[0] = val % adj;
val = val / adj | 0;
sample[1] = val % adj;
sample[2] = val / adj | 0;
} else {
// Decode direct samples
sample[0] = this.bits.read(q.bits);
sample[1] = this.bits.read(q.bits);
sample[2] = this.bits.read(q.bits);
}
// Postmultiply samples
const scale = 65536 / (adj + 1) | 0;
adj = (adj + 1 >> 1) - 1;
val = (adj - sample[0]) * scale;
sample[0] = val * (sf >> 12) + (val * (sf & 4095) + 2048 >> 12) >> 12;
val = (adj - sample[1]) * scale;
sample[1] = val * (sf >> 12) + (val * (sf & 4095) + 2048 >> 12) >> 12;
val = (adj - sample[2]) * scale;
sample[2] = val * (sf >> 12) + (val * (sf & 4095) + 2048 >> 12) >> 12;
}
static MatrixTransform(s, ss, d, dp) {
let t01;
let t02;
let t03;
let t04;
let t05;
let t06;
let t07;
let t08;
let t09;
let t10;
let t11;
let t12;
let t13;
let t14;
let t15;
let t16;
let t17;
let t18;
let t19;
let t20;
let t21;
let t22;
let t23;
let t24;
let t25;
let t26;
let t27;
let t28;
let t29;
let t30;
let t31;
let t32;
let t33;
t01 = s[0][ss] + s[31][ss];
t02 = (s[0][ss] - s[31][ss]) * 0.500602998235;
t03 = s[1][ss] + s[30][ss];
t04 = (s[1][ss] - s[30][ss]) * 0.505470959898;
t05 = s[2][ss] + s[29][ss];
t06 = (s[2][ss] - s[29][ss]) * 0.515447309923;
t07 = s[3][ss] + s[28][ss];
t08 = (s[3][ss] - s[28][ss]) * 0.53104259109;
t09 = s[4][ss] + s[27][ss];
t10 = (s[4][ss] - s[27][ss]) * 0.553103896034;
t11 = s[5][ss] + s[26][ss];
t12 = (s[5][ss] - s[26][ss]) * 0.582934968206;
t13 = s[6][ss] + s[25][ss];
t14 = (s[6][ss] - s[25][ss]) * 0.622504123036;
t15 = s[7][ss] + s[24][ss];
t16 = (s[7][ss] - s[24][ss]) * 0.674808341455;
t17 = s[8][ss] + s[23][ss];
t18 = (s[8][ss] - s[23][ss]) * 0.744536271002;
t19 = s[9][ss] + s[22][ss];
t20 = (s[9][ss] - s[22][ss]) * 0.839349645416;
t21 = s[10][ss] + s[21][ss];
t22 = (s[10][ss] - s[21][ss]) * 0.972568237862;
t23 = s[11][ss] + s[20][ss];
t24 = (s[11][ss] - s[20][ss]) * 1.16943993343;
t25 = s[12][ss] + s[19][ss];
t26 = (s[12][ss] - s[19][ss]) * 1.48416461631;
t27 = s[13][ss] + s[18][ss];
t28 = (s[13][ss] - s[18][ss]) * 2.05778100995;
t29 = s[14][ss] + s[17][ss];
t30 = (s[14][ss] - s[17][ss]) * 3.40760841847;
t31 = s[15][ss] + s[16][ss];
t32 = (s[15][ss] - s[16][ss]) * 10.1900081235;
t33 = t01 + t31;
t31 = (t01 - t31) * 0.502419286188;
t01 = t03 + t29;
t29 = (t03 - t29) * 0.52249861494;
t03 = t05 + t27;
t27 = (t05 - t27) * 0.566944034816;
t05 = t07 + t25;
t25 = (t07 - t25) * 0.64682178336;
t07 = t09 + t23;
t23 = (t09 - t23) * 0.788154623451;
t09 = t11 + t21;
t21 = (t11 - t21) * 1.06067768599;
t11 = t13 + t19;
t19 = (t13 - t19) * 1.72244709824;
t13 = t15 + t17;
t17 = (t15 - t17) * 5.10114861869;
t15 = t33 + t13;
t13 = (t33 - t13) * 0.509795579104;
t33 = t01 + t11;
t01 = (t01 - t11) * 0.601344886935;
t11 = t03 + t09;
t09 = (t03 - t09) * 0.899976223136;
t03 = t05 + t07;
t07 = (t05 - t07) * 2.56291544774;
t05 = t15 + t03;
t15 = (t15 - t03) * 0.541196100146;
t03 = t33 + t11;
t11 = (t33 - t11) * 1.30656296488;
t33 = t05 + t03;
t05 = (t05 - t03) * 0.707106781187;
t03 = t15 + t11;
t15 = (t15 - t11) * 0.707106781187;
t03 += t15;
t11 = t13 + t07;
t13 = (t13 - t07) * 0.541196100146;
t07 = t01 + t09;
t09 = (t01 - t09) * 1.30656296488;
t01 = t11 + t07;
t07 = (t11 - t07) * 0.707106781187;
t11 = t13 + t09;
t13 = (t13 - t09) * 0.707106781187;
t11 += t13;
t01 += t11;
t11 += t07;
t07 += t13;
t09 = t31 + t17;
t31 = (t31 - t17) * 0.509795579104;
t17 = t29 + t19;
t29 = (t29 - t19) * 0.601344886935;
t19 = t27 + t21;
t21 = (t27 - t21) * 0.899976223136;
t27 = t25 + t23;
t23 = (t25 - t23) * 2.56291544774;
t25 = t09 + t27;
t09 = (t09 - t27) * 0.541196100146;
t27 = t17 + t19;
t19 = (t17 - t19) * 1.30656296488;
t17 = t25 + t27;
t27 = (t25 - t27) * 0.707106781187;
t25 = t09 + t19;
t19 = (t09 - t19) * 0.707106781187;
t25 += t19;
t09 = t31 + t23;
t31 = (t31 - t23) * 0.541196100146;
t23 = t29 + t21;
t21 = (t29 - t21) * 1.30656296488;
t29 = t09 + t23;
t23 = (t09 - t23) * 0.707106781187;
t09 = t31 + t21;
t31 = (t31 - t21) * 0.707106781187;
t09 += t31;
t29 += t09;
t09 += t23;
t23 += t31;
t17 += t29;
t29 += t25;
t25 += t09;
t09 += t27;
t27 += t23;
t23 += t19;
t19 += t31;
t21 = t02 + t32;
t02 = (t02 - t32) * 0.502419286188;
t32 = t04 + t30;
t04 = (t04 - t30) * 0.52249861494;
t30 = t06 + t28;
t28 = (t06 - t28) * 0.566944034816;
t06 = t08 + t26;
t08 = (t08 - t26) * 0.64682178336;
t26 = t10 + t24;
t10 = (t10 - t24) * 0.788154623451;
t24 = t12 + t22;
t22 = (t12 - t22) * 1.06067768599;
t12 = t14 + t20;
t20 = (t14 - t20) * 1.72244709824;
t14 = t16 + t18;
t16 = (t16 - t18) * 5.10114861869;
t18 = t21 + t14;
t14 = (t21 - t14) * 0.509795579104;
t21 = t32 + t12;
t32 = (t32 - t12) * 0.601344886935;
t12 = t30 + t24;
t24 = (t30 - t24) * 0.899976223136;
t30 = t06 + t26;
t26 = (t06 - t26) * 2.56291544774;
t06 = t18 + t30;
t18 = (t18 - t30) * 0.541196100146;
t30 = t21 + t12;
t12 = (t21 - t12) * 1.30656296488;
t21 = t06 + t30;
t30 = (t06 - t30) * 0.707106781187;
t06 = t18 + t12;
t12 = (t18 - t12) * 0.707106781187;
t06 += t12;
t18 = t14 + t26;
t26 = (t14 - t26) * 0.541196100146;
t14 = t32 + t24;
t24 = (t32 - t24) * 1.30656296488;
t32 = t18 + t14;
t14 = (t18 - t14) * 0.707106781187;
t18 = t26 + t24;
t24 = (t26 - t24) * 0.707106781187;
t18 += t24;
t32 += t18;
t18 += t14;
t26 = t14 + t24;
t14 = t02 + t16;
t02 = (t02 - t16) * 0.509795579104;
t16 = t04 + t20;
t04 = (t04 - t20) * 0.601344886935;
t20 = t28 + t22;
t22 = (t28 - t22) * 0.899976223136;
t28 = t08 + t10;
t10 = (t08 - t10) * 2.56291544774;
t08 = t14 + t28;
t14 = (t14 - t28) * 0.541196100146;
t28 = t16 + t20;
t20 = (t16 - t20) * 1.30656296488;
t16 = t08 + t28;
t28 = (t08 - t28) * 0.707106781187;
t08 = t14 + t20;
t20 = (t14 - t20) * 0.707106781187;
t08 += t20;
t14 = t02 + t10;
t02 = (t02 - t10) * 0.541196100146;
t10 = t04 + t22;
t22 = (t04 - t22) * 1.30656296488;
t04 = t14 + t10;
t10 = (t14 - t10) * 0.707106781187;
t14 = t02 + t22;
t02 = (t02 - t22) * 0.707106781187;
t14 += t02;
t04 += t14;
t14 += t10;
t10 += t02;
t16 += t04;
t04 += t08;
t08 += t14;
t14 += t28;
t28 += t10;
t10 += t20;
t20 += t02;
t21 += t16;
t16 += t32;
t32 += t04;
t04 += t06;
t06 += t08;
t08 += t18;
t18 += t14;
t14 += t30;
t30 += t28;
t28 += t26;
t26 += t10;
t10 += t12;
t12 += t20;
t20 += t24;
t24 += t02;
d[dp + 48] = -t33;
d[dp + 49] = d[dp + 47] = -t21;
d[dp + 50] = d[dp + 46] = -t17;
d[dp + 51] = d[dp + 45] = -t16;
d[dp + 52] = d[dp + 44] = -t01;
d[dp + 53] = d[dp + 43] = -t32;
d[dp + 54] = d[dp + 42] = -t29;
d[dp + 55] = d[dp + 41] = -t04;
d[dp + 56] = d[dp + 40] = -t03;
d[dp + 57] = d[dp + 39] = -t06;
d[dp + 58] = d[dp + 38] = -t25;
d[dp + 59] = d[dp + 37] = -t08;
d[dp + 60] = d[dp + 36] = -t11;
d[dp + 61] = d[dp + 35] = -t18;
d[dp + 62] = d[dp + 34] = -t09;
d[dp + 63] = d[dp + 33] = -t14;
d[dp + 32] = -t05;
d[dp + 0] = t05;
d[dp + 31] = -t30;
d[dp + 1] = t30;
d[dp + 30] = -t27;
d[dp + 2] = t27;
d[dp + 29] = -t28;
d[dp + 3] = t28;
d[dp + 28] = -t07;
d[dp + 4] = t07;
d[dp + 27] = -t26;
d[dp + 5] = t26;
d[dp + 26] = -t23;
d[dp + 6] = t23;
d[dp + 25] = -t10;
d[dp + 7] = t10;
d[dp + 24] = -t15;
d[dp + 8] = t15;
d[dp + 23] = -t12;
d[dp + 9] = t12;
d[dp + 22] = -t19;
d[dp + 10] = t19;
d[dp + 21] = -t20;
d[dp + 11] = t20;
d[dp + 20] = -t13;
d[dp + 12] = t13;
d[dp + 19] = -t24;
d[dp + 13] = t24;
d[dp + 18] = -t31;
d[dp + 14] = t31;
d[dp + 17] = -t02;
d[dp + 15] = t02;
d[dp + 16] = 0.0;
}
}
MP2.FRAME_SYNC = 0x7ff;
MP2.VERSION = {
MPEG_2_5: 0x0,
MPEG_2: 0x2,
MPEG_1: 0x3
};
MP2.LAYER = {
III: 0x1,
II: 0x2,
I: 0x3
};
MP2.MODE = {
STEREO: 0x0,
JOINT_STEREO: 0x1,
DUAL_CHANNEL: 0x2,
MONO: 0x3
};
MP2.SAMPLE_RATE = new Uint16Array([44100, 48000, 32000, 0,
// MPEG-1
22050, 24000, 16000, 0 // MPEG-2
]);
MP2.BIT_RATE = new Uint16Array([32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384,
// MPEG-1
8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 // MPEG-2
]);
MP2.SCALEFACTOR_BASE = new Uint32Array([0x02000000, 0x01965fea, 0x01428a30]);
MP2.SYNTHESIS_WINDOW = new Float32Array([0.0, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -1.0, -1.0, -1.0, -1.0, -1.5, -1.5, -2.0, -2.0, -2.5, -2.5, -3.0, -3.5, -3.5, -4.0, -4.5, -5.0, -5.5, -6.5, -7.0, -8.0, -8.5, -9.5, -10.5, -12.0, -13.0, -14.5, -15.5, -17.5, -19.0, -20.5, -22.5, -24.5, -26.5, -29.0, -31.5, -34.0, -36.5, -39.5, -42.5, -45.5, -48.5, -52.0, -55.5, -58.5, -62.5, -66.0, -69.5, -73.5, -77.0, -80.5, -84.5, -88.0, -91.5, -95.0, -98.0, -101.0, -104.0, 106.5, 109.0, 111.0, 112.5, 113.5, 114.0, 114.0, 113.5, 112.0, 110.5, 107.5, 104.0, 100.0, 94.5, 88.5, 81.5, 73.0, 63.5, 53.0, 41.5, 28.5, 14.5, -1.0, -18.0, -36.0, -55.5, -76.5, -98.5, -122.0, -147.0, -173.5, -200.5, -229.5, -259.5, -290.5, -322.5, -355.5, -389.5, -424.0, -459.5, -495.5, -532.0, -568.5, -605.0, -641.5, -678.0, -714.0, -749.0, -783.5, -817.0, -849.0, -879.5, -908.5, -935.0, -959.5, -981.0, -1000.5, -1016.0, -1028.5, -1037.5, -1042.5, -1043.5, -1040.0, -1031.5, 1018.5, 1000.0, 976.0, 946.5, 911.0, 869.5, 822.0, 767.5, 707.0, 640.0, 565.5, 485.0, 397.0, 302.5, 201.0, 92.5, -22.5, -144.0, -272.5, -407.0, -547.5, -694.0, -846.0, -1003.0, -1165.0, -1331.5, -1502.0, -1675.5, -1852.5, -2031.5, -2212.5, -2394.0, -2576.5, -2758.5, -2939.5, -3118.5, -3294.5, -3467.5, -3635.5, -3798.5, -3955.0, -4104.5, -4245.5, -4377.5, -4499.0, -4609.5, -4708.0, -4792.5, -4863.5, -4919.0, -4958.0, -4979.5, -4983.0, -4967.5, -4931.5, -4875.0, -4796.0, -4694.5, -4569.5, -4420.0, -4246.0, -4046.0, -3820.0, -3567.0, 3287.0, 2979.5, 2644.0, 2280.5, 1888.0, 1467.5, 1018.5, 541.0, 35.0, -499.0, -1061.0, -1650.0, -2266.5, -2909.0, -3577.0, -4270.0, -4987.5, -5727.5, -6490.0, -7274.0, -8077.5, -8899.5, -9739.0, -10594.5, -11464.5, -12347.0, -13241.0, -14144.5, -15056.0, -15973.5, -16895.5, -17820.0, -18744.5, -19668.0, -20588.0, -21503.0, -22410.5, -23308.5, -24195.0, -25068.5, -25926.5, -26767.0, -27589.0, -28389.0, -29166.5, -29919.0, -30644.5, -31342.0, -32009.5, -32645.0, -33247.0, -33814.5, -34346.0, -34839.5, -35295.0, -35710.0, -36084.5, -36417.5, -36707.5, -36954.0, -37156.5, -37315.0, -37428.0, -37496.0, 37519.0, 37496.0, 37428.0, 37315.0, 37156.5, 36954.0, 36707.5, 36417.5, 36084.5, 35710.0, 35295.0, 34839.5, 34346.0, 33814.5, 33247.0, 32645.0, 32009.5, 31342.0, 30644.5, 29919.0, 29166.5, 28389.0, 27589.0, 26767.0, 25926.5, 25068.5, 24195.0, 23308.5, 22410.5, 21503.0, 20588.0, 19668.0, 18744.5, 17820.0, 16895.5, 15973.5, 15056.0, 14144.5, 13241.0, 12347.0, 11464.5, 10594.5, 9739.0, 8899.5, 8077.5, 7274.0, 6490.0, 5727.5, 4987.5, 4270.0, 3577.0, 2909.0, 2266.5, 1650.0, 1061.0, 499.0, -35.0, -541.0, -1018.5, -1467.5, -1888.0, -2280.5, -2644.0, -2979.5, 3287.0, 3567.0, 3820.0, 4046.0, 4246.0, 4420.0, 4569.5, 4694.5, 4796.0, 4875.0, 4931.5, 4967.5, 4983.0, 4979.5, 4958.0, 4919.0, 4863.5, 4792.5, 4708.0, 4609.5, 4499.0, 4377.5, 4245.5, 4104.5, 3955.0, 3798.5, 3635.5, 3467.5, 3294.5, 3118.5, 2939.5, 2758.5, 2576.5, 2394.0, 2212.5, 2031.5, 1852.5, 1675.5, 1502.0, 1331.5, 1165.0, 1003.0, 846.0, 694.0, 547.5, 407.0, 272.5, 144.0, 22.5, -92.5, -201.0, -302.5, -397.0, -485.0, -565.5, -640.0, -707.0, -767.5, -822.0, -869.5, -911.0, -946.5, -976.0, -1000.0, 1018.5, 1031.5, 1040.0, 1043.5, 1042.5, 1037.5, 1028.5, 1016.0, 1000.5, 981.0, 959.5, 935.0, 908.5, 879.5, 849.0, 817.0, 783.5, 749.0, 714.0, 678.0, 641.5, 605.0, 568.5, 532.0, 495.5, 459.5, 424.0, 389.5, 355.5, 322.5, 290.5, 259.5, 229.5, 200.5, 173.5, 147.0, 122.0, 98.5, 76.5, 55.5, 36.0, 18.0, 1.0, -14.5, -28.5, -41.5, -53.0, -63.5, -73.0, -81.5, -88.5, -94.5, -100.0, -104.0, -107.5, -110.5, -112.0, -113.5, -114.0, -114.0, -113.5, -112.5, -111.0, -109.0, 106.5, 104.0, 101.0, 98.0, 95.0, 91.5, 88.0, 84.5, 80.5, 77.0, 73.5, 69.5, 66.0, 62.5, 58.5, 55.5, 52.0, 48.5, 45.5, 42.5, 39.5, 36.5, 34.0, 31.5, 29.0, 26.5, 24.5, 22.5, 20.5, 19.0, 17.5, 15.5, 14.5, 13.0, 12.0, 10.5, 9.5, 8.5, 8.0, 7.0, 6.5, 5.5, 5.0, 4.5, 4.0, 3.5, 3.5, 3.0, 2.5, 2.5, 2.0, 2.0, 1.5, 1.5, 1.0, 1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]);
// Quantizer lookup, step 1: bitrate classes
MP2.QUANT_LUT_STEP_1 = [
// 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384 <- bitrate
[0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2],
// mono
// 16, 24, 28, 32, 40, 48, 56, 64, 80, 96,112,128,160,192 <- bitrate / chan
[0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2] // stereo
];
// Quantizer lookup, step 2: bitrate class, sample rate -> B2 table idx, sblimit
MP2.QUANT_TAB = {
A: 27 | 64,
// Table 3-B.2a: high-rate, sblimit = 27
B: 30 | 64,
// Table 3-B.2b: high-rate, sblimit = 30
C: 8,
// Table 3-B.2c: low-rate, sblimit = 8
D: 12 // Table 3-B.2d: low-rate, sblimit = 12
};
MP2.QUANT_LUT_STEP_2 = [
// 44.1 kHz, 48 kHz, 32 kHz
[MP2.QUANT_TAB.C, MP2.QUANT_TAB.C, MP2.QUANT_TAB.D],
// 32 - 48 kbit/sec/ch
[MP2.QUANT_TAB.A, MP2.QUANT_TAB.A, MP2.QUANT_TAB.A],
// 56 - 80 kbit/sec/ch
[MP2.QUANT_TAB.B, MP2.QUANT_TAB.A, MP2.QUANT_TAB.B] // 96+ kbit/sec/ch
];
// Quantizer lookup, step 3: B2 table, subband -> nbal, row index
// (upper 4 bits: nbal, lower 4 bits: row index)
MP2.QUANT_LUT_STEP_3 = [
// Low-rate table (3-B.2c and 3-B.2d)
[0x44, 0x44, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34],
// High-rate table (3-B.2a and 3-B.2b)
[0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20],
// MPEG-2 LSR table (B.2 in ISO 13818-3)
[0x45, 0x45, 0x45, 0x45, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24]];
// Quantizer lookup, step 4: table row, allocation[] value -> quant table index
MP2.QUANT_LUT_STEP4 = [[0, 1, 2, 17], [0, 1, 2, 3, 4, 5, 6, 17], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17], [0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]];
MP2.QUANT_TAB = [{
levels: 3,
group: 1,
bits: 5
},
// 1
{
levels: 5,
group: 1,
bits: 7
},
// 2
{
levels: 7,
group: 0,
bits: 3
},
// 3
{
levels: 9,
group: 1,
bits: 10
},
// 4
{
levels: 15,
group: 0,
bits: 4
},
// 5
{
levels: 31,
group: 0,
bits: 5
},
// 6
{
levels: 63,
group: 0,
bits: 6
},
// 7
{
levels: 127,
group: 0,
bits: 7
},
// 8
{
levels: 255,
group: 0,
bits: 8
},
// 9
{
levels: 511,
group: 0,
bits: 9
},
// 10
{
levels: 1023,
group: 0,
bits: 10
},
// 11
{
levels: 2047,
group: 0,
bits: 11
},
// 12
{
levels: 4095,
group: 0,
bits: 12
},
// 13
{
levels: 8191,
group: 0,
bits: 13
},
// 14
{
levels: 16383,
group: 0,
bits: 14
},
// 15
{
levels: 32767,
group: 0,
bits: 15
},
// 16
{
levels: 65535,
group: 0,
bits: 16
} // 17
];
// Based on kjmp2 by Martin J. Fiedler
class MP2WASM extends BaseDecoder {
constructor(options) {
super(options);
this.onDecodeCallback = options.onAudioDecode;
this.module = options.wasmModule;
this.bufferSize = options.audioBufferSize || 128 * 1024;
this.bufferMode = options.streaming ? BitBuffer.MODE.EVICT : BitBuffer.MODE.EXPAND;
this.sampleRate = 0;
}
initializeWasmDecoder() {
if (!this.module.instance) {
console.warn('JSMpeg: WASM module not compiled yet');
return;
}
this.instance = this.module.instance;
this.functions = this.module.instance.exports;
this.decoder = this.functions._mp2_decoder_create(this.bufferSize, this.bufferMode);
}
destroy() {
if (!this.decoder) {
return;
}
this.functions._mp2_decoder_destroy(this.decoder);
}
bufferGetIndex() {
if (!this.decoder) {
return;
}
// eslint-disable-next-line consistent-return
return this.functions._mp2_decoder_get_index(this.decoder);
}
bufferSetIndex(index) {
if (!this.decoder) {
return;
}
this.functions._mp2_decoder_set_index(this.decoder, index);
}
bufferWrite(buffers) {
if (!this.decoder) {
this.initializeWasmDecoder();
}
let totalLength = 0;
for (let i = 0; i < buffers.length; i++) {
totalLength += buffers[i].length;
}
let ptr = this.functions._mp2_decoder_get_write_ptr(this.decoder, totalLength);
for (let i = 0; i < buffers.length; i++) {
this.instance.heapU8.set(buffers[i], ptr);
ptr += buffers[i].length;
}
this.functions._mp2_decoder_did_write(this.decoder, totalLength);
return totalLength;
}
decode() {
const startTime = Now();
if (!this.decoder) {
return false;
}
const decodedBytes = this.functions._mp2_decoder_decode(this.decoder);
if (decodedBytes === 0) {
return false;
}
if (!this.sampleRate) {
this.sampleRate = this.functions._mp2_decoder_get_sample_rate(this.decoder);
}
if (this.destination) {
// Create a Float32 View into the modules output channel data
const leftPtr = this.functions._mp2_decoder_get_left_channel_ptr(this.decoder);
const rightPtr = this.functions._mp2_decoder_get_right_channel_ptr(this.decoder);
const leftOffset = leftPtr / Float32Array.BYTES_PER_ELEMENT;
const rightOffset = rightPtr / Float32Array.BYTES_PER_ELEMENT;
const left = this.instance.heapF32.subarray(leftOffset, leftOffset + MP2WASM.SAMPLES_PER_FRAME);
const right = this.instance.heapF32.subarray(rightOffset, rightOffset + MP2WASM.SAMPLES_PER_FRAME);
this.destination.play(this.sampleRate, left, right);
}
this.advanceDecodedTime(MP2WASM.SAMPLES_PER_FRAME / this.sampleRate);
const elapsedTime = Now() - startTime;
if (this.onDecodeCallback) {
this.onDecodeCallback(this, elapsedTime);
}
return true;
}
getCurrentTime() {
const enqueuedTime = this.destination ? this.destination.enqueuedTime : 0;
return this.decodedTime - enqueuedTime;
}
}
MP2WASM.SAMPLES_PER_FRAME = 1152;
// Inspired by Java MPEG-1 Video Decoder and Player by Zoltan Korandi
class MPEG1 extends BaseDecoder {
constructor(options) {
super(options);
this.onDecodeCallback = options.onVideoDecode;
const bufferSize = options.videoBufferSize || 512 * 1024;
const bufferMode = options.streaming ? BitBuffer.MODE.EVICT : BitBuffer.MODE.EXPAND;
this.bits = new BitBuffer(bufferSize, bufferMode);
this.customIntraQuantMatrix = new Uint8Array(64);
this.customNonIntraQuantMatrix = new Uint8Array(64);
this.blockData = new Int32Array(64);
this.currentFrame = 0;
this.decodeFirstFrame = options.decodeFirstFrame !== false;
}
// eslint-disable-next-line consistent-return
write(pts, buffers) {
BaseDecoder.prototype.write.call(this, pts, buffers);
if (!this.hasSequenceHeader) {
if (this.bits.findStartCode(MPEG1.START.SEQUENCE) === -1) {
return false;
}
this.decodeSequenceHeader();
if (this.decodeFirstFrame) {
this.decode();
}
}
}
decode() {
const startTime = Now();
if (!this.hasSequenceHeader) {
return false;
}
if (this.bits.findStartCode(MPEG1.START.PICTURE) === -1) {
return false;
}
this.decodePicture();
this.advanceDecodedTime(1 / this.frameRate);
const elapsedTime = Now() - startTime;
if (this.onDecodeCallback) {
this.onDecodeCallback(this, elapsedTime);
}
return true;
}
readHuffman(codeTable) {
let state = 0;
do {
state = codeTable[state + this.bits.read(1)];
} while (state >= 0 && codeTable[state] !== 0);
return codeTable[state + 2];
}
decodeSequenceHeader() {
const newWidth = this.bits.read(12);
const newHeight = this.bits.read(12);
// skip pixel aspect ratio
this.bits.skip(4);
this.frameRate = MPEG1.PICTURE_RATE[this.bits.read(4)];
// skip bitRate, marker, bufferSize and constrained bit
this.bits.skip(18 + 1 + 10 + 1);
if (newWidth !== this.width || newHeight !== this.height) {
this.width = newWidth;
this.height = newHeight;
this.initBuffers();
if (this.destination) {
this.destination.resize(newWidth, newHeight);
}
}
if (this.bits.read(1)) {
// load custom intra quant matrix?
for (let i = 0; i < 64; i++) {
this.customIntraQuantMatrix[MPEG1.ZIG_ZAG[i]] = this.bits.read(8);
}
this.intraQuantMatrix = this.customIntraQuantMatrix;
}
if (this.bits.read(1)) {
// load custom non intra quant matrix?
for (let i = 0; i < 64; i++) {
const idx = MPEG1.ZIG_ZAG[i];
this.customNonIntraQuantMatrix[idx] = this.bits.read(8);
}
this.nonIntraQuantMatrix = this.customNonIntraQuantMatrix;
}
this.hasSequenceHeader = true;
}
initBuffers() {
this.intraQuantMatrix = MPEG1.DEFAULT_INTRA_QUANT_MATRIX;
this.nonIntraQuantMatrix = MPEG1.DEFAULT_NON_INTRA_QUANT_MATRIX;
this.mbWidth = this.width + 15 >> 4;
this.mbHeight = this.height + 15 >> 4;
this.mbSize = this.mbWidth * this.mbHeight;
this.codedWidth = this.mbWidth << 4;
this.codedHeight = this.mbHeight << 4;
this.codedSize = this.codedWidth * this.codedHeight;
this.halfWidth = this.mbWidth << 3;
this.halfHeight = this.mbHeight << 3;
// Allocated buffers and resize the canvas
this.currentY = new Uint8ClampedArray(this.codedSize);
this.currentY32 = new Uint32Array(this.currentY.buffer);
this.currentCr = new Uint8ClampedArray(this.codedSize >> 2);
this.currentCr32 = new Uint32Array(this.currentCr.buffer);
this.currentCb = new Uint8ClampedArray(this.codedSize >> 2);
this.currentCb32 = new Uint32Array(this.currentCb.buffer);
this.forwardY = new Uint8ClampedArray(this.codedSize);
this.forwardY32 = new Uint32Array(this.forwardY.buffer);
this.forwardCr = new Uint8ClampedArray(this.codedSize >> 2);
this.forwardCr32 = new Uint32Array(this.forwardCr.buffer);
this.forwardCb = new Uint8ClampedArray(this.codedSize >> 2);
this.forwardCb32 = new Uint32Array(this.forwardCb.buffer);
}
decodePicture() {
this.currentFrame++;
this.bits.skip(10); // skip temporalReference
this.pictureType = this.bits.read(3);
this.bits.skip(16); // skip vbv_delay
// Skip B and D frames or unknown coding type
if (this.pictureType <= 0 || this.pictureType >= MPEG1.PICTURE_TYPE.B) {
return;
}
// full_pel_forward, forward_f_code
if (this.pictureType === MPEG1.PICTURE_TYPE.PREDICTIVE) {
this.fullPelForward = this.bits.read(1);
this.forwardFCode = this.bits.read(3);
if (this.forwardFCode === 0) {
// Ignore picture with zero forward_f_code
return;
}
this.forwardRSize = this.forwardFCode - 1;
this.forwardF = 1 << this.forwardRSize;
}
let code = 0;
do {
code = this.bits.findNextStartCode();
} while (code === MPEG1.START.EXTENSION || code === MPEG1.START.USER_DATA);
while (code >= MPEG1.START.SLICE_FIRST && code <= MPEG1.START.SLICE_LAST) {
this.decodeSlice(code & 0x000000ff);
code = this.bits.findNextStartCode();
}
if (code !== -1) {
// We found the next start code; rewind 32bits and let the main loop
// handle it.
this.bits.rewind(32);
}
// Invoke decode callbacks
if (this.destination) {
this.destination.render(this.currentY, this.currentCr, this.currentCb, true);
}
// If this is a reference picutre then rotate the prediction pointers
if (this.pictureType === MPEG1.PICTURE_TYPE.INTRA || this.pictureType === MPEG1.PICTURE_TYPE.PREDICTIVE) {
const tmpY = this.forwardY;
const tmpY32 = this.forwardY32;
const tmpCr = this.forwardCr;
const tmpCr32 = this.forwardCr32;
const tmpCb = this.forwardCb;
const tmpCb32 = this.forwardCb32;
this.forwardY = this.currentY;
this.forwardY32 = this.currentY32;
this.forwardCr = this.currentCr;
this.forwardCr32 = this.currentCr32;
this.forwardCb = this.currentCb;
this.forwardCb32 = this.currentCb32;
this.currentY = tmpY;
this.currentY32 = tmpY32;
this.currentCr = tmpCr;
this.currentCr32 = tmpCr32;
this.currentCb = tmpCb;
this.currentCb32 = tmpCb32;
}
}
decodeSlice(slice) {
this.sliceBegin = true;
this.macroblockAddress = (slice - 1) * this.mbWidth - 1;
// Reset motion vectors and DC predictors
this.motionFwH = this.motionFwHPrev = 0;
this.motionFwV = this.motionFwVPrev = 0;
this.dcPredictorY = 128;
this.dcPredictorCr = 128;
this.dcPredictorCb = 128;
this.quantizerScale = this.bits.read(5);
// skip extra bits
while (this.bits.read(1)) {
this.bits.skip(8);
}
do {
this.decodeMacroblock();
} while (!this.bits.nextBytesAreStartCode());
}
decodeMacroblock() {
// Decode macroblock_address_increment
let increment = 0;
let t = this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT);
while (t === 34) {
// macroblock_stuffing
t = this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT);
}
while (t === 35) {
// macroblock_escape
increment += 33;
t = this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT);
}
increment += t;
// Process any skipped macroblocks
if (this.sliceBegin) {
// The first macroblock_address_increment of each slice is relative
// to beginning of the preverious row, not the preverious macroblock
this.sliceBegin = false;
this.macroblockAddress += increment;
} else {
if (this.macroblockAddress + increment >= this.mbSize) {
// Illegal (too large) macroblock_address_increment
return;
}
if (increment > 1) {
// Skipped macroblocks reset DC predictors
this.dcPredictorY = 128;
this.dcPredictorCr = 128;
this.dcPredictorCb = 128;
// Skipped macroblocks in P-pictures reset motion vectors
if (this.pictureType === MPEG1.PICTURE_TYPE.PREDICTIVE) {
this.motionFwH = this.motionFwHPrev = 0;
this.motionFwV = this.motionFwVPrev = 0;
}
}
// Predict skipped macroblocks
while (increment > 1) {
this.macroblockAddress++;
this.mbRow = this.macroblockAddress / this.mbWidth | 0;
this.mbCol = this.macroblockAddress % this.mbWidth;
this.copyMacroblock(this.motionFwH, this.motionFwV, this.forwardY, this.forwardCr, this.forwardCb);
increment--;