Last active
January 6, 2020 09:49
-
-
Save stnet253/2c8092f12349cbc35ad12cae282b7540 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
jsmpeg | |
========== | |
#### An MPEG1 Video Decoder in JavaScript #### | |
jsmpeg is a MPEG1 Decoder, written in JavaScript. It's "hand ported", i.e. not compiled with | |
emscripten or similar. This will probably make it obsolete with the advent of asmjs. | |
Some demos and more info: [phoboslab.org/log/2013/05/mpeg1-video-decoder-in-javascript](http://www.phoboslab.org/log/2013/05/mpeg1-video-decoder-in-javascript) | |
### Usage ### | |
```javascript | |
// Synopsis: var player = new jsmpeg(urlToFile, options); | |
// The 'options' argument and all of its properties is optional. If no canvas element | |
// is given, jsmpeg will create its own, to be accessed at .canvas | |
// Example: | |
var canvas = document.getElementById('videoCanvas'); | |
var player = new jsmpeg('file.mpeg', {canvas: canvas, autoplay: true, loop: true}); | |
player.pause(); | |
player.play(); | |
player.stop(); | |
// An 'onload' callback can be specified in the 'options' argument | |
var mpegLoaded = function( player ) { | |
console.log('Loaded', player); | |
// calculateFrameCount() and calculateDuration() can only be called | |
// after the mpeg has been fully loaded. So this callback is the ideal | |
// place to fetch this info | |
var frames = player.calculateFrameCount(), | |
duration = player.calculateDuration(); | |
console.log('Duration: '+duration+' seconds ('+frames+' frames)'); | |
}; | |
var player = new jsmpeg('file.mpeg', {onload:mpegLoaded}); | |
// If you don't use 'autoplay' and don't explicitly call .play(), you can get individual | |
// video frames (a canvas element) like so: | |
var frame = null; | |
while( (frame = player.nextFrame()) ) { | |
someOtherCanvasContext.drawImage(frame, 0, 0); | |
} | |
``` | |
### Live Streaming ### | |
jsmpeg supports streaming live video through WebSockets. You can use ffmpeg and a nodejs server to serve the MPEG video. See this [blog post](http://phoboslab.org/log/2013/09/html5-live-video-streaming-via-websockets) for the details of setting up a server. Also have a look at the `stream-server.js` and `stream-example.html`. | |
To configure jsmpeg to connect to the stream server, simply pass a WebSocket connection instead of a filename to the constructor: | |
```javascript | |
// Setup the WebSocket connection and start the player | |
var client = new WebSocket( 'ws://example.com:8084/' ); | |
var player = new jsmpeg(client, {canvas:canvas}); | |
``` | |
###Stream Recording### | |
To record an MPEG stream clientside in the browser jsmpeg provides the `.startRecording(cb)` and `.stopRecording()` methods. `.stopRecording()` returns a `Blob` object that can be used to create a download link. | |
```javascript | |
player.startRecording(function(player){ | |
// Called when recording really starts; usually | |
// when the next intra frame is received | |
}); | |
// ... | |
// Stop recording and create a download link | |
var blob = player.stopRecording(); | |
var filename = 'jsmpeg-recording.mpg'; | |
var a = document.getElementById('downloadLink'); | |
a.innerHTML = filename; | |
a.download = fileName; | |
a.href = window.URL.createObjectURL(blob); | |
``` | |
### Limitations ### | |
- Playback can only start when the file is fully loaded (when not streaming through WebSockets). I'm waiting for chunked XHR with ArrayBuffers to arrive in browsers. | |
- MPEG files with B-Frames look weird - frames are not reordered. This should be relatively easy | |
to fix, but most encoders seem to not use B-Frames at all by default. | |
- The width of the MPEG video has to be a multiple of 2. | |
- Only raw MPEG video streams are supported. The decoder hates Stream Packet Headers in between | |
macroblocks. | |
You can use [FFmpeg](http://www.ffmpeg.org/) to encode videos in a suited format. This will crop | |
the size to a multiple of 2, omit B-Frames and force a raw video stream: | |
``` | |
ffmpeg -i in.mp4 -f mpeg1video -vf "crop=iw-mod(iw\,2):ih-mod(ih\,2)" -b 0 out.mpg | |
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(window){ "use strict"; | |
// jsmpeg by Dominic Szablewski - phoboslab.org, github.com/phoboslab | |
// | |
// Consider this to be under MIT license. It's largely based an an Open Source | |
// Decoder for Java under GPL, while I looked at another Decoder from Nokia | |
// (under no particular license?) for certain aspects. | |
// I'm not sure if this work is "derivative" enough to have a different license | |
// but then again, who still cares about MPEG1? | |
// | |
// Based on "Java MPEG-1 Video Decoder and Player" by Korandi Zoltan: | |
// http://sourceforge.net/projects/javampeg1video/ | |
// | |
// Inspired by "MPEG Decoder in Java ME" by Nokia: | |
// http://www.developer.nokia.com/Community/Wiki/MPEG_decoder_in_Java_ME | |
var requestAnimFrame = (function(){ | |
return window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
function( callback ){ | |
window.setTimeout(callback, 1000 / 60); | |
}; | |
})(); | |
var jsmpeg = window.jsmpeg = function( url, opts ) { | |
opts = opts || {}; | |
this.benchmark = !!opts.benchmark; | |
this.canvas = opts.canvas || document.createElement('canvas'); | |
this.autoplay = !!opts.autoplay; | |
this.loop = !!opts.loop; | |
this.externalLoadCallback = opts.onload || null; | |
this.externalDecodeCallback = opts.ondecodeframe || null; | |
this.externalFinishedCallback = opts.onfinished || null; | |
this.customIntraQuantMatrix = new Uint8Array(64); | |
this.customNonIntraQuantMatrix = new Uint8Array(64); | |
this.blockData = new Int32Array(64); | |
this.zeroBlockData = new Int32Array(64); | |
this.fillArray(this.zeroBlockData, 0); | |
// use WebGL for YCbCrToRGBA conversion if possible (much faster) | |
if( !opts.forceCanvas2D && this.initWebGL() ) { | |
this.renderFrame = this.renderFrameGL; | |
} else { | |
this.canvasContext = this.canvas.getContext('2d'); | |
this.renderFrame = this.renderFrame2D; | |
} | |
if( url instanceof WebSocket ) { | |
this.client = url; | |
this.client.onopen = this.initSocketClient.bind(this); | |
} | |
else { | |
this.load(url); | |
} | |
}; | |
// ---------------------------------------------------------------------------- | |
// Streaming over WebSockets | |
jsmpeg.prototype.waitForIntraFrame = true; | |
jsmpeg.prototype.socketBufferSize = 512 * 1024; // 512kb each | |
jsmpeg.prototype.onlostconnection = null; | |
jsmpeg.prototype.initSocketClient = function( client ) { | |
this.buffer = new BitReader(new ArrayBuffer(this.socketBufferSize)); | |
this.nextPictureBuffer = new BitReader(new ArrayBuffer(this.socketBufferSize)); | |
this.nextPictureBuffer.writePos = 0; | |
this.nextPictureBuffer.chunkBegin = 0; | |
this.nextPictureBuffer.lastWriteBeforeWrap = 0; | |
this.client.binaryType = 'arraybuffer'; | |
this.client.onmessage = this.receiveSocketMessage.bind(this); | |
}; | |
jsmpeg.prototype.decodeSocketHeader = function( data ) { | |
// Custom header sent to all newly connected clients when streaming | |
// over websockets: | |
// struct { char magic[4] = "jsmp"; unsigned short width, height; }; | |
if( | |
data[0] == SOCKET_MAGIC_BYTES.charCodeAt(0) && | |
data[1] == SOCKET_MAGIC_BYTES.charCodeAt(1) && | |
data[2] == SOCKET_MAGIC_BYTES.charCodeAt(2) && | |
data[3] == SOCKET_MAGIC_BYTES.charCodeAt(3) | |
) { | |
this.width = (data[4] * 256 + data[5]); | |
this.height = (data[6] * 256 + data[7]); | |
this.initBuffers(); | |
} | |
}; | |
jsmpeg.prototype.receiveSocketMessage = function( event ) { | |
var messageData = new Uint8Array(event.data); | |
if( !this.sequenceStarted ) { | |
this.decodeSocketHeader(messageData); | |
} | |
var current = this.buffer; | |
var next = this.nextPictureBuffer; | |
if( next.writePos + messageData.length > next.length ) { | |
next.lastWriteBeforeWrap = next.writePos; | |
next.writePos = 0; | |
next.index = 0; | |
} | |
next.bytes.set( messageData, next.writePos ); | |
next.writePos += messageData.length; | |
var startCode = 0; | |
while( true ) { | |
startCode = next.findNextMPEGStartCode(); | |
if( | |
startCode == BitReader.NOT_FOUND || | |
((next.index >> 3) > next.writePos) | |
) { | |
// We reached the end with no picture found yet; move back a few bytes | |
// in case we are at the beginning of a start code and exit. | |
next.index = Math.max((next.writePos-3), 0) << 3; | |
return; | |
} | |
else if( startCode == START_PICTURE ) { | |
break; | |
} | |
} | |
// If we are still here, we found the next picture start code! | |
// Skip picture decoding until we find the first intra frame? | |
if( this.waitForIntraFrame ) { | |
next.advance(10); // skip temporalReference | |
if( next.getBits(3) == PICTURE_TYPE_I ) { | |
this.waitForIntraFrame = false; | |
next.chunkBegin = (next.index-13) >> 3; | |
} | |
return; | |
} | |
// Last picture hasn't been decoded yet? Decode now but skip output | |
// before scheduling the next one | |
if( !this.currentPictureDecoded ) { | |
this.decodePicture(DECODE_SKIP_OUTPUT); | |
} | |
// Copy the picture chunk over to 'this.buffer' and schedule decoding. | |
var chunkEnd = ((next.index) >> 3); | |
if( chunkEnd > next.chunkBegin ) { | |
// Just copy the current picture chunk | |
current.bytes.set( next.bytes.subarray(next.chunkBegin, chunkEnd) ); | |
current.writePos = chunkEnd - next.chunkBegin; | |
} | |
else { | |
// We wrapped the nextPictureBuffer around, so we have to copy the last part | |
// till the end, as well as from 0 to the current writePos | |
current.bytes.set( next.bytes.subarray(next.chunkBegin, next.lastWriteBeforeWrap) ); | |
var written = next.lastWriteBeforeWrap - next.chunkBegin; | |
current.bytes.set( next.bytes.subarray(0, chunkEnd), written ); | |
current.writePos = chunkEnd + written; | |
} | |
current.index = 0; | |
next.chunkBegin = chunkEnd; | |
// Decode! | |
this.currentPictureDecoded = false; | |
requestAnimFrame( this.scheduleDecoding.bind(this), this.canvas ); | |
}; | |
jsmpeg.prototype.scheduleDecoding = function() { | |
this.decodePicture(); | |
this.currentPictureDecoded = true; | |
}; | |
// ---------------------------------------------------------------------------- | |
// Recording from WebSockets | |
jsmpeg.prototype.isRecording = false; | |
jsmpeg.prototype.recorderWaitForIntraFrame = false; | |
jsmpeg.prototype.recordedFrames = 0; | |
jsmpeg.prototype.recordedSize = 0; | |
jsmpeg.prototype.didStartRecordingCallback = null; | |
jsmpeg.prototype.recordBuffers = []; | |
jsmpeg.prototype.canRecord = function(){ | |
return (this.client && this.client.readyState == this.client.OPEN); | |
}; | |
jsmpeg.prototype.startRecording = function(callback) { | |
if( !this.canRecord() ) { | |
return; | |
} | |
// Discard old buffers and set for recording | |
this.discardRecordBuffers(); | |
this.isRecording = true; | |
this.recorderWaitForIntraFrame = true; | |
this.didStartRecordingCallback = callback || null; | |
this.recordedFrames = 0; | |
this.recordedSize = 0; | |
// Fudge a simple Sequence Header for the MPEG file | |
// 3 bytes width & height, 12 bits each | |
var wh1 = (this.width >> 4), | |
wh2 = ((this.width & 0xf) << 4) | (this.height >> 8), | |
wh3 = (this.height & 0xff); | |
this.recordBuffers.push(new Uint8Array([ | |
0x00, 0x00, 0x01, 0xb3, // Sequence Start Code | |
wh1, wh2, wh3, // Width & height | |
0x13, // aspect ratio & framerate | |
0xff, 0xff, 0xe1, 0x58, // Meh. Bitrate and other boring stuff | |
0x00, 0x00, 0x01, 0xb8, 0x00, 0x08, 0x00, // GOP | |
0x00, 0x00, 0x00, 0x01, 0x00 // First Picture Start Code | |
])); | |
}; | |
jsmpeg.prototype.recordFrameFromCurrentBuffer = function() { | |
if( !this.isRecording ) { return; } | |
if( this.recorderWaitForIntraFrame ) { | |
// Not an intra frame? Exit. | |
if( this.pictureCodingType != PICTURE_TYPE_I ) { return; } | |
// Start recording! | |
this.recorderWaitForIntraFrame = false; | |
if( this.didStartRecordingCallback ) { | |
this.didStartRecordingCallback( this ); | |
} | |
} | |
this.recordedFrames++; | |
this.recordedSize += this.buffer.writePos; | |
// Copy the actual subrange for the current picture into a new Buffer | |
this.recordBuffers.push(new Uint8Array(this.buffer.bytes.subarray(0, this.buffer.writePos))); | |
}; | |
jsmpeg.prototype.discardRecordBuffers = function() { | |
this.recordBuffers = []; | |
this.recordedFrames = 0; | |
}; | |
jsmpeg.prototype.stopRecording = function() { | |
var blob = new Blob(this.recordBuffers, {type: 'video/mpeg'}); | |
this.discardRecordBuffers(); | |
this.isRecording = false; | |
return blob; | |
}; | |
// ---------------------------------------------------------------------------- | |
// Loading via Ajax | |
jsmpeg.prototype.load = function( url ) { | |
this.url = url; | |
var request = new XMLHttpRequest(); | |
var that = this; | |
request.onreadystatechange = function() { | |
if( request.readyState == request.DONE && request.status == 200 ) { | |
that.loadCallback(request.response); | |
} | |
}; | |
request.onprogress = this.gl | |
? this.updateLoaderGL.bind(this) | |
: this.updateLoader2D.bind(this); | |
request.open('GET', url); | |
request.responseType = "arraybuffer"; | |
request.send(); | |
}; | |
jsmpeg.prototype.updateLoader2D = function( ev ) { | |
var | |
p = ev.loaded / ev.total, | |
w = this.canvas.width, | |
h = this.canvas.height, | |
ctx = this.canvasContext; | |
ctx.fillStyle = '#222'; | |
ctx.fillRect(0, 0, w, h); | |
ctx.fillStyle = '#fff'; | |
ctx.fillRect(0, h - h*p, w, h*p); | |
}; | |
jsmpeg.prototype.updateLoaderGL = function( ev ) { | |
var gl = this.gl; | |
gl.uniform1f(gl.getUniformLocation(this.loadingProgram, 'loaded'), (ev.loaded / ev.total)); | |
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | |
}; | |
jsmpeg.prototype.loadCallback = function(file) { | |
this.buffer = new BitReader(file); | |
this.findStartCode(START_SEQUENCE); | |
this.firstSequenceHeader = this.buffer.index; | |
this.decodeSequenceHeader(); | |
// Load the first frame | |
this.nextFrame(); | |
if( this.autoplay ) { | |
this.play(); | |
} | |
if( this.externalLoadCallback ) { | |
this.externalLoadCallback(this); | |
} | |
}; | |
jsmpeg.prototype.play = function(file) { | |
if( this.playing ) { return; } | |
this.targetTime = this.now(); | |
this.playing = true; | |
this.scheduleNextFrame(); | |
}; | |
jsmpeg.prototype.pause = function(file) { | |
this.playing = false; | |
}; | |
jsmpeg.prototype.stop = function(file) { | |
if( this.buffer ) { | |
this.buffer.index = this.firstSequenceHeader; | |
} | |
this.playing = false; | |
if( this.client ) { | |
this.client.close(); | |
this.client = null; | |
} | |
}; | |
// ---------------------------------------------------------------------------- | |
// Utilities | |
jsmpeg.prototype.readCode = function(codeTable) { | |
var state = 0; | |
do { | |
state = codeTable[state + this.buffer.getBits(1)]; | |
} while( state >= 0 && codeTable[state] != 0 ); | |
return codeTable[state+2]; | |
}; | |
jsmpeg.prototype.findStartCode = function( code ) { | |
var current = 0; | |
while( true ) { | |
current = this.buffer.findNextMPEGStartCode(); | |
if( current == code || current == BitReader.NOT_FOUND ) { | |
return current; | |
} | |
} | |
return BitReader.NOT_FOUND; | |
}; | |
jsmpeg.prototype.fillArray = function(a, value) { | |
for( var i = 0, length = a.length; i < length; i++ ) { | |
a[i] = value; | |
} | |
}; | |
jsmpeg.prototype.cachedFrameCount = 0; | |
jsmpeg.prototype.calculateFrameCount = function() { | |
if( !this.buffer || this.cachedFrameCount ) { | |
return this.cachedFrameCount; | |
} | |
// Remember the buffer position, so we can rewind to the beginning and | |
// reset to the current position afterwards | |
var currentPlaybackIndex = this.buffer.index, | |
frames = 0; | |
this.buffer.index = 0; | |
while( this.findStartCode(START_PICTURE) !== BitReader.NOT_FOUND ) { | |
frames++; | |
} | |
this.buffer.index = currentPlaybackIndex; | |
this.cachedFrameCount = frames; | |
return frames; | |
}; | |
jsmpeg.prototype.calculateDuration = function() { | |
return this.calculateFrameCount() * (1/this.pictureRate); | |
}; | |
// ---------------------------------------------------------------------------- | |
// Sequence Layer | |
jsmpeg.prototype.pictureRate = 30; | |
jsmpeg.prototype.lateTime = 0; | |
jsmpeg.prototype.firstSequenceHeader = 0; | |
jsmpeg.prototype.targetTime = 0; | |
jsmpeg.prototype.benchmark = false; | |
jsmpeg.prototype.benchFrame = 0; | |
jsmpeg.prototype.benchDecodeTimes = 0; | |
jsmpeg.prototype.benchAvgFrameTime = 0; | |
jsmpeg.prototype.now = function() { | |
return window.performance | |
? window.performance.now() | |
: Date.now(); | |
} | |
jsmpeg.prototype.nextFrame = function() { | |
if( !this.buffer ) { return; } | |
var frameStart = this.now(); | |
while(true) { | |
var code = this.buffer.findNextMPEGStartCode(); | |
if( code == START_SEQUENCE ) { | |
this.decodeSequenceHeader(); | |
} | |
else if( code == START_PICTURE ) { | |
if( this.playing ) { | |
this.scheduleNextFrame(); | |
} | |
this.decodePicture(); | |
this.benchDecodeTimes += this.now() - frameStart; | |
return this.canvas; | |
} | |
else if( code == BitReader.NOT_FOUND ) { | |
this.stop(); // Jump back to the beginning | |
if( this.externalFinishedCallback ) { | |
this.externalFinishedCallback(this); | |
} | |
// Only loop if we found a sequence header | |
if( this.loop && this.sequenceStarted ) { | |
this.play(); | |
} | |
return null; | |
} | |
else { | |
// ignore (GROUP, USER_DATA, EXTENSION, SLICES...) | |
} | |
} | |
}; | |
jsmpeg.prototype.scheduleNextFrame = function() { | |
this.lateTime = this.now() - this.targetTime; | |
var wait = Math.max(0, (1000/this.pictureRate) - this.lateTime); | |
this.targetTime = this.now() + wait; | |
if( this.benchmark ) { | |
this.benchFrame++; | |
if( this.benchFrame >= 120 ) { | |
this.benchAvgFrameTime = this.benchDecodeTimes / this.benchFrame; | |
this.benchFrame = 0; | |
this.benchDecodeTimes = 0; | |
if( window.console ) { console.log("Average time per frame:", this.benchAvgFrameTime, 'ms'); } | |
} | |
setTimeout( this.nextFrame.bind(this), 0); | |
} | |
else if( wait < 18) { | |
this.scheduleAnimation(); | |
} | |
else { | |
setTimeout( this.scheduleAnimation.bind(this), wait ); | |
} | |
}; | |
jsmpeg.prototype.scheduleAnimation = function() { | |
requestAnimFrame( this.nextFrame.bind(this), this.canvas ); | |
}; | |
jsmpeg.prototype.decodeSequenceHeader = function() { | |
this.width = this.buffer.getBits(12); | |
this.height = this.buffer.getBits(12); | |
this.buffer.advance(4); // skip pixel aspect ratio | |
this.pictureRate = PICTURE_RATE[this.buffer.getBits(4)]; | |
this.buffer.advance(18 + 1 + 10 + 1); // skip bitRate, marker, bufferSize and constrained bit | |
this.initBuffers(); | |
if( this.buffer.getBits(1) ) { // load custom intra quant matrix? | |
for( var i = 0; i < 64; i++ ) { | |
this.customIntraQuantMatrix[ZIG_ZAG[i]] = this.buffer.getBits(8); | |
} | |
this.intraQuantMatrix = this.customIntraQuantMatrix; | |
} | |
if( this.buffer.getBits(1) ) { // load custom non intra quant matrix? | |
for( var i = 0; i < 64; i++ ) { | |
this.customNonIntraQuantMatrix[ZIG_ZAG[i]] = this.buffer.getBits(8); | |
} | |
this.nonIntraQuantMatrix = this.customNonIntraQuantMatrix; | |
} | |
}; | |
jsmpeg.prototype.initBuffers = function() { | |
this.intraQuantMatrix = DEFAULT_INTRA_QUANT_MATRIX; | |
this.nonIntraQuantMatrix = 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; | |
this.quarterSize = this.codedSize >> 2; | |
// Sequence already started? Don't allocate buffers again | |
if( this.sequenceStarted ) { return; } | |
this.sequenceStarted = true; | |
// Manually clamp values when writing macroblocks for shitty browsers | |
// that don't support Uint8ClampedArray | |
var MaybeClampedUint8Array = window.Uint8ClampedArray || window.Uint8Array; | |
if( !window.Uint8ClampedArray ) { | |
this.copyBlockToDestination = this.copyBlockToDestinationClamp; | |
this.addBlockToDestination = this.addBlockToDestinationClamp; | |
} | |
// Allocated buffers and resize the canvas | |
this.currentY = new MaybeClampedUint8Array(this.codedSize); | |
this.currentY32 = new Uint32Array(this.currentY.buffer); | |
this.currentCr = new MaybeClampedUint8Array(this.codedSize >> 2); | |
this.currentCr32 = new Uint32Array(this.currentCr.buffer); | |
this.currentCb = new MaybeClampedUint8Array(this.codedSize >> 2); | |
this.currentCb32 = new Uint32Array(this.currentCb.buffer); | |
this.forwardY = new MaybeClampedUint8Array(this.codedSize); | |
this.forwardY32 = new Uint32Array(this.forwardY.buffer); | |
this.forwardCr = new MaybeClampedUint8Array(this.codedSize >> 2); | |
this.forwardCr32 = new Uint32Array(this.forwardCr.buffer); | |
this.forwardCb = new MaybeClampedUint8Array(this.codedSize >> 2); | |
this.forwardCb32 = new Uint32Array(this.forwardCb.buffer); | |
this.canvas.width = this.width; | |
this.canvas.height = this.height; | |
if( this.gl ) { | |
this.gl.useProgram(this.program); | |
this.gl.viewport(0, 0, this.width, this.height); | |
} | |
else { | |
this.currentRGBA = this.canvasContext.getImageData(0, 0, this.width, this.height); | |
this.fillArray(this.currentRGBA.data, 255); | |
} | |
}; | |
// ---------------------------------------------------------------------------- | |
// Picture Layer | |
jsmpeg.prototype.currentY = null; | |
jsmpeg.prototype.currentCr = null; | |
jsmpeg.prototype.currentCb = null; | |
jsmpeg.prototype.currentRGBA = null; | |
jsmpeg.prototype.pictureCodingType = 0; | |
// Buffers for motion compensation | |
jsmpeg.prototype.forwardY = null; | |
jsmpeg.prototype.forwardCr = null; | |
jsmpeg.prototype.forwardCb = null; | |
jsmpeg.prototype.fullPelForward = false; | |
jsmpeg.prototype.forwardFCode = 0; | |
jsmpeg.prototype.forwardRSize = 0; | |
jsmpeg.prototype.forwardF = 0; | |
jsmpeg.prototype.decodePicture = function(skipOutput) { | |
this.buffer.advance(10); // skip temporalReference | |
this.pictureCodingType = this.buffer.getBits(3); | |
this.buffer.advance(16); // skip vbv_delay | |
// Skip B and D frames or unknown coding type | |
if( this.pictureCodingType <= 0 || this.pictureCodingType >= PICTURE_TYPE_B ) { | |
return; | |
} | |
// full_pel_forward, forward_f_code | |
if( this.pictureCodingType == PICTURE_TYPE_P ) { | |
this.fullPelForward = this.buffer.getBits(1); | |
this.forwardFCode = this.buffer.getBits(3); | |
if( this.forwardFCode == 0 ) { | |
// Ignore picture with zero forward_f_code | |
return; | |
} | |
this.forwardRSize = this.forwardFCode - 1; | |
this.forwardF = 1 << this.forwardRSize; | |
} | |
var code = 0; | |
do { | |
code = this.buffer.findNextMPEGStartCode(); | |
} while( code == START_EXTENSION || code == START_USER_DATA ); | |
while( code >= START_SLICE_FIRST && code <= START_SLICE_LAST ) { | |
this.decodeSlice( (code & 0x000000FF) ); | |
code = this.buffer.findNextMPEGStartCode(); | |
} | |
// We found the next start code; rewind 32bits and let the main loop handle it. | |
this.buffer.rewind(32); | |
// Record this frame, if the recorder wants it | |
this.recordFrameFromCurrentBuffer(); | |
if( skipOutput != DECODE_SKIP_OUTPUT ) { | |
this.renderFrame(); | |
if(this.externalDecodeCallback) { | |
this.externalDecodeCallback(this, this.canvas); | |
} | |
} | |
// If this is a reference picutre then rotate the prediction pointers | |
if( this.pictureCodingType == PICTURE_TYPE_I || this.pictureCodingType == PICTURE_TYPE_P ) { | |
var | |
tmpY = this.forwardY, | |
tmpY32 = this.forwardY32, | |
tmpCr = this.forwardCr, | |
tmpCr32 = this.forwardCr32, | |
tmpCb = this.forwardCb, | |
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; | |
} | |
}; | |
jsmpeg.prototype.YCbCrToRGBA = function() { | |
var pY = this.currentY; | |
var pCb = this.currentCb; | |
var pCr = this.currentCr; | |
var pRGBA = this.currentRGBA.data; | |
// 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. | |
var yIndex1 = 0; | |
var yIndex2 = this.codedWidth; | |
var yNext2Lines = this.codedWidth + (this.codedWidth - this.width); | |
var cIndex = 0; | |
var cNextLine = this.halfWidth - (this.width >> 1); | |
var rgbaIndex1 = 0; | |
var rgbaIndex2 = this.width * 4; | |
var rgbaNext2Lines = this.width * 4; | |
var cols = this.width >> 1; | |
var rows = this.height >> 1; | |
var y, cb, cr, r, g, b; | |
for( var row = 0; row < rows; row++ ) { | |
for( var col = 0; col < cols; col++ ) { | |
cb = pCb[cIndex]; | |
cr = pCr[cIndex]; | |
cIndex++; | |
r = (cr + ((cr * 103) >> 8)) - 179; | |
g = ((cb * 88) >> 8) - 44 + ((cr * 183) >> 8) - 91; | |
b = (cb + ((cb * 198) >> 8)) - 227; | |
// Line 1 | |
var y1 = pY[yIndex1++]; | |
var y2 = pY[yIndex1++]; | |
pRGBA[rgbaIndex1] = y1 + r; | |
pRGBA[rgbaIndex1+1] = y1 - g; | |
pRGBA[rgbaIndex1+2] = y1 + b; | |
pRGBA[rgbaIndex1+4] = y2 + r; | |
pRGBA[rgbaIndex1+5] = y2 - g; | |
pRGBA[rgbaIndex1+6] = y2 + b; | |
rgbaIndex1 += 8; | |
// Line 2 | |
var y3 = pY[yIndex2++]; | |
var y4 = pY[yIndex2++]; | |
pRGBA[rgbaIndex2] = y3 + r; | |
pRGBA[rgbaIndex2+1] = y3 - g; | |
pRGBA[rgbaIndex2+2] = y3 + b; | |
pRGBA[rgbaIndex2+4] = y4 + r; | |
pRGBA[rgbaIndex2+5] = y4 - g; | |
pRGBA[rgbaIndex2+6] = y4 + b; | |
rgbaIndex2 += 8; | |
} | |
yIndex1 += yNext2Lines; | |
yIndex2 += yNext2Lines; | |
rgbaIndex1 += rgbaNext2Lines; | |
rgbaIndex2 += rgbaNext2Lines; | |
cIndex += cNextLine; | |
} | |
}; | |
jsmpeg.prototype.renderFrame2D = function() { | |
this.YCbCrToRGBA(); | |
this.canvasContext.putImageData(this.currentRGBA, 0, 0); | |
}; | |
// ---------------------------------------------------------------------------- | |
// Accelerated WebGL YCbCrToRGBA conversion | |
jsmpeg.prototype.gl = null; | |
jsmpeg.prototype.program = null; | |
jsmpeg.prototype.YTexture = null; | |
jsmpeg.prototype.CBTexture = null; | |
jsmpeg.prototype.CRTexture = null; | |
jsmpeg.prototype.createTexture = function(index, name) { | |
var gl = this.gl; | |
var 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; | |
}; | |
jsmpeg.prototype.compileShader = function(type, source) { | |
var gl = this.gl; | |
var 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; | |
}; | |
jsmpeg.prototype.initWebGL = function() { | |
// attempt to get a webgl context | |
try { | |
var gl = this.gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl'); | |
} catch (e) { | |
return false; | |
} | |
if (!gl) { | |
return false; | |
} | |
// init buffers | |
this.buffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]), gl.STATIC_DRAW); | |
// The main YCbCrToRGBA Shader | |
this.program = gl.createProgram(); | |
gl.attachShader(this.program, this.compileShader(gl.VERTEX_SHADER, SHADER_VERTEX_IDENTITY)); | |
gl.attachShader(this.program, this.compileShader(gl.FRAGMENT_SHADER, SHADER_FRAGMENT_YCBCRTORGBA)); | |
gl.linkProgram(this.program); | |
if( !gl.getProgramParameter(this.program, gl.LINK_STATUS) ) { | |
throw new Error(gl.getProgramInfoLog(this.program)); | |
} | |
gl.useProgram(this.program); | |
// setup textures | |
this.YTexture = this.createTexture(0, 'YTexture'); | |
this.CBTexture = this.createTexture(1, 'CBTexture'); | |
this.CRTexture = this.createTexture(2, 'CRTexture'); | |
var vertexAttr = gl.getAttribLocation(this.program, 'vertex'); | |
gl.enableVertexAttribArray(vertexAttr); | |
gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0); | |
// Shader for the loading screen | |
this.loadingProgram = gl.createProgram(); | |
gl.attachShader(this.loadingProgram, this.compileShader(gl.VERTEX_SHADER, SHADER_VERTEX_IDENTITY)); | |
gl.attachShader(this.loadingProgram, this.compileShader(gl.FRAGMENT_SHADER, SHADER_FRAGMENT_LOADING)); | |
gl.linkProgram(this.loadingProgram); | |
gl.useProgram(this.loadingProgram); | |
vertexAttr = gl.getAttribLocation(this.loadingProgram, 'vertex'); | |
gl.enableVertexAttribArray(vertexAttr); | |
gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0); | |
return true; | |
}; | |
jsmpeg.prototype.renderFrameGL = function() { | |
var gl = this.gl; | |
// WebGL doesn't like Uint8ClampedArrays, so we have to create a Uint8Array view for | |
// each plane | |
var uint8Y = new Uint8Array(this.currentY.buffer), | |
uint8Cr = new Uint8Array(this.currentCr.buffer), | |
uint8Cb = new Uint8Array(this.currentCb.buffer); | |
gl.activeTexture(gl.TEXTURE0); | |
gl.bindTexture(gl.TEXTURE_2D, this.YTexture); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, this.codedWidth, this.height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uint8Y); | |
gl.activeTexture(gl.TEXTURE1); | |
gl.bindTexture(gl.TEXTURE_2D, this.CBTexture); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, this.halfWidth, this.height/2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uint8Cr); | |
gl.activeTexture(gl.TEXTURE2); | |
gl.bindTexture(gl.TEXTURE_2D, this.CRTexture); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, this.halfWidth, this.height/2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uint8Cb); | |
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | |
}; | |
// ---------------------------------------------------------------------------- | |
// Slice Layer | |
jsmpeg.prototype.quantizerScale = 0; | |
jsmpeg.prototype.sliceBegin = false; | |
jsmpeg.prototype.decodeSlice = function(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.buffer.getBits(5); | |
// skip extra bits | |
while( this.buffer.getBits(1) ) { | |
this.buffer.advance(8); | |
} | |
do { | |
this.decodeMacroblock(); | |
// We may have to ignore Video Stream Start Codes here (0xE0)!? | |
} while( !this.buffer.nextBytesAreStartCode() ); | |
} | |
// ---------------------------------------------------------------------------- | |
// Macroblock Layer | |
jsmpeg.prototype.macroblockAddress = 0; | |
jsmpeg.prototype.mbRow = 0; | |
jsmpeg.prototype.mbCol = 0; | |
jsmpeg.prototype.macroblockType = 0; | |
jsmpeg.prototype.macroblockIntra = false; | |
jsmpeg.prototype.macroblockMotFw = false; | |
jsmpeg.prototype.motionFwH = 0; | |
jsmpeg.prototype.motionFwV = 0; | |
jsmpeg.prototype.motionFwHPrev = 0; | |
jsmpeg.prototype.motionFwVPrev = 0; | |
jsmpeg.prototype.decodeMacroblock = function() { | |
// Decode macroblock_address_increment | |
var | |
increment = 0, | |
t = this.readCode(MACROBLOCK_ADDRESS_INCREMENT); | |
while( t == 34 ) { | |
// macroblock_stuffing | |
t = this.readCode(MACROBLOCK_ADDRESS_INCREMENT); | |
} | |
while( t == 35 ) { | |
// macroblock_escape | |
increment += 33; | |
t = this.readCode(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.pictureCodingType == PICTURE_TYPE_P ) { | |
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 | |
this.macroblockType = this.readCode(MACROBLOCK_TYPE_TABLES[this.pictureCodingType]); | |
this.macroblockIntra = (this.macroblockType & 0x01); | |
this.macroblockMotFw = (this.macroblockType & 0x08); | |
// Quantizer scale | |
if( (this.macroblockType & 0x10) != 0 ) { | |
this.quantizerScale = this.buffer.getBits(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 | |
var cbp = ((this.macroblockType & 0x02) != 0) | |
? this.readCode(CODE_BLOCK_PATTERN) | |
: (this.macroblockIntra ? 0x3f : 0); | |
for( var block = 0, mask = 0x20; block < 6; block++ ) { | |
if( (cbp & mask) != 0 ) { | |
this.decodeBlock(block); | |
} | |
mask >>= 1; | |
} | |
}; | |
jsmpeg.prototype.decodeMotionVectors = function() { | |
var code, d, r = 0; | |
// Forward | |
if( this.macroblockMotFw ) { | |
// Horizontal forward | |
code = this.readCode(MOTION); | |
if( (code != 0) && (this.forwardF != 1) ) { | |
r = this.buffer.getBits(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.readCode(MOTION); | |
if( (code != 0) && (this.forwardF != 1) ) { | |
r = this.buffer.getBits(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.pictureCodingType == PICTURE_TYPE_P ) { | |
// No motion information in P-picture, reset vectors | |
this.motionFwH = this.motionFwHPrev = 0; | |
this.motionFwV = this.motionFwVPrev = 0; | |
} | |
}; | |
jsmpeg.prototype.copyMacroblock = function(motionH, motionV, sY, sCr, sCb ) { | |
var | |
width, scan, | |
H, V, oddH, oddV, | |
src, dest, last; | |
// We use 32bit writes here | |
var dY = this.currentY32; | |
var dCb = this.currentCb32; | |
var 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); | |
var y1, y2, y; | |
if( oddH ) { | |
if( oddV ) { | |
while( dest < last ) { | |
y1 = sY[src] + sY[src+width]; src++; | |
for( var 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( var 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( var 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( var 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); | |
var cr1, cr2, cr; | |
var cb1, cb2, cb; | |
if( oddH ) { | |
if( oddV ) { | |
while( dest < last ) { | |
cr1 = sCr[src] + sCr[src+width]; | |
cb1 = sCb[src] + sCb[src+width]; | |
src++; | |
for( var 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( var 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( var 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( var 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 | |
jsmpeg.prototype.dcPredictorY; | |
jsmpeg.prototype.dcPredictorCr; | |
jsmpeg.prototype.dcPredictorCb; | |
jsmpeg.prototype.blockData = null; | |
jsmpeg.prototype.decodeBlock = function(block) { | |
var | |
n = 0, | |
quantMatrix; | |
// Decode DC coefficient of intra-coded blocks | |
if( this.macroblockIntra ) { | |
var | |
predictor, | |
dctSize; | |
// DC prediction | |
if( block < 4 ) { | |
predictor = this.dcPredictorY; | |
dctSize = this.readCode(DCT_DC_SIZE_LUMINANCE); | |
} | |
else { | |
predictor = (block == 4 ? this.dcPredictorCr : this.dcPredictorCb); | |
dctSize = this.readCode(DCT_DC_SIZE_CHROMINANCE); | |
} | |
// Read DC coeff | |
if( dctSize > 0 ) { | |
var differential = this.buffer.getBits(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) | |
var level = 0; | |
while( true ) { | |
var | |
run = 0, | |
coeff = this.readCode(DCT_COEFF); | |
if( (coeff == 0x0001) && (n > 0) && (this.buffer.getBits(1) == 0) ) { | |
// end_of_block | |
break; | |
} | |
if( coeff == 0xffff ) { | |
// escape | |
run = this.buffer.getBits(6); | |
level = this.buffer.getBits(8); | |
if( level == 0 ) { | |
level = this.buffer.getBits(8); | |
} | |
else if( level == 128 ) { | |
level = this.buffer.getBits(8) - 256; | |
} | |
else if( level > 128 ) { | |
level = level - 256; | |
} | |
} | |
else { | |
run = coeff >> 8; | |
level = coeff & 0xff; | |
if( this.buffer.getBits(1) ) { | |
level = -level; | |
} | |
} | |
n += run; | |
var dezigZagged = 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 * PREMULTIPLIER_MATRIX[dezigZagged]; | |
}; | |
// Move block to its place | |
var | |
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) { | |
this.copyValueToDestination((this.blockData[0] + 128) >> 8, destArray, destIndex, scan); | |
this.blockData[0] = 0; | |
} else { | |
this.IDCT(); | |
this.copyBlockToDestination(this.blockData, destArray, destIndex, scan); | |
this.blockData.set(this.zeroBlockData); | |
} | |
} | |
else { | |
// Add data to the predicted macroblock | |
if (n == 1) { | |
this.addValueToDestination((this.blockData[0] + 128) >> 8, destArray, destIndex, scan); | |
this.blockData[0] = 0; | |
} else { | |
this.IDCT(); | |
this.addBlockToDestination(this.blockData, destArray, destIndex, scan); | |
this.blockData.set(this.zeroBlockData); | |
} | |
} | |
n = 0; | |
}; | |
jsmpeg.prototype.copyBlockToDestination = function(blockData, destArray, destIndex, scan) { | |
for( var n = 0; n < 64; n += 8, destIndex += scan+8 ) { | |
destArray[destIndex+0] = blockData[n+0]; | |
destArray[destIndex+1] = blockData[n+1]; | |
destArray[destIndex+2] = blockData[n+2]; | |
destArray[destIndex+3] = blockData[n+3]; | |
destArray[destIndex+4] = blockData[n+4]; | |
destArray[destIndex+5] = blockData[n+5]; | |
destArray[destIndex+6] = blockData[n+6]; | |
destArray[destIndex+7] = blockData[n+7]; | |
} | |
}; | |
jsmpeg.prototype.addBlockToDestination = function(blockData, destArray, destIndex, scan) { | |
for( var n = 0; n < 64; n += 8, destIndex += scan+8 ) { | |
destArray[destIndex+0] += blockData[n+0]; | |
destArray[destIndex+1] += blockData[n+1]; | |
destArray[destIndex+2] += blockData[n+2]; | |
destArray[destIndex+3] += blockData[n+3]; | |
destArray[destIndex+4] += blockData[n+4]; | |
destArray[destIndex+5] += blockData[n+5]; | |
destArray[destIndex+6] += blockData[n+6]; | |
destArray[destIndex+7] += blockData[n+7]; | |
} | |
}; | |
jsmpeg.prototype.copyValueToDestination = function(value, destArray, destIndex, scan) { | |
for( var n = 0; n < 64; n += 8, destIndex += scan+8 ) { | |
destArray[destIndex+0] = value; | |
destArray[destIndex+1] = value; | |
destArray[destIndex+2] = value; | |
destArray[destIndex+3] = value; | |
destArray[destIndex+4] = value; | |
destArray[destIndex+5] = value; | |
destArray[destIndex+6] = value; | |
destArray[destIndex+7] = value; | |
} | |
}; | |
jsmpeg.prototype.addValueToDestination = function(value, destArray, destIndex, scan) { | |
for( var n = 0; n < 64; n += 8, destIndex += scan+8 ) { | |
destArray[destIndex+0] += value; | |
destArray[destIndex+1] += value; | |
destArray[destIndex+2] += value; | |
destArray[destIndex+3] += value; | |
destArray[destIndex+4] += value; | |
destArray[destIndex+5] += value; | |
destArray[destIndex+6] += value; | |
destArray[destIndex+7] += value; | |
} | |
}; | |
// Clamping version for shitty browsers (IE) that don't support Uint8ClampedArray | |
jsmpeg.prototype.copyBlockToDestinationClamp = function(blockData, destArray, destIndex, scan) { | |
var n = 0; | |
for( var i = 0; i < 8; i++ ) { | |
for( var j = 0; j < 8; j++ ) { | |
var p = blockData[n++]; | |
destArray[destIndex++] = p > 255 ? 255 : (p < 0 ? 0 : p); | |
} | |
destIndex += scan; | |
} | |
}; | |
jsmpeg.prototype.addBlockToDestinationClamp = function(blockData, destArray, destIndex, scan) { | |
var n = 0; | |
for( var i = 0; i < 8; i++ ) { | |
for( var j = 0; j < 8; j++ ) { | |
var p = blockData[n++] + destArray[destIndex]; | |
destArray[destIndex++] = p > 255 ? 255 : (p < 0 ? 0 : p); | |
} | |
destIndex += scan; | |
} | |
}; | |
jsmpeg.prototype.IDCT = function() { | |
// See http://vsr.informatik.tu-chemnitz.de/~jan/MPEG/HTML/IDCT.html | |
// for more info. | |
var | |
b1, b3, b4, b6, b7, tmp1, tmp2, m0, | |
x0, x1, x2, x3, x4, y3, y4, y5, y6, y7, | |
i, | |
blockData = this.blockData; | |
// Transform columns | |
for( i = 0; i < 8; ++i ) { | |
b1 = blockData[4*8+i]; | |
b3 = blockData[2*8+i] + blockData[6*8+i]; | |
b4 = blockData[5*8+i] - blockData[3*8+i]; | |
tmp1 = blockData[1*8+i] + blockData[7*8+i]; | |
tmp2 = blockData[3*8+i] + blockData[5*8+i]; | |
b6 = blockData[1*8+i] - blockData[7*8+i]; | |
b7 = tmp1 + tmp2; | |
m0 = blockData[0*8+i]; | |
x4 = ((b6*473 - b4*196 + 128) >> 8) - b7; | |
x0 = x4 - (((tmp1 - tmp2)*362 + 128) >> 8); | |
x1 = m0 - b1; | |
x2 = (((blockData[2*8+i] - blockData[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); | |
blockData[0*8+i] = b7 + y4; | |
blockData[1*8+i] = x4 + y3; | |
blockData[2*8+i] = y5 - x0; | |
blockData[3*8+i] = y6 - y7; | |
blockData[4*8+i] = y6 + y7; | |
blockData[5*8+i] = x0 + y5; | |
blockData[6*8+i] = y3 - x4; | |
blockData[7*8+i] = y4 - b7; | |
} | |
// Transform rows | |
for( i = 0; i < 64; i += 8 ) { | |
b1 = blockData[4+i]; | |
b3 = blockData[2+i] + blockData[6+i]; | |
b4 = blockData[5+i] - blockData[3+i]; | |
tmp1 = blockData[1+i] + blockData[7+i]; | |
tmp2 = blockData[3+i] + blockData[5+i]; | |
b6 = blockData[1+i] - blockData[7+i]; | |
b7 = tmp1 + tmp2; | |
m0 = blockData[0+i]; | |
x4 = ((b6*473 - b4*196 + 128) >> 8) - b7; | |
x0 = x4 - (((tmp1 - tmp2)*362 + 128) >> 8); | |
x1 = m0 - b1; | |
x2 = (((blockData[2+i] - blockData[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); | |
blockData[0+i] = (b7 + y4 + 128) >> 8; | |
blockData[1+i] = (x4 + y3 + 128) >> 8; | |
blockData[2+i] = (y5 - x0 + 128) >> 8; | |
blockData[3+i] = (y6 - y7 + 128) >> 8; | |
blockData[4+i] = (y6 + y7 + 128) >> 8; | |
blockData[5+i] = (x0 + y5 + 128) >> 8; | |
blockData[6+i] = (y3 - x4 + 128) >> 8; | |
blockData[7+i] = (y4 - b7 + 128) >> 8; | |
} | |
}; | |
// ---------------------------------------------------------------------------- | |
// VLC Tables and Constants | |
var | |
SOCKET_MAGIC_BYTES = 'jsmp', | |
DECODE_SKIP_OUTPUT = 1, | |
PICTURE_RATE = [ | |
0.000, 23.976, 24.000, 25.000, 29.970, 30.000, 50.000, 59.940, | |
60.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000 | |
], | |
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 | |
]), | |
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 | |
]), | |
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 | |
]), | |
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. | |
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 | |
// | |
MACROBLOCK_TYPE_I = new Int8Array([ | |
1*3, 2*3, 0, // 0 | |
-1, 3*3, 0, // 1 0 | |
0, 0, 0x01, // 2 1. | |
0, 0, 0x11 // 3 01. | |
]), | |
MACROBLOCK_TYPE_P = 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. | |
]), | |
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. | |
]), | |
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. | |
]), | |
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. | |
]), | |
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. | |
]), | |
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. | |
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. | |
]), | |
PICTURE_TYPE_I = 1, | |
PICTURE_TYPE_P = 2, | |
PICTURE_TYPE_B = 3, | |
PICTURE_TYPE_D = 4, | |
START_SEQUENCE = 0xB3, | |
START_SLICE_FIRST = 0x01, | |
START_SLICE_LAST = 0xAF, | |
START_PICTURE = 0x00, | |
START_EXTENSION = 0xB5, | |
START_USER_DATA = 0xB2, | |
// Shaders for accelerated WebGL YCbCrToRGBA conversion | |
SHADER_FRAGMENT_YCBCRTORGBA = [ | |
'precision mediump float;', | |
'uniform sampler2D YTexture;', | |
'uniform sampler2D CBTexture;', | |
'uniform sampler2D CRTexture;', | |
'varying vec2 texCoord;', | |
'void main() {', | |
'float y = texture2D(YTexture, texCoord).r;', | |
'float cr = texture2D(CBTexture, texCoord).r - 0.5;', | |
'float cb = texture2D(CRTexture, texCoord).r - 0.5;', | |
'gl_FragColor = vec4(', | |
'y + 1.4 * cr,', | |
'y + -0.343 * cb - 0.711 * cr,', | |
'y + 1.765 * cb,', | |
'1.0', | |
');', | |
'}' | |
].join('\n'), | |
SHADER_FRAGMENT_LOADING = [ | |
'precision mediump float;', | |
'uniform float loaded;', | |
'varying vec2 texCoord;', | |
'void main() {', | |
'float c = ceil(loaded-(1.0-texCoord.y));', | |
//'float c = ceil(loaded-(1.0-texCoord.y) +sin((texCoord.x+loaded)*16.0)*0.01);', // Fancy wave anim | |
'gl_FragColor = vec4(c,c,c,1);', | |
'}' | |
].join('\n'), | |
SHADER_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'); | |
var MACROBLOCK_TYPE_TABLES = [ | |
null, | |
MACROBLOCK_TYPE_I, | |
MACROBLOCK_TYPE_P, | |
MACROBLOCK_TYPE_B | |
]; | |
// ---------------------------------------------------------------------------- | |
// Bit Reader | |
var BitReader = function(arrayBuffer) { | |
this.bytes = new Uint8Array(arrayBuffer); | |
this.length = this.bytes.length; | |
this.writePos = this.bytes.length; | |
this.index = 0; | |
}; | |
BitReader.NOT_FOUND = -1; | |
BitReader.prototype.findNextMPEGStartCode = function() { | |
for( var i = (this.index+7 >> 3); i < this.writePos; 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.writePos << 3); | |
return BitReader.NOT_FOUND; | |
}; | |
BitReader.prototype.nextBytesAreStartCode = function() { | |
var i = (this.index+7 >> 3); | |
return ( | |
i >= this.writePos || ( | |
this.bytes[i] == 0x00 && | |
this.bytes[i+1] == 0x00 && | |
this.bytes[i+2] == 0x01 | |
) | |
); | |
}; | |
BitReader.prototype.nextBits = function(count) { | |
var | |
byteOffset = this.index >> 3, | |
room = (8 - this.index % 8); | |
if( room >= count ) { | |
return (this.bytes[byteOffset] >> (room - count)) & (0xff >> (8-count)); | |
} | |
var | |
leftover = (this.index + count) % 8, // Leftover bits in last byte | |
end = (this.index + count -1) >> 3, | |
value = this.bytes[byteOffset] & (0xff >> (8-room)); // Fill out first byte | |
for( byteOffset++; byteOffset < end; byteOffset++ ) { | |
value <<= 8; // Shift and | |
value |= this.bytes[byteOffset]; // Put next byte | |
} | |
if (leftover > 0) { | |
value <<= leftover; // Make room for remaining bits | |
value |= (this.bytes[byteOffset] >> (8 - leftover)); | |
} | |
else { | |
value <<= 8; | |
value |= this.bytes[byteOffset]; | |
} | |
return value; | |
}; | |
BitReader.prototype.getBits = function(count) { | |
var value = this.nextBits(count); | |
this.index += count; | |
return value; | |
}; | |
BitReader.prototype.advance = function(count) { | |
return (this.index += count); | |
}; | |
BitReader.prototype.rewind = function(count) { | |
return (this.index -= count); | |
}; | |
})(window); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta name="viewport" content="width=320, initial-scale=1"/> | |
<title>jsmpeg streaming</title> | |
<style type="text/css"> | |
body { | |
background: #333; | |
text-align: center; | |
margin-top: 10%; | |
} | |
#videoCanvas { | |
/* Always stretch the canvas to 640x480, regardless of its | |
internal size. */ | |
width: 640px; | |
height: 480px; | |
} | |
</style> | |
</head> | |
<body> | |
<!-- The Canvas size specified here is the "initial" internal resolution. jsmpeg will | |
change this internal resolution to whatever the source provides. The size the | |
canvas is displayed on the website is dictated by the CSS style. | |
--> | |
<canvas id="videoCanvas" width="640" height="480"> | |
<p> | |
Please use a browser that supports the Canvas Element, like | |
<a href="http://www.google.com/chrome">Chrome</a>, | |
<a href="http://www.mozilla.com/firefox/">Firefox</a>, | |
<a href="http://www.apple.com/safari/">Safari</a> or Internet Explorer 10 | |
</p> | |
</canvas> | |
<script type="text/javascript" src="jsmpg.js"></script> | |
<script type="text/javascript"> | |
// Show loading notice | |
var canvas = document.getElementById('videoCanvas'); | |
var ctx = canvas.getContext('2d'); | |
ctx.fillStyle = '#444'; | |
ctx.fillText('Loading...', canvas.width/2-30, canvas.height/3); | |
// Setup the WebSocket connection and start the player | |
var client = new WebSocket( 'ws://125.227.73.145:7099/123456/640/480/' ); | |
var player = new jsmpeg(client, {canvas:canvas}); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if( process.argv.length < 3 ) { | |
console.log( | |
'Usage: \n' + | |
'node stream-server.js <secret> [<stream-port> <websocket-port>]' | |
); | |
process.exit(); | |
} | |
var STREAM_SECRET = process.argv[2], | |
STREAM_PORT = process.argv[3] || 8082, | |
WEBSOCKET_PORT = process.argv[4] || 8084, | |
STREAM_MAGIC_BYTES = 'jsmp'; // Must be 4 bytes | |
var width = 320, | |
height = 240; | |
// Websocket Server | |
var socketServer = new (require('ws').Server)({port: WEBSOCKET_PORT}); | |
socketServer.on('connection', function(socket) { | |
// Send magic bytes and video size to the newly connected socket | |
// struct { char magic[4]; unsigned short width, height;} | |
var streamHeader = new Buffer(8); | |
streamHeader.write(STREAM_MAGIC_BYTES); | |
streamHeader.writeUInt16BE(width, 4); | |
streamHeader.writeUInt16BE(height, 6); | |
socket.send(streamHeader, {binary:true}); | |
console.log( 'New WebSocket Connection ('+socketServer.clients.length+' total)' ); | |
socket.on('close', function(code, message){ | |
console.log( 'Disconnected WebSocket ('+socketServer.clients.length+' total)' ); | |
}); | |
}); | |
socketServer.broadcast = function(data, opts) { | |
for( var i in this.clients ) { | |
if (this.clients[i].readyState == 1) { | |
this.clients[i].send(data, opts); | |
} | |
else { | |
console.log( 'Error: Client ('+i+') not connected.' ); | |
} | |
} | |
}; | |
// HTTP Server to accept incomming MPEG Stream | |
var streamServer = require('http').createServer( function(request, response) { | |
var params = request.url.substr(1).split('/'); | |
if( params[0] == STREAM_SECRET ) { | |
width = (params[1] || 320)|0; | |
height = (params[2] || 240)|0; | |
console.log( | |
'Stream Connected: ' + request.socket.remoteAddress + | |
':' + request.socket.remotePort + ' size: ' + width + 'x' + height | |
); | |
request.on('data', function(data){ | |
socketServer.broadcast(data, {binary:true}); | |
}); | |
} | |
else { | |
console.log( | |
'Failed Stream Connection: '+ request.socket.remoteAddress + | |
request.socket.remotePort + ' - wrong secret.' | |
); | |
response.end(); | |
} | |
}).listen(STREAM_PORT); | |
console.log('Listening for MPEG Stream on http://127.0.0.1:'+STREAM_PORT+'/<secret>/<width>/<height>'); | |
console.log('Awaiting WebSocket connections on ws://127.0.0.1:'+WEBSOCKET_PORT+'/'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment