diff --git a/src/views/components/jsmpeg/index.js b/src/views/components/jsmpeg/index.js new file mode 100644 index 0000000..3c45e26 --- /dev/null +++ b/src/views/components/jsmpeg/index.js @@ -0,0 +1,3 @@ +import JSMpeg from './modules/jsmpeg' + +export default JSMpeg diff --git a/src/views/components/jsmpeg/modules/audio-output/index.js b/src/views/components/jsmpeg/modules/audio-output/index.js new file mode 100644 index 0000000..c38d0fb --- /dev/null +++ b/src/views/components/jsmpeg/modules/audio-output/index.js @@ -0,0 +1,7 @@ +import WebAudioOut from './webaudio' + +const AudioOutput = { + WebAudio: WebAudioOut +} + +export default AudioOutput diff --git a/src/views/components/jsmpeg/modules/audio-output/webaudio.js b/src/views/components/jsmpeg/modules/audio-output/webaudio.js new file mode 100644 index 0000000..04d1c0c --- /dev/null +++ b/src/views/components/jsmpeg/modules/audio-output/webaudio.js @@ -0,0 +1,157 @@ +import { Now } from '../../utils' + +export default class WebAudioOut { + /** @type {AudioContext} */ + context + /** @type {GainNode} */ + gain + /** @type {GainNode} */ + destination + /** @type {number} */ + startTime + buffer + /** @type {number} */ + wallclockStartTime + /** @type {number} */ + volume + /** @type {boolean} */ + enabled + /** @type {boolean} */ + unlocked + constructor(options) { + this.context = WebAudioOut.CachedContext = + WebAudioOut.CachedContext || + new (window.AudioContext || window.webkitAudioContext)() + + this.gain = this.context.createGain() + this.destination = this.gain + + // Keep track of the number of connections to this AudioContext, so we + // can safely close() it when we're the only one connected to it. + this.gain.connect(this.context.destination) + this.context._connections = (this.context._connections || 0) + 1 + + this.startTime = 0 + this.buffer = null + this.wallclockStartTime = 0 + this.volume = 1 + this.enabled = true + + this.unlocked = !WebAudioOut.NeedsUnlocking() + + Object.defineProperty(this, 'enqueuedTime', { get: this.getEnqueuedTime }) + } + + destroy() { + this.gain.disconnect() + this.context._connections-- + + if (this.context._connections === 0) { + this.context.close() + WebAudioOut.CachedContext = null + } + } + + play(sampleRate, left, right) { + if (!this.enabled) { + return + } + + // If the context is not unlocked yet, we simply advance the start time + // to "fake" actually playing audio. This will keep the video in sync. + if (!this.unlocked) { + const ts = Now() + if (this.wallclockStartTime < ts) { + this.wallclockStartTime = ts + } + this.wallclockStartTime += left.length / sampleRate + return + } + + this.gain.gain.value = this.volume + + const buffer = this.context.createBuffer(2, left.length, sampleRate) + buffer.getChannelData(0).set(left) + buffer.getChannelData(1).set(right) + + const source = this.context.createBufferSource() + source.buffer = buffer + source.connect(this.destination) + + const now = this.context.currentTime + const duration = buffer.duration + if (this.startTime < now) { + this.startTime = now + this.wallclockStartTime = Now() + } + + source.start(this.startTime) + this.startTime += duration + this.wallclockStartTime += duration + } + + stop() { + // Meh; there seems to be no simple way to get a list of currently + // active source nodes from the Audio Context, and maintaining this + // list ourselfs would be a pain, so we just set the gain to 0 + // to cut off all enqueued audio instantly. + this.gain.gain.value = 0 + } + + getEnqueuedTime() { + // The AudioContext.currentTime is only updated every so often, so if we + // want to get exact timing, we need to rely on the system time. + return Math.max(this.wallclockStartTime - Now(), 0) + } + + resetEnqueuedTime() { + this.startTime = this.context.currentTime + this.wallclockStartTime = Now() + } + + unlock(callback) { + if (this.unlocked) { + if (callback) { + callback() + } + return + } + + this.unlockCallback = callback + + // Create empty buffer and play it + const buffer = this.context.createBuffer(1, 1, 22050) + const source = this.context.createBufferSource() + source.buffer = buffer + source.connect(this.destination) + source.start(0) + + setTimeout(this.checkIfUnlocked.bind(this, source, 0), 0) + } + + checkIfUnlocked(source, attempt) { + if ( + source.playbackState === source.PLAYING_STATE || + source.playbackState === source.FINISHED_STATE + ) { + this.unlocked = true + if (this.unlockCallback) { + this.unlockCallback() + this.unlockCallback = null + } + } else if (attempt < 10) { + // Jeez, what a shit show. Thanks iOS! + setTimeout(this.checkIfUnlocked.bind(this, source, attempt + 1), 100) + } + } + + static NeedsUnlocking() { + return /iPhone|iPad|iPod/i.test(navigator.userAgent) + } + + static IsSupported() { + return window.AudioContext || window.webkitAudioContext + } + + static CachedContext = null +} diff --git a/src/views/components/jsmpeg/modules/buffer.js b/src/views/components/jsmpeg/modules/buffer.js new file mode 100644 index 0000000..af22be3 --- /dev/null +++ b/src/views/components/jsmpeg/modules/buffer.js @@ -0,0 +1,191 @@ +/* eslint-disable */ +'use strict' +export default class BitBuffer { + /** @type {Uint8Array} */ + bytes + /** @type {number} */ + byteLength + /** @type {1|2} */ + mode + /** @type {number} */ + index + constructor(bufferOrLength, mode = BitBuffer.MODE.EXPAND) { + 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 + 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 = this.byteLength - bytePos + this.index -= bytePos << 3 + return + } + + write(buffers) { + const isArrayOfBuffers = typeof buffers[0] === 'object' + let totalLength = 0 + const available = this.bytes.length - this.byteLength + + // Calculate total byte length + if (isArrayOfBuffers) { + // let 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) { + let current = 0 + while (true) { + 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 + } + + static MODE = { + EVICT: 1, + EXPAND: 2 + } +} diff --git a/src/views/components/jsmpeg/modules/decoder/decoder.js b/src/views/components/jsmpeg/modules/decoder/decoder.js new file mode 100644 index 0000000..7caf457 --- /dev/null +++ b/src/views/components/jsmpeg/modules/decoder/decoder.js @@ -0,0 +1,114 @@ +/* eslint-disable */ +import WebAudioOut from '../audio-output/webaudio' +import CanvasRenderer from '../renderer/canvas2d' +import WebGLRenderer from '../renderer/webgl' + +export default class BaseDecoder { + /** + * @type {WebGLRenderer|CanvasRenderer|WebAudioOut} + */ + destination + 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.bufferSetIndex(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 + } +} diff --git a/src/views/components/jsmpeg/modules/decoder/index.js b/src/views/components/jsmpeg/modules/decoder/index.js new file mode 100644 index 0000000..51b5aba --- /dev/null +++ b/src/views/components/jsmpeg/modules/decoder/index.js @@ -0,0 +1,15 @@ +import BaseDecoder from './decoder' +import MPEG1 from './mpeg1' +import MPEG1WASM from './mpeg1-wasm' +import MP2 from './mp2' +import MP2WASM from './mp2-wasm' + +const Decoder = { + Base: BaseDecoder, + MPEG1Video: MPEG1, + MPEG1VideoWASM: MPEG1WASM, + MP2Audio: MP2, + MP2AudioWASM: MP2WASM +} + +export default Decoder diff --git a/src/views/components/jsmpeg/modules/decoder/mp2-wasm.js b/src/views/components/jsmpeg/modules/decoder/mp2-wasm.js new file mode 100644 index 0000000..76e6d06 --- /dev/null +++ b/src/views/components/jsmpeg/modules/decoder/mp2-wasm.js @@ -0,0 +1,134 @@ +import { Now } from '../../utils' +import BitBuffer from '../buffer' +import BaseDecoder from './decoder' + +export default 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 + } + 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 + } + + static SAMPLES_PER_FRAME = 1152 +} diff --git a/src/views/components/jsmpeg/modules/decoder/mp2.js b/src/views/components/jsmpeg/modules/decoder/mp2.js new file mode 100644 index 0000000..85b6ba0 --- /dev/null +++ b/src/views/components/jsmpeg/modules/decoder/mp2.js @@ -0,0 +1,831 @@ +'use strict' + +import { Fill, Now } from '../../utils' +import BitBuffer from '../buffer' +import BaseDecoder from './decoder' + +/** + * Based on kjmp2 by Martin J. Fiedler + * http://keyj.emphy.de/kjmp2/ + */ +export default 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 + ) { + return 0 // Invalid header or unsupported version + } + + let bitrateIndex = this.bits.read(4) - 1 + if (bitrateIndex > 13) { + return 0 // Invalid bit rate or 'free format' + } + + let sampleRateIndex = this.bits.read(2) + let sampleRate = MP2.SAMPLE_RATE[sampleRateIndex] + if (sampleRateIndex === 3) { + return 0 // Invalid sample rate + } + if (version === MP2.VERSION.MPEG_2) { + sampleRateIndex += 4 + bitrateIndex += 14 + } + const padding = this.bits.read(1) + // const privat = 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 + sampleRate = MP2.SAMPLE_RATE[sampleRateIndex] + const bitrate = MP2.BIT_RATE[bitrateIndex] + 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) { + this.scaleFactor[1][sb][0] = this.scaleFactor[0][sb][0] + this.scaleFactor[1][sb][1] = this.scaleFactor[0][sb][1] + 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) + this.sample[1][sb][0] = this.sample[0][sb][0] + this.sample[1][sb][1] = this.sample[0][sb][1] + 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, + t02, + t03, + t04, + t05, + t06, + t07, + t08, + t09, + t10, + t11, + t12, + t13, + t14, + t15, + t16, + t17, + t18, + t19, + t20, + t21, + t22, + t23, + t24, + t25, + t26, + t27, + t28, + t29, + t30, + t31, + t32, + 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 + } + + static FRAME_SYNC = 0x7ff + + static VERSION = { + MPEG_2_5: 0x0, + MPEG_2: 0x2, + MPEG_1: 0x3 + } + + static LAYER = { + III: 0x1, + II: 0x2, + I: 0x3 + } + + static MODE = { + STEREO: 0x0, + JOINT_STEREO: 0x1, + DUAL_CHANNEL: 0x2, + MONO: 0x3 + } + + static SAMPLE_RATE = new Uint16Array([ + 44100, + 48000, + 32000, + 0, // MPEG-1 + 22050, + 24000, + 16000, + 0 // MPEG-2 + ]) + + static 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 + ]) + + static SCALEFACTOR_BASE = new Uint32Array([ + 0x02000000, 0x01965fea, 0x01428a30 + ]) + + static 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 + static 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 + static 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 + } + + static 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) + static 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 + static 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] + ] + + static 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 + ] +} diff --git a/src/views/components/jsmpeg/modules/decoder/mpeg1-wasm.js b/src/views/components/jsmpeg/modules/decoder/mpeg1-wasm.js new file mode 100644 index 0000000..a087f01 --- /dev/null +++ b/src/views/components/jsmpeg/modules/decoder/mpeg1-wasm.js @@ -0,0 +1,155 @@ +import { Now } from '../../utils' +import BitBuffer from '../buffer' +import BaseDecoder from './decoder' + +export default class MPEG1WASM extends BaseDecoder { + options = null + /** 分辨率 */ + resolution = { + width: 0, + height: 0 + } + constructor(options) { + super(options) + + this.onDecodeCallback = options.onVideoDecode + this.module = options.wasmModule + + this.bufferSize = options.videoBufferSize || 512 * 1024 + this.bufferMode = options.streaming + ? BitBuffer.MODE.EVICT + : BitBuffer.MODE.EXPAND + + this.decodeFirstFrame = options.decodeFirstFrame !== false + this.hasSequenceHeader = false + this.options = options + } + + 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._mpeg1_decoder_create( + this.bufferSize, + this.bufferMode + ) + } + + destroy() { + if (!this.decoder) { + return + } + this.functions._mpeg1_decoder_destroy(this.decoder) + } + + bufferGetIndex() { + if (!this.decoder) { + return + } + return this.functions._mpeg1_decoder_get_index(this.decoder) + } + + bufferSetIndex(index) { + if (!this.decoder) { + return + } + this.functions._mpeg1_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._mpeg1_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._mpeg1_decoder_did_write(this.decoder, totalLength) + return totalLength + } + + write(pts, buffers) { + super.write(pts, buffers) + + if ( + !this.hasSequenceHeader && + this.functions._mpeg1_decoder_has_sequence_header(this.decoder) + ) { + this.loadSequnceHeader() + } + } + + loadSequnceHeader() { + this.hasSequenceHeader = true + this.frameRate = this.functions._mpeg1_decoder_get_frame_rate(this.decoder) + this.codedSize = this.functions._mpeg1_decoder_get_coded_size(this.decoder) + + if (this.destination) { + const w = this.functions._mpeg1_decoder_get_width(this.decoder) + const h = this.functions._mpeg1_decoder_get_height(this.decoder) + this.destination.resize(w, h) + this.resolution.width = w + this.resolution.height = h + this.options.onResolutionDecode?.(w, h) + } + + if (this.decodeFirstFrame) { + this.decode() + } + } + + decode() { + const startTime = Now() + + if (!this.decoder) { + return false + } + + const didDecode = this.functions._mpeg1_decoder_decode(this.decoder) + if (!didDecode) { + return false + } + + // Invoke decode callbacks + if (this.destination) { + const ptrY = this.functions._mpeg1_decoder_get_y_ptr(this.decoder) + const ptrCr = this.functions._mpeg1_decoder_get_cr_ptr(this.decoder) + const ptrCb = this.functions._mpeg1_decoder_get_cb_ptr(this.decoder) + + const dy = this.instance.heapU8.subarray(ptrY, ptrY + this.codedSize) + const dcr = this.instance.heapU8.subarray( + ptrCr, + ptrCr + (this.codedSize >> 2) + ) + const dcb = this.instance.heapU8.subarray( + ptrCb, + ptrCb + (this.codedSize >> 2) + ) + + this.destination.render(dy, dcr, dcb, false) + } + + this.advanceDecodedTime(1 / this.frameRate) + + const elapsedTime = Now() - startTime + if (this.onDecodeCallback) { + this.onDecodeCallback(this, elapsedTime) + } + return true + } +} diff --git a/src/views/components/jsmpeg/modules/decoder/mpeg1.js b/src/views/components/jsmpeg/modules/decoder/mpeg1.js new file mode 100644 index 0000000..898826d --- /dev/null +++ b/src/views/components/jsmpeg/modules/decoder/mpeg1.js @@ -0,0 +1,2846 @@ +/* eslint-disable */ +import { Fill, Now } from '../../utils' +import BitBuffer from '../buffer' +import BaseDecoder from './decoder' + +/** + * Inspired by Java MPEG-1 Video Decoder and Player by Zoltan Korandi + * https://sourceforge.net/projects/javampeg1video/ + */ +export default class MPEG1 extends BaseDecoder { + /** + * @type {boolean} + * 是否有序列头 + */ + hasSequenceHeader + /** 分辨率 */ + resolution = { + width: 0, + height: 0 + } + 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 + this.options = options + } + + write(pts, buffers) { + super.write(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) { + const bufferedBytes = this.bits.byteLength - (this.bits.index >> 3) + 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] + } + + // Sequence Layer + + frameRate = 30 + 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) + this.resolution.width = w + this.resolution.height = h + this.options.onResolutionDecode?.(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) + } + + // Picture Layer + + currentY = null + currentCr = null + currentCb = null + + pictureType = 0 + + // Buffers for motion compensation + forwardY = null + forwardCr = null + forwardCb = null + + fullPelForward = false + forwardFCode = 0 + forwardRSize = 0 + forwardF = 0 + + decodePicture(skipOutput) { + 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 + } + } + + // Slice Layer + + quantizerScale = 0 + sliceBegin = false + + 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()) + } + + // Macroblock Layer + + macroblockAddress = 0 + mbRow = 0 + mbCol = 0 + + macroblockType = 0 + macroblockIntra = false + macroblockMotFw = false + + motionFwH = 0 + motionFwV = 0 + motionFwHPrev = 0 + motionFwVPrev = 0 + + 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-- + } + this.macroblockAddress++ + } + this.mbRow = (this.macroblockAddress / this.mbWidth) | 0 + this.mbCol = this.macroblockAddress % this.mbWidth + + // Process the current macroblock + const mbTable = MPEG1.MACROBLOCK_TYPE[this.pictureType] + this.macroblockType = this.readHuffman(mbTable) + this.macroblockIntra = this.macroblockType & 0x01 + this.macroblockMotFw = this.macroblockType & 0x08 + + // Quantizer scale + if ((this.macroblockType & 0x10) !== 0) { + this.quantizerScale = this.bits.read(5) + } + + if (this.macroblockIntra) { + // Intra-coded macroblocks reset motion vectors + this.motionFwH = this.motionFwHPrev = 0 + this.motionFwV = this.motionFwVPrev = 0 + } else { + // Non-intra macroblocks reset DC predictors + this.dcPredictorY = 128 + this.dcPredictorCr = 128 + this.dcPredictorCb = 128 + + this.decodeMotionVectors() + this.copyMacroblock( + this.motionFwH, + this.motionFwV, + this.forwardY, + this.forwardCr, + this.forwardCb + ) + } + + // Decode blocks + const cbp = + (this.macroblockType & 0x02) !== 0 + ? this.readHuffman(MPEG1.CODE_BLOCK_PATTERN) + : this.macroblockIntra + ? 0x3f + : 0 + + for (let block = 0, mask = 0x20; block < 6; block++) { + if ((cbp & mask) !== 0) { + this.decodeBlock(block) + } + mask >>= 1 + } + } + + decodeMotionVectors() { + let code + let d + let r = 0 + + // Forward + if (this.macroblockMotFw) { + // Horizontal forward + code = this.readHuffman(MPEG1.MOTION) + if (code !== 0 && this.forwardF !== 1) { + r = this.bits.read(this.forwardRSize) + d = ((Math.abs(code) - 1) << this.forwardRSize) + r + 1 + if (code < 0) { + d = -d + } + } else { + d = code + } + + this.motionFwHPrev += d + if (this.motionFwHPrev > (this.forwardF << 4) - 1) { + this.motionFwHPrev -= this.forwardF << 5 + } else if (this.motionFwHPrev < -this.forwardF << 4) { + this.motionFwHPrev += this.forwardF << 5 + } + + this.motionFwH = this.motionFwHPrev + if (this.fullPelForward) { + this.motionFwH <<= 1 + } + + // Vertical forward + code = this.readHuffman(MPEG1.MOTION) + if (code !== 0 && this.forwardF !== 1) { + r = this.bits.read(this.forwardRSize) + d = ((Math.abs(code) - 1) << this.forwardRSize) + r + 1 + if (code < 0) { + d = -d + } + } else { + d = code + } + + this.motionFwVPrev += d + if (this.motionFwVPrev > (this.forwardF << 4) - 1) { + this.motionFwVPrev -= this.forwardF << 5 + } else if (this.motionFwVPrev < -this.forwardF << 4) { + this.motionFwVPrev += this.forwardF << 5 + } + + this.motionFwV = this.motionFwVPrev + if (this.fullPelForward) { + this.motionFwV <<= 1 + } + } else if (this.pictureType === MPEG1.PICTURE_TYPE.PREDICTIVE) { + // No motion information in P-picture, reset vectors + this.motionFwH = this.motionFwHPrev = 0 + this.motionFwV = this.motionFwVPrev = 0 + } + } + + copyMacroblock(motionH, motionV, sY, sCr, sCb) { + let width, scan, H, V, oddH, oddV, src, dest, last + + // We use 32bit writes here + const dY = this.currentY32 + const dCb = this.currentCb32 + const dCr = this.currentCr32 + + // Luminance + width = this.codedWidth + scan = width - 16 + + H = motionH >> 1 + V = motionV >> 1 + oddH = (motionH & 1) === 1 + oddV = (motionV & 1) === 1 + + src = ((this.mbRow << 4) + V) * width + (this.mbCol << 4) + H + dest = (this.mbRow * width + this.mbCol) << 2 + last = dest + (width << 2) + + let x, y1, y2, y + if (oddH) { + if (oddV) { + while (dest < last) { + y1 = sY[src] + sY[src + width] + src++ + for (x = 0; x < 4; x++) { + y2 = sY[src] + sY[src + width] + src++ + y = ((y1 + y2 + 2) >> 2) & 0xff + + y1 = sY[src] + sY[src + width] + src++ + y |= ((y1 + y2 + 2) << 6) & 0xff00 + + y2 = sY[src] + sY[src + width] + src++ + y |= ((y1 + y2 + 2) << 14) & 0xff0000 + + y1 = sY[src] + sY[src + width] + src++ + y |= ((y1 + y2 + 2) << 22) & 0xff000000 + + dY[dest++] = y + } + dest += scan >> 2 + src += scan - 1 + } + } else { + while (dest < last) { + y1 = sY[src++] + for (x = 0; x < 4; x++) { + y2 = sY[src++] + y = ((y1 + y2 + 1) >> 1) & 0xff + + y1 = sY[src++] + y |= ((y1 + y2 + 1) << 7) & 0xff00 + + y2 = sY[src++] + y |= ((y1 + y2 + 1) << 15) & 0xff0000 + + y1 = sY[src++] + y |= ((y1 + y2 + 1) << 23) & 0xff000000 + + dY[dest++] = y + } + dest += scan >> 2 + src += scan - 1 + } + } + } else { + if (oddV) { + while (dest < last) { + for (x = 0; x < 4; x++) { + y = ((sY[src] + sY[src + width] + 1) >> 1) & 0xff + src++ + y |= ((sY[src] + sY[src + width] + 1) << 7) & 0xff00 + src++ + y |= ((sY[src] + sY[src + width] + 1) << 15) & 0xff0000 + src++ + y |= ((sY[src] + sY[src + width] + 1) << 23) & 0xff000000 + src++ + + dY[dest++] = y + } + dest += scan >> 2 + src += scan + } + } else { + while (dest < last) { + for (x = 0; x < 4; x++) { + y = sY[src] + src++ + y |= sY[src] << 8 + src++ + y |= sY[src] << 16 + src++ + y |= sY[src] << 24 + src++ + + dY[dest++] = y + } + dest += scan >> 2 + src += scan + } + } + } + + // Chrominance + + width = this.halfWidth + scan = width - 8 + + H = (motionH / 2) >> 1 + V = (motionV / 2) >> 1 + oddH = ((motionH / 2) & 1) === 1 + oddV = ((motionV / 2) & 1) === 1 + + src = ((this.mbRow << 3) + V) * width + (this.mbCol << 3) + H + dest = (this.mbRow * width + this.mbCol) << 1 + last = dest + (width << 1) + + let cr1, cr2, cr, cb1, cb2, cb + if (oddH) { + if (oddV) { + while (dest < last) { + cr1 = sCr[src] + sCr[src + width] + cb1 = sCb[src] + sCb[src + width] + src++ + for (x = 0; x < 2; x++) { + cr2 = sCr[src] + sCr[src + width] + cb2 = sCb[src] + sCb[src + width] + src++ + cr = ((cr1 + cr2 + 2) >> 2) & 0xff + cb = ((cb1 + cb2 + 2) >> 2) & 0xff + + cr1 = sCr[src] + sCr[src + width] + cb1 = sCb[src] + sCb[src + width] + src++ + cr |= ((cr1 + cr2 + 2) << 6) & 0xff00 + cb |= ((cb1 + cb2 + 2) << 6) & 0xff00 + + cr2 = sCr[src] + sCr[src + width] + cb2 = sCb[src] + sCb[src + width] + src++ + cr |= ((cr1 + cr2 + 2) << 14) & 0xff0000 + cb |= ((cb1 + cb2 + 2) << 14) & 0xff0000 + + cr1 = sCr[src] + sCr[src + width] + cb1 = sCb[src] + sCb[src + width] + src++ + cr |= ((cr1 + cr2 + 2) << 22) & 0xff000000 + cb |= ((cb1 + cb2 + 2) << 22) & 0xff000000 + + dCr[dest] = cr + dCb[dest] = cb + dest++ + } + dest += scan >> 2 + src += scan - 1 + } + } else { + while (dest < last) { + cr1 = sCr[src] + cb1 = sCb[src] + src++ + for (x = 0; x < 2; x++) { + cr2 = sCr[src] + cb2 = sCb[src++] + cr = ((cr1 + cr2 + 1) >> 1) & 0xff + cb = ((cb1 + cb2 + 1) >> 1) & 0xff + + cr1 = sCr[src] + cb1 = sCb[src++] + cr |= ((cr1 + cr2 + 1) << 7) & 0xff00 + cb |= ((cb1 + cb2 + 1) << 7) & 0xff00 + + cr2 = sCr[src] + cb2 = sCb[src++] + cr |= ((cr1 + cr2 + 1) << 15) & 0xff0000 + cb |= ((cb1 + cb2 + 1) << 15) & 0xff0000 + + cr1 = sCr[src] + cb1 = sCb[src++] + cr |= ((cr1 + cr2 + 1) << 23) & 0xff000000 + cb |= ((cb1 + cb2 + 1) << 23) & 0xff000000 + + dCr[dest] = cr + dCb[dest] = cb + dest++ + } + dest += scan >> 2 + src += scan - 1 + } + } + } else { + if (oddV) { + while (dest < last) { + for (x = 0; x < 2; x++) { + cr = ((sCr[src] + sCr[src + width] + 1) >> 1) & 0xff + cb = ((sCb[src] + sCb[src + width] + 1) >> 1) & 0xff + src++ + + cr |= ((sCr[src] + sCr[src + width] + 1) << 7) & 0xff00 + cb |= ((sCb[src] + sCb[src + width] + 1) << 7) & 0xff00 + src++ + + cr |= ((sCr[src] + sCr[src + width] + 1) << 15) & 0xff0000 + cb |= ((sCb[src] + sCb[src + width] + 1) << 15) & 0xff0000 + src++ + + cr |= ((sCr[src] + sCr[src + width] + 1) << 23) & 0xff000000 + cb |= ((sCb[src] + sCb[src + width] + 1) << 23) & 0xff000000 + src++ + + dCr[dest] = cr + dCb[dest] = cb + dest++ + } + dest += scan >> 2 + src += scan + } + } else { + while (dest < last) { + for (x = 0; x < 2; x++) { + cr = sCr[src] + cb = sCb[src] + src++ + + cr |= sCr[src] << 8 + cb |= sCb[src] << 8 + src++ + + cr |= sCr[src] << 16 + cb |= sCb[src] << 16 + src++ + + cr |= sCr[src] << 24 + cb |= sCb[src] << 24 + src++ + + dCr[dest] = cr + dCb[dest] = cb + dest++ + } + dest += scan >> 2 + src += scan + } + } + } + } + + // Block layer + + dcPredictorY = 0 + dcPredictorCr = 0 + dcPredictorCb = 0 + + blockData = null + + decodeBlock(block) { + let n = 0 + let quantMatrix + + // Decode DC coefficient of intra-coded blocks + if (this.macroblockIntra) { + let predictor, dctSize + + // DC prediction + + if (block < 4) { + predictor = this.dcPredictorY + dctSize = this.readHuffman(MPEG1.DCT_DC_SIZE_LUMINANCE) + } else { + predictor = block === 4 ? this.dcPredictorCr : this.dcPredictorCb + dctSize = this.readHuffman(MPEG1.DCT_DC_SIZE_CHROMINANCE) + } + + // Read DC coeff + if (dctSize > 0) { + const differential = this.bits.read(dctSize) + if ((differential & (1 << (dctSize - 1))) !== 0) { + this.blockData[0] = predictor + differential + } else { + this.blockData[0] = predictor + ((-1 << dctSize) | (differential + 1)) + } + } else { + this.blockData[0] = predictor + } + + // Save predictor value + if (block < 4) { + this.dcPredictorY = this.blockData[0] + } else if (block === 4) { + this.dcPredictorCr = this.blockData[0] + } else { + this.dcPredictorCb = this.blockData[0] + } + + // Dequantize + premultiply + this.blockData[0] <<= 3 + 5 + + quantMatrix = this.intraQuantMatrix + n = 1 + } else { + quantMatrix = this.nonIntraQuantMatrix + } + + // Decode AC coefficients (+DC for non-intra) + let level = 0 + while (true) { + let run = 0 + const coeff = this.readHuffman(MPEG1.DCT_COEFF) + + if (coeff === 0x0001 && n > 0 && this.bits.read(1) === 0) { + // end_of_block + break + } + if (coeff === 0xffff) { + // escape + run = this.bits.read(6) + level = this.bits.read(8) + if (level === 0) { + level = this.bits.read(8) + } else if (level === 128) { + level = this.bits.read(8) - 256 + } else if (level > 128) { + level = level - 256 + } + } else { + run = coeff >> 8 + level = coeff & 0xff + if (this.bits.read(1)) { + level = -level + } + } + + n += run + const dezigZagged = MPEG1.ZIG_ZAG[n] + n++ + + // Dequantize, oddify, clip + level <<= 1 + if (!this.macroblockIntra) { + level += level < 0 ? -1 : 1 + } + level = (level * this.quantizerScale * quantMatrix[dezigZagged]) >> 4 + if ((level & 1) === 0) { + level -= level > 0 ? 1 : -1 + } + if (level > 2047) { + level = 2047 + } else if (level < -2048) { + level = -2048 + } + + // Save premultiplied coefficient + this.blockData[dezigZagged] = + level * MPEG1.PREMULTIPLIER_MATRIX[dezigZagged] + } + + // Move block to its place + let destArray, destIndex, scan + + if (block < 4) { + destArray = this.currentY + scan = this.codedWidth - 8 + destIndex = (this.mbRow * this.codedWidth + this.mbCol) << 4 + if ((block & 1) !== 0) { + destIndex += 8 + } + if ((block & 2) !== 0) { + destIndex += this.codedWidth << 3 + } + } else { + destArray = block === 4 ? this.currentCb : this.currentCr + scan = (this.codedWidth >> 1) - 8 + destIndex = ((this.mbRow * this.codedWidth) << 2) + (this.mbCol << 3) + } + + if (this.macroblockIntra) { + // Overwrite (no prediction) + if (n === 1) { + MPEG1.CopyValueToDestination( + (this.blockData[0] + 128) >> 8, + destArray, + destIndex, + scan + ) + this.blockData[0] = 0 + } else { + MPEG1.IDCT(this.blockData) + MPEG1.CopyBlockToDestination(this.blockData, destArray, destIndex, scan) + Fill(this.blockData, 0) + } + } else { + // Add data to the predicted macroblock + if (n === 1) { + MPEG1.AddValueToDestination( + (this.blockData[0] + 128) >> 8, + destArray, + destIndex, + scan + ) + this.blockData[0] = 0 + } else { + MPEG1.IDCT(this.blockData) + MPEG1.AddBlockToDestination(this.blockData, destArray, destIndex, scan) + Fill(this.blockData, 0) + } + } + + n = 0 + } + + // #region 常量 + // VLC Tables and Constants + + static PICTURE_RATE = [ + 0.0, 23.976, 24.0, 25.0, 29.97, 30.0, 50.0, 59.94, 60.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0 + ] + + static ZIG_ZAG = new Uint8Array([ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, + 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, + 54, 47, 55, 62, 63 + ]) + + static DEFAULT_INTRA_QUANT_MATRIX = new Uint8Array([ + 8, 16, 19, 22, 26, 27, 29, 34, 16, 16, 22, 24, 27, 29, 34, 37, 19, 22, 26, + 27, 29, 34, 34, 38, 22, 22, 26, 27, 29, 34, 37, 40, 22, 26, 27, 29, 32, 35, + 40, 48, 26, 27, 29, 32, 35, 40, 48, 58, 26, 27, 29, 34, 38, 46, 56, 69, 27, + 29, 35, 38, 46, 56, 69, 83 + ]) + + static DEFAULT_NON_INTRA_QUANT_MATRIX = new Uint8Array([ + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16 + ]) + + static PREMULTIPLIER_MATRIX = new Uint8Array([ + 32, 44, 42, 38, 32, 25, 17, 9, 44, 62, 58, 52, 44, 35, 24, 12, 42, 58, 55, + 49, 42, 33, 23, 12, 38, 52, 49, 44, 38, 30, 20, 10, 32, 44, 42, 38, 32, 25, + 17, 9, 25, 35, 33, 30, 25, 20, 14, 7, 17, 24, 23, 20, 17, 14, 9, 5, 9, 12, + 12, 10, 9, 7, 5, 2 + ]) + + // MPEG-1 VLC + + // macroblock_stuffing decodes as 34. + // macroblock_escape decodes as 35. + + static MACROBLOCK_ADDRESS_INCREMENT = new Int16Array([ + 1 * 3, + 2 * 3, + 0, // 0 + 3 * 3, + 4 * 3, + 0, // 1 0 + 0, + 0, + 1, // 2 1. + 5 * 3, + 6 * 3, + 0, // 3 00 + 7 * 3, + 8 * 3, + 0, // 4 01 + 9 * 3, + 10 * 3, + 0, // 5 000 + 11 * 3, + 12 * 3, + 0, // 6 001 + 0, + 0, + 3, // 7 010. + 0, + 0, + 2, // 8 011. + 13 * 3, + 14 * 3, + 0, // 9 0000 + 15 * 3, + 16 * 3, + 0, // 10 0001 + 0, + 0, + 5, // 11 0010. + 0, + 0, + 4, // 12 0011. + 17 * 3, + 18 * 3, + 0, // 13 0000 0 + 19 * 3, + 20 * 3, + 0, // 14 0000 1 + 0, + 0, + 7, // 15 0001 0. + 0, + 0, + 6, // 16 0001 1. + 21 * 3, + 22 * 3, + 0, // 17 0000 00 + 23 * 3, + 24 * 3, + 0, // 18 0000 01 + 25 * 3, + 26 * 3, + 0, // 19 0000 10 + 27 * 3, + 28 * 3, + 0, // 20 0000 11 + -1, + 29 * 3, + 0, // 21 0000 000 + -1, + 30 * 3, + 0, // 22 0000 001 + 31 * 3, + 32 * 3, + 0, // 23 0000 010 + 33 * 3, + 34 * 3, + 0, // 24 0000 011 + 35 * 3, + 36 * 3, + 0, // 25 0000 100 + 37 * 3, + 38 * 3, + 0, // 26 0000 101 + 0, + 0, + 9, // 27 0000 110. + 0, + 0, + 8, // 28 0000 111. + 39 * 3, + 40 * 3, + 0, // 29 0000 0001 + 41 * 3, + 42 * 3, + 0, // 30 0000 0011 + 43 * 3, + 44 * 3, + 0, // 31 0000 0100 + 45 * 3, + 46 * 3, + 0, // 32 0000 0101 + 0, + 0, + 15, // 33 0000 0110. + 0, + 0, + 14, // 34 0000 0111. + 0, + 0, + 13, // 35 0000 1000. + 0, + 0, + 12, // 36 0000 1001. + 0, + 0, + 11, // 37 0000 1010. + 0, + 0, + 10, // 38 0000 1011. + 47 * 3, + -1, + 0, // 39 0000 0001 0 + -1, + 48 * 3, + 0, // 40 0000 0001 1 + 49 * 3, + 50 * 3, + 0, // 41 0000 0011 0 + 51 * 3, + 52 * 3, + 0, // 42 0000 0011 1 + 53 * 3, + 54 * 3, + 0, // 43 0000 0100 0 + 55 * 3, + 56 * 3, + 0, // 44 0000 0100 1 + 57 * 3, + 58 * 3, + 0, // 45 0000 0101 0 + 59 * 3, + 60 * 3, + 0, // 46 0000 0101 1 + 61 * 3, + -1, + 0, // 47 0000 0001 00 + -1, + 62 * 3, + 0, // 48 0000 0001 11 + 63 * 3, + 64 * 3, + 0, // 49 0000 0011 00 + 65 * 3, + 66 * 3, + 0, // 50 0000 0011 01 + 67 * 3, + 68 * 3, + 0, // 51 0000 0011 10 + 69 * 3, + 70 * 3, + 0, // 52 0000 0011 11 + 71 * 3, + 72 * 3, + 0, // 53 0000 0100 00 + 73 * 3, + 74 * 3, + 0, // 54 0000 0100 01 + 0, + 0, + 21, // 55 0000 0100 10. + 0, + 0, + 20, // 56 0000 0100 11. + 0, + 0, + 19, // 57 0000 0101 00. + 0, + 0, + 18, // 58 0000 0101 01. + 0, + 0, + 17, // 59 0000 0101 10. + 0, + 0, + 16, // 60 0000 0101 11. + 0, + 0, + 35, // 61 0000 0001 000. -- macroblock_escape + 0, + 0, + 34, // 62 0000 0001 111. -- macroblock_stuffing + 0, + 0, + 33, // 63 0000 0011 000. + 0, + 0, + 32, // 64 0000 0011 001. + 0, + 0, + 31, // 65 0000 0011 010. + 0, + 0, + 30, // 66 0000 0011 011. + 0, + 0, + 29, // 67 0000 0011 100. + 0, + 0, + 28, // 68 0000 0011 101. + 0, + 0, + 27, // 69 0000 0011 110. + 0, + 0, + 26, // 70 0000 0011 111. + 0, + 0, + 25, // 71 0000 0100 000. + 0, + 0, + 24, // 72 0000 0100 001. + 0, + 0, + 23, // 73 0000 0100 010. + 0, + 0, + 22 // 74 0000 0100 011. + ]) + + // macroblock_type bitmap: + // 0x10 macroblock_quant + // 0x08 macroblock_motion_forward + // 0x04 macroblock_motion_backward + // 0x02 macrobkock_pattern + // 0x01 macroblock_intra + // + + static MACROBLOCK_TYPE_INTRA = new Int8Array([ + 1 * 3, + 2 * 3, + 0, // 0 + -1, + 3 * 3, + 0, // 1 0 + 0, + 0, + 0x01, // 2 1. + 0, + 0, + 0x11 // 3 01. + ]) + + static MACROBLOCK_TYPE_PREDICTIVE = new Int8Array([ + 1 * 3, + 2 * 3, + 0, // 0 + 3 * 3, + 4 * 3, + 0, // 1 0 + 0, + 0, + 0x0a, // 2 1. + 5 * 3, + 6 * 3, + 0, // 3 00 + 0, + 0, + 0x02, // 4 01. + 7 * 3, + 8 * 3, + 0, // 5 000 + 0, + 0, + 0x08, // 6 001. + 9 * 3, + 10 * 3, + 0, // 7 0000 + 11 * 3, + 12 * 3, + 0, // 8 0001 + -1, + 13 * 3, + 0, // 9 00000 + 0, + 0, + 0x12, // 10 00001. + 0, + 0, + 0x1a, // 11 00010. + 0, + 0, + 0x01, // 12 00011. + 0, + 0, + 0x11 // 13 000001. + ]) + + static MACROBLOCK_TYPE_B = new Int8Array([ + 1 * 3, + 2 * 3, + 0, // 0 + 3 * 3, + 5 * 3, + 0, // 1 0 + 4 * 3, + 6 * 3, + 0, // 2 1 + 8 * 3, + 7 * 3, + 0, // 3 00 + 0, + 0, + 0x0c, // 4 10. + 9 * 3, + 10 * 3, + 0, // 5 01 + 0, + 0, + 0x0e, // 6 11. + 13 * 3, + 14 * 3, + 0, // 7 001 + 12 * 3, + 11 * 3, + 0, // 8 000 + 0, + 0, + 0x04, // 9 010. + 0, + 0, + 0x06, // 10 011. + 18 * 3, + 16 * 3, + 0, // 11 0001 + 15 * 3, + 17 * 3, + 0, // 12 0000 + 0, + 0, + 0x08, // 13 0010. + 0, + 0, + 0x0a, // 14 0011. + -1, + 19 * 3, + 0, // 15 00000 + 0, + 0, + 0x01, // 16 00011. + 20 * 3, + 21 * 3, + 0, // 17 00001 + 0, + 0, + 0x1e, // 18 00010. + 0, + 0, + 0x11, // 19 000001. + 0, + 0, + 0x16, // 20 000010. + 0, + 0, + 0x1a // 21 000011. + ]) + + static MACROBLOCK_TYPE = [ + null, + MPEG1.MACROBLOCK_TYPE_INTRA, + MPEG1.MACROBLOCK_TYPE_PREDICTIVE, + MPEG1.MACROBLOCK_TYPE_B + ] + + static CODE_BLOCK_PATTERN = new Int16Array([ + 2 * 3, + 1 * 3, + 0, // 0 + 3 * 3, + 6 * 3, + 0, // 1 1 + 4 * 3, + 5 * 3, + 0, // 2 0 + 8 * 3, + 11 * 3, + 0, // 3 10 + 12 * 3, + 13 * 3, + 0, // 4 00 + 9 * 3, + 7 * 3, + 0, // 5 01 + 10 * 3, + 14 * 3, + 0, // 6 11 + 20 * 3, + 19 * 3, + 0, // 7 011 + 18 * 3, + 16 * 3, + 0, // 8 100 + 23 * 3, + 17 * 3, + 0, // 9 010 + 27 * 3, + 25 * 3, + 0, // 10 110 + 21 * 3, + 28 * 3, + 0, // 11 101 + 15 * 3, + 22 * 3, + 0, // 12 000 + 24 * 3, + 26 * 3, + 0, // 13 001 + 0, + 0, + 60, // 14 111. + 35 * 3, + 40 * 3, + 0, // 15 0000 + 44 * 3, + 48 * 3, + 0, // 16 1001 + 38 * 3, + 36 * 3, + 0, // 17 0101 + 42 * 3, + 47 * 3, + 0, // 18 1000 + 29 * 3, + 31 * 3, + 0, // 19 0111 + 39 * 3, + 32 * 3, + 0, // 20 0110 + 0, + 0, + 32, // 21 1010. + 45 * 3, + 46 * 3, + 0, // 22 0001 + 33 * 3, + 41 * 3, + 0, // 23 0100 + 43 * 3, + 34 * 3, + 0, // 24 0010 + 0, + 0, + 4, // 25 1101. + 30 * 3, + 37 * 3, + 0, // 26 0011 + 0, + 0, + 8, // 27 1100. + 0, + 0, + 16, // 28 1011. + 0, + 0, + 44, // 29 0111 0. + 50 * 3, + 56 * 3, + 0, // 30 0011 0 + 0, + 0, + 28, // 31 0111 1. + 0, + 0, + 52, // 32 0110 1. + 0, + 0, + 62, // 33 0100 0. + 61 * 3, + 59 * 3, + 0, // 34 0010 1 + 52 * 3, + 60 * 3, + 0, // 35 0000 0 + 0, + 0, + 1, // 36 0101 1. + 55 * 3, + 54 * 3, + 0, // 37 0011 1 + 0, + 0, + 61, // 38 0101 0. + 0, + 0, + 56, // 39 0110 0. + 57 * 3, + 58 * 3, + 0, // 40 0000 1 + 0, + 0, + 2, // 41 0100 1. + 0, + 0, + 40, // 42 1000 0. + 51 * 3, + 62 * 3, + 0, // 43 0010 0 + 0, + 0, + 48, // 44 1001 0. + 64 * 3, + 63 * 3, + 0, // 45 0001 0 + 49 * 3, + 53 * 3, + 0, // 46 0001 1 + 0, + 0, + 20, // 47 1000 1. + 0, + 0, + 12, // 48 1001 1. + 80 * 3, + 83 * 3, + 0, // 49 0001 10 + 0, + 0, + 63, // 50 0011 00. + 77 * 3, + 75 * 3, + 0, // 51 0010 00 + 65 * 3, + 73 * 3, + 0, // 52 0000 00 + 84 * 3, + 66 * 3, + 0, // 53 0001 11 + 0, + 0, + 24, // 54 0011 11. + 0, + 0, + 36, // 55 0011 10. + 0, + 0, + 3, // 56 0011 01. + 69 * 3, + 87 * 3, + 0, // 57 0000 10 + 81 * 3, + 79 * 3, + 0, // 58 0000 11 + 68 * 3, + 71 * 3, + 0, // 59 0010 11 + 70 * 3, + 78 * 3, + 0, // 60 0000 01 + 67 * 3, + 76 * 3, + 0, // 61 0010 10 + 72 * 3, + 74 * 3, + 0, // 62 0010 01 + 86 * 3, + 85 * 3, + 0, // 63 0001 01 + 88 * 3, + 82 * 3, + 0, // 64 0001 00 + -1, + 94 * 3, + 0, // 65 0000 000 + 95 * 3, + 97 * 3, + 0, // 66 0001 111 + 0, + 0, + 33, // 67 0010 100. + 0, + 0, + 9, // 68 0010 110. + 106 * 3, + 110 * 3, + 0, // 69 0000 100 + 102 * 3, + 116 * 3, + 0, // 70 0000 010 + 0, + 0, + 5, // 71 0010 111. + 0, + 0, + 10, // 72 0010 010. + 93 * 3, + 89 * 3, + 0, // 73 0000 001 + 0, + 0, + 6, // 74 0010 011. + 0, + 0, + 18, // 75 0010 001. + 0, + 0, + 17, // 76 0010 101. + 0, + 0, + 34, // 77 0010 000. + 113 * 3, + 119 * 3, + 0, // 78 0000 011 + 103 * 3, + 104 * 3, + 0, // 79 0000 111 + 90 * 3, + 92 * 3, + 0, // 80 0001 100 + 109 * 3, + 107 * 3, + 0, // 81 0000 110 + 117 * 3, + 118 * 3, + 0, // 82 0001 001 + 101 * 3, + 99 * 3, + 0, // 83 0001 101 + 98 * 3, + 96 * 3, + 0, // 84 0001 110 + 100 * 3, + 91 * 3, + 0, // 85 0001 011 + 114 * 3, + 115 * 3, + 0, // 86 0001 010 + 105 * 3, + 108 * 3, + 0, // 87 0000 101 + 112 * 3, + 111 * 3, + 0, // 88 0001 000 + 121 * 3, + 125 * 3, + 0, // 89 0000 0011 + 0, + 0, + 41, // 90 0001 1000. + 0, + 0, + 14, // 91 0001 0111. + 0, + 0, + 21, // 92 0001 1001. + 124 * 3, + 122 * 3, + 0, // 93 0000 0010 + 120 * 3, + 123 * 3, + 0, // 94 0000 0001 + 0, + 0, + 11, // 95 0001 1110. + 0, + 0, + 19, // 96 0001 1101. + 0, + 0, + 7, // 97 0001 1111. + 0, + 0, + 35, // 98 0001 1100. + 0, + 0, + 13, // 99 0001 1011. + 0, + 0, + 50, // 100 0001 0110. + 0, + 0, + 49, // 101 0001 1010. + 0, + 0, + 58, // 102 0000 0100. + 0, + 0, + 37, // 103 0000 1110. + 0, + 0, + 25, // 104 0000 1111. + 0, + 0, + 45, // 105 0000 1010. + 0, + 0, + 57, // 106 0000 1000. + 0, + 0, + 26, // 107 0000 1101. + 0, + 0, + 29, // 108 0000 1011. + 0, + 0, + 38, // 109 0000 1100. + 0, + 0, + 53, // 110 0000 1001. + 0, + 0, + 23, // 111 0001 0001. + 0, + 0, + 43, // 112 0001 0000. + 0, + 0, + 46, // 113 0000 0110. + 0, + 0, + 42, // 114 0001 0100. + 0, + 0, + 22, // 115 0001 0101. + 0, + 0, + 54, // 116 0000 0101. + 0, + 0, + 51, // 117 0001 0010. + 0, + 0, + 15, // 118 0001 0011. + 0, + 0, + 30, // 119 0000 0111. + 0, + 0, + 39, // 120 0000 0001 0. + 0, + 0, + 47, // 121 0000 0011 0. + 0, + 0, + 55, // 122 0000 0010 1. + 0, + 0, + 27, // 123 0000 0001 1. + 0, + 0, + 59, // 124 0000 0010 0. + 0, + 0, + 31 // 125 0000 0011 1. + ]) + + static MOTION = new Int16Array([ + 1 * 3, + 2 * 3, + 0, // 0 + 4 * 3, + 3 * 3, + 0, // 1 0 + 0, + 0, + 0, // 2 1. + 6 * 3, + 5 * 3, + 0, // 3 01 + 8 * 3, + 7 * 3, + 0, // 4 00 + 0, + 0, + -1, // 5 011. + 0, + 0, + 1, // 6 010. + 9 * 3, + 10 * 3, + 0, // 7 001 + 12 * 3, + 11 * 3, + 0, // 8 000 + 0, + 0, + 2, // 9 0010. + 0, + 0, + -2, // 10 0011. + 14 * 3, + 15 * 3, + 0, // 11 0001 + 16 * 3, + 13 * 3, + 0, // 12 0000 + 20 * 3, + 18 * 3, + 0, // 13 0000 1 + 0, + 0, + 3, // 14 0001 0. + 0, + 0, + -3, // 15 0001 1. + 17 * 3, + 19 * 3, + 0, // 16 0000 0 + -1, + 23 * 3, + 0, // 17 0000 00 + 27 * 3, + 25 * 3, + 0, // 18 0000 11 + 26 * 3, + 21 * 3, + 0, // 19 0000 01 + 24 * 3, + 22 * 3, + 0, // 20 0000 10 + 32 * 3, + 28 * 3, + 0, // 21 0000 011 + 29 * 3, + 31 * 3, + 0, // 22 0000 101 + -1, + 33 * 3, + 0, // 23 0000 001 + 36 * 3, + 35 * 3, + 0, // 24 0000 100 + 0, + 0, + -4, // 25 0000 111. + 30 * 3, + 34 * 3, + 0, // 26 0000 010 + 0, + 0, + 4, // 27 0000 110. + 0, + 0, + -7, // 28 0000 0111. + 0, + 0, + 5, // 29 0000 1010. + 37 * 3, + 41 * 3, + 0, // 30 0000 0100 + 0, + 0, + -5, // 31 0000 1011. + 0, + 0, + 7, // 32 0000 0110. + 38 * 3, + 40 * 3, + 0, // 33 0000 0011 + 42 * 3, + 39 * 3, + 0, // 34 0000 0101 + 0, + 0, + -6, // 35 0000 1001. + 0, + 0, + 6, // 36 0000 1000. + 51 * 3, + 54 * 3, + 0, // 37 0000 0100 0 + 50 * 3, + 49 * 3, + 0, // 38 0000 0011 0 + 45 * 3, + 46 * 3, + 0, // 39 0000 0101 1 + 52 * 3, + 47 * 3, + 0, // 40 0000 0011 1 + 43 * 3, + 53 * 3, + 0, // 41 0000 0100 1 + 44 * 3, + 48 * 3, + 0, // 42 0000 0101 0 + 0, + 0, + 10, // 43 0000 0100 10. + 0, + 0, + 9, // 44 0000 0101 00. + 0, + 0, + 8, // 45 0000 0101 10. + 0, + 0, + -8, // 46 0000 0101 11. + 57 * 3, + 66 * 3, + 0, // 47 0000 0011 11 + 0, + 0, + -9, // 48 0000 0101 01. + 60 * 3, + 64 * 3, + 0, // 49 0000 0011 01 + 56 * 3, + 61 * 3, + 0, // 50 0000 0011 00 + 55 * 3, + 62 * 3, + 0, // 51 0000 0100 00 + 58 * 3, + 63 * 3, + 0, // 52 0000 0011 10 + 0, + 0, + -10, // 53 0000 0100 11. + 59 * 3, + 65 * 3, + 0, // 54 0000 0100 01 + 0, + 0, + 12, // 55 0000 0100 000. + 0, + 0, + 16, // 56 0000 0011 000. + 0, + 0, + 13, // 57 0000 0011 110. + 0, + 0, + 14, // 58 0000 0011 100. + 0, + 0, + 11, // 59 0000 0100 010. + 0, + 0, + 15, // 60 0000 0011 010. + 0, + 0, + -16, // 61 0000 0011 001. + 0, + 0, + -12, // 62 0000 0100 001. + 0, + 0, + -14, // 63 0000 0011 101. + 0, + 0, + -15, // 64 0000 0011 011. + 0, + 0, + -11, // 65 0000 0100 011. + 0, + 0, + -13 // 66 0000 0011 111. + ]) + + static DCT_DC_SIZE_LUMINANCE = new Int8Array([ + 2 * 3, + 1 * 3, + 0, // 0 + 6 * 3, + 5 * 3, + 0, // 1 1 + 3 * 3, + 4 * 3, + 0, // 2 0 + 0, + 0, + 1, // 3 00. + 0, + 0, + 2, // 4 01. + 9 * 3, + 8 * 3, + 0, // 5 11 + 7 * 3, + 10 * 3, + 0, // 6 10 + 0, + 0, + 0, // 7 100. + 12 * 3, + 11 * 3, + 0, // 8 111 + 0, + 0, + 4, // 9 110. + 0, + 0, + 3, // 10 101. + 13 * 3, + 14 * 3, + 0, // 11 1111 + 0, + 0, + 5, // 12 1110. + 0, + 0, + 6, // 13 1111 0. + 16 * 3, + 15 * 3, + 0, // 14 1111 1 + 17 * 3, + -1, + 0, // 15 1111 11 + 0, + 0, + 7, // 16 1111 10. + 0, + 0, + 8 // 17 1111 110. + ]) + + static DCT_DC_SIZE_CHROMINANCE = new Int8Array([ + 2 * 3, + 1 * 3, + 0, // 0 + 4 * 3, + 3 * 3, + 0, // 1 1 + 6 * 3, + 5 * 3, + 0, // 2 0 + 8 * 3, + 7 * 3, + 0, // 3 11 + 0, + 0, + 2, // 4 10. + 0, + 0, + 1, // 5 01. + 0, + 0, + 0, // 6 00. + 10 * 3, + 9 * 3, + 0, // 7 111 + 0, + 0, + 3, // 8 110. + 12 * 3, + 11 * 3, + 0, // 9 1111 + 0, + 0, + 4, // 10 1110. + 14 * 3, + 13 * 3, + 0, // 11 1111 1 + 0, + 0, + 5, // 12 1111 0. + 16 * 3, + 15 * 3, + 0, // 13 1111 11 + 0, + 0, + 6, // 14 1111 10. + 17 * 3, + -1, + 0, // 15 1111 111 + 0, + 0, + 7, // 16 1111 110. + 0, + 0, + 8 // 17 1111 1110. + ]) + + // dct_coeff bitmap: + // 0xff00 run + // 0x00ff level + + // Decoded values are unsigned. Sign bit follows in the stream. + + // Interpretation of the value 0x0001 + // for dc_coeff_first: run=0, level=1 + // for dc_coeff_next: If the next bit is 1: run=0, level=1 + // If the next bit is 0: end_of_block + + // escape decodes as 0xffff. + + static DCT_COEFF = new Int32Array([ + 1 * 3, + 2 * 3, + 0, // 0 + 4 * 3, + 3 * 3, + 0, // 1 0 + 0, + 0, + 0x0001, // 2 1. + 7 * 3, + 8 * 3, + 0, // 3 01 + 6 * 3, + 5 * 3, + 0, // 4 00 + 13 * 3, + 9 * 3, + 0, // 5 001 + 11 * 3, + 10 * 3, + 0, // 6 000 + 14 * 3, + 12 * 3, + 0, // 7 010 + 0, + 0, + 0x0101, // 8 011. + 20 * 3, + 22 * 3, + 0, // 9 0011 + 18 * 3, + 21 * 3, + 0, // 10 0001 + 16 * 3, + 19 * 3, + 0, // 11 0000 + 0, + 0, + 0x0201, // 12 0101. + 17 * 3, + 15 * 3, + 0, // 13 0010 + 0, + 0, + 0x0002, // 14 0100. + 0, + 0, + 0x0003, // 15 0010 1. + 27 * 3, + 25 * 3, + 0, // 16 0000 0 + 29 * 3, + 31 * 3, + 0, // 17 0010 0 + 24 * 3, + 26 * 3, + 0, // 18 0001 0 + 32 * 3, + 30 * 3, + 0, // 19 0000 1 + 0, + 0, + 0x0401, // 20 0011 0. + 23 * 3, + 28 * 3, + 0, // 21 0001 1 + 0, + 0, + 0x0301, // 22 0011 1. + 0, + 0, + 0x0102, // 23 0001 10. + 0, + 0, + 0x0701, // 24 0001 00. + 0, + 0, + 0xffff, // 25 0000 01. -- escape + 0, + 0, + 0x0601, // 26 0001 01. + 37 * 3, + 36 * 3, + 0, // 27 0000 00 + 0, + 0, + 0x0501, // 28 0001 11. + 35 * 3, + 34 * 3, + 0, // 29 0010 00 + 39 * 3, + 38 * 3, + 0, // 30 0000 11 + 33 * 3, + 42 * 3, + 0, // 31 0010 01 + 40 * 3, + 41 * 3, + 0, // 32 0000 10 + 52 * 3, + 50 * 3, + 0, // 33 0010 010 + 54 * 3, + 53 * 3, + 0, // 34 0010 001 + 48 * 3, + 49 * 3, + 0, // 35 0010 000 + 43 * 3, + 45 * 3, + 0, // 36 0000 001 + 46 * 3, + 44 * 3, + 0, // 37 0000 000 + 0, + 0, + 0x0801, // 38 0000 111. + 0, + 0, + 0x0004, // 39 0000 110. + 0, + 0, + 0x0202, // 40 0000 100. + 0, + 0, + 0x0901, // 41 0000 101. + 51 * 3, + 47 * 3, + 0, // 42 0010 011 + 55 * 3, + 57 * 3, + 0, // 43 0000 0010 + 60 * 3, + 56 * 3, + 0, // 44 0000 0001 + 59 * 3, + 58 * 3, + 0, // 45 0000 0011 + 61 * 3, + 62 * 3, + 0, // 46 0000 0000 + 0, + 0, + 0x0a01, // 47 0010 0111. + 0, + 0, + 0x0d01, // 48 0010 0000. + 0, + 0, + 0x0006, // 49 0010 0001. + 0, + 0, + 0x0103, // 50 0010 0101. + 0, + 0, + 0x0005, // 51 0010 0110. + 0, + 0, + 0x0302, // 52 0010 0100. + 0, + 0, + 0x0b01, // 53 0010 0011. + 0, + 0, + 0x0c01, // 54 0010 0010. + 76 * 3, + 75 * 3, + 0, // 55 0000 0010 0 + 67 * 3, + 70 * 3, + 0, // 56 0000 0001 1 + 73 * 3, + 71 * 3, + 0, // 57 0000 0010 1 + 78 * 3, + 74 * 3, + 0, // 58 0000 0011 1 + 72 * 3, + 77 * 3, + 0, // 59 0000 0011 0 + 69 * 3, + 64 * 3, + 0, // 60 0000 0001 0 + 68 * 3, + 63 * 3, + 0, // 61 0000 0000 0 + 66 * 3, + 65 * 3, + 0, // 62 0000 0000 1 + 81 * 3, + 87 * 3, + 0, // 63 0000 0000 01 + 91 * 3, + 80 * 3, + 0, // 64 0000 0001 01 + 82 * 3, + 79 * 3, + 0, // 65 0000 0000 11 + 83 * 3, + 86 * 3, + 0, // 66 0000 0000 10 + 93 * 3, + 92 * 3, + 0, // 67 0000 0001 10 + 84 * 3, + 85 * 3, + 0, // 68 0000 0000 00 + 90 * 3, + 94 * 3, + 0, // 69 0000 0001 00 + 88 * 3, + 89 * 3, + 0, // 70 0000 0001 11 + 0, + 0, + 0x0203, // 71 0000 0010 11. + 0, + 0, + 0x0104, // 72 0000 0011 00. + 0, + 0, + 0x0007, // 73 0000 0010 10. + 0, + 0, + 0x0402, // 74 0000 0011 11. + 0, + 0, + 0x0502, // 75 0000 0010 01. + 0, + 0, + 0x1001, // 76 0000 0010 00. + 0, + 0, + 0x0f01, // 77 0000 0011 01. + 0, + 0, + 0x0e01, // 78 0000 0011 10. + 105 * 3, + 107 * 3, + 0, // 79 0000 0000 111 + 111 * 3, + 114 * 3, + 0, // 80 0000 0001 011 + 104 * 3, + 97 * 3, + 0, // 81 0000 0000 010 + 125 * 3, + 119 * 3, + 0, // 82 0000 0000 110 + 96 * 3, + 98 * 3, + 0, // 83 0000 0000 100 + -1, + 123 * 3, + 0, // 84 0000 0000 000 + 95 * 3, + 101 * 3, + 0, // 85 0000 0000 001 + 106 * 3, + 121 * 3, + 0, // 86 0000 0000 101 + 99 * 3, + 102 * 3, + 0, // 87 0000 0000 011 + 113 * 3, + 103 * 3, + 0, // 88 0000 0001 110 + 112 * 3, + 116 * 3, + 0, // 89 0000 0001 111 + 110 * 3, + 100 * 3, + 0, // 90 0000 0001 000 + 124 * 3, + 115 * 3, + 0, // 91 0000 0001 010 + 117 * 3, + 122 * 3, + 0, // 92 0000 0001 101 + 109 * 3, + 118 * 3, + 0, // 93 0000 0001 100 + 120 * 3, + 108 * 3, + 0, // 94 0000 0001 001 + 127 * 3, + 136 * 3, + 0, // 95 0000 0000 0010 + 139 * 3, + 140 * 3, + 0, // 96 0000 0000 1000 + 130 * 3, + 126 * 3, + 0, // 97 0000 0000 0101 + 145 * 3, + 146 * 3, + 0, // 98 0000 0000 1001 + 128 * 3, + 129 * 3, + 0, // 99 0000 0000 0110 + 0, + 0, + 0x0802, // 100 0000 0001 0001. + 132 * 3, + 134 * 3, + 0, // 101 0000 0000 0011 + 155 * 3, + 154 * 3, + 0, // 102 0000 0000 0111 + 0, + 0, + 0x0008, // 103 0000 0001 1101. + 137 * 3, + 133 * 3, + 0, // 104 0000 0000 0100 + 143 * 3, + 144 * 3, + 0, // 105 0000 0000 1110 + 151 * 3, + 138 * 3, + 0, // 106 0000 0000 1010 + 142 * 3, + 141 * 3, + 0, // 107 0000 0000 1111 + 0, + 0, + 0x000a, // 108 0000 0001 0011. + 0, + 0, + 0x0009, // 109 0000 0001 1000. + 0, + 0, + 0x000b, // 110 0000 0001 0000. + 0, + 0, + 0x1501, // 111 0000 0001 0110. + 0, + 0, + 0x0602, // 112 0000 0001 1110. + 0, + 0, + 0x0303, // 113 0000 0001 1100. + 0, + 0, + 0x1401, // 114 0000 0001 0111. + 0, + 0, + 0x0702, // 115 0000 0001 0101. + 0, + 0, + 0x1101, // 116 0000 0001 1111. + 0, + 0, + 0x1201, // 117 0000 0001 1010. + 0, + 0, + 0x1301, // 118 0000 0001 1001. + 148 * 3, + 152 * 3, + 0, // 119 0000 0000 1101 + 0, + 0, + 0x0403, // 120 0000 0001 0010. + 153 * 3, + 150 * 3, + 0, // 121 0000 0000 1011 + 0, + 0, + 0x0105, // 122 0000 0001 1011. + 131 * 3, + 135 * 3, + 0, // 123 0000 0000 0001 + 0, + 0, + 0x0204, // 124 0000 0001 0100. + 149 * 3, + 147 * 3, + 0, // 125 0000 0000 1100 + 172 * 3, + 173 * 3, + 0, // 126 0000 0000 0101 1 + 162 * 3, + 158 * 3, + 0, // 127 0000 0000 0010 0 + 170 * 3, + 161 * 3, + 0, // 128 0000 0000 0110 0 + 168 * 3, + 166 * 3, + 0, // 129 0000 0000 0110 1 + 157 * 3, + 179 * 3, + 0, // 130 0000 0000 0101 0 + 169 * 3, + 167 * 3, + 0, // 131 0000 0000 0001 0 + 174 * 3, + 171 * 3, + 0, // 132 0000 0000 0011 0 + 178 * 3, + 177 * 3, + 0, // 133 0000 0000 0100 1 + 156 * 3, + 159 * 3, + 0, // 134 0000 0000 0011 1 + 164 * 3, + 165 * 3, + 0, // 135 0000 0000 0001 1 + 183 * 3, + 182 * 3, + 0, // 136 0000 0000 0010 1 + 175 * 3, + 176 * 3, + 0, // 137 0000 0000 0100 0 + 0, + 0, + 0x0107, // 138 0000 0000 1010 1. + 0, + 0, + 0x0a02, // 139 0000 0000 1000 0. + 0, + 0, + 0x0902, // 140 0000 0000 1000 1. + 0, + 0, + 0x1601, // 141 0000 0000 1111 1. + 0, + 0, + 0x1701, // 142 0000 0000 1111 0. + 0, + 0, + 0x1901, // 143 0000 0000 1110 0. + 0, + 0, + 0x1801, // 144 0000 0000 1110 1. + 0, + 0, + 0x0503, // 145 0000 0000 1001 0. + 0, + 0, + 0x0304, // 146 0000 0000 1001 1. + 0, + 0, + 0x000d, // 147 0000 0000 1100 1. + 0, + 0, + 0x000c, // 148 0000 0000 1101 0. + 0, + 0, + 0x000e, // 149 0000 0000 1100 0. + 0, + 0, + 0x000f, // 150 0000 0000 1011 1. + 0, + 0, + 0x0205, // 151 0000 0000 1010 0. + 0, + 0, + 0x1a01, // 152 0000 0000 1101 1. + 0, + 0, + 0x0106, // 153 0000 0000 1011 0. + 180 * 3, + 181 * 3, + 0, // 154 0000 0000 0111 1 + 160 * 3, + 163 * 3, + 0, // 155 0000 0000 0111 0 + 196 * 3, + 199 * 3, + 0, // 156 0000 0000 0011 10 + 0, + 0, + 0x001b, // 157 0000 0000 0101 00. + 203 * 3, + 185 * 3, + 0, // 158 0000 0000 0010 01 + 202 * 3, + 201 * 3, + 0, // 159 0000 0000 0011 11 + 0, + 0, + 0x0013, // 160 0000 0000 0111 00. + 0, + 0, + 0x0016, // 161 0000 0000 0110 01. + 197 * 3, + 207 * 3, + 0, // 162 0000 0000 0010 00 + 0, + 0, + 0x0012, // 163 0000 0000 0111 01. + 191 * 3, + 192 * 3, + 0, // 164 0000 0000 0001 10 + 188 * 3, + 190 * 3, + 0, // 165 0000 0000 0001 11 + 0, + 0, + 0x0014, // 166 0000 0000 0110 11. + 184 * 3, + 194 * 3, + 0, // 167 0000 0000 0001 01 + 0, + 0, + 0x0015, // 168 0000 0000 0110 10. + 186 * 3, + 193 * 3, + 0, // 169 0000 0000 0001 00 + 0, + 0, + 0x0017, // 170 0000 0000 0110 00. + 204 * 3, + 198 * 3, + 0, // 171 0000 0000 0011 01 + 0, + 0, + 0x0019, // 172 0000 0000 0101 10. + 0, + 0, + 0x0018, // 173 0000 0000 0101 11. + 200 * 3, + 205 * 3, + 0, // 174 0000 0000 0011 00 + 0, + 0, + 0x001f, // 175 0000 0000 0100 00. + 0, + 0, + 0x001e, // 176 0000 0000 0100 01. + 0, + 0, + 0x001c, // 177 0000 0000 0100 11. + 0, + 0, + 0x001d, // 178 0000 0000 0100 10. + 0, + 0, + 0x001a, // 179 0000 0000 0101 01. + 0, + 0, + 0x0011, // 180 0000 0000 0111 10. + 0, + 0, + 0x0010, // 181 0000 0000 0111 11. + 189 * 3, + 206 * 3, + 0, // 182 0000 0000 0010 11 + 187 * 3, + 195 * 3, + 0, // 183 0000 0000 0010 10 + 218 * 3, + 211 * 3, + 0, // 184 0000 0000 0001 010 + 0, + 0, + 0x0025, // 185 0000 0000 0010 011. + 215 * 3, + 216 * 3, + 0, // 186 0000 0000 0001 000 + 0, + 0, + 0x0024, // 187 0000 0000 0010 100. + 210 * 3, + 212 * 3, + 0, // 188 0000 0000 0001 110 + 0, + 0, + 0x0022, // 189 0000 0000 0010 110. + 213 * 3, + 209 * 3, + 0, // 190 0000 0000 0001 111 + 221 * 3, + 222 * 3, + 0, // 191 0000 0000 0001 100 + 219 * 3, + 208 * 3, + 0, // 192 0000 0000 0001 101 + 217 * 3, + 214 * 3, + 0, // 193 0000 0000 0001 001 + 223 * 3, + 220 * 3, + 0, // 194 0000 0000 0001 011 + 0, + 0, + 0x0023, // 195 0000 0000 0010 101. + 0, + 0, + 0x010b, // 196 0000 0000 0011 100. + 0, + 0, + 0x0028, // 197 0000 0000 0010 000. + 0, + 0, + 0x010c, // 198 0000 0000 0011 011. + 0, + 0, + 0x010a, // 199 0000 0000 0011 101. + 0, + 0, + 0x0020, // 200 0000 0000 0011 000. + 0, + 0, + 0x0108, // 201 0000 0000 0011 111. + 0, + 0, + 0x0109, // 202 0000 0000 0011 110. + 0, + 0, + 0x0026, // 203 0000 0000 0010 010. + 0, + 0, + 0x010d, // 204 0000 0000 0011 010. + 0, + 0, + 0x010e, // 205 0000 0000 0011 001. + 0, + 0, + 0x0021, // 206 0000 0000 0010 111. + 0, + 0, + 0x0027, // 207 0000 0000 0010 001. + 0, + 0, + 0x1f01, // 208 0000 0000 0001 1011. + 0, + 0, + 0x1b01, // 209 0000 0000 0001 1111. + 0, + 0, + 0x1e01, // 210 0000 0000 0001 1100. + 0, + 0, + 0x1002, // 211 0000 0000 0001 0101. + 0, + 0, + 0x1d01, // 212 0000 0000 0001 1101. + 0, + 0, + 0x1c01, // 213 0000 0000 0001 1110. + 0, + 0, + 0x010f, // 214 0000 0000 0001 0011. + 0, + 0, + 0x0112, // 215 0000 0000 0001 0000. + 0, + 0, + 0x0111, // 216 0000 0000 0001 0001. + 0, + 0, + 0x0110, // 217 0000 0000 0001 0010. + 0, + 0, + 0x0603, // 218 0000 0000 0001 0100. + 0, + 0, + 0x0b02, // 219 0000 0000 0001 1010. + 0, + 0, + 0x0e02, // 220 0000 0000 0001 0111. + 0, + 0, + 0x0d02, // 221 0000 0000 0001 1000. + 0, + 0, + 0x0c02, // 222 0000 0000 0001 1001. + 0, + 0, + 0x0f02 // 223 0000 0000 0001 0110. + ]) + + static PICTURE_TYPE = { + INTRA: 1, + PREDICTIVE: 2, + B: 3 + } + + static START = { + SEQUENCE: 0xb3, + SLICE_FIRST: 0x01, + SLICE_LAST: 0xaf, + PICTURE: 0x00, + EXTENSION: 0xb5, + USER_DATA: 0xb2 + } + // #endregion + + // #region 静态方法 + static CopyBlockToDestination(block, dest, index, scan) { + for (let n = 0; n < 64; n += 8, index += scan + 8) { + dest[index + 0] = block[n + 0] + dest[index + 1] = block[n + 1] + dest[index + 2] = block[n + 2] + dest[index + 3] = block[n + 3] + dest[index + 4] = block[n + 4] + dest[index + 5] = block[n + 5] + dest[index + 6] = block[n + 6] + dest[index + 7] = block[n + 7] + } + } + + static AddBlockToDestination(block, dest, index, scan) { + for (let n = 0; n < 64; n += 8, index += scan + 8) { + dest[index + 0] += block[n + 0] + dest[index + 1] += block[n + 1] + dest[index + 2] += block[n + 2] + dest[index + 3] += block[n + 3] + dest[index + 4] += block[n + 4] + dest[index + 5] += block[n + 5] + dest[index + 6] += block[n + 6] + dest[index + 7] += block[n + 7] + } + } + + static CopyValueToDestination(value, dest, index, scan) { + for (let n = 0; n < 64; n += 8, index += scan + 8) { + dest[index + 0] = value + dest[index + 1] = value + dest[index + 2] = value + dest[index + 3] = value + dest[index + 4] = value + dest[index + 5] = value + dest[index + 6] = value + dest[index + 7] = value + } + } + + static AddValueToDestination(value, dest, index, scan) { + for (let n = 0; n < 64; n += 8, index += scan + 8) { + dest[index + 0] += value + dest[index + 1] += value + dest[index + 2] += value + dest[index + 3] += value + dest[index + 4] += value + dest[index + 5] += value + dest[index + 6] += value + dest[index + 7] += value + } + } + + static IDCT(block) { + // See http://vsr.informatik.tu-chemnitz.de/~jan/MPEG/HTML/IDCT.html + // for more info. + + let b1, + b3, + b4, + b6, + b7, + tmp1, + tmp2, + m0, + x0, + x1, + x2, + x3, + x4, + y3, + y4, + y5, + y6, + y7 + + // Transform columns + for (let i = 0; i < 8; ++i) { + b1 = block[4 * 8 + i] + b3 = block[2 * 8 + i] + block[6 * 8 + i] + b4 = block[5 * 8 + i] - block[3 * 8 + i] + tmp1 = block[1 * 8 + i] + block[7 * 8 + i] + tmp2 = block[3 * 8 + i] + block[5 * 8 + i] + b6 = block[1 * 8 + i] - block[7 * 8 + i] + b7 = tmp1 + tmp2 + m0 = block[0 * 8 + i] + x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7 + x0 = x4 - (((tmp1 - tmp2) * 362 + 128) >> 8) + x1 = m0 - b1 + x2 = (((block[2 * 8 + i] - block[6 * 8 + i]) * 362 + 128) >> 8) - b3 + x3 = m0 + b1 + y3 = x1 + x2 + y4 = x3 + b3 + y5 = x1 - x2 + y6 = x3 - b3 + y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8) + block[0 * 8 + i] = b7 + y4 + block[1 * 8 + i] = x4 + y3 + block[2 * 8 + i] = y5 - x0 + block[3 * 8 + i] = y6 - y7 + block[4 * 8 + i] = y6 + y7 + block[5 * 8 + i] = x0 + y5 + block[6 * 8 + i] = y3 - x4 + block[7 * 8 + i] = y4 - b7 + } + + // Transform rows + for (let i = 0; i < 64; i += 8) { + b1 = block[4 + i] + b3 = block[2 + i] + block[6 + i] + b4 = block[5 + i] - block[3 + i] + tmp1 = block[1 + i] + block[7 + i] + tmp2 = block[3 + i] + block[5 + i] + b6 = block[1 + i] - block[7 + i] + b7 = tmp1 + tmp2 + m0 = block[0 + i] + x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7 + x0 = x4 - (((tmp1 - tmp2) * 362 + 128) >> 8) + x1 = m0 - b1 + x2 = (((block[2 + i] - block[6 + i]) * 362 + 128) >> 8) - b3 + x3 = m0 + b1 + y3 = x1 + x2 + y4 = x3 + b3 + y5 = x1 - x2 + y6 = x3 - b3 + y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8) + block[0 + i] = (b7 + y4 + 128) >> 8 + block[1 + i] = (x4 + y3 + 128) >> 8 + block[2 + i] = (y5 - x0 + 128) >> 8 + block[3 + i] = (y6 - y7 + 128) >> 8 + block[4 + i] = (y6 + y7 + 128) >> 8 + block[5 + i] = (x0 + y5 + 128) >> 8 + block[6 + i] = (y3 - x4 + 128) >> 8 + block[7 + i] = (y4 - b7 + 128) >> 8 + } + } + // #endregion +} diff --git a/src/views/components/jsmpeg/modules/demuxer/index.js b/src/views/components/jsmpeg/modules/demuxer/index.js new file mode 100644 index 0000000..b910741 --- /dev/null +++ b/src/views/components/jsmpeg/modules/demuxer/index.js @@ -0,0 +1,7 @@ +import TS from './ts' + +const Demuxer = { + TS +} + +export default Demuxer diff --git a/src/views/components/jsmpeg/modules/demuxer/ts.js b/src/views/components/jsmpeg/modules/demuxer/ts.js new file mode 100644 index 0000000..b142fa7 --- /dev/null +++ b/src/views/components/jsmpeg/modules/demuxer/ts.js @@ -0,0 +1,228 @@ +/* eslint-disable */ +import BitBuffer from '../buffer' +import MP2 from '../decoder/mp2' +import MP2WASM from '../decoder/mp2-wasm' +import MPEG1 from '../decoder/mpeg1' +import MPEG1WASM from '../decoder/mpeg1-wasm' + +export default class TS { + /** @type {BitBuffer} */ + bits + /** @type {{[key:string]: {destination: MPEG1|MPEG1WASM|MP2|MP2WASM,currentLength: number,totalLength: number, pts: number, buffers: BitBuffer}}} */ + pesPacketInfo + constructor(options) { + this.bits = null + this.leftoverBytes = null + + this.guessVideoFrameEnd = true + this.pidsToStreamIds = {} + + this.pesPacketInfo = {} + this.startTime = 0 + this.currentTime = 0 + } + + connect(streamId, destination) { + this.pesPacketInfo[streamId] = { + destination: destination, + currentLength: 0, + totalLength: 0, + pts: 0, + buffers: [] + } + } + + write(buffer) { + if (this.leftoverBytes) { + const totalLength = buffer.byteLength + this.leftoverBytes.byteLength + this.bits = new BitBuffer(totalLength) + this.bits.write([this.leftoverBytes, buffer]) + } else { + this.bits = new BitBuffer(buffer) + } + + while (this.bits.has(188 << 3) && this.parsePacket()) {} + + const leftoverCount = this.bits.byteLength - (this.bits.index >> 3) + this.leftoverBytes = + leftoverCount > 0 ? this.bits.bytes.subarray(this.bits.index >> 3) : null + } + + parsePacket() { + // Check if we're in sync with packet boundaries; attempt to resync if not. + if (this.bits.read(8) !== 0x47) { + if (!this.resync()) { + // Couldn't resync; maybe next time... + return false + } + } + + const end = (this.bits.index >> 3) + 187 + const transportError = this.bits.read(1) + const payloadStart = this.bits.read(1) + const transportPriority = this.bits.read(1) + const pid = this.bits.read(13) + const transportScrambling = this.bits.read(2) + const adaptationField = this.bits.read(2) + const continuityCounter = this.bits.read(4) + + // If this is the start of a new payload; signal the end of the previous + // frame, if we didn't do so already. + let streamId = this.pidsToStreamIds[pid] + if (payloadStart && streamId) { + const pi = this.pesPacketInfo[streamId] + if (pi && pi.currentLength) { + this.packetComplete(pi) + } + } + + // Extract current payload + if (adaptationField & 0x1) { + if (adaptationField & 0x2) { + const adaptationFieldLength = this.bits.read(8) + this.bits.skip(adaptationFieldLength << 3) + } + + if (payloadStart && this.bits.nextBytesAreStartCode()) { + this.bits.skip(24) + streamId = this.bits.read(8) + this.pidsToStreamIds[pid] = streamId + + const packetLength = this.bits.read(16) + this.bits.skip(8) + const ptsDtsFlag = this.bits.read(2) + this.bits.skip(6) + const headerLength = this.bits.read(8) + const payloadBeginIndex = this.bits.index + (headerLength << 3) + + const pi = this.pesPacketInfo[streamId] + if (pi) { + let pts = 0 + if (ptsDtsFlag & 0x2) { + // The Presentation Timestamp is encoded as 33(!) bit + // integer, but has a "marker bit" inserted at weird places + // in between, making the whole thing 5 bytes in size. + // You can't make this shit up... + this.bits.skip(4) + const p32_30 = this.bits.read(3) + this.bits.skip(1) + const p29_15 = this.bits.read(15) + this.bits.skip(1) + const p14_0 = this.bits.read(15) + this.bits.skip(1) + + // Can't use bit shifts here; we need 33 bits of precision, + // so we're using JavaScript's double number type. Also + // divide by the 90khz clock to get the pts in seconds. + pts = (p32_30 * 1073741824 + p29_15 * 32768 + p14_0) / 90000 + + this.currentTime = pts + if (this.startTime === -1) { + this.startTime = pts + } + } + + const payloadLength = packetLength ? packetLength - headerLength - 3 : 0 + this.packetStart(pi, pts, payloadLength) + } + + // Skip the rest of the header without parsing it + this.bits.index = payloadBeginIndex + } + + if (streamId) { + // Attempt to detect if the PES packet is complete. For Audio (and + // other) packets, we received a total packet length with the PES + // header, so we can check the current length. + + // For Video packets, we have to guess the end by detecting if this + // TS packet was padded - there's no good reason to pad a TS packet + // in between, but it might just fit exactly. If this fails, we can + // only wait for the next PES header for that stream. + + const pi = this.pesPacketInfo[streamId] + if (pi) { + const start = this.bits.index >> 3 + const complete = this.packetAddData(pi, start, end) + + const hasPadding = !payloadStart && adaptationField & 0x2 + if (complete || (this.guessVideoFrameEnd && hasPadding)) { + this.packetComplete(pi) + } + } + } + } + + this.bits.index = end << 3 + return true + } + + resync() { + // Check if we have enough data to attempt a resync. We need 5 full packets. + if (!this.bits.has((188 * 6) << 3)) { + return false + } + + const byteIndex = this.bits.index >> 3 + + // Look for the first sync token in the first 187 bytes + for (let i = 0; i < 187; i++) { + if (this.bits.bytes[byteIndex + i] === 0x47) { + // Look for 4 more sync tokens, each 188 bytes appart + let foundSync = true + for (let j = 1; j < 5; j++) { + if (this.bits.bytes[byteIndex + i + 188 * j] !== 0x47) { + foundSync = false + break + } + } + + if (foundSync) { + this.bits.index = (byteIndex + i + 1) << 3 + return true + } + } + } + + // In theory, we shouldn't arrive here. If we do, we had enough data but + // still didn't find sync - this can only happen if we were fed garbage + // data. Check your source! + // console.warn('JSMpeg: Possible garbage data. Skipping.') + this.bits.skip(187 << 3) + return false + } + + packetStart(pi, pts, payloadLength) { + pi.totalLength = payloadLength + pi.currentLength = 0 + pi.pts = pts + } + + packetAddData(pi, start, end) { + pi.buffers.push(this.bits.bytes.subarray(start, end)) + pi.currentLength += end - start + + const complete = pi.totalLength !== 0 && pi.currentLength >= pi.totalLength + return complete + } + + packetComplete(pi) { + // 在这里将视频流写入了解码器 + pi.destination.write(pi.pts, pi.buffers) + pi.totalLength = 0 + pi.currentLength = 0 + pi.buffers = [] + } + + static STREAM = { + PACK_HEADER: 0xba, + SYSTEM_HEADER: 0xbb, + PROGRAM_MAP: 0xbc, + PRIVATE_1: 0xbd, + PADDING: 0xbe, + PRIVATE_2: 0xbf, + AUDIO_1: 0xc0, + VIDEO_1: 0xe0, + DIRECTORY: 0xff + } +} diff --git a/src/views/components/jsmpeg/modules/jsmpeg.js b/src/views/components/jsmpeg/modules/jsmpeg.js new file mode 100644 index 0000000..874de5d --- /dev/null +++ b/src/views/components/jsmpeg/modules/jsmpeg.js @@ -0,0 +1,94 @@ +/*! jsmpeg v1.0 | (c) Dominic Szablewski | MIT license */ + +import AudioOutput from './audio-output' +import BitBuffer from './buffer' +import Decoder from './decoder' +import Demuxer from './demuxer' +import Player from './player' +import Renderer from './renderer' +import Source from './source' +import VideoElement from './video-element' + +// This sets up the JSMpeg "Namespace". The object is empty apart from the Now() +// utility function and the automatic CreateVideoElements() after DOMReady. +export default class JSMpeg { + // The Player sets up the connections between source, demuxer, decoders, + // renderer and audio output. It ties everything together, is responsible + // of scheduling decoding and provides some convenience methods for + // external users. + static Player = Player + + // A Video Element wraps the Player, shows HTML controls to start/pause + // the video and handles Audio unlocking on iOS. VideoElements can be + // created directly in HTML using the
tag. + static VideoElement = VideoElement + + // The BitBuffer wraps a Uint8Array and allows reading an arbitrary number + // of bits at a time. On writing, the BitBuffer either expands its + // internal buffer (for static files) or deletes old data (for streaming). + static BitBuffer = BitBuffer + + // A Source provides raw data from HTTP, a WebSocket connection or any + // other mean. Sources must support the following API: + // .connect(destinationNode) + // .write(buffer) + // .start() - start reading + // .resume(headroom) - continue reading; headroom to play pos in seconds + // .established - boolean, true after connection is established + // .completed - boolean, true if the source is completely loaded + // .progress - float 0-1 + static Source = Source + + // A Demuxer may sit between a Source and a Decoder. It separates the + // incoming raw data into Video, Audio and other Streams. API: + // .connect(streamId, destinationNode) + // .write(buffer) + // .currentTime – float, in seconds + // .startTime - float, in seconds + static Demuxer = Demuxer + + // A Decoder accepts an incoming Stream of raw Audio or Video data, buffers + // it and upon `.decode()` decodes a single frame of data. Video decoders + // call `destinationNode.render(Y, Cr, CB)` with the decoded pixel data; + // Audio decoders call `destinationNode.play(left, right)` with the decoded + // PCM data. API: + // .connect(destinationNode) + // .write(pts, buffer) + // .decode() + // .seek(time) + // .currentTime - float, in seconds + // .startTime - float, in seconds + static Decoder = Decoder + + // A Renderer accepts raw YCrCb data in 3 separate buffers via the render() + // method. Renderers typically convert the data into the RGBA color space + // and draw it on a Canvas, but other output - such as writing PNGs - would + // be conceivable. API: + // .render(y, cr, cb) - pixel data as Uint8Arrays + // .enabled - wether the renderer does anything upon receiving data + static Renderer = Renderer + + // Audio Outputs accept raw Stero PCM data in 2 separate buffers via the + // play() method. Outputs typically play the audio on the user's device. + // API: + // .play(sampleRate, left, right) - rate in herz; PCM data as Uint8Arrays + // .stop() + // .enqueuedTime - float, in seconds + // .enabled - wether the output does anything upon receiving data + static AudioOutput = AudioOutput + + static CreateVideoElements() { + const elements = document.querySelectorAll('.jsmpeg') + for (let i = 0; i < elements.length; i++) { + new VideoElement(elements[i]) + } + } +} + +// Automatically create players for all found
elements. +// if (document.readyState === 'complete') { +// JSMpeg.CreateVideoElements(); +// } +// else { +// document.addEventListener('DOMContentLoaded', JSMpeg.CreateVideoElements); +// } diff --git a/src/views/components/jsmpeg/modules/player.js b/src/views/components/jsmpeg/modules/player.js new file mode 100644 index 0000000..ebaf08b --- /dev/null +++ b/src/views/components/jsmpeg/modules/player.js @@ -0,0 +1,728 @@ + +/* eslint-disable */ +import Renderer from './renderer' +import { Base64ToArrayBuffer, saveToLocal, Now } from '../utils' +import AudioOutput from './audio-output' +import Decoder from './decoder' +import Demuxer from './demuxer' +import Source from './source' +import WASM from './wasm-module' +import AjaxSource from './source/ajax' +import AjaxProgressiveSource from './source/ajax-progressive' +import WSSource from './source/websocket' +import BitBuffer from './buffer' + +// The build process may append `JSMpeg.WASM_BINARY_INLINED = base64data;` +// to the minified source. +// If this property is present, jsmpeg will use the inlined binary data +// instead of trying to load a jsmpeg.wasm file via Ajax. +/** + * @class {import('../jsmpeg').JSMpegPlayer} + * @author 刘灿民 + * @description 基于jsmpeg二次封装 + */ +export default class Player { + /** @type {WSSource|AjaxSource|AjaxProgressiveSource} */ + source = null + /** @type {HTMLCanvasElement} */ + canvas = null + /** @type {HTMLElement} */ + contianer = null + + /** 录制器属性 */ + recorder = { + /** 录制持续时间 */ + duration: 0, + timer: null, + /** @type {'canvas'|'ws'} */ + mode: '', + running: false, + saveName: '', + /** + * @type {MediaRecorder} + * 媒体录制器 + */ + mediaRecorder: null, + /** + * @type {MediaStream} + * 视频流 + */ + stream: null, + /** + * @type {ArrayBuffer[]} + * 视频数据块 + */ + chunks: null, + startTiming() { + this.duration = 0 + this.timer = setInterval(() => { + this.duration += 1 + }, 1000) + }, + pauseTiming() { + clearInterval(this.timer) + this.timer = null + }, + continueTiming() { + this.timer = setInterval(() => { + this.duration += 1 + }, 1000) + }, + stopTiming() { + this.pauseTiming() + this.duration = 0 + }, + clear() { + this.running = false + this.mediaRecorder = null + this.stream = null + this.chunks = null + } + } + /** 变量存储器 */ + store = { + canvasAngle: 0 + } + + /** 是否循环播放 */ + loop = false + /** 是否正在播放 */ + isPlaying = false + /** 表示播放器处于后台(不可见状态) */ + isBackground = false + get isRecording() { + return this.recorder.running + } + /** 录制持续时间 */ + get recordingDuration() { + return this.recorder.duration + } + /** + * + * @param {string} url + * @param {import('../types').PlayerOptions} options + */ + constructor(url, options = {}) { + Object.defineProperty(this, 'currentTime', { + get: this.getCurrentTime, + set: this.setCurrentTime + }) + Object.defineProperty(this, 'volume', { + get: this.getVolume, + set: this.setVolume + }) + this.init(url, options) + } + /** + * + * @param {string} url + * @param {import('../types').PlayerOptions} options + */ + init(url, options) { + this.options = options + + this.initCanvas() + this.initSource(url) + + this.maxAudioLag = options.maxAudioLag || 0.25 + this.loop = options.loop !== false + this.autoplay = !!options.autoplay || options.streaming + + this.demuxer = new Demuxer.TS(options) + this.source.connect(this.demuxer) + + // this.demuxer.write(new Array(1024).fill(255)) + + if (!options.disableWebAssembly && WASM.IsSupported()) { + this.wasmModule = WASM.GetModule() + options.wasmModule = this.wasmModule + } + + this.initVideo() + this.initAudio() + + this.paused = true + this.unpauseOnShow = false + document.addEventListener('visibilitychange', this.showHide.bind(this)) + + // If we have WebAssembly support, wait until the module is compiled before + // loading the source. Otherwise the decoders won't know what to do with + // the source data. + if (this.wasmModule) { + if (this.wasmModule.ready) { + this.startLoading() + } else if (Player.WASM_BINARY_INLINED) { + const wasm = Base64ToArrayBuffer(Player.WASM_BINARY_INLINED) + this.wasmModule.loadFromBuffer(wasm, this.startLoading.bind(this)) + } else { + this.wasmModule.loadFromFile( + 'jsmpeg.wasm', + this.startLoading.bind(this) + ) + } + } else { + this.startLoading() + } + this.renderer.clear() + } + initCanvas() { + const options = this.options + + if (!(options.canvas instanceof HTMLCanvasElement)) { + options.canvas = document.createElement('canvas') + } + this.canvas = options.canvas + + if (typeof options.contianer === 'string') { + options.contianer = document.querySelector(options.contianer) + } else if (!options.contianer && this.canvas.parentElement) { + options.contianer = this.canvas.parentElement + } + + if (!(options.contianer instanceof HTMLElement)) { + throw new Error('[Player] 找不到容器元素') + } + this.contianer = options.contianer + if (!this.contianer.contains(this.canvas)) { + this.contianer.appendChild(this.canvas) + } + } + initSource(url = null) { + const options = this.options + this.url = url + if (options.source) { + this.source = new options.source(url, options) + options.streaming = !!this.source.streaming + } else if (url.match(/^wss?:\/\//)) { + this.source = new Source.WebSocket(url, { + ...options, + onSourceEstablished: this.handleSourceEstablished.bind(this), + onSourceClosedCallback: this.handleSourceClosed.bind(this), + onSourceStreamInterrupt: this.handleSourceStreamInterrupt.bind(this), + onSourceConnected: this.handleSourceConnected.bind(this) + }) + options.streaming = true + } else if (options.progressive !== false) { + this.source = new Source.AjaxProgressive(url, options) + options.streaming = false + } else { + this.source = new Source.Ajax(url, options) + options.streaming = false + } + + if (this.source.destination == null && this.demuxer) { + this.source.connect(this.demuxer) + } + } + initVideo() { + const options = this.options + + if (options.video !== false) { + this.video = options.wasmModule + ? new Decoder.MPEG1VideoWASM(options) + : new Decoder.MPEG1Video(options) + this.video.player = this + + this.renderer = + !options.disableGl && Renderer.WebGL.IsSupported() + ? new Renderer.WebGL(options) + : new Renderer.Canvas2D(options) + + this.demuxer.connect(Demuxer.TS.STREAM.VIDEO_1, this.video) + this.video.connect(this.renderer) + } + } + initAudio() { + const options = this.options + + if (options.audio !== false && AudioOutput.WebAudio.IsSupported()) { + this.audio = options.wasmModule + ? new Decoder.MP2AudioWASM(options) + : new Decoder.MP2Audio(options) + this.audioOut = new AudioOutput.WebAudio(options) + this.demuxer.connect(Demuxer.TS.STREAM.AUDIO_1, this.audio) + this.audio.connect(this.audioOut) + } + } + + setUrl(url = '') { + if (this.isPlaying) this.stop(true) + + if (this.source instanceof WSSource) { + this.source.changeUrl(url) + } else { + this.source.url = url + if (!!url && !this.isBackground) { + this.play() + } + } + } + + /** + * + * @param {keyof import('../types').PlayerOptions} name + * @param {any} value + */ + setOption(name, value) { + console.log('setOption: ', name, value) + if (typeof value === undefined) return + + switch (name) { + case 'pauseWhenHidden': + this.options.pauseWhenHidden = value + break + } + } + + startLoading() { + this.source?.start() + if (this.autoplay) { + this.play() + } + } + + showHide(ev) { + if (!this.options.pauseWhenHidden) { + this.play() + return + } + + if (document.visibilityState === 'hidden') { + this.unpauseOnShow = this.wantsToPlay + this.intoBackground() + } else if (this.unpauseOnShow) { + this.intoFront() + } + } + + getVolume() { + return this.audioOut ? this.audioOut.volume : 0 + } + + setVolume(volume) { + if (this.audioOut) { + this.audioOut.volume = volume + } + } + + intoFront() { + this.isBackground = false + if (this.paused) { + this.play() + } + } + + intoBackground() { + this.isBackground = true + if (this.options.pauseWhenHidden) { + this.pause() + } + } + /** + * 旋转画布 + * @param {number} angle 角度 + * @param {boolean} append 是否为追加角度 + * @returns + */ + rotate(angle, append = false) { + if (!this.canvas || typeof angle !== 'number') return + + const canvas = this.canvas + + angle = append ? this.store.canvasAngle + angle : angle + angle = angle >= 360 ? angle - 360 : angle <= -360 ? angle + 360 : angle + + if ((Math.abs(angle) / 90) % 2 === 1) { + // 如果是90整数倍,表示为垂直状态 + const containerBound = this.contianer.getBoundingClientRect() + const canvasBound = canvas.getBoundingClientRect() + + if (canvas.width > canvas.height) { + // 宽>高,取容器高度作为canvas最大宽度 + canvas.style.width = containerBound.height + 'px' + } else { + // 宽<=高,取容器宽度作为canvas最大高度 + canvas.style.height = containerBound.width + 'px' + } + } else { + canvas.style.width = null + canvas.style.height = null + } + canvas.style.transform = `rotate(${angle}deg)` + + this.store.canvasAngle = angle + } + /** + * 截图 + * @author 刘灿民 + * @param {string} name + */ + snapshot(name = 'JSMPeg') { + if (this.canvas) { + const mime = 'image/png' + const url = this.canvas.toDataURL(mime) + + saveToLocal( + url.replace(mime, 'image/octet-stream'), + `${name}_截图_${new Date().toLocaleTimeString()}.png`, + mime + ) + } + } + /** + * 视频录制 + * @author 刘灿民 + * @param {string} name + * @param {'ws'|'canvas'} mode + */ + recording(name = 'JSMpeg', mode = 'ws') { + try { + if (!this.isPlaying) { + return + } + if (this.isRecording || this.recorder.stream) { + // 停止录制 + this.recorder.stopTiming() + // https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API + if ( + this.recorder.mode === 'canvas' && + this.recorder.mediaRecorder && + this.recorder.stream instanceof MediaStream + ) { + this.recorder.mediaRecorder.stop() + saveToLocal( + this.recorder.chunks, + `${this.recorder.saveName}.webm`, + 'video/webm;codecs=vp9' + ) + this.recorder.mediaRecorder = null + } else if ( + this.recorder.mode === 'ws' && + this.recorder.chunks instanceof Array + ) { + saveToLocal( + this.recorder.chunks, + `${this.recorder.saveName}.ts`, + 'video/MP2T' + ) + this.source.recorder = undefined + } + this.recorder.clear() + this.recorder.running = false + } else { + // 开始录制 + if (mode === 'canvas') { + // 此方法兼容性较差,captureStream、MediaRecorder好像都是新规范,有些浏览器不支持 + const chunks = [] + /** @type {MediaStream} */ + const stream = this.canvas?.captureStream(25) + const mediaRecorder = new MediaRecorder(stream, { + mimeType: 'video/webm;codecs=vp9' + }) + mediaRecorder.ondataavailable = (e) => { + chunks.push(e.data) + } + mediaRecorder.start() + this.recorder.mediaRecorder = mediaRecorder + this.recorder.chunks = chunks + this.recorder.stream = stream + this.recorder.mode = 'canvas' + this.recorder.running = true + // this.recording.recorder = new MediaRecorder(this.recording.stream) + } else if (mode === 'ws') { + // 服务端转发过来的流就是ffmpeg已转码的ts视频流数据,所以在websocket收到数据的时候,存放到数组中即可实现录制 + + this.recorder.chunks = [] + this.recorder.chunks.write = function(data) { + this.push(data) + } + this.source.recorder = this.recorder.chunks + this.recorder.mode = 'ws' + this.recorder.running = true + } else return + this.recorder.saveName = `${name}_录制_${new Date().toLocaleTimeString()}` + this.recorder.startTiming() + } + } catch (error) { + console.error(error) + } + } + /** + * + * @returns + */ + play() { + if (this.animationId) { + return + } else if (this.isBackground) { + this.wantsToPlay = true + return + } + + this.animationId = requestAnimationFrame(this.update.bind(this)) + this.wantsToPlay = true + this.paused = false + if (this.source.isPaused) { + this.source.continue() + } + if (this.recorder.running) { + this.recorder.continueTiming() + } + } + + /** + * + * @returns + */ + pause() { + if (this.paused) { + return + } + + cancelAnimationFrame(this.animationId) + this.source?.pause() + this.animationId = null + this.wantsToPlay = false + this.isPlaying = false + this.paused = true + + if (this.audio && this.audio.canPlay) { + // Seek to the currentTime again - audio may already be enqueued a bit + // further, so we have to rewind it. + this.audioOut.stop() + this.seek(this.currentTime) + } + + if (this.options.onPause) { + this.options.onPause(this) + } + if (this.recorder.running) { + this.recorder.pauseTiming() + } + } + + stop(clear = true) { + this.pause() + this.seek(0) + if (this.video && this.options.decodeFirstFrame !== false) { + this.video.decode() + } + + if (clear) { + this.clearPlayer() + } + } + + clearPlayer() { + this.renderer.clear() + + if (this.video.hasSequenceHeader) { + const bufferSize = this.options.videoBufferSize ?? 512 * 1024 + const bufferMode = this.options.streaming + ? BitBuffer.MODE.EVICT + : BitBuffer.MODE.EXPAND + this.video.bits = new BitBuffer(bufferSize, bufferMode) + // this.demuxer.write(new BitBuffer(bufferSize, bufferMode)) + } + } + + reload() {} + + destroy() { + this.pause() + this.source.destroy() + this.video && this.video.destroy() + this.renderer && this.renderer.destroy() + this.audio && this.audio.destroy() + this.audioOut && this.audioOut.destroy() + this.canvas?.remove() + this.canvas = null + this.options.canvas = null + } + + seek(time) { + const startOffset = + this.audio && this.audio.canPlay + ? this.audio.startTime + : this.video.startTime + + if (this.video) { + this.video.seek(time + startOffset) + } + if (this.audio) { + this.audio.seek(time + startOffset) + } + + this.startTime = Now() - time + } + + getCurrentTime() { + return this.video.currentTime - this.video.startTime + // return this.audio && this.audio.canPlay ? this.audio.currentTime - this.audio.startTime : this.video.currentTime - this.video.startTime + } + + setCurrentTime(time) { + this.seek(time) + } + + update() { + this.animationId = requestAnimationFrame(this.update.bind(this)) + + if (!this.source.established) { + if (this.renderer) { + this.renderer.clear() + // this.renderer.renderProgress(this.source.progress) + } + return + } + + if (!this.isPlaying) { + this.isPlaying = true + this.startTime = Now() - this.currentTime + + if (this.options.onPlay) { + this.options.onPlay(this) + } + } + try { + if (this.options.streaming) { + this.updateForStreaming() + } else { + this.updateForStaticFile() + } + } catch (error) { + if (error.message?.includes('memory access out of bounds')) { + console.error('内存溢出,尝试重新加载') + this.destroy() + setTimeout(() => this.init(this.url, this.options), 3000) + } else { + console.error(error) + } + } + this.options.onUpdate?.() + } + + updateForStreaming() { + // 当流到达时,立即解码所有已经缓冲的,以减少播放延迟。 + + if (this.video) { + // console.log('update') + this.video.decode() + } + + if (this.audio) { + let decoded = false + do { + // 如果已经有很多音频流排队,禁用输出并跟上编码。 + if (this.audioOut.enqueuedTime > this.maxAudioLag) { + this.audioOut.resetEnqueuedTime() + this.audioOut.enabled = false + } + decoded = this.audio.decode() + } while (decoded) + + this.audioOut.enabled = true + } + } + + nextFrame() { + if (this.source.established && this.video) { + return this.video.decode() + } + return false + } + + updateForStaticFile() { + let notEnoughData = false + let headroom = 0 + + // If we have an audio track, we always try to sync the video to the audio. + // Gaps and discontinuities are far more percetable in audio than in video. + + if (this.audio && this.audio.canPlay) { + // Do we have to decode and enqueue some more audio data? + while ( + !notEnoughData && + this.audio.decodedTime - this.audio.currentTime < 0.25 + ) { + notEnoughData = !this.audio.decode() + } + + // Sync video to audio + if (this.video && this.video.currentTime < this.audio.currentTime) { + notEnoughData = !this.video.decode() + } + + headroom = this.demuxer.currentTime - this.audio.currentTime + } else if (this.video) { + // Video only - sync it to player's wallclock + const targetTime = Now() - this.startTime + this.video.startTime + const lateTime = targetTime - this.video.currentTime + const frameTime = 1 / this.video.frameRate + + if (this.video && lateTime > 0) { + // If the video is too far behind (>2 frames), simply reset the + // target time to the next frame instead of trying to catch up. + if (lateTime > frameTime * 2) { + this.startTime += lateTime + } + + notEnoughData = !this.video.decode() + } + + headroom = this.demuxer.currentTime - targetTime + } + + // Notify the source of the playhead headroom, so it can decide whether to + // continue loading further data. + this.source.continue(headroom) + + // If we failed to decode and the source is complete, it means we reached + // the end of our data. We may want to loop. + if (notEnoughData && this.source.completed) { + if (this.loop) { + this.seek(0) + } else { + this.pause() + if (this.options.onEnded) { + this.options.onEnded(this) + } + } + } + + // If there's not enough data and the source is not completed, we have + // just stalled. + else if (notEnoughData && this.options.onStalled) { + this.options.onStalled(this) + } + } + + handleSourceConnected() { + this.options.onSourceConnected?.(this) + } + handleSourceEstablished() { + if (this.isBackground) { + this.source.pause() + } else if (this.paused) { + this.play() + } + this.options.onSourceEstablished?.(this) + } + handleSourceStreamInterrupt() { + if (this.options.onSourceStreamInterrupt) { + this.options.onSourceStreamInterrupt(this) + } + } + handleSourceClosed() { + this.pause() + if (this.isRecording) { + this.recording() + } + + if (this.options.onSourceClosed) { + this.options.onSourceClosed(this) + } + } + static WASM_BINARY_INLINED = + 'AGFzbQEAAAAADwZkeWxpbmuA0MACBAAAAAE0CWAEf39/fwBgAX8Bf2ACf38Bf2ABfwBgAn9/AGABfwF9YAZ/f39/f38AYAN/f38Bf2AAAAJGBANlbnYOX19fYXNzZXJ0X2ZhaWwAAANlbnYFX3NicmsAAQNlbnYNX19tZW1vcnlfYmFzZQN/AANlbnYGbWVtb3J5AgCAAgM9PAIDAgEEBAMBBQEBAQEBAQEDBAMGAwQAAwAAAAIDAgEEBAEBAQEBAAACAwIBAgECAQEBAQMEAwMCBwcHCAYLAn8BQQALfwFBAAsHggYfEl9fcG9zdF9pbnN0YW50aWF0ZQA9BV9mcmVlADgHX21hbGxvYwAxB19tZW1jcHkAOghfbWVtbW92ZQA7B19tZW1zZXQAPBNfbXAyX2RlY29kZXJfY3JlYXRlAB0TX21wMl9kZWNvZGVyX2RlY29kZQAmFF9tcDJfZGVjb2Rlcl9kZXN0cm95AB4WX21wMl9kZWNvZGVyX2RpZF93cml0ZQAiFl9tcDJfZGVjb2Rlcl9nZXRfaW5kZXgAICFfbXAyX2RlY29kZXJfZ2V0X2xlZnRfY2hhbm5lbF9wdHIAJCJfbXAyX2RlY29kZXJfZ2V0X3JpZ2h0X2NoYW5uZWxfcHRyACUcX21wMl9kZWNvZGVyX2dldF9zYW1wbGVfcmF0ZQAjGl9tcDJfZGVjb2Rlcl9nZXRfd3JpdGVfcHRyAB8WX21wMl9kZWNvZGVyX3NldF9pbmRleAAhFV9tcGVnMV9kZWNvZGVyX2NyZWF0ZQACFV9tcGVnMV9kZWNvZGVyX2RlY29kZQARFl9tcGVnMV9kZWNvZGVyX2Rlc3Ryb3kAAxhfbXBlZzFfZGVjb2Rlcl9kaWRfd3JpdGUABxlfbXBlZzFfZGVjb2Rlcl9nZXRfY2JfcHRyABAdX21wZWcxX2RlY29kZXJfZ2V0X2NvZGVkX3NpemUACxlfbXBlZzFfZGVjb2Rlcl9nZXRfY3JfcHRyAA8dX21wZWcxX2RlY29kZXJfZ2V0X2ZyYW1lX3JhdGUAChlfbXBlZzFfZGVjb2Rlcl9nZXRfaGVpZ2h0AA0YX21wZWcxX2RlY29kZXJfZ2V0X2luZGV4AAUYX21wZWcxX2RlY29kZXJfZ2V0X3dpZHRoAAwcX21wZWcxX2RlY29kZXJfZ2V0X3dy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BQJ/IANBzQBHDQIgACgCgAFBBhAwIQMCQAJAIAAoAoABQQgQMCIEIgYEQCAGQYABRgRADAIFDAMLAAsgACgCgAFBCBAwDAILIAAoAoABQQgQMEGAfmoMAQsgBEGAfmogBCAEQYABShsLCyEEDAELIAZB/wFxIgNBACADayAAKAKAAUEBEDBFGyEEIAZBCHUhAwsgAiADaiIGIwBBQGtqLQAAIgMgBWotAAAgACgCREEAIARBAXQiAkEfdUEBciAAKAJcGyACamxsIgJBBHVBAEEBQX8gAkEPShsgAkEQcRtrIgJBgHAgAkGAcEobIQIgAEGcAWogA0ECdGogAyMAQZA1amotAAAgAkH/DyACQf8PSBtsNgIAIAZBAWohAgwBCwsgAUEESARAIABBhAFqIQUgAUEDdEEIcSAAKAJUIAAoAhgiAyAAKAJQbGpBBHRyIANBA3RBACABQQJxG2ohBAUgAEGMAWogAEGIAWogAUEERhshBSAAKAJQIAAoAhgiAUECdGwgACgCVEEDdGohBCABQQF1IQMLIANBeGohAyAFKAIAIQUgAkEBRiECIABBnAFqIQEgACgCXARAIAIEQCABKAIAQYABakEIdSAFIAQgAxAYIAFBADYCAAUgARAZIAEgBSAEIAMQGiABQQBBgAIQPBoLBSACBEAgASgCAEGAAWpBCHUgBSAEIAMQGyABQQA2AgAFIAEQGSABIAUgBCADEBwgAUEAQYACEDwaCwsL7AYAIAEgAmogAEEAIABBAEobIgBB/wEgAEH/AUgbQf8BcSIAOgAAIAEgAkEBamogADoAACABIAJBAmpqIAA6AAAgASACQQNqaiAAOgAAIAEgAkEEamogADoAACABIAJBBWpqIAA6AAAgASACQQZqaiAAOgAAIAEgAkEHamogADoAACABIAIgA0EIaiIDaiICaiAAOgAAIAEgAkEBamogADoAACABIAJBAmpqIAA6AAAgASACQQNqaiAAOgAAIAEgAkEEamogADoAACABIAJBBWpqIAA6AAAgASACQQZqaiAAOgAAIAEgAkEHamogADoAACABIAIgA2oiAmogADoAACABIAJBAWpqIAA6AAAgASACQQJqaiAAOgAAIAEgAkEDamogADoAACABIAJBBGpqIAA6AAAgASACQQVqaiAAOgAAIAEgAkEGamogADoAACABIAJBB2pqIAA6AAAgASACIANqIgJqIAA6AAAgASACQQFqaiAAOgAAIAEgAkECamogADoAACABIAJBA2pqIAA6AAAgASACQQRqaiAAOgAAIAEgAkEFamogADoAACABIAJBBmpqIAA6AAAgASACQQdqaiAAOgAAIAEgAiADaiICaiAAOgAAIAEgAkEBamogADoAACABIAJBAmpqIAA6AAAgASACQQNqaiAAOgAAIAEgAkEEamogADoAACABIAJBBWpqIAA6AAAgASACQQZqaiAAOgAAIAEgAkEHamogADoAACABIAIgA2oiAmogADoAACABIAJBAWpqIAA6AAAgASACQQJqaiAAOgAAIAEgAkEDamogADoAACABIAJBBGpqIAA6AAAgASACQQVqaiAAOgAAIAEgAkEGamogADoAACABIAJBB2pqIAA6AAAgASACIANqIgJqIAA6AAAgASACQQFqaiAAOgAAIAEgAkECamogADoAACABIAJBA2pqIAA6AAAgASACQQRqaiAAOgAAIAEgAkEFamogADoAACABIAJBBmpqIAA6AAAgASACQQdqaiAAOgAAIAEgAiADaiICaiAAOgAAIAEgAkEBamogADoAACABIAJBAmpqIAA6AAAgASACQQNqaiAAOgAAIAEgAkEEamogADoAACABIAJBBWpqIAA6AAAgASACQQZqaiAAOgAAIAEgAkEHamogADoAAAubBgEUfwNAIAFBEGpBAnQgAGoiBygCACIGIAFBMGpBAnQgAGoiDSgCACIJaiEFIAFBCGpBAnQgAGoiDigCACICIAFBOGpBAnQgAGoiDygCACIDaiEEIAIgA2siEEHZA2wgAUEoakECdCAAaiIKKAIAIgIgAUEYakECdCAAaiIRKAIAIgNrIgtBvH5sQYABampBCHUgBCACIANqIghqIgJrIgMgBCAIa0HqAmxBgAFqQQh1ayEEIAFBAnQgAGoiCCgCACIMIAFBIGpBAnQgAGoiEigCACITayIUIAYgCWtB6gJsQYABakEIdSAFayIJaiEGIAggAiAMIBNqIgggBWoiDGo2AgAgDiADIAZqNgIAIAcgFCAJayIHIARrNgIAIBEgC0HZA2xBgAFqIBBBxAFsakEIdSAEaiIJIAggBWsiBWo2AgAgEiAFIAlrNgIAIAogBCAHajYCACANIAYgA2s2AgAgDyAMIAJr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aiABQQxsaiAAKAIIQQYQMCICNgLUAiAAIANBgANsaiABQQxsaiACNgLQAiAFIAI2AgAMAgsgBSAAKAIIQQYQMDYCACAAIANBgANsaiABQQxsaiAAKAIIQQYQMCICNgLUAiAAIANBgANsaiABQQxsaiACNgLQAgsLCyADQQFqIgMgCEkNAAsgAUEBaiIBIAtHDQALCwsLaiALQSBJIRsgAEHMtgFqIQQgAEHMCGohHCAAQcz2AGohHSAAQcwLaiEeIABBzJYBaiEfA0BBACEWIBQhAwNAIBgEQEEAIQEDQCAAQQAgASAOECggAEEBIAEgDhAoIAFBAWoiASANSA0ACwsgGQRAIA0hAQNAIABBACABIA4QKCAAQcwLaiABQQxsaiAAQcwIaiABQQxsaigCADYCACAAIAFBDGxqQdALaiAAIAFBDGxqQdAIaigCADYCACAAIAFBDGxqQdQLaiAAIAFBDGxqQdQIaigCADYCACABQQFqIgEgC0gNAAsLIBsEQCALIQEDQCAAQcwIaiABQQxsakEANgIAIAAgAUEMbGpB0AhqQQA2AgAgACABQQxsakHUCGpBADYCACAAQcwLaiABQQxsakEANgIAIAAgAUEMbGpB0AtqQQA2AgAgACABQQxsakHUC2pBADYCACABQQFqIgFBIEcNAAsLIAAoAgQhASADIRFBACESA0AgACABQcAHakH/B3EiATYCBCAcIBIgHSABECkgBEIANwIAIARCADcCCCAEQgA3AhAgBEIANwIYIARCADcCICAEQgA3AiggBEIANwIwIARCADcCOCAEQUBrQgA3AgAgBEIANwJIIARCADcCUCAEQgA3AlggBEIANwJgIARCADcCaCAEQgA3AnAgBEIANwJ4IAAoAgQiD0EBdSETQf8HIA9BgAFvQQF1IgxrIgFBgH9xIRUgAUEHdkEGdEHABGohEEGABCATayEGIAwhAQNAIAYhB0EAIQkgASECA0AgB0EBaiEIIAJBAWohBSAAQcy2AWogCUECdGoiCiAAQczWAGogB0ECdGoqAgAgAEHM9gBqIAJBAnRqKgIAlCAKKAIAspKoNgIAIAlBAWoiCUEgRwRAIAghByAFIQIMAQsLIAFBgAFqIQIgBkFAayEGIAFBgAdIBEAgAiEBDAELC0HgByAMIBVqayIBQYAISARAIBAgE2tBoHxqIQYD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IkogWCBLk7tEujBFka7n9D+itiJLk7tEuEt/Zp6g5j+itiI6kiEqIANBMGpBAnQgAmogTSA8kow4AgAgA0EvakECdCACaiApICiSIEwgSZIgJJIiKJKMIik4AgAgA0ExakECdCACaiApOAIAIANBLmpBAnQgAmogRiBFkiAjkowiKTgCACADQTJqQQJ0IAJqICk4AgAgA0EtakECdCACaiAwIC2SIEAgPpIgJpIiKZIiPCAokowiKDgCACADQTNqQQJ0IAJqICg4AgAgA0EsakECdCACaiA2IE+SIFEgUJIgNZIiKJKMIjY4AgAgA0E0akECdCACaiA2OAIAIANBK2pBAnQgAmogPCA/IDiSIDOSIjwgJJIiJJKMIjY4AgAgA0E1akECdCACaiA2OAIAIANBKmpBAnQgAmogPSBSkiA7kiI9ICOSjCIjOAIAIANBNmpBAnQgAmogIzgCACADQSlqQQJ0IAJqIEsgSpIgOpIiIyAkkowiJDgCACADQTdqQQJ0IAJqICQ4AgAgA0EoakECdCACaiBIIE6SIDGSjCIkOAIAIANBOGpBAnQgAmogJDgCACADQSdqQQJ0IAJqICMgPCArkiIjkowiJDgCACADQTlqQQJ0IAJqICQ4AgAgA0EmakECdCACaiA9ICeSjCIkOAIAIANBOmpBAnQgAmogJDgCACADQSVqQQJ0IAJqICkgLSAwk7tEuEt/Zp6g5j+itiIpkiItICOSjCIjOAIAIANBO2pBAnQgAmogIzgCACADQSRqQQJ0IAJqICggR5KMIiM4AgAgA0E8akECdCACaiAjOAIAIANBI2pBAnQgAmogLSArIDKSIiOSjCIoOAIAIANBPWpBAnQgAmogKDgCACADQSJqQQJ0IAJqICcgU5KMIic4AgAgA0E+akECdCACaiAnOAIAIANBIWpBAnQgAmogIyBCkowiIzgCACADQT9qQQJ0IAJqICM4AgAgA0EgakECdCACaiA0jDgCACADQQJ0IAJqIDQ4AgAgA0EfakECdCACaiAsjDgCACADQQFqQQJ0IAJqICw4AgAgA0EeakECdCACaiA5jDgCACADQQJqQQJ0IAJqIDk4AgAgA0EdakECdCACaiApICaSIjQgQZIiI4w4AgAgA0EDakECdCACaiAjOAIAIANBHGpBAnQgAmogL4w4AgAgA0EEakECdCACaiAvOAIAIANBG2pBAnQgAmogNCA3IDOSIjSSIi+MOAIAIANBBWpBAnQgAmogLzgCACADQRpqQQJ0IAJqIEQgO5IiL4w4AgAgA0EGakECdCACaiAvOAIAIANBGWpBAnQgAmogNCA6kiI0jDgCACADQQdqQQJ0IAJqIDQ4AgAgA0EYakECdCACaiAxjDgCACADQQhqQQJ0IAJqIDE4AgAgA0EXakECdCACaiAqjDgCACADQQlqQQJ0IAJqICo4AgAgA0EWakECdCACaiA7IC6SIjGMOAIAIANBCmpBAnQgAmogMTgCACADQRVqQQJ0IAJqIEMgJpIiMYw4AgAgA0ELakECdCACaiAxOAIAIANBFGpBAnQgAmogNYw4AgAgA0EMakECdCACaiA1OAIAIANBE2pBAnQgAmogJiAlkiImjDgCACADQQ1qQQJ0IAJqICY4AgAgA0ESakECdCACaiAujDgCACADQQ5qQQJ0IAJqIC44AgAgA0ERakECdCACaiAljDgCACADQQ9qQQJ0IAJqICU4AgAgA0EQakECdCACakMAAAAAOAIACy8BAX9BFBAxIgIgATYCECACIAAQMTYCACACIAA2AgggAkEANgIMIAJBADYCBCACCw0AIAAoAgAQOCAAEDgL6QEBBH8gACgCCCIDIAAoAgwiAmsiBCABSQRAAkAgACgCEEECRgRAIAAgACgCACABIARrIANBAXQiAiACIARqIAFJGyIBEDk2AgAgACABNgIIIAAoAgQgACgCDCIBQQN0IgJNDQEgACACNgIEDAELIAIgACgCBCIFQQN2IgNGIAMgBGogAUlyBEAgAEEANgIMIABBADYCBEEAIQEMAQsgAwRAIAAoAgAiASABIANqIAIgA2sQOxogACAAKAIMIANrIgE2AgwgACAAKAIEIAVBeHFrNgIEBSACIQELCwUgAiEBCyAAKAIAIAFqC40BAQR/IAAoAgRBB2pBA3YiASAAKAIMIgRJBEACQCAAKAIAIQIDQAJAIAFBAWohAyABIAJqLAAARQRAIAIgA2osAABFBEAgAiABQQJqaiwAAEEBRg0CCwsgAyAETw0CIAMhAQwBCwsgACABQQN0QSBqNgIEIAIgAUEDamotAAAPCwsgACAEQQN0NgIEQX8LqQEBBH8gACgCBEEHakEDdiICIAAoAgwiBUkEQAJAIAAoAgAh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aiIGQRBLDQALIAMgCWohAyAIayECIQEMAgsgAyABKAIAIgU6AAAgAyABLAABOgABIAJBbGpBcHEiCEESaiIJIAFqIAJBbmogA0ECaiEEIAJBfmohBiABQQJqIQIgBSEBA0AgBCACKAICIgVBEHQgAUEQdnI2AgAgBCACKAIGIgFBEHQgBUEQdnI2AgQgBCACKAIKIgVBEHQgAUEQdnI2AgggBCACKAIOIgFBEHQgBUEQdnI2AgwgAkEQaiECIARBEGohBCAGQXBqIgZBEUsNAAsgAyAJaiEDIAhrIQIhAQwBCyADIAEoAgAiBToAACACQWxqQXBxIghBEWoiCSABaiACQW9qIANBAWohBCACQX9qIQYgAUEBaiECIAUhAQNAIAQgAigCAyIFQRh0IAFBCHZyNgIAIAQgAigCByIBQRh0IAVBCHZyNgIEIAQgAigCCyIFQRh0IAFBCHZyNgIIIAQgAigCDyIBQRh0IAVBCHZyNgIMIAJBEGohAiAEQRBqIQQgBkFwaiIGQRJLDQALIAMgCWohAyAIayECIQELCyACQRBxBEAgAyABLAAAOgAAIAMgASwAAToAASADIAEsAAI6AAIgAyABLAADOgADIAMgASwABDoABCADIAEsAAU6AAUgAyABLAAGOgAGIAMgASwABzoAByADIAEsAAg6AAggAyABLAAJOgAJIAMgASwACjoACiADIAEsAAs6AAsgAyABLAAMOgAMIAMgASwADToADSADIAEsAA46AA4gAyABLAAPOgAPIANBEGohAyABQRBqIQELIAJBCHEEQCADIAEsAAA6AAAgAyABLAABOgABIAMgASwAAjoAAiADIAEsAAM6AAMgAyABLAAEOgAEIAMgASwABToABSADIAEsAAY6AAYgAyABLAAHOgAHIANBCGohAyABQQhqIQELIAJBBHEEQCADIAEsAAA6AAAgAyABLAABOgABIAMgASwAAjoAAiADIAEsAAM6AAMgA0EEaiEDIAFBBGohAQsgAkECcQRAIAMgASwAADoAACADIAEsAAE6AAEgA0ECaiEDIAFBAmohAQsgAkEBcUUEQCAADwsgAyABLAAAOgAAIAALwAMBBn8gACABRgRAIAAPCyABIAJqIABLIAAgAmoiBSABS3FFBEAgACABIAIQOhogAA8LIAEgACIDc0EDcUUhBCADIAFJBH8g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JCQkJCQkJCQkJCQkJCQkJCQAAAABAhEAAAAAAAAAAAAAAAAAAQIDBAUGEQAAAAAAAAAAAAECAwQFBgcICQoLDA0OEQABAwUGBwgJCgsMDQ4PEBEAAQIEBQYHCAkKCwwNDg8RAAECAwQFBgcICQoLDA0ODwMAAQUFAAEHBwAAAwkAAQoPAAAEHwAABT8AAAZ/AAAH/wAACP8BAAn/AwAK/wcAC/8PAAz/HwAN/z8ADv9/AA///wAQAAAAAAAAAAAAAAAAAAAAAupflgEwikIBCAgMW1tbXltec2l6ZSA+IDAAL2hvbWUvZG9taW5pYy9wcm9qZWN0cy9lbXNkay9mYXN0Y29tcC9lbXNjcmlwdGVuLy9zeXN0ZW0vbGliL2VtbWFsbG9jLmNwcABnZXRGcmVlTGlzdEluZGV4AE1JTl9GUkVFTElTVF9JTkRFWCA8PSBpbmRleCAmJiBpbmRleCA8IE1BWF9GUkVFTElTVF9JTkRFWABnZXRBZnRlcihyZWdpb24pIDw9IHNicmsoMCkAZW1tYWxsb2NfbWFsbG9jAHJlZ2lvbi0+Z2V0VXNlZCgpAGdldFBheWxvYWQAKGNoYXIqKWV4dHJhUHRyID09IChjaGFyKilwdHIgKyBzYnJrU2l6ZQBhbGxvY2F0ZVJlZ2lvbgAhbGFzdFJlZ2lvbgAhZmlyc3RSZWdpb24AZmlyc3RSZWdpb24AcHRyID09IGdldEFmdGVyKGxhc3RSZWdpb24pAGV4dGVuZExhc3RSZWdpb24AYWRkVG9GcmVlTGlzdABnZXRCaWdFbm91Z2hGcmVlTGlzdEluZGV4AHBheWxvYWRTaXplID49IHNpemUAcG9zc2libHlTcGxpdFJlbWFpbmRlcgBleHRyYSA+PSBNSU5fUkVHSU9OX1NJWkUAdG90YWxTcGxpdFNpemUgPj0gTUlOX1JFR0lPTl9TSVpFAG1lcmdlSW50b0V4aXN0aW5nRnJlZVJlZ2lvbgByZWdpb24gPT0gbGFzdFJlZ2lvbgBlbW1hbGxvY19yZWFsbG9j' +} diff --git a/src/views/components/jsmpeg/modules/renderer/canvas2d.js b/src/views/components/jsmpeg/modules/renderer/canvas2d.js new file mode 100644 index 0000000..f4aea15 --- /dev/null +++ b/src/views/components/jsmpeg/modules/renderer/canvas2d.js @@ -0,0 +1,130 @@ +import { Fill } from '../../utils' + +export default class CanvasRenderer { + /** @type {HTMLCanvasElement} */ + canvas + /** + * + * @param {import('../../types').PlayerOptions} options + */ + 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 + } + + clear() { + if (!this.context) return + + const w = this.canvas.width + const h = this.canvas.height + + this.context.fillStyle = '#000' + this.context.fillRect(0, 0, w, h) + } + + 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, ccr, r, g, 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 + } + } +} diff --git a/src/views/components/jsmpeg/modules/renderer/index.js b/src/views/components/jsmpeg/modules/renderer/index.js new file mode 100644 index 0000000..6083050 --- /dev/null +++ b/src/views/components/jsmpeg/modules/renderer/index.js @@ -0,0 +1,9 @@ +import CanvasRenderer from './canvas2d' +import WebGLRenderer from './webgl' + +const Renderer = { + Canvas2D: CanvasRenderer, + WebGL: WebGLRenderer +} + +export default Renderer diff --git a/src/views/components/jsmpeg/modules/renderer/webgl.js b/src/views/components/jsmpeg/modules/renderer/webgl.js new file mode 100644 index 0000000..54f3581 --- /dev/null +++ b/src/views/components/jsmpeg/modules/renderer/webgl.js @@ -0,0 +1,307 @@ +export default class WebGLRenderer { + /** @type {HTMLCanvasElement} */ + canvas + /** @type {WebGLRenderingContext} */ + gl + constructor(options) { + this.canvas = options.canvas ?? document.createElement('canvas') + this.width = this.canvas.width + this.height = this.canvas.height + this.enabled = true + + this.hasTextureData = {} + + const contextCreateOptions = { + preserveDrawingBuffer: !!options.preserveDrawingBuffer, + alpha: false, + depth: false, + stencil: false, + antialias: false, + premultipliedAlpha: false + } + + this.gl = + this.canvas.getContext('webgl', contextCreateOptions) || + this.canvas.getContext('experimental-webgl', contextCreateOptions) + + if (!this.gl) { + throw new Error('Failed to get WebGL Context') + } + + const gl = this.gl + let vertexAttr = null + + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false) + + // Init buffers + this.vertexBuffer = gl.createBuffer() + const vertexCoords = new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]) + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer) + gl.bufferData(gl.ARRAY_BUFFER, vertexCoords, gl.STATIC_DRAW) + + // Setup the main YCrCbToRGBA shader + this.program = this.createProgram( + WebGLRenderer.SHADER.VERTEX_IDENTITY, + WebGLRenderer.SHADER.FRAGMENT_YCRCB_TO_RGBA + ) + vertexAttr = gl.getAttribLocation(this.program, 'vertex') + gl.enableVertexAttribArray(vertexAttr) + gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0) + + this.textureY = this.createTexture(0, 'textureY') + this.textureCb = this.createTexture(1, 'textureCb') + this.textureCr = this.createTexture(2, 'textureCr') + + // Setup the loading animation shader + this.loadingProgram = this.createProgram( + WebGLRenderer.SHADER.VERTEX_IDENTITY, + WebGLRenderer.SHADER.FRAGMENT_LOADING + ) + vertexAttr = gl.getAttribLocation(this.loadingProgram, 'vertex') + gl.enableVertexAttribArray(vertexAttr) + gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0) + + this.shouldCreateUnclampedViews = !this.allowsClampedTextureData() + } + + destroy(removeCanvas = true) { + const gl = this.gl + + this.deleteTexture(gl.TEXTURE0, this.textureY) + this.deleteTexture(gl.TEXTURE1, this.textureCb) + this.deleteTexture(gl.TEXTURE2, this.textureCr) + + gl.useProgram(null) + gl.deleteProgram(this.program) + gl.deleteProgram(this.loadingProgram) + + gl.bindBuffer(gl.ARRAY_BUFFER, null) + gl.deleteBuffer(this.vertexBuffer) + + gl.getExtension('WEBGL_lose_context')?.loseContext() + // gl.clear() + + if (removeCanvas) { + // 默认不移除canvas + this.canvas.remove() + } + } + + clear() { + // 设置背景颜色 + this.gl?.clearColor(0, 0, 0, 1) + this.gl?.clear(this.gl.COLOR_BUFFER_BIT) + } + + resize(width, height) { + this.width = width | 0 + this.height = height | 0 + + this.canvas.width = this.width + this.canvas.height = this.height + + this.gl.useProgram(this.program) + + const codedWidth = ((this.width + 15) >> 4) << 4 + this.gl.viewport(0, 0, codedWidth, this.height) + } + + createTexture(index, name) { + const gl = this.gl + const texture = gl.createTexture() + + gl.bindTexture(gl.TEXTURE_2D, texture) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + gl.uniform1i(gl.getUniformLocation(this.program, name), index) + + return texture + } + + createProgram(vsh, fsh) { + const gl = this.gl + const program = gl.createProgram() + + gl.attachShader(program, this.compileShader(gl.VERTEX_SHADER, vsh)) + gl.attachShader(program, this.compileShader(gl.FRAGMENT_SHADER, fsh)) + gl.linkProgram(program) + gl.useProgram(program) + + return program + } + + compileShader(type, source) { + const gl = this.gl + const shader = gl.createShader(type) + gl.shaderSource(shader, source) + gl.compileShader(shader) + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + throw new Error(gl.getShaderInfoLog(shader)) + } + + return shader + } + + allowsClampedTextureData() { + const gl = this.gl + const texture = gl.createTexture() + + gl.bindTexture(gl.TEXTURE_2D, texture) + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.LUMINANCE, + 1, + 1, + 0, + gl.LUMINANCE, + gl.UNSIGNED_BYTE, + new Uint8ClampedArray([0]) + ) + return gl.getError() === 0 + } + + renderProgress(progress) { + const gl = this.gl + + gl.useProgram(this.loadingProgram) + + const loc = gl.getUniformLocation(this.loadingProgram, 'progress') + gl.uniform1f(loc, progress) + + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4) + } + + render(y, cb, cr, isClampedArray) { + if (!this.enabled) { + return + } + + const gl = this.gl + const w = ((this.width + 15) >> 4) << 4 + const h = this.height + const w2 = w >> 1 + const h2 = h >> 1 + + // In some browsers WebGL doesn't like Uint8ClampedArrays (this is a bug + // and should be fixed soon-ish), so we have to create a Uint8Array view + // for each plane. + if (isClampedArray && this.shouldCreateUnclampedViews) { + y = new Uint8Array(y.buffer) + cb = new Uint8Array(cb.buffer) + cr = new Uint8Array(cr.buffer) + } + + gl.useProgram(this.program) + + this.updateTexture(gl.TEXTURE0, this.textureY, w, h, y) + this.updateTexture(gl.TEXTURE1, this.textureCb, w2, h2, cb) + this.updateTexture(gl.TEXTURE2, this.textureCr, w2, h2, cr) + + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4) + } + + updateTexture(unit, texture, w, h, data) { + const gl = this.gl + gl.activeTexture(unit) + gl.bindTexture(gl.TEXTURE_2D, texture) + + if (this.hasTextureData[unit]) { + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + w, + h, + gl.LUMINANCE, + gl.UNSIGNED_BYTE, + data + ) + } else { + this.hasTextureData[unit] = true + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.LUMINANCE, + w, + h, + 0, + gl.LUMINANCE, + gl.UNSIGNED_BYTE, + data + ) + } + } + + deleteTexture(unit, texture) { + const gl = this.gl + gl.activeTexture(unit) + gl.bindTexture(gl.TEXTURE_2D, null) + gl.deleteTexture(texture) + } + + static IsSupported() { + try { + if (!window.WebGLRenderingContext) { + return false + } + + const canvas = document.createElement('canvas') + return !!( + canvas.getContext('webgl') || canvas.getContext('experimental-webgl') + ) + } catch (err) { + return false + } + } + + static SHADER = { + FRAGMENT_YCRCB_TO_RGBA: [ + 'precision mediump float;', + 'uniform sampler2D textureY;', + 'uniform sampler2D textureCb;', + 'uniform sampler2D textureCr;', + 'varying vec2 texCoord;', + + 'mat4 rec601 = mat4(', + '1.16438, 0.00000, 1.59603, -0.87079,', + '1.16438, -0.39176, -0.81297, 0.52959,', + '1.16438, 2.01723, 0.00000, -1.08139,', + '0, 0, 0, 1', + ');', + + 'void main() {', + 'float y = texture2D(textureY, texCoord).r;', + 'float cb = texture2D(textureCb, texCoord).r;', + 'float cr = texture2D(textureCr, texCoord).r;', + + 'gl_FragColor = vec4(y, cr, cb, 1.0) * rec601;', + '}' + ].join('\n'), + + FRAGMENT_LOADING: [ + 'precision mediump float;', + 'uniform float progress;', + 'varying vec2 texCoord;', + + 'void main() {', + 'float c = ceil(progress-(1.0-texCoord.y));', + 'gl_FragColor = vec4(c,c,c,1);', + '}' + ].join('\n'), + + VERTEX_IDENTITY: [ + 'attribute vec2 vertex;', + 'varying vec2 texCoord;', + + 'void main() {', + 'texCoord = vertex;', + 'gl_Position = vec4((vertex * 2.0 - 1.0) * vec2(1, -1), 0.0, 1.0);', + '}' + ].join('\n') + } +} diff --git a/src/views/components/jsmpeg/modules/source/ajax-progressive.js b/src/views/components/jsmpeg/modules/source/ajax-progressive.js new file mode 100644 index 0000000..41922b4 --- /dev/null +++ b/src/views/components/jsmpeg/modules/source/ajax-progressive.js @@ -0,0 +1,133 @@ +import { Now } from '../../utils' + +export default class AjaxProgressiveSource { + 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.fileSize = 0 + this.loadedSize = 0 + this.chunkSize = options.chunkSize || 1024 * 1024 + + this.isLoading = false + this.loadStartTime = 0 + this.throttled = options.throttled !== false + this.aborted = false + + this.onEstablishedCallback = options.onSourceEstablished + this.onCompletedCallback = options.onSourceCompleted + } + + connect(destination) { + this.destination = destination + } + + start() { + this.request = new XMLHttpRequest() + + this.request.onreadystatechange = function() { + if (this.request.readyState === this.request.DONE) { + this.fileSize = parseInt( + this.request.getResponseHeader('Content-Length') + ) + this.loadNextChunk() + } + }.bind(this) + + this.request.onprogress = this.onProgress.bind(this) + this.request.open('HEAD', this.url) + this.request.send() + } + + resume(secondsHeadroom) { + if (this.isLoading || !this.throttled) { + return + } + + // Guess the worst case loading time with lots of safety margin. This is + // somewhat arbitrary... + const worstCaseLoadingTime = this.loadTime * 8 + 2 + if (worstCaseLoadingTime > secondsHeadroom) { + this.loadNextChunk() + } + } + + destroy() { + this.request.abort() + this.aborted = true + } + + loadNextChunk() { + const start = this.loadedSize + const end = Math.min(this.loadedSize + this.chunkSize - 1, this.fileSize - 1) + + if (start >= this.fileSize || this.aborted) { + this.completed = true + if (this.onCompletedCallback) { + this.onCompletedCallback(this) + } + return + } + + this.isLoading = true + this.loadStartTime = Now() + this.request = new XMLHttpRequest() + + this.request.onreadystatechange = function() { + if ( + this.request.readyState === this.request.DONE && + this.request.status >= 200 && + this.request.status < 300 + ) { + this.onChunkLoad(this.request.response) + } else if (this.request.readyState === this.request.DONE) { + // Retry? + if (this.loadFails++ < 3) { + this.loadNextChunk() + } + } + }.bind(this) + + if (start === 0) { + this.request.onprogress = this.onProgress.bind(this) + } + + this.request.open('GET', this.url + '?' + start + '-' + end) + this.request.setRequestHeader('Range', 'bytes=' + start + '-' + end) + this.request.responseType = 'arraybuffer' + this.request.send() + } + + onProgress(ev) { + this.progress = ev.loaded / ev.total + } + + onChunkLoad(data) { + const isFirstChunk = !this.established + this.established = true + this.progress = 1 + + this.loadedSize += data.byteLength + this.loadFails = 0 + this.isLoading = false + + if (isFirstChunk && this.onEstablishedCallback) { + this.onEstablishedCallback(this) + } + + if (this.destination) { + this.destination.write(data) + } + + this.loadTime = Now() - this.loadStartTime + if (!this.throttled) { + this.loadNextChunk() + } + } +} diff --git a/src/views/components/jsmpeg/modules/source/ajax.js b/src/views/components/jsmpeg/modules/source/ajax.js new file mode 100644 index 0000000..f8a1044 --- /dev/null +++ b/src/views/components/jsmpeg/modules/source/ajax.js @@ -0,0 +1,68 @@ +'use strict' + +export default 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 + } + + connect(destination) { + this.destination = destination + } + + start() { + this.request = new XMLHttpRequest() + + 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(secondsHeadroom) { + // 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.onEstablishedCallback) { + this.onEstablishedCallback(this) + } + if (this.onCompletedCallback) { + this.onCompletedCallback(this) + } + + if (this.destination) { + this.destination.write(data) + } + } +} diff --git a/src/views/components/jsmpeg/modules/source/fetch.js b/src/views/components/jsmpeg/modules/source/fetch.js new file mode 100644 index 0000000..8b4c374 --- /dev/null +++ b/src/views/components/jsmpeg/modules/source/fetch.js @@ -0,0 +1,80 @@ +'use strict' + +export default class FetchSource { + constructor(url, options) { + this.url = url + this.destination = null + this.request = null + this.streaming = true + + this.completed = false + this.established = false + this.progress = 0 + this.aborted = false + + this.onEstablishedCallback = options.onSourceEstablished + this.onCompletedCallback = options.onSourceCompleted + } + + connect(destination) { + this.destination = destination + } + + start() { + const params = { + method: 'GET', + headers: new Headers(), + keepAlive: 'default' + } + + self + .fetch(this.url, params) + .then( + function(res) { + if (res.ok && res.status >= 200 && res.status <= 299) { + this.progress = 1 + this.established = true + return this.pump(res.body.getReader()) + } else { + // error + } + }.bind(this) + ) + .catch(function(err) { + throw err + }) + } + + pump(reader) { + return reader + .read() + .then( + function(result) { + if (result.done) { + this.completed = true + } else { + if (this.aborted) { + return reader.cancel() + } + + if (this.destination) { + this.destination.write(result.value.buffer) + } + + return this.pump(reader) + } + }.bind(this) + ) + .catch(function(err) { + throw err + }) + } + + resume(secondsHeadroom) { + // Nothing to do here + } + + abort() { + this.aborted = true + } +} diff --git a/src/views/components/jsmpeg/modules/source/index.js b/src/views/components/jsmpeg/modules/source/index.js new file mode 100644 index 0000000..ac7a4bc --- /dev/null +++ b/src/views/components/jsmpeg/modules/source/index.js @@ -0,0 +1,13 @@ +import AjaxSource from './ajax' +import AjaxProgressiveSource from './ajax-progressive' +import FetchSource from './fetch' +import WSSource from './websocket' + +const Source = { + Ajax: AjaxSource, + AjaxProgressive: AjaxProgressiveSource, + Fetch: FetchSource, + WebSocket: WSSource +} + +export default Source diff --git a/src/views/components/jsmpeg/modules/source/websocket.js b/src/views/components/jsmpeg/modules/source/websocket.js new file mode 100644 index 0000000..969e43c --- /dev/null +++ b/src/views/components/jsmpeg/modules/source/websocket.js @@ -0,0 +1,237 @@ + +/* eslint-disable */ +'use strict' + +import TS from '../demuxer/ts' + +export default class WSSource { + timer = { + heartbeat: null, + streamInterrupt: null + } + reconnectInterval + shouldAttemptReconnect + progress = 0 + reconnectTimeoutId = 0 + reconnectCount = 0 + callbacks = { connect: [], data: [] } + streaming = true + completed = false + established = false + isPaused = false + isStreamInterrupt = false + /** @type {TS} */ + destination + /** @type {WebSocket} */ + socket + /** @type {string} */ + url + onEstablishedCallback + onCompletedCallback + onClosedCallback + onStreamInterruptCallback + onConnectedCallback + onStreamTimeoutFirstReceiveCallback + /** + * + * @param {string} url + * @param {import('../../types').PlayerOptions} options + */ + constructor(url, options) { + this.url = url + this.options = options + + this.reconnectInterval = + options.reconnectInterval !== undefined ? options.reconnectInterval : 5 + this.shouldAttemptReconnect = !!this.reconnectInterval + + this.onEstablishedCallback = options.onSourceEstablished + this.onCompletedCallback = options.onSourceCompleted // Never used + this.onClosedCallback = options.onSourceClosed + this.onConnectedCallback = options.onSourceConnected + this.onStreamInterruptCallback = options.onSourceStreamInterrupt + this.onStreamContinueCallback = options.onSourceStreamContinue + } + + connect(destination) { + this.destination = destination + } + + changeUrl(url = '') { + clearTimeout(this.timer.streamInterrupt) + + if (typeof url === 'string' && url !== '') { + if (this.url !== url) { + this.destroy() + this.url = url + this.start() + } + } else { + this.destroy() + this.url = '' + } + } + + reload() { + this.destroy() + this.start() + } + + destroy() { + clearTimeout(this.reconnectTimeoutId) + this.reconnectTimeoutId = 0 + this.shouldAttemptReconnect = false + this.socket && this.socket.close() + if (this.socket) { + this.socket.onmessage = null + this.socket.onopen = null + this.socket.onerror = null + this.socket.onclose = null + this.socket.onmessage = null + this.socket = null + } + } + + start() { + this.reconnectTimeoutId = 0 + this.reconnectCount = 0 + this.shouldAttemptReconnect = !!this.reconnectInterval + this.progress = 0 + this.established = false + this.isPaused = false + + this.wsConnect() + } + + wsConnect() { + if (!this.url) return + // 连java的websocket时,第二个参数要么传值,要么不传值,不能传null,否则会一直出现连接失败的问题 + try { + this.socket = new WebSocket(this.url, this.options?.protocols) + this.socket.binaryType = 'arraybuffer' + this.socket.onmessage = this.onMessage.bind(this) + this.socket.onopen = this.onOpen.bind(this) + this.socket.onerror = this.onError.bind(this) + this.socket.onclose = this.onClose.bind(this) + } catch (error) { + console.error('websocket connect error: ', error) + } + } + + pause() { + if (!this.isPaused) { + clearTimeout(this.timer.streamInterrupt) + this.isPaused = true + if (this.socket?.readyState === WebSocket.OPEN) { + this.socket.onmessage = null + } + } + // if (this.reconnectTimeoutId) { + // clearTimeout(this.reconnectTimeoutId) + // this.reconnectTimeoutId = null + // } + } + + continue() { + // Nothing to do here + if (this.isPaused) { + this.isPaused = false + if (this.socket == null) { + this.start() + } else if (this.socket?.readyState === WebSocket.OPEN) { + this.socket.onmessage = this.onMessage.bind(this) + this.startStreamTimeoutTimer() + } + } + } + + onOpen() { + this.progress = 1 + this.reconnectTimeoutId = 0 + this.reconnectCount = 0 + this.isOpened = true + if (this.onConnectedCallback) { + this.onConnectedCallback(this) + } + this.startStreamTimeoutTimer() + } + + onError(err) { + // console.error(err) + } + + onClose() { + this.established = false + if (this.progress >= 1) { + // progress>=1,表示已经建立连接后的断开 + this.progress = 0 + if (this.onClosedCallback) { + this.onClosedCallback(this) + } + clearTimeout(this.reconnectTimeoutId) + this.reconnectTimeoutId = setTimeout(this.start.bind(this), 5000) + return + } + + if (this.shouldAttemptReconnect && this.reconnectCount < 10) { + // 最多重连10次 + clearTimeout(this.reconnectTimeoutId) + this.reconnectTimeoutId = setTimeout( + this.wsConnect.bind(this), + this.reconnectInterval * 1000 + ) + this.reconnectCount += 1 + console.log('websocket 重连次数: ', this.reconnectCount) + } + } + + /** + * + * @param {MessageEvent} ev + */ + onMessage(ev) { + this.startStreamTimeoutTimer() + try { + if (!this.established) { + this.established = true + this.isStreamInterrupt = false + this.onEstablishedCallback?.(this) + console.log(ev) + } else if (this.isStreamInterrupt) { + this.isStreamInterrupt = false + this.onStreamContinueCallback?.(this) + } + + if (this.destination) { + this.destination.write(ev.data) + } + } catch (error) { + if (error.message?.indexOf('memory access out of bounds') > -1) { + this.reload() + } else { + console.error(error) + } + } + if (this.recorder) { + try { + this.recorder.write?.(ev.data) + } catch (error) { + this.recorder = null + } + } + } + + startStreamTimeoutTimer() { + if (this.timer.streamInterrupt) { + clearTimeout(this.timer.streamInterrupt) + } + this.timer.streamInterrupt = setTimeout(() => { + console.warn('[JSMpeg]: 等待视频流超时') + this.timer.streamInterrupt = null + this.isStreamInterrupt = true + if (this.onStreamInterruptCallback) { + this.onStreamInterruptCallback() + } + }, 5000) + } +} diff --git a/src/views/components/jsmpeg/modules/video-element.js b/src/views/components/jsmpeg/modules/video-element.js new file mode 100644 index 0000000..439ccad --- /dev/null +++ b/src/views/components/jsmpeg/modules/video-element.js @@ -0,0 +1,178 @@ +/* eslint-disable */ +import Player from './player' + +export default class VideoElement { + constructor(element) { + const url = element.dataset.url + + if (!url) { + throw 'VideoElement has no `data-url` attribute' + } + + // Setup the div container, canvas and play button + function addStyles(element, styles) { + for (const name in styles) { + element.style[name] = styles[name] + } + } + + this.container = element + addStyles(this.container, { + display: 'inline-block', + position: 'relative', + minWidth: '80px', + minHeight: '80px' + }) + + this.canvas = document.createElement('canvas') + this.canvas.width = 960 + this.canvas.height = 540 + addStyles(this.canvas, { + display: 'block', + width: '100%' + }) + this.container.appendChild(this.canvas) + + this.playButton = document.createElement('div') + this.playButton.innerHTML = VideoElement.PLAY_BUTTON + addStyles(this.playButton, { + zIndex: 2, + position: 'absolute', + top: '0', + bottom: '0', + left: '0', + right: '0', + maxWidth: '75px', + maxHeight: '75px', + margin: 'auto', + opacity: '0.7', + cursor: 'pointer' + }) + this.container.appendChild(this.playButton) + + // Parse the data-options - we try to decode the values as json. This way + // we can get proper boolean and number values. If JSON.parse() fails, + // treat it as a string. + const options = { canvas: this.canvas } + for (const option in element.dataset) { + try { + options[option] = JSON.parse(element.dataset[option]) + } catch (err) { + options[option] = element.dataset[option] + } + } + + // Create the player instance + this.player = new Player(url, options) + element.playerInstance = this.player + + // Setup the poster element, if any + if (options.poster && !options.autoplay && !this.player.options.streaming) { + options.decodeFirstFrame = false + this.poster = new Image() + this.poster.src = options.poster + this.poster.addEventListener('load', this.posterLoaded) + addStyles(this.poster, { + display: 'block', + zIndex: 1, + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + right: 0 + }) + this.container.appendChild(this.poster) + } + + // Add the click handler if this video is pausable + if (!this.player.options.streaming) { + this.container.addEventListener('click', this.onClick.bind(this)) + } + + // Hide the play button if this video immediately begins playing + if (options.autoplay || this.player.options.streaming) { + this.playButton.style.display = 'none' + } + + // Set up the unlock audio buton for iOS devices. iOS only allows us to + // play audio after a user action has initiated playing. For autoplay or + // streaming players we set up a muted speaker icon as the button. For all + // others, we can simply use the play button. + if (this.player.audioOut && !this.player.audioOut.unlocked) { + let unlockAudioElement = this.container + + if (options.autoplay || this.player.options.streaming) { + this.unmuteButton = document.createElement('div') + this.unmuteButton.innerHTML = VideoElement.UNMUTE_BUTTON + addStyles(this.unmuteButton, { + zIndex: 2, + position: 'absolute', + bottom: '10px', + right: '20px', + width: '75px', + height: '75px', + margin: 'auto', + opacity: '0.7', + cursor: 'pointer' + }) + this.container.appendChild(this.unmuteButton) + unlockAudioElement = this.unmuteButton + } + + this.unlockAudioBound = this.onUnlockAudio.bind(this, unlockAudioElement) + unlockAudioElement.addEventListener( + 'touchstart', + this.unlockAudioBound, + false + ) + unlockAudioElement.addEventListener('click', this.unlockAudioBound, true) + } + } + + onUnlockAudio(element, ev) { + if (this.unmuteButton) { + ev.preventDefault() + ev.stopPropagation() + } + this.player.audioOut.unlock( + function() { + if (this.unmuteButton) { + this.unmuteButton.style.display = 'none' + } + element.removeEventListener('touchstart', this.unlockAudioBound) + element.removeEventListener('click', this.unlockAudioBound) + }.bind(this) + ) + } + + onClick(ev) { + if (this.player.isPlaying) { + this.player.pause() + this.playButton.style.display = 'block' + } else { + this.player.play() + this.playButton.style.display = 'none' + if (this.poster) { + this.poster.style.display = 'none' + } + } + } + + static PLAY_BUTTON = + '' + + '' + + '' + + '' + + static UNMUTE_BUTTON = + '' + + '' + + '' + + '' + + '' + + '' + + '' +} diff --git a/src/views/components/jsmpeg/modules/wasm-module.js b/src/views/components/jsmpeg/modules/wasm-module.js new file mode 100644 index 0000000..bf85624 --- /dev/null +++ b/src/views/components/jsmpeg/modules/wasm-module.js @@ -0,0 +1,159 @@ + +/* eslint-disable */ +import Source from './source' + +export default class WASM { + constructor() { + this.stackSize = 5 * 1024 * 1024 // emscripten default + this.pageSize = 64 * 1024 // wasm page size + this.onInitCallback = null + this.ready = false + } + + write(buffer) { + this.loadFromBuffer(buffer, this.onInitCallback) + } + + loadFromFile(url, callback) { + this.onInitCallback = callback + const ajax = new Source.Ajax(url, {}) + ajax.connect(this) + ajax.start() + } + + loadFromBuffer(buffer, callback) { + this.moduleInfo = this.readDylinkSection(buffer) + if (!this.moduleInfo) { + this.callback && this.callback(null) + return + } + + this.memory = new WebAssembly.Memory({ initial: 256 }) + const env = { + memory: this.memory, + memoryBase: 0, + __memory_base: 0, + table: new WebAssembly.Table({ + initial: this.moduleInfo.tableSize, + element: 'anyfunc' + }), + tableBase: 0, + __table_base: 0, + abort: this.c_abort.bind(this), + ___assert_fail: this.c_assertFail.bind(this), + _sbrk: this.c_sbrk.bind(this) + } + + this.brk = this.align(this.moduleInfo.memorySize + this.stackSize) + WebAssembly.instantiate(buffer, { env: env }).then( + function(results) { + this.instance = results.instance + if (this.instance.exports.__post_instantiate) { + this.instance.exports.__post_instantiate() + } + this.createHeapViews() + this.ready = true + callback && callback(this) + }.bind(this) + ) + } + + createHeapViews() { + this.instance.heapU8 = new Uint8Array(this.memory.buffer) + this.instance.heapU32 = new Uint32Array(this.memory.buffer) + this.instance.heapF32 = new Float32Array(this.memory.buffer) + } + + align(addr) { + const a = Math.pow(2, this.moduleInfo.memoryAlignment) + return Math.ceil(addr / a) * a + } + + c_sbrk(size) { + const previousBrk = this.brk + this.brk += size + + if (this.brk > this.memory.buffer.byteLength) { + const bytesNeeded = this.brk - this.memory.buffer.byteLength + const pagesNeeded = Math.ceil(bytesNeeded / this.pageSize) + this.memory.grow(pagesNeeded) + this.createHeapViews() + } + return previousBrk + } + + c_abort(size) { + console.warn('JSMPeg: WASM abort', arguments) + } + + c_assertFail(size) { + console.warn('JSMPeg: WASM ___assert_fail', arguments) + } + + readDylinkSection(buffer) { + // Read the WASM header and dylink section of the .wasm binary data + // to get the needed table size and static data size. + + // https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md + // https://github.com/kripken/emscripten/blob/20602efb955a7c6c20865a495932427e205651d2/src/support.js + + const bytes = new Uint8Array(buffer) + let next = 0 + + function readVarUint() { + let ret = 0 + let mul = 1 + while (1) { + const byte = bytes[next++] + ret += (byte & 0x7f) * mul + mul *= 0x80 + if (!(byte & 0x80)) { + return ret + } + } + } + + function matchNextBytes(expected) { + for (let i = 0; i < expected.length; i++) { + const b = + typeof expected[i] === 'string' + ? expected[i].charCodeAt(0) + : expected[i] + if (bytes[next++] !== b) { + return false + } + } + return true + } + + // Make sure we have a wasm header + if (!matchNextBytes([0, 'a', 's', 'm'])) { + console.warn('JSMpeg: WASM header not found') + return null + } + + // Make sure we have a dylink section + next = 9 + const sectionSize = readVarUint() + if (!matchNextBytes([6, 'd', 'y', 'l', 'i', 'n', 'k'])) { + console.warn('JSMpeg: No dylink section found in WASM') + return null + } + + return { + memorySize: readVarUint(), + memoryAlignment: readVarUint(), + tableSize: readVarUint(), + tableAlignment: readVarUint() + } + } + + static IsSupported() { + return !!window.WebAssembly + } + + static GetModule() { + WASM.CACHED_MODULE = WASM.CACHED_MODULE || new WASM() + return WASM.CACHED_MODULE + } +} diff --git a/src/views/components/jsmpeg/types/index.d.ts b/src/views/components/jsmpeg/types/index.d.ts new file mode 100644 index 0000000..e7714ad --- /dev/null +++ b/src/views/components/jsmpeg/types/index.d.ts @@ -0,0 +1,94 @@ +import WSSource from '../modules/source/websocket' + +export interface JSMpeg { + Player(url, options: PlayerOptions): JSMpegPlayer +} + +export interface PlayerOptions { + /** 容器元素或选择器字符串 */ + contianer: HTMLElement | string + /** 用于视频渲染的HTML画布元素。如果没有给出,渲染器将创建自己的Canvas元素。 */ + canvas?: HTMLCanvasElement + /** 是否循环播放视频(仅静态文件)。默认true */ + autoplay?: boolean + /** 是否解码音频。默认true */ + audio?: boolean + /** 是否解码视频。默认true */ + video?: boolean + /** 一个图像的URL,用来在视频播放之前作为海报显示。 */ + poster?: string + /** 是否禁用后台播放,当TAB处于非活动状态时是否暂停播放。注意,浏览器通常会在非活动标签中限制JS。默认true */ + pauseWhenHidden?: boolean + /** 是否禁用WebGL,始终使用Canvas2D渲染器。默认.false */ + disableGl?: boolean + /** 是否禁用WebAssembly并始终使用JavaScript解码器。默认false */ + disableWebAssembly?: boolean + /** WebGL上下文是否创建-必要的“截图”通过。默认false */ + preserveDrawingBuffer?: boolean + /** 是否以块的形式加载数据(仅静态文件)。当启用时,回放可以在完整加载源之前开始 */ + progressive?: boolean + /** 使用时,当不需要回放时是否推迟加载块。默认=progressive */ + throttled?: boolean + /** 使用时,以字节为单位加载的块大小。默认(1 mb)1024*1024 */ + chunkSize?: number + /** 是否解码并显示视频的第一帧。设置画布大小和使用框架作为“海报”图像很有用。这在使用或流源时没有影响。默认true */ + decodeFirstFrame?: boolean + /** 流媒体时,以秒为单位的最大排队音频长度。 */ + maxAudioLag?: number + /** 流媒体时,视频解码缓冲区的字节大小。默认的512 * 1024 (512 kb)。对于非常高的比特率,您可能需要增加此值。 */ + videoBufferSize?: string + /** 流媒体时,音频解码缓冲区的字节大小。默认的128 * 1024 (128 kb)。对于非常高的比特率,您可能需要增加此值。 */ + audioBufferSize?: string + /** 在每个解码和渲染视频帧后调用的回调 */ + onVideoDecode?: (decoder, time: number) => void + /** 在每个解码音频帧之后调用的回调函数 */ + onAudioDecode?: (decoder, time: number) => void + /** 每当播放开始时调用的回调 */ + onPlay?: (player: Player) => void + /** 当回放暂停时(例如.pause()被调用或源程序结束时)调用的回调函数。 */ + onPause?: (player: Player) => void + /** 当回放到达源端时调用的回调函数(仅在loop=false时调用) */ + onEnded?: (player: Player) => void + /** 当没有足够的数据供回放时调用的回调 */ + onStalled?: (player: Player) => void + /** 当源首次接收到数据时调用的回调 */ + onSourceEstablished?: (source: WSSource) => void + /** 当源接收到所有数据时调用的回调 */ + onSourceCompleted: (source: WSSource) => void + /** 当onSourceStreamInterrupt触发后websocket第一次接收到流时触发 */ + onSourceStreamContinue: (source: WSSource) => void + /** 当websocket超过一定时间没有收到流时触发 */ + onSourceStreamInterrupt: (source: WSSource) => void + /** 当websocket连接上服务端时触发 */ + onSourceConnected: (source: WSSource) => void + /** 当websocket关闭后触发 */ + onSourceClosed: (source: WSSource) => void + /** 当获取到分辨率时触发 */ + onResolutionDecode: (width: number, height: number) => void +} + +export interface JSMpegPlayer { + /** 只读,是否暂停播放 */ + readonly paused: boolean + /** 获取或设置音频音量(0-1) */ + volume: number + /** 以秒为单位获取或设置当前播放位置 */ + currentTime: number + startTime: number + /** 开始播放 */ + play(): void + /** 暂停播放 */ + pause(): void + /** 停止回放,搜索开始 */ + stop(): void + /** 一个视频帧的提前播放。这并不解码音频。如果没有足够的数据,则返回成功 */ + nextFrame(): void + /** 停止播放,断开源和清理WebGL和WebAudio状态。该播放器将不能再使用。 */ + destroy(): void + video + source + renderer + wasmModule + audioOut + audio +} diff --git a/src/views/components/jsmpeg/utils/index.js b/src/views/components/jsmpeg/utils/index.js new file mode 100644 index 0000000..1f98000 --- /dev/null +++ b/src/views/components/jsmpeg/utils/index.js @@ -0,0 +1,70 @@ +export function Now() { + return window.performance + ? window.performance.now() / 1000 + : Date.now() / 1000 +} + +export function Fill(array, value) { + if (array.fill) { + array.fill(value) + } else { + for (let i = 0; i < array.length; i++) { + array[i] = value + } + } +} + +export function Base64ToArrayBuffer(base64) { + const binary = window.atob(base64) + const length = binary.length + const bytes = new Uint8Array(length) + for (let i = 0; i < length; i++) { + bytes[i] = binary.charCodeAt(i) + } + return bytes.buffer +} + +/** + * + * @param {object} param + * @param {string|object|Array} param.data 数据,传入后url参数将被忽略 + * @param {string} param.url 文件下载地址 + * @param {string} param.name 文件名称 + * @param {string} param.mimeType 文件mime类型 + * @returns + */ +export function saveToLocal( + blob, + name = 'JSMpeg_' + Date.now(), + mimeType = '' +) { + if (!blob) return + + const a = document.createElement('a') + a.style.display = 'none' + a.download = name + if (typeof blob === 'string') { + a.href = blob + } else { + blob = + blob instanceof Blob + ? blob + : new Blob(blob instanceof Array ? blob : [blob], { + type: mimeType + }) + a.href = URL.createObjectURL(blob) + } + + setTimeout(() => { + a.click() + }, 0) + setTimeout(() => { + a.remove() + }, 1) + + if (blob instanceof Blob) { + setTimeout(() => { + URL.revokeObjectURL(blob) + }, 1000) + } +} diff --git a/src/views/storeManage/warehouse3D/index.vue b/src/views/storeManage/warehouse3D/index.vue index e1a0c7e..68ad711 100644 --- a/src/views/storeManage/warehouse3D/index.vue +++ b/src/views/storeManage/warehouse3D/index.vue @@ -28,18 +28,19 @@