shithub: h264bsd

ref: d6f22738160011bec3a5317d7542d8d756a14a38
dir: /js/h264bsd.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.
 */
function H264Decoder(Module, targetCanvas) {
	H264Decoder.Module = Module;
	H264Decoder.released = false;

	H264Decoder.pStorage = H264Decoder.h264bsdAlloc();

	H264Decoder.h264bsdInit(H264Decoder.Module, H264Decoder.pStorage, 0);
	H264Decoder.targetCanvas = targetCanvas;
};


H264Decoder.RDY = 0;
H264Decoder.PIC_RDY = 1;
H264Decoder.HDRS_RDY = 2;
H264Decoder.ERROR = 3;
H264Decoder.PARAM_SET_ERROR = 4;
H264Decoder.MEMALLOC_ERROR = 5;

H264Decoder.Module = null;
H264Decoder.released = false;
H264Decoder.pStorage = null;
H264Decoder.targetCanvas = null;
H264Decoder.yuvCanvas = null;

H264Decoder.prototype.release = function() {
	if(this.released) return;

	this.released = true;
	H264Decoder.h264bsdShutdown(this.Module, this.pStorage);
	H264Decoder.h264bsdFree(this.Module, this.pStorage);
};

H264Decoder.prototype.decode = function(data) {
	if(data === undefined || !(data instanceof ArrayBuffer)) {
		throw new Error("data must be a ArrayBuffer instance")
	}
	
	data = new Uint8Array(data);
	
	var pData = 0; //The offset into the heap when decoding 
	var pAlloced = 0; //The original pointer to the data buffer (for freeing)
	var pBytesRead = 0; //Pointer to bytesRead
	var length = data.byteLength; //The byte-wise length of the data to decode	
	var bytesRead = 0;  //The number of bytes read from a decode operation
	var retCode = 0; //Return code from a decode operation
	var lastPicId = 0; //ID of the last picture decoded

	//Get a pointer into the heap were our decoded bytes will live
	pData = pAlloced = H264Decoder.malloc(H264Decoder.Module, length);
	H264Decoder.Module.HEAPU8.set(data, pData);

	//get a pointer to where bytesRead will be stored: Uint32 = 4 bytes
	pBytesRead = H264Decoder.malloc(H264Decoder.Module, 4);

	//Keep decoding frames while there is still something to decode
	while(length > 0) {

		retCode = H264Decoder.h264bsdDecode(H264Decoder.Module, H264Decoder.pStorage, pData, length, lastPicId, pBytesRead);		
		bytesRead = H264Decoder.Module.getValue(pBytesRead, 'i32');
		window.console.log('Ret: ' , retCode, ' bytesRead: ', bytesRead);

		switch(retCode){
			case H264Decoder.PIC_RDY:
				lastPicId++;
				window.console.log('h264bsdDecode, PIC_RDY: ', lastPicId);
				H264Decoder.getNextOutputPicture();
				break;
			case H264Decoder.HDRS_RDY:

				break;
		}

		length = length - bytesRead;		
		pData = pData + bytesRead;
	}

	if(pAlloced != 0) {
		H264Decoder.free(H264Decoder.Module, pAlloced);
	}
	
	if(pBytesRead != 0) {
		H264Decoder.free(H264Decoder.Module, pBytesRead);
	}

};

H264Decoder.clamp = function(num, max, min) {
  return Math.min(Math.max(num, min), max);
};

H264Decoder.getNextOutputPicture = function(){
	var length = H264Decoder.getYUVLength();

	var pPicId = H264Decoder.malloc(H264Decoder.Module, 4);
	var picId = 0;

	var pIsIdrPic = H264Decoder.malloc(H264Decoder.Module, 4);
	var isIdrPic = 0;

	var pNumErrMbs = H264Decoder.malloc(H264Decoder.Module, 4);
	var numErrMbs = 0;

	var pBytes = H264Decoder.h264bsdNextOutputPicture(H264Decoder.Module, H264Decoder.pStorage, pPicId, pIsIdrPic, pNumErrMbs);
	var bytes = null;

	picId = H264Decoder.Module.getValue(pPicId, 'i32');	
	isIdrPic = H264Decoder.Module.getValue(pIsIdrPic, 'i32');	
	numErrMbs = H264Decoder.Module.getValue(pNumErrMbs, 'i32');

	window.console.log('getNextOutputPicture: picId: ' + picId + ', isIdrPic: ' + isIdrPic + ', numErrMbs: ' + numErrMbs + ', length: ' + length);

	bytes = new Uint8Array();
	bytes = H264Decoder.Module.HEAPU8.subarray(pBytes, (pBytes + length));


	if (pPicId != 0){
        H264Decoder.free(pPicId);		
	}
               
    if (pIsIdrPic != 0){
    	H264Decoder.free(pIsIdrPic);
    }
            
    if (pNumErrMbs != 0){
        H264Decoder.free(pNumErrMbs);	
    }

    var croppingInfo = H264Decoder.getCroppingInfo();
    H264Decoder.drawWebGl(bytes, croppingInfo);
    //var result = H264Decoder.convertYUV2RGB(bytes, croppingInfo);
    
    H264Decoder.free(pPicId);		
  	H264Decoder.free(pIsIdrPic);
    H264Decoder.free(pNumErrMbs);	

};

H264Decoder.drawWebGl = function(yuvBytes, croppingInfo){
	if (yuvBytes == null)
		return;

	var width = croppingInfo.width - croppingInfo.left;
	var height = croppingInfo.height - croppingInfo.top;
	H264Decoder.yuvCanvas = new YUVWebGLCanvas(H264Decoder.targetCanvas, new Size(width, height));
	
	var startTime = (new Date);	
	var lumaSize = width * height;
	var chromaSize = lumaSize >> 2;

    H264Decoder.yuvCanvas.YTexture.fill(yuvBytes.subarray(0, lumaSize), true);
    H264Decoder.yuvCanvas.UTexture.fill(yuvBytes.subarray(lumaSize, lumaSize + chromaSize), true);
    H264Decoder.yuvCanvas.VTexture.fill(yuvBytes.subarray(lumaSize + chromaSize, lumaSize + 2 * chromaSize), true);
    H264Decoder.yuvCanvas.drawScene();

    console.log("WebGL YUV decode: " + ((new Date) - startTime));

	// var thisFrame = (self.thisLoop = new Date) - self.lastLoop;
 //  	self.frameTime += (thisFrame - self.frameTime) / self.filterStrength;
 //  	self.lastLoop = self.thisLoop;
 //  	self.stats['fps']  = (1000/self.frameTime).toFixed(1);
 //  	if (self.onImageUpdated)
 //  		self.onImageUpdated(self.stats);		
 //  	self.frameCount = 0;

};

//Excessively pink
H264Decoder.convertYUV2RGB = function(yuvBytes, croppingInfo){
	var width = croppingInfo.width - croppingInfo.left;
	var height = croppingInfo.height - croppingInfo.top;

	var buffer = document.createElement('canvas');
	buffer.height = height;
	buffer.width = width; 
	var context = buffer.getContext('2d');
	var output = context.createImageData(width,height);
	var rgbBytes = output.data;

	var cb = width * height;
	var cr = cb + ((width * height) / 2);	
	var numPixels = width * height;

	var dst = 0;
	var dst_width = 0;

	var k = 0;
	for (var i = 0; i < numPixels; i += 2)
	{
		k += 1;

		var y1 = yuvBytes[i] & 0xff;
		var y2 = yuvBytes[i + 1] & 0xff;
		var y3 = yuvBytes[width + i] & 0xff;
		var y4 = yuvBytes[width + i + 1] & 0xff;
		
		var v = yuvBytes[cr + k] & 0xff;
		var u = yuvBytes[cb + k] & 0xff;
		
		v = v - 128;
		u = u - 128;

		dst = i * 4;
		dst_width = width*4 + dst;

		// i
		rgbBytes[dst] = 0xff;
		rgbBytes[dst + 1] = H264Decoder.clamp((298 * (y1 - 16) + 409 * v + 128) >> 8, 255, 0);
		rgbBytes[dst + 2] = H264Decoder.clamp((298 * (y1 - 16) - 100 * u - 208 *v + 128) >> 8, 255,0);
		rgbBytes[dst + 3] = H264Decoder.clamp((298 * y1 + 516*u + 128) >> 8, 255, 0);
				
		// i + 1
		rgbBytes[dst + 4] = 0xff;
		rgbBytes[dst + 5] = H264Decoder.clamp((298 * (y2 - 16) + 409 * v + 128) >> 8, 255, 0);
		rgbBytes[dst + 6] = H264Decoder.clamp((298 * (y2 - 16) - 100 * u - 208 *v + 128) >> 8, 255,0);
		rgbBytes[dst + 7] = H264Decoder.clamp((298 * y2 + 516*u + 128) >> 8, 255, 0);
		
		//width
		rgbBytes[dst_width] = 0xff;
		rgbBytes[dst_width + 1] = H264Decoder.clamp((298 * (y3 - 16) + 409 * v + 128) >> 8, 255, 0);
		rgbBytes[dst_width + 2] = H264Decoder.clamp((298 * (y2 - 16) - 100 * u - 208 *v + 128) >> 8, 255,0);
		rgbBytes[dst_width + 3] = H264Decoder.clamp((298 * y3 + 516*u + 128) >> 8, 255, 0);
				
		//width + 1
		rgbBytes[dst_width + 4] = 0xff;
		rgbBytes[dst_width + 5] = H264Decoder.clamp((298 * (y4 - 16) + 409 * v + 128) >> 8, 255, 0);
		rgbBytes[dst_width + 6] = H264Decoder.clamp((298 * (y4 - 16) - 100 * u - 208 *v + 128) >> 8, 255,0);
		rgbBytes[dst_width + 7] = H264Decoder.clamp((298 * y4 + 516*u + 128) >> 8, 255, 0);

		if (i != 0 && (i+2)%width ==0) {
			i += width;					
		} 
	}

	var c = document.getElementById('canvas');
	c.height = height;
	c.width = width;
	c.style.height = height;
	c.style.width = width;
	var outputContext = c.getContext('2d'); 
	outputContext.putImageData(output,0,0);
};

H264Decoder.getCroppingInfo = function(){
	var pCroppingFlag = H264Decoder.malloc(H264Decoder.Module, 4);
	var pLeftOffset = H264Decoder.malloc(H264Decoder.Module, 4);
	var pTopOffset = H264Decoder.malloc(H264Decoder.Module, 4);
	var pWidth = H264Decoder.malloc(H264Decoder.Module, 4);
	var pHeight = H264Decoder.malloc(H264Decoder.Module, 4);

	var result = {
		'width': (H264Decoder.h264bsdPicWidth(H264Decoder.Module, H264Decoder.pStorage)*16),
		'height': (H264Decoder.h264bsdPicHeight(H264Decoder.Module, H264Decoder.pStorage)*16),
		'top': 0,
		'left': 0
	};

	H264Decoder.free(pCroppingFlag);
	H264Decoder.free(pLeftOffset);
	H264Decoder.free(pTopOffset);
	H264Decoder.free(pWidth);
	H264Decoder.free(pHeight);

	return result;
};

H264Decoder.getYUVLength = function(){
	var width = H264Decoder.h264bsdPicWidth(H264Decoder.Module, H264Decoder.pStorage);
	var height = H264Decoder.h264bsdPicHeight(H264Decoder.Module, H264Decoder.pStorage);
    return (width * 16 * height * 16) + (2 * width * 16 * height * 8);
};

// u32 h264bsdDecode(storage_t *pStorage, u8 *byteStrm, u32 len, u32 picId, u32 *readBytes);
H264Decoder.h264bsdDecode = function(Module, pStorage, pBytes, len, picId, pBytesRead) {
	return H264Decoder.Module.ccall('h264bsdDecode', Number, 
		[Number, Number, Number, Number, Number], 
		[pStorage, pBytes, len, picId, pBytesRead]);
};

// storage_t* h264bsdAlloc();
H264Decoder.h264bsdAlloc = function(Module) {
	return H264Decoder.Module.ccall('h264bsdAlloc', Number);
};

// void h264bsdFree(storage_t *pStorage);
H264Decoder.h264bsdFree = function(Module, pStorage) {
	H264Decoder.Module.ccall('h264bsdFree', null, [Number], [pStorage]);
};

// u32 h264bsdInit(storage_t *pStorage, u32 noOutputReordering);
H264Decoder.h264bsdInit = function(Module, pStorage, noOutputReordering) {
	return H264Decoder.Module.ccall('h264bsdInit', Number, [Number, Number], [pStorage, noOutputReordering]);
};

//void h264bsdShutdown(storage_t *pStorage);
H264Decoder.h264bsdShutdown = function(Module, pStorage) {
	H264Decoder.Module.ccall('h264bsdShutdown', null, [Number], [pStorage]);
};

// u8* h264bsdNextOutputPicture(storage_t *pStorage, u32 *picId, u32 *isIdrPic, u32 *numErrMbs);
H264Decoder.h264bsdNextOutputPicture = function(Module, pStorage, pPicId, pIsIdrPic, pNumErrMbs) {
	return H264Decoder.Module.ccall('h264bsdNextOutputPicture', 
		Number, 
		[Number, Number, Number, Number], 
		[pStorage, pPicId, pIsIdrPic, pNumErrMbs]);
};

// u32 h264bsdPicWidth(storage_t *pStorage);
H264Decoder.h264bsdPicWidth = function(Module, pStorage) {
	return H264Decoder.Module.ccall('h264bsdPicWidth', Number, [Number], [pStorage]);
};

// u32 h264bsdPicHeight(storage_t *pStorage);
H264Decoder.h264bsdPicHeight = function(Module, pStorage) {
	return H264Decoder.Module.ccall('h264bsdPicHeight', Number, [Number], [pStorage]);
};

// void h264bsdCroppingParams(storage_t *pStorage, u32 *croppingFlag, u32 *left, u32 *width, u32 *top, u32 *height);
H264Decoder.h264bsdCroppingParams = function(Module, pStorage, pCroppingFlag, pLeft, pWidth, pTop, pHeight) {
	return H264Decoder.Module.ccall('h264bsdCroppingParams', 
		Number, 
		[Number, Number, Number, Number, Number, Number, Number], 
		[pStorage, pCroppingFlag, pLeft, pWidth, pTop, pHeight]);
};

// u32 h264bsdCheckValidParamSets(storage_t *pStorage);
H264Decoder.h264bsdCheckValidParamSets = function(Module, pStorage){
	return H264Decoder.Module.ccall('h264bsdCheckValidParamSets', Number, [Number], [pStorage]);
};

// void* malloc(size_t size);
H264Decoder.malloc = function(Module, size){
	return H264Decoder.Module.ccall('malloc', Number, [Number], [size]);
};

// void free(void* ptr);
H264Decoder.free = function(Module, ptr){
	return H264Decoder.Module.ccall('free', null, [Number], [ptr]);
};

// void* memcpy(void* dest, void* src, size_t size);
H264Decoder.memcpy = function(Module, length){
	return H264Decoder.Module.ccall('malloc', Number, [Number, Number, Number], [dest, src, size]);
};