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; }