ref: bb4b01a1f96ee6576fb0ccc8237cf635e89a01ec
dir: /js/h264bsdCanvas.js/
//
// 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]);
};