shithub: h264bsd

ref: bb4b01a1f96ee6576fb0ccc8237cf635e89a01ec
dir: h264bsd/js/h264bsdCanvas.js

View raw version
//
//  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]);
};