ref: 3967fd3c5323ec35b5e9f39bbf736e7e9571db9d
parent: bb4b01a1f96ee6576fb0ccc8237cf635e89a01ec
author: Sam Leitch <[email protected]>
date: Sat Mar 8 17:05:40 EST 2014
After discovering the exports from emscripten I cleaned up a bunch of code in the decoder. Decided to use a different approach to decoding that more accurately matched the original h264bsd API. Changed the names of the classes and made the canvas match the new API. Still needs testing.
--- a/js/h264bsdCanvas.js
+++ /dev/null
@@ -1,309 +1,0 @@
-//
-// Copyright (c) 2014 Sam Leitch. All rights reserved.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//
-// TODO: Incorporate cropping information
-
-/**
- * This class grabs content from a video element and feeds it to a canvas element.
- * If available the content is modified using a custom WebGL shader program.
- * This class depends on the h264bsd_asm.js Module implementation.
- */
-function H264bsdCanvas(canvas, Module, forceRGB) {
- this.Module = Module;
- this.canvasElement = canvas;
- this.initGlContext();
-
- if(this.contextGl && !forceRGB) {
- this.initProgram();
- this.initBuffers();
- this.initTextures();
- } else {
- this.context2D = canvas.getContext('2d');
- this.rgbBufferSize = 0;
- this.rgbBufferPtr = 0;
- }
-}
-
-/**
- * Create the GL context from the canvas element
- */
-H264bsdCanvas.prototype.initGlContext = function() {
- var canvas = this.canvasElement;
- var gl = null;
-
- var validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"];
- var i = 0;
-
- while(!gl && nameIndex < validNames.length) {
- var contextName = validContextNames[i];
-
- try {
- gl = canvas.getContext(contextName);
- } catch (e) {
- gl = null;
- }
-
- if(!gl || typeof gl.getParameter !== "function") {
- gl = null;
- }
-
- ++i;
- }
-
- this.contextGl = gl;
-}
-
-/**
- * Initialize GL shader program
- */
-H264bsdCanvas.prototype.initProgram = function() {
- var gl = this.contextGl;
-
- var vertexShaderScript = [
- 'attribute vec4 vertexPos;',
- 'attribute vec4 texturePos;',
- 'varying vec2 textureCoord;',
-
- 'void main()',
- '{',
- 'gl_Position = vertexPos;',
- 'textureCoord = texturePos.xy;',
- '}'
- ].join('\n');
-
- var fragmentShaderScript = [
- 'precision highp float;',
- 'varying highp vec2 textureCoord;',
- 'uniform sampler2D ySampler;',
- 'uniform sampler2D uSampler;',
- 'uniform sampler2D vSampler;',
- 'const mat4 YUV2RGB = mat4',
- '(',
- '1.1643828125, 0, 1.59602734375, -.87078515625,',
- '1.1643828125, -.39176171875, -.81296875, .52959375,',
- '1.1643828125, 2.017234375, 0, -1.081390625,',
- '0, 0, 0, 1',
- ');',
-
- 'void main(void) {',
- 'highp float y = texture2D(ySampler, textureCoord).r;'
- 'highp float u = texture2D(uSampler, textureCoord).r;'
- 'highp float v = texture2D(vSampler, textureCoord).r;'
- 'gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
- '}'
- ].join('\n');
-
- var vertexShader = gl.createShader(gl.VERTEX_SHADER);
- gl.shaderSource(vertexShader, vertexShaderScript);
- gl.compileShader(vertexShader);
- if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
- console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader));
- }
-
- var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
- gl.shaderSource(fragmentShader, fragmentShaderScript);
- gl.compileShader(fragmentShader);
- if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
- console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader));
- }
-
- var program = gl.createProgram();
- gl.attachShader(program, vertexShader);
- gl.attachShader(program, fragmentShader);
- gl.linkProgram(program);
- if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
- console.log('Program failed to compile: ' + gl.getProgramInfoLog(program));
- }
-
- gl.useProgram(program);
-
- this.shaderProgram = program;
-}
-
-/**
- * Initialize vertex buffers and attach to shader program
- */
-H264bsdCanvas.prototype.initBuffers = function() {
- var gl = this.contextGl;
- var program = this.shaderProgram;
-
- var vertexPosBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW);
-
- var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
- gl.enableVertexAttribArray(vertexPosRef);
- gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
-
- var texturePosBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
-
- var texturePosRef = gl.getAttribLocation(program, 'texturePos');
- gl.enableVertexAttribArray(texturePosRef);
- gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0);
-}
-
-/**
- * Initialize GL textures and attach to shader program
- */
-H264bsdCanvas.prototype.initTextures = function() {
- var gl = this.contextGl;
- var program = this.shaderProgram;
-
- var yTextureRef = this.initTexture();
- var ySamplerRef = gl.getUniformLocation(program, 'ySampler');
- gl.uniform1i(ySamplerRef, 0);
- this.yTextureRef = yTextureRef;
-
- var uTextureRef = this.initTexture();
- var uSamplerRef = gl.getUniformLocation(program, 'uSampler');
- gl.uniform1i(uSamplerRef, 1);
- this.uTextureRef = uTextureRef;
-
- var vTextureRef = this.initTexture();
- var vSamplerRef = gl.getUniformLocation(program, 'vSampler');
- gl.uniform1i(vSamplerRef, 2);
- this.vTextureRef = vTextureRef;
-}
-
-/**
- * Create and configure a single texture
- */
-H264bsdCanvas.prototype.initTexture = function() {
- var textureRef = gl.createTexture();
- gl.bindTexture(gl.TEXTURE_2D, textureRef);
- 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.bindTexture(gl.TEXTURE_2D, null);
-
- return textureRef;
-}
-
-/**
- * Draw yuvData in the best way possible
- */
-H264bsdCanvas.prototype.drawNextPicture = function(pStorage) {
- var gl = this.contextGl;
-
- if(gl) {
- this.drawNextPictureGl(pStorage);
- } else {
- this.drawNextPictureARGB(pStorage);
- }
-}
-
-/**
- * Setup GL viewport and draw the yuvData
- */
-H264bsdCanvas.prototype.drawNextPictureGl = function(pStorage) {
- var gl = this.contextGl;
- var yTextureRef = this.yTextureRef;
- var uTextureRef = this.uTextureRef;
- var vTextureRef = this.vTextureRef;
-
- gl.viewport(0, 0, size.w, size.h);
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, size.w, size.h, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, pYuvData);
-
- gl.activeTexture(gl.TEXTURE1);
- gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, size.w/2, size.h/2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, pYuvData);
-
- gl.activeTexture(gl.TEXTURE2);
- gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, size.w/2, size.h/2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, pYuvData);
-
- gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
-}
-
-/**
- * Convert yuvData to ARGB data and draw to canvas
- */
-H264bsdCanvas.prototype.drawNextPictureARGB = function(pStorage) {
- var ctx = this.context2D;
- var rgbBufferSize = this.rgbBufferSize;
- var rgbBufferPtr = this.rgbBufferPtr;
- var imageData = this.imageData;
-
- var rgbSize = size.w * size.h * 4;
-
- if(rgbBufferSize < rgbSize) {
- if(rgbBufferPtr != 0) this.free(rgbBufferPtr);
-
- rgbBufferSize = rgbSize;
- rgbBufferPtr = this.malloc(rgbBufferSize);
-
- this.rgbBufferSize = rgbBufferSize;
- this.rgbBufferPtr = rgbBufferPtr;
- }
-
- this.h264bsdConvertToARGB(size.w, size.h, pYuvData, pRgbData);
-
- if(!imageData ||
- imageData.width != size.w ||
- imageData.height != size.h) {
- imageData = ctx.createImageData(size.w, size.h);
- this.imageData = imageData;
- }
-
- var rgbData = this.Module.HEAPU8.subarray(rgbBufferPtr, rgbBufferPtr + rgbSize);
- imageData.data.set(rgbData);
- ctx.putImageData(imageData, 0, 0);
-}
-
-//void h264bsdConvertToARGB(u32 width, u32 height, u8* data, u32 *rgbData);
-H264bsdCanvas.prototype.h264bsdConvertToARGB = function(width, height, pData, pRgbData) {
- this.Module.ccall('h264bsdConvertToARGB',
- Number,
- [Number, Number, Number, Number],
- [width, height, pData, pRgbData]);
-};
-
-// u8* h264bsdNextOutputPicture(storage_t *pStorage, u32 *picId, u32 *isIdrPic, u32 *numErrMbs);
-H264bsdCanvas.prototype.h264bsdNextOutputPicture_ = function(pStorage, pPicId, pIsIdrPic, pNumErrMbs) {
- return this.Module.ccall('h264bsdNextOutputPicture',
- Number,
- [Number, Number, Number, Number],
- [pStorage, pPicId, pIsIdrPic, pNumErrMbs]);
-};
-
-// u32* h264bsdNextOutputPictureARGB(storage_t *pStorage, u32 *picId, u32 *isIdrPic, u32 *numErrMbs);
-H264bsdCanvas.prototype.h264bsdNextOutputPictureARGB_ = function(pStorage, pPicId, pIsIdrPic, pNumErrMbs){
- return this.Module.ccall('h264bsdNextOutputPictureARGB',
- Number,
- [Number, Number, Number, Number],
- [pStorage, pPicId, pIsIdrPic, pNumErrMbs]);
-};
-
-// void* malloc(size_t size);
-H264bsdCanvas.prototype.malloc = function(size) {
- return this.Module.ccall('malloc', Number, [Number], [size]);
-};
-
-// void free(void* ptr);
-H264bsdCanvas.prototype.free = function(ptr) {
- this.Module.ccall('free', null, [Number], [ptr]);
-};
--- /dev/null
+++ b/js/h264bsd_canvas.js
@@ -1,0 +1,261 @@
+//
+// Copyright (c) 2014 Sam Leitch. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+// TODO: Incorporate cropping information
+
+/**
+ * This class can be used to render output pictures from an H264bsdDecoder to a canvas element.
+ * If available the content is rendered using WebGL.
+ */
+function H264bsdCanvas(canvas, forceRGB) {
+ this.canvasElement = canvas;
+ this.initContextGL();
+
+ if(this.contextGL && !forceRGB) {
+ this.initProgram();
+ this.initBuffers();
+ this.initTextures();
+ } else {
+ this.context2D = canvas.getContext('2d');
+ }
+}
+
+/**
+ * Create the GL context from the canvas element
+ */
+H264bsdCanvas.prototype.initContextGL = function() {
+ var canvas = this.canvasElement;
+ var gl = null;
+
+ var validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"];
+ var i = 0;
+
+ while(!gl && nameIndex < validNames.length) {
+ var contextName = validContextNames[i];
+
+ try {
+ gl = canvas.getContext(contextName);
+ } catch (e) {
+ gl = null;
+ }
+
+ if(!gl || typeof gl.getParameter !== "function") {
+ gl = null;
+ }
+
+ ++i;
+ }
+
+ this.contextGL = gl;
+}
+
+/**
+ * Initialize GL shader program
+ */
+H264bsdCanvas.prototype.initProgram = function() {
+ var gl = this.contextGL;
+
+ var vertexShaderScript = [
+ 'attribute vec4 vertexPos;',
+ 'attribute vec4 texturePos;',
+ 'varying vec2 textureCoord;',
+
+ 'void main()',
+ '{',
+ 'gl_Position = vertexPos;',
+ 'textureCoord = texturePos.xy;',
+ '}'
+ ].join('\n');
+
+ var fragmentShaderScript = [
+ 'precision highp float;',
+ 'varying highp vec2 textureCoord;',
+ 'uniform sampler2D ySampler;',
+ 'uniform sampler2D uSampler;',
+ 'uniform sampler2D vSampler;',
+ 'const mat4 YUV2RGB = mat4',
+ '(',
+ '1.1643828125, 0, 1.59602734375, -.87078515625,',
+ '1.1643828125, -.39176171875, -.81296875, .52959375,',
+ '1.1643828125, 2.017234375, 0, -1.081390625,',
+ '0, 0, 0, 1',
+ ');',
+
+ 'void main(void) {',
+ 'highp float y = texture2D(ySampler, textureCoord).r;'
+ 'highp float u = texture2D(uSampler, textureCoord).r;'
+ 'highp float v = texture2D(vSampler, textureCoord).r;'
+ 'gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
+ '}'
+ ].join('\n');
+
+ var vertexShader = gl.createShader(gl.VERTEX_SHADER);
+ gl.shaderSource(vertexShader, vertexShaderScript);
+ gl.compileShader(vertexShader);
+ if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
+ console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader));
+ }
+
+ var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
+ gl.shaderSource(fragmentShader, fragmentShaderScript);
+ gl.compileShader(fragmentShader);
+ if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
+ console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader));
+ }
+
+ var program = gl.createProgram();
+ gl.attachShader(program, vertexShader);
+ gl.attachShader(program, fragmentShader);
+ gl.linkProgram(program);
+ if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
+ console.log('Program failed to compile: ' + gl.getProgramInfoLog(program));
+ }
+
+ gl.useProgram(program);
+
+ this.shaderProgram = program;
+}
+
+/**
+ * Initialize vertex buffers and attach to shader program
+ */
+H264bsdCanvas.prototype.initBuffers = function() {
+ var gl = this.contextGL;
+ var program = this.shaderProgram;
+
+ var vertexPosBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW);
+
+ var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
+ gl.enableVertexAttribArray(vertexPosRef);
+ gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
+
+ var texturePosBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);
+
+ var texturePosRef = gl.getAttribLocation(program, 'texturePos');
+ gl.enableVertexAttribArray(texturePosRef);
+ gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0);
+}
+
+/**
+ * Initialize GL textures and attach to shader program
+ */
+H264bsdCanvas.prototype.initTextures = function() {
+ var gl = this.contextGL;
+ var program = this.shaderProgram;
+
+ var yTextureRef = this.initTexture();
+ var ySamplerRef = gl.getUniformLocation(program, 'ySampler');
+ gl.uniform1i(ySamplerRef, 0);
+ this.yTextureRef = yTextureRef;
+
+ var uTextureRef = this.initTexture();
+ var uSamplerRef = gl.getUniformLocation(program, 'uSampler');
+ gl.uniform1i(uSamplerRef, 1);
+ this.uTextureRef = uTextureRef;
+
+ var vTextureRef = this.initTexture();
+ var vSamplerRef = gl.getUniformLocation(program, 'vSampler');
+ gl.uniform1i(vSamplerRef, 2);
+ this.vTextureRef = vTextureRef;
+}
+
+/**
+ * Create and configure a single texture
+ */
+H264bsdCanvas.prototype.initTexture = function() {
+ var gl = this.contextGL;
+
+ var textureRef = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, textureRef);
+ 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.bindTexture(gl.TEXTURE_2D, null);
+
+ return textureRef;
+}
+
+/**
+ * Draw yuvData in the best way possible
+ */
+H264bsdCanvas.prototype.drawNextOutputPicture = function(decoder) {
+ var gl = this.contextGL;
+
+ if(gl) {
+ this.drawNextOuptutPictureGL(decoder);
+ } else {
+ this.drawNextOuptutPictureARGB(decoder);
+ }
+}
+
+/**
+ * Draw the next output picture using WebGL
+ */
+H264bsdCanvas.prototype.drawNextOuptutPictureGL = function(decoder) {
+ var gl = this.contextGL;
+ var yTextureRef = this.yTextureRef;
+ var uTextureRef = this.uTextureRef;
+ var vTextureRef = this.vTextureRef;
+
+ var sizeMB = decoder.outputSizeMB;
+ var width = sizeMB.width * 16;
+ var height = sizeMB.height * 16;
+
+ gl.viewport(0, 0, width, height);
+
+ var i420Data = decoder.nextOutputPicture();
+
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, i420Data);
+
+ gl.activeTexture(gl.TEXTURE1);
+ gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width/2, height/2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, i420Data);
+
+ gl.activeTexture(gl.TEXTURE2);
+ gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width/2, height/2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, i420Data);
+
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+}
+
+/**
+ * Draw next output picture using ARGB data on a 2d canvas.
+ */
+H264bsdCanvas.prototype.drawNextOuptutPictureARGB = function(decoder) {
+ var ctx = this.context2D;
+
+ var sizeMB = decoder.outputSizeMB;
+ var width = sizeMB.width * 16;
+ var height = sizeMB.height * 16;
+
+ var argbData = decoder.nextOutputPictureARGB();
+
+ var imageData = ctx.createImageData(width, height);
+ imageData.data.set(argbData);
+ ctx.putImageData(imageData, 0, 0);
+}
--- /dev/null
+++ b/js/h264bsd_decoder.js
@@ -1,0 +1,258 @@
+//
+// Copyright (c) 2013 Sam Leitch. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+
+/**
+ * This class wraps the details of the h264bsd library.
+ * Module object is an Emscripten module provided globally by h264bsd_asm.js
+ *
+ * In order to use this class, you first queue encoded data using queueData.
+ * Each call to decode() will decode a single encoded element.
+ * When decode() return H264bsdDecoder.PIC_RDY, a picture is ready in the output buffer.
+ * The output buffer can be accessed by calling getNextOutputPicture()
+ * An output picture may also be decoded using an H264bsdCanvas.
+ * When you're done decoding, make sure to call release() to clean up internal buffers.
+ */
+function H264bsdDecoder(module) {
+ this.module = module;
+ this.released = false;
+
+ this.pInput = 0;
+ this.inputLength = 0;
+ this.inputOffset = 0;
+
+ this.pStorage = module._h264bsdAlloc();
+ module._h264bsdInit(this.pStorage, 0);
+};
+
+H264bsdDecoder.RDY = 0;
+H264bsdDecoder.PIC_RDY = 1;
+H264bsdDecoder.HDRS_RDY = 2;
+H264bsdDecoder.ERROR = 3;
+H264bsdDecoder.PARAM_SET_ERROR = 4;
+H264bsdDecoder.MEMALLOC_ERROR = 5;
+
+/**
+ * Clean up memory used by the decoder
+ */
+H264bsdDecoder.prototype.release = function() {
+ var module = this.module;
+ var pStorage = this.pStorage;
+ var pInput = this.pInput;
+
+ if(pStorage != 0) {
+ module._h264bsdShutdown(pStorage);
+ module._h264bsdFree(pStorage);
+ }
+
+ if(pInput != 0) {
+ module._free(pInput);
+ }
+
+ this.pStorage = 0;
+ this.pInput = 0;
+ this.inputLength = 0;
+ this.inputOffset = 0;
+};
+
+/**
+ * Queue ArrayBuffer data to be decoded
+ */
+H264bsdDecoder.prototype.queueData(data) {
+ var module = this.module
+ var pInput = this.pInput;
+ var inputLength = this.inputLength;
+ var inputOffset = this.inputOffset;
+
+ if(typeof data === 'undefined' || !(data instanceof ArrayBuffer)) {
+ throw new Error("data must be a ArrayBuffer instance")
+ }
+
+ data = new Uint8Array(data);
+
+ if(pInput === 0) {
+ inputLength = data.byteLength;
+ pInput = module._malloc(inputLength);
+ inputOffset = 0;
+
+ module.HEAPU8.set(data, pInput);
+ } else {
+ var remainingInputLength = inputLength - inputOffset;
+ var newInputLength = remainingInputLength + data.byteLength;
+ var pNewInput = module._malloc(newInputLength);
+
+ module._memcpy(pNewInput, pInput + inputOffset, remainingInputLength);
+ module.HEAPU8.set(data, pNewInput + remainingInputLength);
+
+ module._free(pInput);
+
+ pInput = pNewInput;
+ inputLength = newInputLength;
+ inputOffset = 0;
+ }
+
+ this.pInput = input;
+ this.inputLength = inputLength;
+ this.inputOffset = inputOffset;
+}
+
+/**
+ * Decodes the next NAL unit from the queued data.
+ * Returns H264bsdDecoder.PIC_RDY when a new picture is ready.
+ * Pictures can be accessed using nextOutputPicture() or nextOutputPictureARGB()
+ */
+H264bsdDecoder.prototype.decode = function(picId) {
+ var module = this.module;
+ var pStorage = this.pStorage;
+ var pInput = this.pInput;
+ var inputLength = this.inputLength;
+ var inputOffset = this.inputOffset;
+
+ if(pInput == 0) return H264bsdDecoder.ERROR;
+
+ var pBytesRead = module._malloc(4);
+
+ var retCode = module._h264bsdDecode(pStorage, pInput + inputOffset, inputLength - inputOffset, 0, pBytesRead);
+
+ var bytesRead = module.getValue(pBytesRead, 'i32');
+ module._free(pBytesRead);
+
+ inputOffset += bytesRead;
+
+ if(inputOffset >= inputLength) {
+ module._free(pInput);
+ pInput = 0;
+ inputOffset = 0;
+ inputLength = 0;
+ }
+
+ this.pInput = pInput;
+ this.inputLength = inputLength;
+ this.inputOffset = inputOffset;
+
+ return retCode;
+};
+
+/**
+ * Returns the next output picture as an I420 encoded image.
+ */
+H264bsdDecoder.prototype.nextOutputPicture = function() {
+ var module = this.module;
+ var pStorage = this.pStorage;
+
+ var pPicId = module._malloc(4);
+ var pIsIdrPic = module._malloc(4);
+ var pNumErrMbs = module._malloc(4);
+
+ var pBytes = module._h264bsdNextOutputPicture(pStorage, pPicId, pIsIdrPic, pNumErrMbs);
+
+ // None of these values are currently used.
+ module._free(pPicId);
+ module._free(pIsIdrPic);
+ module._free(pNumErrMbs);
+
+ var outputSizeMB = this.getOutputSizeMB();
+ var outputLength = (outputSizeMB.width * outputSizeMB.height) * 3 / 2;
+
+ var outputBytes = new UInt8Array(self.Module.HEAPU8, pBytes, outputLength);
+
+ return outputBytes
+};
+
+/**
+ * Returns the next output picture as an ARGB encoded image.
+ * Note: There is extra overhead required to convert the image to ARGB.
+ * This method should be avoided if possible.
+ */
+H264bsdDecoder.prototype.nextOutputPictureARGB = function() {
+ var module = this.module;
+ var pStorage = this.pStorage;
+
+ var pPicId = module._malloc(4);
+ var pIsIdrPic = module._malloc(4);
+ var pNumErrMbs = module._malloc(4);
+
+ var pBytes = module._h264bsdNextOutputPictureARGB(pStorage, pPicId, pIsIdrPic, pNumErrMbs);
+
+ // None of these values are currently used.
+ module._free(pPicId);
+ module._free(pIsIdrPic);
+ module._free(pNumErrMbs);
+
+ var outputSizeMB = this.getOutputSizeMB();
+ var outputLength = (outputSizeMB.width * outputSizeMB.height) * 4;
+
+ var outputBytes = new UInt8Array(self.Module.HEAPU8, pBytes, outputLength);
+
+ return outputBytes
+};
+
+/**
+ * Returns an object containing the width and height of output pictures in MB.
+ * This value is only valid after at least one call to decode() has returned H264bsdDecoder.HDRS_RDY
+ */
+H264bsdDecoder.prototype.outputSizeMB = function() {
+ var module = this.module;
+ var pStorage = this.pStorage;
+
+ var width = module._h264bsdPicWidth(pStorage);
+ var height = module._h264bsdPicHeight(pStorage);
+
+ return {'width': width, 'height': height};
+};
+
+/**
+ * Returns the info used to crop output images to there final viewing dimensions.
+ * If this method returns null no cropping info is provided and the full image should be presented.
+ */
+H264bsdDecoder.prototype.croppingInfo = function(){
+ var module = this.module;
+ var pStorage = this.pStorage;
+
+ var pCroppingFlag = self.malloc_(4);
+ var pLeftOffset = self.malloc_(4);
+ var pWidth = self.malloc_(4);
+ var pTopOffset = self.malloc_(4);
+ var pHeight = self.malloc_(4);
+
+ module._h264bsdCroppingParams(pStorage, pCroppingFlag, pLeftOffset, pWidth, pTopOffset, pHeight);
+
+ var croppingFlag = self.Module.getValue(pCroppingFlag, 'i32');
+ var leftOffset = self.Module.getValue(pLeftOffset, 'i32');
+ var width = self.Module.getValue(pWidth, 'i32');
+ var topOffset = self.Module.getValue(pTopOffset, 'i32');
+ var height = self.Module.getValue(pHeight, 'i32');
+
+ module._free(pCroppingFlag);
+ module._free(pLeftOffset);
+ module._free(pWidth);
+ module._free(pTopOffset);
+ module._free(pHeight);
+
+ if(croppingFlag === 0) return null;
+
+ return {
+ 'width': width,
+ 'height': height,
+ 'top': topOffset,
+ 'left': leftOffset
+ };
+};