www.pudn.com > mediator15src.zip > VideoSequenceCompressor.cpp


/* 
 * VideoSequenceCompressor.cpp 
 * Copyright (C) 2002 Arno Hornberger  
 * Original Version Copyright (C) 1998-2001 Avery Lee 
 * 
 * This file is part of MPEG Mediator, a free MPEG stream converter. 
 * 
 * MPEG Mediator is free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation; either version 2 of the License, or 
 * (at your option) any later version. 
 * 
 * MPEG Mediator is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */ 
 
#include "VideoSequenceCompressor.h" 
 
static bool isEqualFOURCC(FOURCC fccA, FOURCC fccB) 
{ 
	int i; 
 
	for (i = 0; i < 4; i++) 
	{ 
		if (tolower((unsigned char)fccA) != tolower((unsigned char)fccB)) 
			return false; 
 
		fccA >>= 8; 
		fccB >>= 8; 
	} 
 
	return true; 
} 
 
////////////////////////////////////////////////////////////////////////////// 
// 
//	IMITATING WIN2K AVISAVEV() BEHAVIOR IN 0x7FFFFFFF EASY STEPS 
// 
//	ICM_COMPRESS_FRAMES_INFO: 
// 
//		dwFlags			Trashed with address of lKeyRate in tests. Something 
//						might be looking for a non-zero value here, so better 
//						set it. 
//		lpbiOutput		NULL. 
//		lOutput			0. 
//		lpbiInput		NULL. 
//		lInput			0. 
//		lStartFrame		0. 
//		lFrameCount		Number of frames. 
//		lQuality		Set to quality factor, or zero if not supported. 
//		lDataRate		Set to data rate in 1024*kilobytes, or zero if not 
//						supported. 
//		lKeyRate		Set to the desired maximum keyframe interval.  For 
//						all keyframes, set to 1.		 
// 
//	ICM_COMPRESS: 
// 
//		dwFlags			Equal to ICCOMPRESS_KEYFRAME if a keyframe is 
//						required, and zero otherwise. 
//		lpckid			Always points to zero. 
//		lpdwFlags		Points to AVIIF_KEYFRAME if a keyframe is required, 
//						and zero otherwise. 
//		lFrameNum		Ascending from zero. 
//		dwFrameSize		Always set to 7FFFFFFF (Win9x) or 00FFFFFF (WinNT) 
//						for first frame.  Set to zero for subsequent frames 
//						if data rate control is not active or not supported, 
//						and to the desired frame size in bytes if it is. 
//		dwQuality		Set to quality factor from 0-10000 if quality is 
//						supported.  Otherwise, it is zero. 
//		lpbiPrev		Set to NULL if not required. 
//		lpPrev			Set to NULL if not required. 
// 
////////////////////////////////////////////////////////////////////////////// 
 
VideoSequenceCompressor::VideoSequenceCompressor(HIC hic, LPBITMAPINFOHEADER lpbih, 
									int rate, int scale, long lQuality, long lKeyRate, long lDataRate) 
{ 
	pPrevBuffer	= pOutputBuffer	= 0; 
	pbiInput = pbiOutput = 0; 
	cbConfigData = 0; 
	pConfigData = 0; 
	fCompressionStarted = false; 
 
	BITMAPINFO bi_in; 
  BITMAPINFO *bi_out; 
	int outBmpSize; 
   
  memcpy(&bi_in.bmiHeader, lpbih, sizeof(BITMAPINFOHEADER)); 
	 
  if (!(outBmpSize = ICCompressGetFormatSize(hic, &bi_in))) 
		throw MyError("VideoSequenceCompressor::VideoSequenceCompressor - ICCompressGetFormatSize() returns 0"); 
 
	if (!(bi_out = (BITMAPINFO *)new BYTE[outBmpSize])) 
		throw MyError("VideoSequenceCompressor::VideoSequenceCompressor - not enough memory"); 
 
  if (ICCompressGetFormat(hic, &bi_in, bi_out) != ICERR_OK) 
	{ 
		delete []bi_out; 
		throw MyError("VideoSequenceCompressor::VideoSequenceCompressor - ICCompressGetFormat() returns an error"); 
	} 
 
  try 
	{ 
    init(hic, &bi_in, bi_out, lQuality, lKeyRate); 
    int nUsPerFrame = MulDiv(1000000, scale, rate); 
		setDataRate(lDataRate, nUsPerFrame, 0x0FFFFFFF); 
    start(); 
  } 
  catch (...) 
  { 
		DeInitVideoCompressor(); 
		delete []bi_out; 
    throw; 
  } 
 
  delete []bi_out; 
} 
 
VideoSequenceCompressor::~VideoSequenceCompressor() 
{ 
	DeInitVideoCompressor(); 
} 
 
BITMAPINFOHEADER *VideoSequenceCompressor::GetOutputFormat() 
{ 
	return (BITMAPINFOHEADER *)pbiOutput; 
} 
 
int VideoSequenceCompressor::GetOutputFormatSize() 
{ 
	return pbiOutput->bmiHeader.biSize + pbiOutput->bmiHeader.biClrUsed * 4; 
} 
 
void VideoSequenceCompressor::init(HIC hic, BITMAPINFO *pbiInput, BITMAPINFO *pbiOutput, long lQ, long lKeyRate) 
{ 
	ICINFO	info; 
	LRESULT	res; 
	int cbSizeIn, cbSizeOut; 
 
	cbSizeIn = pbiInput->bmiHeader.biSize + pbiInput->bmiHeader.biClrUsed * 4; 
	cbSizeOut = pbiOutput->bmiHeader.biSize + pbiOutput->bmiHeader.biClrUsed * 4; 
 
	this->hic	= hic; 
	this->pbiInput = (BITMAPINFO *)new char[cbSizeIn]; 
  this->pbiOutput	= (BITMAPINFO *)new char[cbSizeOut]; 
	this->lKeyRate = lKeyRate; 
 
	memcpy(this->pbiInput, pbiInput, cbSizeIn); 
	memcpy(this->pbiOutput, pbiOutput, cbSizeOut); 
 
	lKeyRateCounter = 1; 
 
	// Retrieve compressor information. 
 
	res = ICGetInfo(hic, &info, sizeof(ICINFO)); 
 
	if (!res) 
		throw MyError("VideoSequenceCompressor::init - unable to retrieve video compressor information"); 
 
	// Analyze compressor. 
 
	this->dwFlags = info.dwFlags; 
 
	if (info.dwFlags & VIDCF_TEMPORAL) { 
		if (!(info.dwFlags & VIDCF_FASTTEMPORALC)) { 
			// Allocate backbuffer 
			if (!(pPrevBuffer = new char[pbiInput->bmiHeader.biSizeImage])) 
				throw MyError("VideoSequenceCompressor::init - memory allocation error"); 
		} 
	} 
 
	if (info.dwFlags & VIDCF_QUALITY) 
		lQuality = lQ; 
	else 
		lQuality = 0; 
 
	// Allocate destination buffer 
 
	lMaxPackedSize = ICCompressGetSize(hic, pbiInput, pbiOutput); 
 
	// Work around a bug in Huffyuv.  Ben tried to save some memory 
	// and specified a "near-worst-case" bound in the codec instead 
	// of the actual worst case bound.  Unfortunately, it's actually 
	// not that hard to exceed the codec's estimate with noisy 
	// captures -- the most common way is accidentally capturing 
	// static from a non-existent channel. 
	// 
	// According to the 2.1.1 comments, Huffyuv uses worst-case 
	// values of 24-bpp for YUY2/UYVY and 40-bpp for RGB, while the 
	// actual worst case values are 43 and 51.  We'll compute the 
	// 43/51 value, and use the higher of the two. 
 
	if (isEqualFOURCC(info.fccHandler, 'UYFH')) { 
		long lRealMaxPackedSize = pbiInput->bmiHeader.biWidth * pbiInput->bmiHeader.biHeight; 
 
		if (pbiInput->bmiHeader.biCompression == BI_RGB) 
			lRealMaxPackedSize = (lRealMaxPackedSize * 51) >> 3; 
		else 
			lRealMaxPackedSize = (lRealMaxPackedSize * 43) >> 3; 
 
		if (lRealMaxPackedSize > lMaxPackedSize) 
			lMaxPackedSize = lRealMaxPackedSize; 
	} 
 
	if (!(pOutputBuffer = new char[lMaxPackedSize])) 
		throw MyError("VideoSequenceCompressor::init - memory allocation error"); 
 
	// Save configuration state. 
	// 
	// Ordinarily, we wouldn't do this, but there seems to be a bug in 
	// the Microsoft MPEG-4 compressor that causes it to reset its 
	// configuration data after a compression session.  This occurs 
	// in all versions from V1 through V3. 
	// 
	// Stupid fscking Matrox driver returns -1!!! 
 
	cbConfigData = ICGetStateSize(hic); 
 
	if (cbConfigData > 0) { 
		if (!(pConfigData = new char[cbConfigData])) 
			throw MyError("VideoSequenceCompressor::init - memory allocation error"); 
 
		cbConfigData = ICGetState(hic, pConfigData, cbConfigData); 
 
		// As odd as this may seem, if this isn't done, then the Indeo5 
		// compressor won't allow data rate control until the next 
		// compression operation! 
 
		if (cbConfigData) 
			ICSetState(hic, pConfigData, cbConfigData); 
	} 
 
	lMaxFrameSize = 0; 
	lSlopSpace = 0; 
} 
 
void VideoSequenceCompressor::setDataRate(long lDataRate, long lUsPerFrame, long lFrameCount) 
{ 
	if (lDataRate && (dwFlags & VIDCF_CRUNCH)) 
		lMaxFrameSize = MulDiv(lDataRate, lUsPerFrame, 1000000); 
	else 
		lMaxFrameSize = 0; 
 
	// Indeo 5 needs this message for data rate clamping. 
 
	// The Morgan codec requires the message otherwise it assumes 100% 
	// quality :( 
 
	// The original version (2700) MPEG-4 V1 requires this message, period. 
	// V3 (DivX) gives crap if we don't send it.  So special case it. 
 
	ICINFO ici; 
 
	ICGetInfo(hic, &ici, sizeof ici); 
 
	{ 
		ICCOMPRESSFRAMES icf; 
 
		memset(&icf, 0, sizeof icf); 
 
		icf.dwFlags		= (DWORD)&icf.lKeyRate; 
		icf.lStartFrame = 0; 
		icf.lFrameCount = lFrameCount; 
		icf.lQuality	= lQuality; 
		icf.lDataRate	= lDataRate; 
		icf.lKeyRate	= lKeyRate; 
		icf.dwRate		= 1000000; 
		icf.dwScale		= lUsPerFrame; 
 
		ICSendMessage(hic, ICM_COMPRESS_FRAMES_INFO, (WPARAM)&icf, sizeof(ICCOMPRESSFRAMES)); 
	} 
} 
 
void VideoSequenceCompressor::start() 
{ 
	// start compression process 
 
	if (ICCompressBegin(hic, pbiInput, pbiOutput) != ICERR_OK) 
		throw MyError("VideoSequenceCompressor::start - unable to start video compression"); 
 
	// start decompression process if necessary 
 
	if (pPrevBuffer) 
		if (ICDecompressBegin(hic, pbiOutput, pbiInput) != ICERR_OK) 
		{ 
			ICCompressEnd(hic); 
			throw MyError("VideoSequenceCompressor::start - unable to start video decompression"); 
		} 
 
	fCompressionStarted = true; 
	lFrameNum = 0; 
} 
 
/* 
void VideoSequenceCompressor::dropFrame() 
{ 
	if (lKeyRate && lKeyRateCounter > 1) 
		--lKeyRateCounter; 
 
	++lFrameNum; 
} 
*/ 
 
void *VideoSequenceCompressor::packFrame(void *pBits, bool *pfKeyframe, long *plSize) 
{ 
	DWORD dwChunkId = 0; 
	DWORD dwFlags=0, dwFlagsIn = ICCOMPRESS_KEYFRAME; 
	DWORD res; 
	DWORD sizeImage; 
	long lAllowableFrameSize = 0;//xFFFFFF;	// yes, this is illegal according 
											// to the docs (see below) 
 
	// Figure out if we should force a keyframe.  If we don't have any 
	// keyframe interval, force only the first frame.  Otherwise, make 
	// sure that the key interval is lKeyRate or less.  We count from 
	// the last emitted keyframe, since the compressor can opt to 
	// make keyframes on its own. 
 
	if (!lKeyRate) { 
		if (lFrameNum) 
			dwFlagsIn = 0; 
	} 
	else { 
		if (--lKeyRateCounter) 
			dwFlagsIn = 0; 
		else 
			lKeyRateCounter = lKeyRate; 
	} 
 
	// Figure out how much space to give the compressor, if we are using 
	// data rate stricting.  If the compressor takes up less than quota 
	// on a frame, save the space for later frames.  If the compressor 
	// uses too much, reduce the quota for successive frames, but do not 
	// reduce below half datarate. 
 
	if (lMaxFrameSize) { 
		lAllowableFrameSize = lMaxFrameSize + (lSlopSpace >> 2); 
 
		if (lAllowableFrameSize < (lMaxFrameSize >> 1)) 
			lAllowableFrameSize = lMaxFrameSize >> 1; 
	} 
 
	// A couple of notes: 
	// 
	//	o  ICSeqCompressFrame() passes 0x7FFFFFFF when data rate control 
	//	   is inactive.  Docs say 0.  We pass 0x7FFFFFFF here to avoid 
	//	   a bug in the Indeo 5 QC driver, which page faults if 
	//	   keyframe interval=0 and max frame size = 0. 
 
	sizeImage = pbiOutput->bmiHeader.biSizeImage; 
 
//	pbiOutput->bmiHeader.biSizeImage = 0; 
 
	// Compress! 
 
	if (dwFlagsIn) 
		dwFlags = AVIIF_KEYFRAME; 
 
	res = ICCompress(hic, dwFlagsIn, 
			(LPBITMAPINFOHEADER)pbiOutput, pOutputBuffer, 
			(LPBITMAPINFOHEADER)pbiInput, pBits, 
			&dwChunkId, 
			&dwFlags, 
			lFrameNum, 
			lFrameNum ? lAllowableFrameSize : 0xFFFFFF, 
			lQuality, 
			dwFlagsIn & ICCOMPRESS_KEYFRAME ? NULL : (LPBITMAPINFOHEADER)pbiInput, 
			dwFlagsIn & ICCOMPRESS_KEYFRAME ? NULL : pPrevBuffer); 
 
	_RPT2(0,"Compressed frame %d: %d bytes\n", lFrameNum, pbiOutput->bmiHeader.biSizeImage); 
 
	++lFrameNum; 
 
	*plSize = pbiOutput->bmiHeader.biSizeImage; 
 
	// If we're using a compressor with a stupid algorithm (Microsoft Video 1), 
	// we have to decompress the frame again to compress the next one.... 
 
	if (res == ICERR_OK && pPrevBuffer && (!lKeyRate || lKeyRateCounter > 1)) { 
		res = ICDecompress(hic, dwFlags & AVIIF_KEYFRAME ? 0 : ICDECOMPRESS_NOTKEYFRAME 
				,(LPBITMAPINFOHEADER)pbiOutput 
				,pOutputBuffer 
				,(LPBITMAPINFOHEADER)pbiInput 
				,pPrevBuffer); 
	} 
 
	pbiOutput->bmiHeader.biSizeImage = sizeImage; 
 
	if (res != ICERR_OK) 
		throw MyError("VideoSequenceCompressor::packFrame - error compressing/decompressing frame"); 
 
	// Update quota. 
 
	if (lMaxFrameSize) { 
		lSlopSpace += lMaxFrameSize - *plSize; 
 
		_RPT3(0,"Compression: allowed %d, actual %d, slop %+d\n", lAllowableFrameSize, *plSize, lSlopSpace); 
	} 
 
	// Was it a keyframe? 
 
	if (dwFlags & AVIIF_KEYFRAME) 
	{ 
		*pfKeyframe = true; 
		lKeyRateCounter = lKeyRate; 
	} 
	else 
		*pfKeyframe = false; 
 
	return pOutputBuffer; 
} 
 
bool VideoSequenceCompressor::DeInitVideoCompressor() 
{ 
	if (fCompressionStarted) 
	{ 
		if (pPrevBuffer) 
			ICDecompressEnd(hic); 
 
		ICCompressEnd(hic); 
	} 
	// Reset MPEG-4 compressor 
 
	if (cbConfigData && pConfigData) 
		ICSetState(hic, pConfigData, cbConfigData); 
 
	delete []pConfigData; 
	delete []pbiInput; 
	delete []pbiOutput; 
	delete []pOutputBuffer; 
	delete []pPrevBuffer; 
 
	fCompressionStarted = false; 
 
  return true; 
}