// // Copyright (c) 2013-2022 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.onPictureReady = null; this.onHeadersReady = null; 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, flush) { var module = this.module var pInput = this.pInput; var inputLength = this.inputLength; var inputOffset = this.inputOffset; if (data instanceof ArrayBuffer) { data = new Uint8Array(data) } if(flush === true && pInput !== 0) { module._free(pInput); pInput = 0 } 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 bytesRead = 0; var retCode = module._h264bsdDecode(pStorage, pInput + inputOffset, inputLength - inputOffset, 0, pBytesRead); if (retCode == H264bsdDecoder.RDY || retCode == H264bsdDecoder.PIC_RDY || retCode == H264bsdDecoder.HDRS_RDY) { 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(); } if(retCode == H264bsdDecoder.HDRS_RDY && this.onHeadersReady instanceof Function) { this.onHeadersReady(); } 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 outputLength = this.outputPictureSizeBytes(); 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 outputLength = this.outputPictureSizeBytesRGBA(); var outputBytes = new Uint8Array(module.HEAPU8.subarray(pBytes, pBytes + outputLength)); return outputBytes; }; /** * Returns an object containing the width and height of output pictures in pixels. * This value is only valid after at least one call to decode() has returned H264bsdDecoder.HDRS_RDY * You can also use onHeadersReady callback to determine when this value changes. */ H264bsdDecoder.prototype.outputPictureWidth = function() { var module = this.module; var pStorage = this.pStorage; return module._h264bsdPicWidth(pStorage) * 16; }; /** * Returns an object containing the width and height of output pictures in pixels. * This value is only valid after at least one call to decode() has returned H264bsdDecoder.HDRS_RDY * You can also use onHeadersReady callback to determine when this value changes. */ H264bsdDecoder.prototype.outputPictureHeight = function() { var module = this.module; var pStorage = this.pStorage; return module._h264bsdPicHeight(pStorage) * 16; }; /** * Returns integer byte length of output pictures in bytes. * This value is only valid after at least one call to decode() has returned H264bsdDecoder.HDRS_RDY */ H264bsdDecoder.prototype.outputPictureSizeBytes = function() { var width = this.outputPictureWidth(); var height = this.outputPictureHeight(); return (width * height) * 3 / 2; }; /** * Returns integer byte length of RGBA output pictures in bytes. * This value is only valid after at least one call to decode() has returned H264bsdDecoder.HDRS_RDY */ H264bsdDecoder.prototype.outputPictureSizeBytesRGBA = function() { var width = this.outputPictureWidth(); var height = this.outputPictureHeight(); return (width * height) * 4; }; /** * 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.croppingParams = function() { var module = this.module; var pStorage = this.pStorage; var pCroppingFlag = module._malloc(4); var pLeftOffset = module._malloc(4); var pWidth = module._malloc(4); var pTopOffset = module._malloc(4); var pHeight = module._malloc(4); module._h264bsdCroppingParams(pStorage, pCroppingFlag, pLeftOffset, pWidth, pTopOffset, pHeight); var croppingFlag = module.getValue(pCroppingFlag, 'i32'); var leftOffset = module.getValue(pLeftOffset, 'i32'); var width = module.getValue(pWidth, 'i32'); var topOffset = module.getValue(pTopOffset, 'i32'); var height = 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 }; }; if (typeof module !== "undefined") { module.exports = H264bsdDecoder; }