shithub: h264bsd

ref: 91b26d283f8869cd0b660bec23450a6d48e33aad
dir: /js/h264bsd_decoder.js/

View raw version
//
//  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() returns H264bsdDecoder.PIC_RDY, a picture is ready in the output buffer.
 * You can also use the onPictureReady() function to determine when a picture is ready.
 * 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;
H264bsdDecoder.NO_INPUT = 1024;

/**
 * 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.queueInput = function(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 = pInput;
    this.inputLength = inputLength;
    this.inputOffset = inputOffset;
}

/**
 * Returns the numbre of bytes remaining in the decode queue.
 */
H264bsdDecoder.prototype.inputBytesRemaining = function() {
    return this.inputLength - this.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 nextOutputPictureRGBA()
 * decode() will return H264bsdDecoder.NO_INPUT when there is no more data to be decoded.
 */
H264bsdDecoder.prototype.decode = function() {
    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.NO_INPUT;

    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;

    if(retCode == H264bsdDecoder.PIC_RDY && this.onPictureReady instanceof Function) {
        this.onPictureReady();
    }

    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.outputSizeMB();
    var outputLength = (outputSizeMB.width * 16 * outputSizeMB.height * 16) * 3 / 2;

    var outputBytes = new Uint8Array(module.HEAPU8.subarray(pBytes, pBytes + outputLength));

    return outputBytes;
};

/**
 * Returns the next output picture as an RGBA encoded image.
 * Note: There is extra overhead required to convert the image to RGBA.
 * This method should be avoided if possible.
 */
H264bsdDecoder.prototype.nextOutputPictureRGBA = 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._h264bsdNextOutputPictureRGBA(pStorage, pPicId, pIsIdrPic, pNumErrMbs);

    // None of these values are currently used.
    module._free(pPicId);
    module._free(pIsIdrPic);
    module._free(pNumErrMbs);

    var outputSizeMB = this.outputSizeMB();
    var outputLength = (outputSizeMB.width * 16 * outputSizeMB.height * 16) * 4;

    var outputBytes = new Uint8Array(module.HEAPU8.subarray(pBytes, 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
    };
};