/****************************************************************************
 *
 *  AVICMPRS.C
 *
 *  routine for compressing AVI files...
 *
 *      AVISave()
 *
 *  Copyright (c) 1992 Microsoft Corporation.  All Rights Reserved.
 *
 *  You have a royalty-free right to use, modify, reproduce and
 *  distribute the Sample Files (and/or any modified version) in
 *  any way you find useful, provided that you agree that
 *  Microsoft has no warranty obligations or liability for any
 *  Sample Application Files which are modified.
 *
 ***************************************************************************/


//
// What this file does:
//
// Given an AVI Stream (that is, essentially, a function that it can call
// to get video frames), this presents the same sort of interface and allows
// other people to call it to get compressed frames.
//

#include <win32.h>
#include <compobj.h>
#include <compman.h>
#include <avifmt.h>
#include "avifile.h"
#include "avifilei.h"
#include "avicmprs.h"
#include "debug.h"

#define ALIGNULONG(i)     ((i+3)&(~3))                  /* ULONG aligned ! */
#define WIDTHBYTES(i)     ((unsigned)((i+31)&(~31))/8)  /* ULONG aligned ! */
#define DIBWIDTHBYTES(bi) (int)WIDTHBYTES((int)(bi).biWidth * (int)(bi).biBitCount)
#define DIBPTR(lpbi) ((LPBYTE)(lpbi) + \
            (int)(lpbi)->biSize + \
            (int)(lpbi)->biClrUsed * sizeof(RGBQUAD) )

void CAVICmpStream::ResetInst(void)
{
    lFrameCurrent = -1;
    lLastKeyFrame = 0;
    dwQualityLast = ICQUALITY_HIGH;
    dwSaved = 0;
}

/*	-	-	-	-	-	-	-	-	*/

HRESULT CAVICmpStream::Create(
	IUnknown FAR*	pUnknownOuter,
	const IID FAR&	riid,
	void FAR* FAR*	ppv)
{
	IUnknown FAR*	pUnknown;
	CAVICmpStream FAR*	pAVIStream;
	HRESULT	hresult;

	pAVIStream = new FAR CAVICmpStream(pUnknownOuter, &pUnknown);
	if (!pAVIStream)
		return ResultFromScode(E_OUTOFMEMORY);
	hresult = pUnknown->QueryInterface(riid, ppv);
	if (FAILED(GetScode(hresult)))
		delete pAVIStream;
	return hresult;
}

/*	-	-	-	-	-	-	-	-	*/

CAVICmpStream::CAVICmpStream(
	IUnknown FAR*	pUnknownOuter,
	IUnknown FAR* FAR*	ppUnknown) :
	m_Unknown(this),
	m_AVIStream(this)
{
	// !!! clear extra junk!
	pavi = 0;
	pgf = 0;
	hic = 0;
	lpbiC = 0;
	lpbiU = 0;
	lpFormat = 0;
	cbFormat = 0;
	lpFormatOrig = 0;
	cbFormatOrig = 0;
	lpHandler = 0;
	cbHandler = 0;
	
	if (pUnknownOuter)
		m_pUnknownOuter = pUnknownOuter;
	else
		m_pUnknownOuter = &m_Unknown;
	*ppUnknown = &m_Unknown;
}

/*	-	-	-	-	-	-	-	-	*/

CAVICmpStream::CUnknownImpl::CUnknownImpl(
	CAVICmpStream FAR*	pAVIStream)
{
	m_pAVIStream = pAVIStream;
	m_refs = 0;
}

/*	-	-	-	-	-	-	-	-	*/

STDMETHODIMP CAVICmpStream::CUnknownImpl::QueryInterface(
	const IID FAR&	iid,
	void FAR* FAR*	ppv)
{
	if (iid == IID_IUnknown)
		*ppv = &m_pAVIStream->m_Unknown;
	else if (iid == IID_IAVIStream)
		*ppv = &m_pAVIStream->m_AVIStream;
	else
		return ResultFromScode(E_NOINTERFACE);
	AddRef();
	return AVIERR_OK;
}

/*	-	-	-	-	-	-	-	-	*/

STDMETHODIMP_(ULONG) CAVICmpStream::CUnknownImpl::AddRef()
{
	uUseCount++;
	return ++m_refs;
}

/*	-	-	-	-	-	-	-	-	*/

CAVICmpStream::CAVICmpStreamImpl::CAVICmpStreamImpl(
	CAVICmpStream FAR*	pAVIStream)
{
	m_pAVIStream = pAVIStream;
}

/*	-	-	-	-	-	-	-	-	*/

CAVICmpStream::CAVICmpStreamImpl::~CAVICmpStreamImpl()
{
}

/*	-	-	-	-	-	-	-	-	*/

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::QueryInterface(
	const IID FAR&	iid,
	void FAR* FAR*	ppv)
{
	return m_pAVIStream->m_pUnknownOuter->QueryInterface(iid, ppv);
}

/*	-	-	-	-	-	-	-	-	*/

STDMETHODIMP_(ULONG) CAVICmpStream::CAVICmpStreamImpl::AddRef()
{
	return m_pAVIStream->m_pUnknownOuter->AddRef();
}

/*	-	-	-	-	-	-	-	-	*/

STDMETHODIMP_(ULONG) CAVICmpStream::CAVICmpStreamImpl::Release()
{
	return m_pAVIStream->m_pUnknownOuter->Release();
}

/*	-	-	-	-	-	-	-	-	*/

HRESULT CAVICmpStream::SetUpCompression()
{
    LONG		lRet = AVIERR_OK;
    LPBITMAPINFOHEADER  lpbi;
    CAVICmpStream FAR * pinst = this;	// for convenience....
    DWORD		dw;
    
    pinst->pgf = AVIStreamGetFrameOpen(pinst->pavi, NULL);
    if (!pinst->pgf) {
	// !!! we couldn't decompress the stream!
	lRet = AVIERR_INTERNAL;
	goto exit;
    }

    if (pinst->avistream.fccHandler == comptypeDIB)
	goto exit;
    
    lpbi = (LPBITMAPINFOHEADER) AVIStreamGetFrame(pinst->pgf, 0);

    if (lpbi == NULL) {
	lRet = AVIERR_INTERNAL;
	goto exit;
    }
    
    /*
    ** get the size requied to hold the format.
    */
    dw = ICCompressGetFormatSize(pinst->hic, lpbi);
    if ((LONG) dw < sizeof(BITMAPINFOHEADER))
	goto ic_error;

    pinst->cbFormat = dw;
    pinst->lpFormat = (LPBITMAPINFOHEADER) GlobalAllocPtr(GHND | GMEM_SHARE, pinst->cbFormat);
    if (!pinst->lpFormat) {
	lRet = AVIERR_MEMORY;
	goto exit;
    }

    /*
    ** get the compressed format from the compressor.
    */
    dw = ICCompressGetFormat(pinst->hic, lpbi, pinst->lpFormat);
    if ((LONG) dw < 0)
	goto ic_error;
    
    pinst->avistream.rcFrame.right = pinst->avistream.rcFrame.left +
					  (int) pinst->lpFormat->biWidth;
    pinst->avistream.rcFrame.bottom = pinst->avistream.rcFrame.top +
					  (int) pinst->lpFormat->biHeight;
    
    dw = ICCompressBegin(pinst->hic, lpbi, pinst->lpFormat);
    
    if (dw != ICERR_OK)
	goto ic_error;

    /*
    ** allocate buffer to hold compressed data.
    */
    dw = ICCompressGetSize(pinst->hic, lpbi, pinst->lpFormat);

    pinst->lpbiC = (LPBITMAPINFOHEADER)
	GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, pinst->cbFormat + dw);
    
    if (!pinst->lpbiC) {
	lRet = AVIERR_MEMORY;
	goto exit;
    }

    hmemcpy((LPVOID)pinst->lpbiC, pinst->lpFormat, pinst->cbFormat);

    pinst->lpC = (LPSTR) pinst->lpbiC + pinst->lpbiC->biSize +
				pinst->lpbiC->biClrUsed * sizeof(RGBQUAD);
	
    //
    //  check for temporal compress, and alocate a previous
    //  DIB buffer if needed
    //
    if (pinst->dwKeyFrameEvery != 1) {
	pinst->lpbiU = (LPBITMAPINFOHEADER)
	    GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
		    sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD));

	if (!pinst->lpbiU) {
	    lRet = AVIERR_MEMORY;
	    goto exit;
	}

	dw = ICDecompressGetFormat(pinst->hic, pinst->lpFormat, pinst->lpbiU);

	if ((LONG) dw < 0)
	    goto ic_error;

	if (pinst->lpbiU->biSizeImage == 0)
	    pinst->lpbiU->biSizeImage = pinst->lpbiU->biHeight *
					    DIBWIDTHBYTES(*pinst->lpbiU);

	pinst->lpbiU = (LPBITMAPINFOHEADER)
	    GlobalReAllocPtr(pinst->lpbiU,
		pinst->lpbiU->biSize +
			pinst->lpbiU->biClrUsed * sizeof(RGBQUAD) +
			pinst->lpbiU->biSizeImage,
		GMEM_MOVEABLE | GMEM_SHARE);

	if (!pinst->lpbiU) {
	    lRet = AVIERR_MEMORY;
	    goto exit;
	}

	pinst->lpU = (LPSTR) pinst->lpbiU + pinst->lpbiU->biSize +
				pinst->lpbiU->biClrUsed * sizeof(RGBQUAD);
	
	dw = ICDecompressBegin(pinst->hic, pinst->lpFormat, pinst->lpbiU);

	if (dw != ICERR_OK)
	    goto ic_error;
    }

    // !!! We really should check if the new stream has palette changes....
    
exit:
    if (lRet != AVIERR_OK)
	// Clean up before returning...
	;
    
    return ResultFromScode(lRet);
    
ic_error:
    if (dw == ICERR_BADFORMAT)
	lRet = AVIERR_BADFORMAT;
    else if (dw == ICERR_MEMORY)
	lRet = AVIERR_MEMORY;
    else
	lRet = AVIERR_INTERNAL;
    goto exit;
}

/*	-	-	-	-	-	-	-	-	*/

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::Create(LONG lParam1, LONG lParam2)
{
    CAVICmpStream FAR * pinst = m_pAVIStream;
    ICINFO	    icinfo;
    AVICOMPRESSOPTIONS FAR *lpOpt = (AVICOMPRESSOPTIONS FAR *)lParam2;
    LONG	    lRet = AVIERR_OK;

    // The AVI Stream that we're compressing is passsed in in the <szFile>
    // parameter.
    pinst->pavi = (PAVISTREAM)lParam1;

    // Make sure the uncompressed stream doesn't go away without our
    // knowledge....
    AVIStreamAddRef(pinst->pavi);
    // !!! how can we check if pinst->pavi is valid?
    
    // Get the stream header for future reference....    
    AVIStreamInfo(pinst->pavi, &pinst->avistream, sizeof(pinst->avistream));

    pinst->ResetInst();

    if (!lpOpt || (lpOpt->fccHandler == comptypeDIB)) {
	pinst->avistream.fccHandler = comptypeDIB;
	lRet = AVIERR_OK;
	goto exit;
    }
    
    pinst->avistream.fccHandler = lpOpt->fccHandler;

    // Open the compressor they asked for in the options structure...
    pinst->hic = ICOpen(ICTYPE_VIDEO, lpOpt->fccHandler, ICMODE_COMPRESS);
    
    if (!pinst->hic) {
	lRet = AVIERR_NOCOMPRESSOR;
	goto exit;
    }
    
    if (lpOpt->cbParms) {
	ICSetState(pinst->hic, lpOpt->lpParms, lpOpt->cbParms);
    }

    pinst->avistream.dwQuality = lpOpt->dwQuality;

    if (pinst->avistream.dwQuality == ICQUALITY_DEFAULT) {
	pinst->avistream.dwQuality = ICGetDefaultQuality(pinst->hic);
    }

    /*
    **  get info about this compressor
    */
    ICGetInfo(pinst->hic,&icinfo,sizeof(icinfo));

    pinst->dwICFlags = icinfo.dwFlags;

    if (lpOpt->dwFlags & AVICOMPRESSF_KEYFRAMES)
        pinst->dwKeyFrameEvery = lpOpt->dwKeyFrameEvery;
    else
        pinst->dwKeyFrameEvery = 1;

    if (!(icinfo.dwFlags & VIDCF_TEMPORAL))
	pinst->dwKeyFrameEvery = 1;     // compressor doesn't do temporal

    
    if (lpOpt->dwFlags & AVICOMPRESSF_DATARATE)
	pinst->dwMaxSize = muldiv32(lpOpt->dwBytesPerSecond,
				pinst->avistream.dwScale,
				pinst->avistream.dwRate);
    else
	pinst->dwMaxSize = 0;


    {
	ICCOMPRESSFRAMES    iccf;
	DWORD		    dw;


	iccf.lpbiOutput = pinst->lpbiC;
	iccf.lOutput = 0;

	iccf.lpbiInput = pinst->lpbiU;
	iccf.lInput = 0;

	iccf.lStartFrame = 0;
	iccf.lFrameCount = (LONG) pinst->avistream.dwLength;

	iccf.lQuality = (LONG) pinst->avistream.dwQuality;
	iccf.lDataRate = (LONG) lpOpt->dwBytesPerSecond;

	iccf.lKeyRate = (LONG) pinst->dwKeyFrameEvery;

	iccf.dwRate = pinst->avistream.dwRate;
	iccf.dwScale = pinst->avistream.dwScale;

	iccf.dwOverheadPerFrame = 0;
	iccf.dwReserved2 = 0;
	iccf.GetData = NULL;
	iccf.PutData = NULL;

	dw = ICSendMessage(pinst->hic,
		      ICM_COMPRESS_FRAMES_INFO,
		      (DWORD) (LPVOID) &iccf,
		      sizeof(iccf));

	// If they support this message, don't give
	// warning for data rate!
	if (dw == ICERR_OK) {
	    DPF("Compressor supports COMPRESSFRAMESINFO\n");
	    // !!! fDataRateChanged = TRUE;
	}

#ifdef STATUSCALLBACKS
	ICSetStatusProc(pinst->hic,
			0,
			pinst,
			CompressStatusProc);
#endif
    }


exit:
    if (lRet != AVIERR_OK)
	// Clean up before returning...
	;
    
    return ResultFromScode(lRet);
}

STDMETHODIMP_(ULONG) CAVICmpStream::CUnknownImpl::Release()
{
    CAVICmpStream FAR * pinst = m_pAVIStream;
    
    uUseCount--;
    if (!--m_refs) {
	if (pinst->hic) {
	    ICCompressEnd(pinst->hic);

	    if (pinst->dwKeyFrameEvery != 1 && pinst->lpbiU)
		ICDecompressEnd(pinst->hic);

	    if (pinst->lpbiU)
		GlobalFreePtr((LPVOID) pinst->lpbiU);

	    if (pinst->lpbiC)
		GlobalFreePtr((LPVOID) pinst->lpbiC);

	    ICClose(pinst->hic);
	}

	if (pinst->pgf) {
	    AVIStreamGetFrameClose(pinst->pgf);
	    pinst->pgf = 0;
	}

	if (pinst->pavi) {
	    // Release our hold on the uncompressed stream....
	    AVIStreamClose(pinst->pavi);
	}

	if (pinst->lpFormat)
	    GlobalFreePtr(pinst->lpFormat);

	if (pinst->lpFormatOrig)
	    GlobalFreePtr(pinst->lpFormatOrig);

	delete pinst;
	return 0;
    }

    return m_refs;
}


STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::Info(AVISTREAMINFO FAR * psi, LONG lSize)
{
    CAVICmpStream FAR * pinst = m_pAVIStream;
    
    hmemcpy(psi, &pinst->avistream, min(lSize, sizeof(pinst->avistream)));
    
//    return sizeof(pinst->avistream);
    return ResultFromScode(0);
}

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::ReadFormat(LONG lPos, LPVOID lpFormat, LONG FAR *lpcbFormat)
{
    CAVICmpStream FAR * pinst = m_pAVIStream;

    LPBITMAPINFOHEADER	lpbi;

    if (!pinst->pgf) {
	HRESULT	    hr;
	hr = pinst->SetUpCompression();

	if (hr != NOERROR)
	    return hr;
    }
    
    lpbi = (LPBITMAPINFOHEADER) AVIStreamGetFrame(pinst->pgf, lPos);

    if (!lpbi)
	return ResultFromScode(AVIERR_MEMORY);
    
    if (pinst->hic == 0) {
	pinst->cbFormat = lpbi->biSize + lpbi->biClrUsed * sizeof(RGBQUAD);

	if (lpFormat)
	    hmemcpy(lpFormat, lpbi, min(*lpcbFormat, (LONG) pinst->cbFormat));
    } else {
	if (lpFormat) {
	    hmemcpy(lpFormat, pinst->lpFormat, min(*lpcbFormat, (LONG) pinst->cbFormat));

	    if (pinst->lpFormat->biClrUsed > 0) {
		// Make sure we have the right colors!
		// !!! This is bad--We may need to restart the compressor...
		hmemcpy((LPBYTE) lpFormat + pinst->lpFormat->biSize,
			(LPBYTE) lpbi + lpbi->biSize,
			pinst->lpFormat->biClrUsed * sizeof(RGBQUAD));
	    }
	}
    }
    
    *lpcbFormat = pinst->cbFormat;
    return AVIERR_OK;
}

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::Read(
                      LONG       lStart,
                      LONG       lSamples,
                      LPVOID     lpBuffer,
                      LONG       cbBuffer,
                      LONG FAR * plBytes,
                      LONG FAR * plSamples)
{
    CAVICmpStream FAR * pinst = m_pAVIStream;
    LPBITMAPINFOHEADER	lpbi;
    LONG		lRet;

    if (!pinst->pgf) {
	HRESULT	    hr;
	hr = pinst->SetUpCompression();

	if (hr != NOERROR)
	    return hr;
    }
    
    if (pinst->hic == 0) {
	lpbi = (LPBITMAPINFOHEADER) AVIStreamGetFrame(pinst->pgf, lStart);

	if (!lpbi)
	    return ResultFromScode(AVIERR_MEMORY);

	if (plBytes)
	    *plBytes = lpbi->biSizeImage;

	if ((LONG) lpbi->biSizeImage > cbBuffer)
	    return ResultFromScode(AVIERR_BUFFERTOOSMALL);
	
	if (lpBuffer)
	    hmemcpy(lpBuffer, DIBPTR(lpbi), min((DWORD) cbBuffer, lpbi->biSizeImage));

	if (plSamples)
	    *plSamples = 1;

	return AVIERR_OK;
    }
    
    if (lStart < pinst->lFrameCurrent)
	pinst->ResetInst();

    while (pinst->lFrameCurrent < lStart) {
	++pinst->lFrameCurrent;
    
	lpbi = (LPBITMAPINFOHEADER) AVIStreamGetFrame(pinst->pgf, pinst->lFrameCurrent);

	if (lpbi == NULL) {
	    pinst->ResetInst();	// Make sure we don't assume anything
	    return ResultFromScode(AVIERR_INTERNAL);
	}
	
	// !!! Check if format has changed!

	lRet = pinst->ICCrunch(lpbi, DIBPTR(lpbi));
	if (lRet != AVIERR_OK) {
	    pinst->ResetInst();	// Make sure we don't assume anything
	    return ResultFromScode(AVIERR_INTERNAL);    // !!! error < 0.
	}
    }

    if (plBytes)
	*plBytes = pinst->lpbiC->biSizeImage;

    if ((LONG) pinst->lpbiC->biSizeImage > cbBuffer)
	return ResultFromScode(AVIERR_BUFFERTOOSMALL);
	
    if (lpBuffer)
	hmemcpy(lpBuffer, pinst->lpC,
		min((DWORD) cbBuffer, pinst->lpbiC->biSizeImage));

    if (plSamples)
	*plSamples = 1;
    
    return AVIERR_OK;
}

STDMETHODIMP_(LONG) CAVICmpStream::CAVICmpStreamImpl::FindSample(LONG lPos, LONG lFlags)
{
    CAVICmpStream FAR * pinst = m_pAVIStream;
    if (lFlags & FIND_KEY) {
	if (pinst->hic == 0)
	    return lPos;
	    
	if (lFlags & FIND_PREV) {
	    /* If the frame they're asking about isn't the one we have,
	    ** we have to go actually do the work and find out.
	    */
	    if (lPos < pinst->lLastKeyFrame || lPos > pinst->lFrameCurrent)
		Read(lPos, 1, NULL, 0, NULL, NULL);

	    return pinst->lLastKeyFrame;
	} else {
	    return -1; // !!! Find Next KeyFrame
	}
    }
    if (lFlags & FIND_ANY) {
	return lPos;
    }
    if (lFlags & FIND_FORMAT) {
	// !!! This is wrong in the case where we're compressing something
	// with a palette change and the compressor preserves it....
	if (lFlags & FIND_PREV)
	    return 0;
	else
	    return -1;
    }

    return -1;

}

/////////////////////////////////////////////////////////////////////////////
//
//  ICCrunch()
//
//  crunch a frame and make it fit into the specifed size, by varing the
//  quality.  the suplied quality is the upper bound.
//
//  if the compressor can crunch, then let it crunch
//
//  if the compressor does quality, then vary the quality
//
//  if the compressor does not do quality, then the caller gets what
//  ever it will do.
//
//
//  The frame to be compressed is passed in in lpbi.
//
//  The compressed frame can be found in the lpC member variable....
//
/////////////////////////////////////////////////////////////////////////////

LONG CAVICmpStream::ICCrunch(LPBITMAPINFOHEADER lpbi, LPVOID lp)
{
    DWORD   dw;
    DWORD   dwFlags;
    DWORD   dwSize;
    DWORD   ckid;
    DWORD   dwQuality = avistream.dwQuality;
    DWORD   dwQualityMin;
    DWORD   dwQualityMax;
    DWORD   dwMaxSizeThisFrame;
    DWORD   dwSizeMin;
    DWORD   dwSizeMax;
    BOOL    fKeyFrame=FALSE;
    BOOL    fCrunch;            /* are we crunching? */
    BOOL    fFirst=TRUE;

    dwMaxSizeThisFrame = dwMaxSize;
    
    if (lFrameCurrent == 0 || (dwKeyFrameEvery != 0 &&
	    lFrameCurrent - lLastKeyFrame >= (long)dwKeyFrameEvery)) {
        fKeyFrame = TRUE;
    }

    //
    //  give the key frames more space, and take some away from the
    //  non key frames.
    //
    //  give the key frame two shares, assuming we have more frames to
    //  go around.
    //
    if (dwKeyFrameEvery > 0) {
	if (lFrameCurrent == 0) {
	    dwMaxSizeThisFrame = 0xffffff;
	} else if (fKeyFrame) {
            dwMaxSizeThisFrame = dwMaxSizeThisFrame + dwSaved;
	    dwSaved = 0;
        } else {
	    DWORD	dwTakeAway;

	    dwTakeAway = dwMaxSizeThisFrame / dwKeyFrameEvery;
	    if (dwSaved > dwMaxSizeThisFrame)
		dwTakeAway = 0;
	    
	    /* If we're padding, take away a multiple of 2K. */
	    if (fPad) {
		if (dwMaxSizeThisFrame > dwTakeAway + 2048)
		    dwTakeAway += 2047;
		dwTakeAway -= dwTakeAway % 2048;
	    }
            dwMaxSizeThisFrame -= dwTakeAway; 
	    dwSaved += dwTakeAway;

	    if (!fPad) {
		/* Try to give a little extra space to each frame */
		dwMaxSizeThisFrame += dwSaved / dwKeyFrameEvery;
		dwSaved -= dwSaved / dwKeyFrameEvery;
	    }
        }
    } else {
        // the only key frame is frame zero
        if (lFrameCurrent == 0)
            dwMaxSizeThisFrame = 0xffffff;
	else {
	    /* Give each frame whatever extra there is.... */
	    dwMaxSizeThisFrame += dwSaved;
	    dwSaved = 0;
	}
    }

    //
    //  if the device supports crunching or does not do quality we dont
    //  crunch.
    //
    fCrunch = dwMaxSizeThisFrame > 0 && !(dwICFlags & VIDCF_CRUNCH) &&
         (dwICFlags & VIDCF_QUALITY);

////if (lFrameCurrent > 0 && fCrunch)
////    dwQuality = dwQualityLast;

    DPF("ICCrunch: Frame %ld, Quality = %ld, MaxSize = %ld\n", lFrameCurrent, avistream.dwQuality, dwMaxSizeThisFrame);

    dwQualityMin = 0;
    dwQualityMax = dwQuality;

    dwSizeMin = 0;
    dwSizeMax = dwMaxSizeThisFrame;

    for (;;) {
        ckid = 0L;
        dwFlags = fKeyFrame ? AVIIF_KEYFRAME : 0;

        //
        //  compress the frame
        //
        dw = ICCompress(hic,
                0,              // flags
                lpbiC,          // ouput format
		lpC,            // output data
                lpbi,           // format of frame to compress
                lp,	        // frame data to compress
                &ckid,          // ckid for data in AVI file
                &dwFlags,       // flags in the AVI index.
                lFrameCurrent,  // frame number of seq.
                dwMaxSizeThisFrame,	// reqested size in bytes. (if non zero)
                dwQuality,	// quality value
                fKeyFrame ? NULL : lpbiU,
                fKeyFrame ? NULL : lpU);

        if (dw != ICERR_OK)
            break;

        dwSize = lpbiC->biSizeImage;

	DPF("                     Quality = %ld, Size = %ld, %c\n", dwQuality, dwSize, (dwFlags & AVIIF_KEYFRAME) ? 'K' : ' ');

        //
        // if the device can't crunch (does not do it it self, or does not do
        // quality) then we are done.
        //
        if (!fCrunch)
            break;

        //
        //  we are crunching, see if the frame fit.
        //
        if (dwSize <= dwMaxSizeThisFrame) {
            dwQualityMin = dwQuality;
            dwSizeMin = dwSize;

            //
            //  when the quality gets too close bail out.
            //
            if (dwQualityMax - dwQualityMin <= 10)
                break;

            //
            //  if we get within 512 bytes it is good enough
            //
            if ((LONG) (dwMaxSizeThisFrame - dwSize) <= (LONG) min(512L, dwMaxSizeThisFrame / 8L))
                break;

            //
            // if the first try, (with the user specifed quality) made it
            // then use it.  otherwise we need to search.
            //
            if (fFirst)
                break;
        }
        else {
	    //
	    //  when the quality gets too close bail out.
	    //
	    if (dwQualityMax - dwQualityMin <= 1)
		break;

            dwQualityMax = dwQuality;
            dwSizeMax = dwSize;
        }

        if (fFirst && dwQuality != dwQualityLast)
            dwQuality = dwQualityLast;
        else
            dwQuality = (dwQualityMin + dwQualityMax) / 2;

#if 0
            //
            // make a guess based on how close we are now.
            //
            dwQuality = dwQualityMin + muldiv32(dwQualityMax-dwQualityMin,
                        dwMaxSizeThisFrame-dwSizeMin,dwSizeMax-dwSizeMin);
#endif
        fFirst = FALSE;
    }

#if 0
    /* If this wasn't the first frame, save up any extra space for later */
    if (dwSize < dwMaxSizeThisFrame && lFrameCurrent > 0) {
	dwSaved += dwMaxSizeThisFrame - dwSize;
	if (fPad) {
	    dwSaved -= ((dwMaxSizeThisFrame - dwSize) % 2048L);
	}

	// HACK: limit this, so it doesn't get too big!!!
	if (dwSaved > 32768L)
	    dwSaved = 32768L;
	if (dwSaved > dwMaxSizeThisFrame * 5)
	    dwSaved = dwMaxSizeThisFrame * 5;
    }
#endif

    if (dw != ICERR_OK) {
        if (dw == ICERR_BADFORMAT)
	    return AVIERR_BADFORMAT;
        else
	    return AVIERR_INTERNAL;
    }

    if (dwFlags & AVIIF_KEYFRAME) {
        lLastKeyFrame = lFrameCurrent;
    }

    //
    // remember the quality that worked, it will be the best guess next time.
    //
    dwQualityLast = dwQuality;

    //
    //  decompress the image into the offscreen buffer, for use next time.
    //
    if (dwKeyFrameEvery != 1 && lpbiU) {
        dw = ICDecompress(hic, 0,
            lpbiC,lpC,
            lpbiU,lpU);

	// !!! error check?
    }

    //
    // return the dwFlags and ckid, by stuffing them in the stream info.
    //
    m_ckid = ckid;
    m_dwFlags = dwFlags;

    return AVIERR_OK;
}

/**************************************************************************
* @doc  INTERNAL DRAWDIB
*
* @api BOOL | DibEq | This function compares two dibs.
*
* @parm LPBITMAPINFOHEADER lpbi1 | Pointer to one bitmap.
*       this DIB is assumed to have the colors after the BITMAPINFOHEADER
*
* @parm LPBITMAPINFOHEADER | lpbi2 | Pointer to second bitmap.
*       this DIB is assumed to have the colors after biSize bytes.
*
* @rdesc Returns TRUE if bitmaps are identical, FALSE otherwise.
*
**************************************************************************/
inline BOOL DibEq(LPBITMAPINFOHEADER lpbi1, LPBITMAPINFOHEADER lpbi2)
{
    return
        lpbi1->biCompression == lpbi2->biCompression   &&
        lpbi1->biSize        == lpbi2->biSize          &&
        lpbi1->biWidth       == lpbi2->biWidth         &&
        lpbi1->biHeight      == lpbi2->biHeight        &&
        lpbi1->biBitCount    == lpbi2->biBitCount;
}

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::SetFormat(LONG lPos,LPVOID lpFormat,LONG cbFormat)
{
    CAVICmpStream FAR * pinst = m_pAVIStream;

    LONG		lRet = AVIERR_OK;
    HRESULT		hr;
    LPBITMAPINFOHEADER  lpbi = (LPBITMAPINFOHEADER) lpFormat;
    DWORD		dw;
    
    if (pinst->pgf)
	return ResultFromScode(AVIERR_UNSUPPORTED);
	
    if (lpbi->biCompression != BI_RGB)
	return ResultFromScode(AVIERR_UNSUPPORTED);
    
    if (pinst->avistream.fccHandler == comptypeDIB)
	goto exit;

    if (pinst->lpFormatOrig) {
	if ((cbFormat = pinst->cbFormatOrig) &&
	    (_fmemcmp(pinst->lpFormatOrig, lpFormat, (int) cbFormat) == 0))
	    return AVIERR_OK;

	DPF("AVICmprs: SetFormat when format already set!\n");
    }
    
    //
    // Can only currently set the palette at the end of the file
    //
    if (lPos < (LONG) (pinst->avistream.dwStart + pinst->avistream.dwLength))
	return ResultFromScode(AVIERR_UNSUPPORTED);

    if (pinst->lpFormatOrig) {
	//
	// We can only change the palette for things with palettes....
	//
	if (lpbi->biBitCount > 8 || lpbi->biClrUsed == 0)
	    return ResultFromScode(AVIERR_UNSUPPORTED);

	//
	// Be sure only the palette is changing, nothing else....
	//
	if (cbFormat != pinst->cbFormatOrig)
	    return ResultFromScode(AVIERR_UNSUPPORTED);

	if (!DibEq((LPBITMAPINFOHEADER) lpFormat,
		   (LPBITMAPINFOHEADER) pinst->lpFormatOrig))
	    return ResultFromScode(AVIERR_UNSUPPORTED);

	dw = ICCompressGetFormat(pinst->hic, lpFormat, pinst->lpFormat);
	if ((LONG) dw < 0)
	    goto ic_error;
    
	ICCompressEnd(pinst->hic);
	dw = ICCompressBegin(pinst->hic, lpFormat, pinst->lpFormat);

	if (dw != ICERR_OK)
	    goto ic_error;

	
	if (pinst->dwKeyFrameEvery != 1 && pinst->lpbiU) {
	    ICDecompressEnd(pinst->hic);

	    dw = ICDecompressGetFormat(pinst->hic, pinst->lpFormat, pinst->lpbiU);

	    if ((LONG) dw < 0)
		goto ic_error;

	    dw = ICDecompressBegin(pinst->hic, pinst->lpFormat, pinst->lpbiU);

	    if (dw != ICERR_OK)
		goto ic_error;
	}

	goto setformatandexit;
    }


    pinst->lpFormatOrig = (LPBITMAPINFOHEADER)
	GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, cbFormat);
    pinst->cbFormatOrig = cbFormat;
    
    if (!pinst->lpFormatOrig) {
	lRet = AVIERR_MEMORY;
	goto exit;
    }

    hmemcpy(pinst->lpFormatOrig, lpFormat, cbFormat);
    
    /*
    ** get the size requied to hold the format.
    */
    dw = ICCompressGetFormatSize(pinst->hic, lpFormat);
    if ((LONG) dw < sizeof(BITMAPINFOHEADER))
	goto ic_error;

    pinst->cbFormat = dw;
    pinst->lpFormat = (LPBITMAPINFOHEADER) GlobalAllocPtr(GHND | GMEM_SHARE, pinst->cbFormat);
    if (!pinst->lpFormat) {
	lRet = AVIERR_MEMORY;
	goto exit;
    }

    /*
    ** get the compressed format from the compressor.
    */
    dw = ICCompressGetFormat(pinst->hic, lpFormat, pinst->lpFormat);
    if ((LONG) dw < 0)
	goto ic_error;
    
    pinst->avistream.rcFrame.right = pinst->avistream.rcFrame.left +
					  (int) pinst->lpFormat->biWidth;
    pinst->avistream.rcFrame.bottom = pinst->avistream.rcFrame.top +
					  (int) pinst->lpFormat->biHeight;
    
    dw = ICCompressBegin(pinst->hic, lpFormat, pinst->lpFormat);
    
    if (dw != ICERR_OK)
	goto ic_error;

    /*
    ** allocate buffer to hold compressed data.
    */
    dw = ICCompressGetSize(pinst->hic, lpFormat, pinst->lpFormat);

    pinst->lpbiC = (LPBITMAPINFOHEADER)
	GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, pinst->cbFormat + dw);
    
    if (!pinst->lpbiC) {
	lRet = AVIERR_MEMORY;
	goto exit;
    }

    hmemcpy((LPVOID)pinst->lpbiC, pinst->lpFormat, pinst->cbFormat);

    pinst->lpC = (LPSTR) pinst->lpbiC + pinst->lpbiC->biSize +
				pinst->lpbiC->biClrUsed * sizeof(RGBQUAD);
	
    //
    //  check for temporal compress, and alocate a previous
    //  DIB buffer if needed
    //
    if (pinst->dwKeyFrameEvery != 1) {
	pinst->lpbiU = (LPBITMAPINFOHEADER)
	    GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
		    sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD));

	if (!pinst->lpbiU) {
	    lRet = AVIERR_MEMORY;
	    goto exit;
	}

	dw = ICDecompressGetFormat(pinst->hic, pinst->lpFormat, pinst->lpbiU);

	if ((LONG) dw < 0)
	    goto ic_error;

	if (pinst->lpbiU->biSizeImage == 0)
	    pinst->lpbiU->biSizeImage = pinst->lpbiU->biHeight *
					    DIBWIDTHBYTES(*pinst->lpbiU);

	pinst->lpbiU = (LPBITMAPINFOHEADER)
	    GlobalReAllocPtr(pinst->lpbiU,
		pinst->lpbiU->biSize +
			pinst->lpbiU->biClrUsed * sizeof(RGBQUAD) +
			pinst->lpbiU->biSizeImage,
		GMEM_MOVEABLE | GMEM_SHARE);

	if (!pinst->lpbiU) {
	    lRet = AVIERR_MEMORY;
	    goto exit;
	}

	pinst->lpU = (LPSTR) pinst->lpbiU + pinst->lpbiU->biSize +
				pinst->lpbiU->biClrUsed * sizeof(RGBQUAD);
	
	dw = ICDecompressBegin(pinst->hic, pinst->lpFormat, pinst->lpbiU);

	if (dw != ICERR_OK)
	    goto ic_error;
    }

setformatandexit:
    hr = AVIStreamSetFormat(pinst->pavi, lPos,
			    pinst->lpFormat, pinst->cbFormat);

    if (hr != NOERROR)
	return hr;

exit:
    if (lRet != AVIERR_OK)
	// Clean up before returning...
	;
    
    return ResultFromScode(lRet);
    
ic_error:
    if (dw == ICERR_BADFORMAT)
	lRet = AVIERR_BADFORMAT;
    else if (dw == ICERR_MEMORY)
	lRet = AVIERR_MEMORY;
    else
	lRet = AVIERR_INTERNAL;
    goto exit;
} 

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::Write(LONG lStart,
						     LONG lSamples,
						     LPVOID lpBuffer,
						     LONG cbBuffer,
						     DWORD dwFlags,
						     LONG FAR *plSampWritten,
						     LONG FAR *plBytesWritten)
{
    CAVICmpStream FAR * pinst = m_pAVIStream;

    LONG		lRet;
    
    if (pinst->pgf)
	return ResultFromScode(AVIERR_UNSUPPORTED);
	
    if (lStart < (LONG) (pinst->avistream.dwStart + pinst->avistream.dwLength))
	return ResultFromScode(AVIERR_UNSUPPORTED);
    
    if (lSamples > 1)
	return ResultFromScode(AVIERR_UNSUPPORTED);

    pinst->lFrameCurrent = lStart;

    if (pinst->avistream.fccHandler == comptypeDIB) {
	dwFlags |= AVIIF_KEYFRAME;
    } else {
	lRet = pinst->ICCrunch(pinst->lpFormatOrig, lpBuffer);
	if (lRet != AVIERR_OK)
	    return ResultFromScode(lRet);
	lpBuffer = pinst->lpC;
	cbBuffer = pinst->lpbiC->biSizeImage;
	dwFlags = pinst->lLastKeyFrame == lStart ? AVIIF_KEYFRAME : 0;
    }

    return AVIStreamWrite(pinst->pavi,
			  lStart,
			  lSamples,
			  lpBuffer,
			  cbBuffer,
			  dwFlags,
			  plSampWritten,
			  plBytesWritten);
}

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::Delete(LONG lStart,LONG lSamples)
{
    CAVICmpStream FAR * pinst = m_pAVIStream;

    return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::ReadData(DWORD fcc, LPVOID lp, LONG FAR *lpcb)
{
    CAVICmpStream FAR * pinst = m_pAVIStream;

    // Don't pass through 'strd' data!
    if (fcc == ckidSTREAMHANDLERDATA) {
	if (pinst->cbHandler) {
	    hmemcpy(lp, pinst->lpHandler, min(*lpcb, pinst->cbHandler));
	}
	*lpcb = pinst->cbHandler;
	return AVIERR_OK;
    }
    
    return AVIStreamReadData(pinst->pavi, fcc, lp, lpcb);
}

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::WriteData(DWORD fcc, LPVOID lp, LONG cb)
{
    CAVICmpStream FAR * pinst = m_pAVIStream;

    return ResultFromScode(AVIERR_UNSUPPORTED);
}

#if 0
STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::Clone(PAVISTREAM FAR * ppaviNew)
{
    CAVICmpStream FAR * pinst = m_pAVIStream;

    return ResultFromScode(AVIERR_UNSUPPORTED);
}

#endif


STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::Reserved1(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::Reserved2(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::Reserved3(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::Reserved4(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP CAVICmpStream::CAVICmpStreamImpl::Reserved5(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}