/* * Copyright 2002 Michael Günnewig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include <assert.h> #include <stdarg.h> #include "windef.h" #include "winbase.h" #include "wingdi.h" #include "winuser.h" #include "winerror.h" #include "mmsystem.h" #include "vfw.h" #include "avifile_private.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(avifile); #define MAX_FRAMESIZE (16 * 1024 * 1024) #define MAX_FRAMESIZE_DIFF 512 /***********************************************************************/ typedef struct _IAVIStreamImpl { /* IUnknown stuff */ IAVIStream IAVIStream_iface; LONG ref; /* IAVIStream stuff */ PAVISTREAM pStream; AVISTREAMINFOW sInfo; PGETFRAME pg; HIC hic; DWORD dwICMFlags; LONG lCurrent; LONG lLastKey; LONG lKeyFrameEvery; DWORD dwLastQuality; DWORD dwBytesPerFrame; DWORD dwUnusedBytes; LPBITMAPINFOHEADER lpbiCur; /* current frame */ LPVOID lpCur; LPBITMAPINFOHEADER lpbiPrev; /* previous frame */ LPVOID lpPrev; LPBITMAPINFOHEADER lpbiOutput; /* output format of codec */ LONG cbOutput; LPBITMAPINFOHEADER lpbiInput; /* input format for codec */ LONG cbInput; } IAVIStreamImpl; /***********************************************************************/ static HRESULT AVIFILE_EncodeFrame(IAVIStreamImpl *This, LPBITMAPINFOHEADER lpbi, LPVOID lpBits); static HRESULT AVIFILE_OpenGetFrame(IAVIStreamImpl *This); static inline IAVIStreamImpl *impl_from_IAVIStream(IAVIStream *iface) { return CONTAINING_RECORD(iface, IAVIStreamImpl, IAVIStream_iface); } static inline void AVIFILE_Reset(IAVIStreamImpl *This) { This->lCurrent = -1; This->lLastKey = 0; This->dwLastQuality = ICQUALITY_HIGH; This->dwUnusedBytes = 0; } static HRESULT WINAPI ICMStream_fnQueryInterface(IAVIStream *iface, REFIID refiid, LPVOID *obj) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); TRACE("(%p,%s,%p)\n", iface, debugstr_guid(refiid), obj); if (IsEqualGUID(&IID_IUnknown, refiid) || IsEqualGUID(&IID_IAVIStream, refiid)) { *obj = &This->IAVIStream_iface; IAVIStream_AddRef(iface); return S_OK; } return OLE_E_ENUM_NOMORE; } static ULONG WINAPI ICMStream_fnAddRef(IAVIStream *iface) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); ULONG ref = InterlockedIncrement(&This->ref); TRACE("(%p) -> %d\n", iface, ref); /* also add reference to the nested stream */ if (This->pStream != NULL) IAVIStream_AddRef(This->pStream); return ref; } static ULONG WINAPI ICMStream_fnRelease(IAVIStream* iface) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); ULONG ref = InterlockedDecrement(&This->ref); TRACE("(%p) -> %d\n", iface, ref); if (ref == 0) { /* destruct */ if (This->pg != NULL) { AVIStreamGetFrameClose(This->pg); This->pg = NULL; } if (This->pStream != NULL) { IAVIStream_Release(This->pStream); This->pStream = NULL; } if (This->hic != NULL) { if (This->lpbiPrev != NULL) { ICDecompressEnd(This->hic); HeapFree(GetProcessHeap(), 0, This->lpbiPrev); This->lpbiPrev = NULL; This->lpPrev = NULL; } ICCompressEnd(This->hic); This->hic = NULL; } if (This->lpbiCur != NULL) { HeapFree(GetProcessHeap(), 0, This->lpbiCur); This->lpbiCur = NULL; This->lpCur = NULL; } if (This->lpbiOutput != NULL) { HeapFree(GetProcessHeap(), 0, This->lpbiOutput); This->lpbiOutput = NULL; This->cbOutput = 0; } if (This->lpbiInput != NULL) { HeapFree(GetProcessHeap(), 0, This->lpbiInput); This->lpbiInput = NULL; This->cbInput = 0; } HeapFree(GetProcessHeap(), 0, This); return 0; } /* also release reference to the nested stream */ if (This->pStream != NULL) IAVIStream_Release(This->pStream); return ref; } /* lParam1: PAVISTREAM * lParam2: LPAVICOMPRESSOPTIONS */ static HRESULT WINAPI ICMStream_fnCreate(IAVIStream *iface, LPARAM lParam1, LPARAM lParam2) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); ICINFO icinfo; ICCOMPRESSFRAMES icFrames; LPAVICOMPRESSOPTIONS pco = (LPAVICOMPRESSOPTIONS)lParam2; TRACE("(%p,0x%08lX,0x%08lX)\n", iface, lParam1, lParam2); /* check parameter */ if ((LPVOID)lParam1 == NULL) return AVIERR_BADPARAM; /* get infos from stream */ IAVIStream_Info((PAVISTREAM)lParam1, &This->sInfo, sizeof(This->sInfo)); if (This->sInfo.fccType != streamtypeVIDEO) return AVIERR_ERROR; /* error in registry or AVIMakeCompressedStream */ /* add reference to the stream */ This->pStream = (PAVISTREAM)lParam1; IAVIStream_AddRef(This->pStream); AVIFILE_Reset(This); if (pco != NULL && pco->fccHandler != comptypeDIB) { /* we should compress */ This->sInfo.fccHandler = pco->fccHandler; This->hic = ICOpen(ICTYPE_VIDEO, pco->fccHandler, ICMODE_COMPRESS); if (This->hic == NULL) return AVIERR_NOCOMPRESSOR; /* restore saved state of codec */ if (pco->cbParms > 0 && pco->lpParms != NULL) { ICSetState(This->hic, pco->lpParms, pco->cbParms); } /* set quality -- resolve default quality */ This->sInfo.dwQuality = pco->dwQuality; if (pco->dwQuality == ICQUALITY_DEFAULT) This->sInfo.dwQuality = ICGetDefaultQuality(This->hic); /* get capabilities of codec */ ICGetInfo(This->hic, &icinfo, sizeof(icinfo)); This->dwICMFlags = icinfo.dwFlags; /* use keyframes? */ if ((pco->dwFlags & AVICOMPRESSF_KEYFRAMES) && (icinfo.dwFlags & (VIDCF_TEMPORAL|VIDCF_FASTTEMPORALC))) { This->lKeyFrameEvery = pco->dwKeyFrameEvery; } else This->lKeyFrameEvery = 1; /* use datarate? */ if ((pco->dwFlags & AVICOMPRESSF_DATARATE)) { /* Do we have a chance to reduce size to desired one? */ if ((icinfo.dwFlags & (VIDCF_CRUNCH|VIDCF_QUALITY)) == 0) return AVIERR_NOCOMPRESSOR; assert(This->sInfo.dwRate != 0); This->dwBytesPerFrame = MulDiv(pco->dwBytesPerSecond, This->sInfo.dwScale, This->sInfo.dwRate); } else { pco->dwBytesPerSecond = 0; This->dwBytesPerFrame = 0; } if (icinfo.dwFlags & VIDCF_COMPRESSFRAMES) { memset(&icFrames, 0, sizeof(icFrames)); icFrames.lpbiOutput = This->lpbiOutput; icFrames.lpbiInput = This->lpbiInput; icFrames.lFrameCount = This->sInfo.dwLength; icFrames.lQuality = This->sInfo.dwQuality; icFrames.lDataRate = pco->dwBytesPerSecond; icFrames.lKeyRate = This->lKeyFrameEvery; icFrames.dwRate = This->sInfo.dwRate; icFrames.dwScale = This->sInfo.dwScale; ICSendMessage(This->hic, ICM_COMPRESS_FRAMES_INFO, (LPARAM)&icFrames, (LPARAM)sizeof(icFrames)); } } else This->sInfo.fccHandler = comptypeDIB; return AVIERR_OK; } static HRESULT WINAPI ICMStream_fnInfo(IAVIStream *iface,LPAVISTREAMINFOW psi, LONG size) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); TRACE("(%p,%p,%d)\n", iface, psi, size); if (psi == NULL) return AVIERR_BADPARAM; if (size < 0) return AVIERR_BADSIZE; memcpy(psi, &This->sInfo, min((DWORD)size, sizeof(This->sInfo))); if ((DWORD)size < sizeof(This->sInfo)) return AVIERR_BUFFERTOOSMALL; return AVIERR_OK; } static LONG WINAPI ICMStream_fnFindSample(IAVIStream *iface, LONG pos, LONG flags) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); TRACE("(%p,%d,0x%08X)\n",iface,pos,flags); if (flags & FIND_FROM_START) { pos = This->sInfo.dwStart; flags &= ~(FIND_FROM_START|FIND_PREV); flags |= FIND_NEXT; } if (flags & FIND_RET) WARN(": FIND_RET flags will be ignored!\n"); if (flags & FIND_KEY) { if (This->hic == NULL) return pos; /* we decompress so every frame is a keyframe */ if (flags & FIND_PREV) { /* need to read old or new frames? */ if (This->lLastKey <= pos || pos < This->lCurrent) IAVIStream_Read(iface, pos, 1, NULL, 0, NULL, NULL); return This->lLastKey; } } else if (flags & FIND_ANY) { return pos; /* We really don't know, reread is too expensive, so guess. */ } else if (flags & FIND_FORMAT) { if (flags & FIND_PREV) return 0; } return -1; } static HRESULT WINAPI ICMStream_fnReadFormat(IAVIStream *iface, LONG pos, LPVOID format, LONG *formatsize) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); LPBITMAPINFOHEADER lpbi; HRESULT hr; TRACE("(%p,%d,%p,%p)\n", iface, pos, format, formatsize); if (formatsize == NULL) return AVIERR_BADPARAM; if (This->pg == NULL) { hr = AVIFILE_OpenGetFrame(This); if (FAILED(hr)) return hr; } lpbi = AVIStreamGetFrame(This->pg, pos); if (lpbi == NULL) return AVIERR_MEMORY; if (This->hic == NULL) { LONG size = lpbi->biSize + lpbi->biClrUsed * sizeof(RGBQUAD); if (size > 0) { if (This->sInfo.dwSuggestedBufferSize < lpbi->biSizeImage) This->sInfo.dwSuggestedBufferSize = lpbi->biSizeImage; This->cbOutput = size; if (format != NULL) { if (This->lpbiOutput != NULL) memcpy(format, This->lpbiOutput, min(*formatsize, This->cbOutput)); else memcpy(format, lpbi, min(*formatsize, size)); } } } else if (format != NULL) memcpy(format, This->lpbiOutput, min(*formatsize, This->cbOutput)); if (*formatsize < This->cbOutput) hr = AVIERR_BUFFERTOOSMALL; else hr = AVIERR_OK; *formatsize = This->cbOutput; return hr; } static HRESULT WINAPI ICMStream_fnSetFormat(IAVIStream *iface, LONG pos, LPVOID format, LONG formatsize) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); TRACE("(%p,%d,%p,%d)\n", iface, pos, format, formatsize); /* check parameters */ if (format == NULL || formatsize <= 0) return AVIERR_BADPARAM; /* We can only accept RGB data for writing */ if (((LPBITMAPINFOHEADER)format)->biCompression != BI_RGB) { WARN(": need RGB data as input\n"); return AVIERR_UNSUPPORTED; } /* Input format already known? * Changing of palette is supported, but be quiet if it's the same */ if (This->lpbiInput != NULL) { if (This->cbInput != formatsize) return AVIERR_UNSUPPORTED; if (memcmp(format, This->lpbiInput, formatsize) == 0) return AVIERR_OK; } /* Does the nested stream support writing? */ if ((This->sInfo.dwCaps & AVIFILECAPS_CANWRITE) == 0) return AVIERR_READONLY; /* check if frame is already written */ if (This->sInfo.dwLength + This->sInfo.dwStart > pos) return AVIERR_UNSUPPORTED; /* check if we should compress */ if (This->sInfo.fccHandler == 0 || This->sInfo.fccHandler == mmioFOURCC('N','O','N','E')) This->sInfo.fccHandler = comptypeDIB; /* only pass through? */ if (This->sInfo.fccHandler == comptypeDIB) return IAVIStream_SetFormat(This->pStream, pos, format, formatsize); /* initial format setting? */ if (This->lpbiInput == NULL) { ULONG size; assert(This->hic != NULL); /* get memory for input format */ This->lpbiInput = HeapAlloc(GetProcessHeap(), 0, formatsize); if (This->lpbiInput == NULL) return AVIERR_MEMORY; This->cbInput = formatsize; memcpy(This->lpbiInput, format, formatsize); /* get output format */ size = ICCompressGetFormatSize(This->hic, This->lpbiInput); if (size < sizeof(BITMAPINFOHEADER)) return AVIERR_COMPRESSOR; This->lpbiOutput = HeapAlloc(GetProcessHeap(), 0, size); if (This->lpbiOutput == NULL) return AVIERR_MEMORY; This->cbOutput = size; if (ICCompressGetFormat(This->hic,This->lpbiInput,This->lpbiOutput) < S_OK) return AVIERR_COMPRESSOR; /* update AVISTREAMINFO structure */ This->sInfo.rcFrame.right = This->sInfo.rcFrame.left + This->lpbiOutput->biWidth; This->sInfo.rcFrame.bottom = This->sInfo.rcFrame.top + This->lpbiOutput->biHeight; /* prepare codec for compression */ if (ICCompressBegin(This->hic, This->lpbiInput, This->lpbiOutput) != S_OK) return AVIERR_COMPRESSOR; /* allocate memory for compressed frame */ size = ICCompressGetSize(This->hic, This->lpbiInput, This->lpbiOutput); This->lpbiCur = HeapAlloc(GetProcessHeap(), 0, This->cbOutput + size); if (This->lpbiCur == NULL) return AVIERR_MEMORY; memcpy(This->lpbiCur, This->lpbiOutput, This->cbOutput); This->lpCur = DIBPTR(This->lpbiCur); /* allocate memory for last frame if needed */ if (This->lKeyFrameEvery != 1 && (This->dwICMFlags & VIDCF_FASTTEMPORALC) == 0) { size = ICDecompressGetFormatSize(This->hic, This->lpbiOutput); This->lpbiPrev = HeapAlloc(GetProcessHeap(), 0, size); if (This->lpbiPrev == NULL) return AVIERR_MEMORY; if (ICDecompressGetFormat(This->hic, This->lpbiOutput, This->lpbiPrev) < S_OK) return AVIERR_COMPRESSOR; if (This->lpbiPrev->biSizeImage == 0) { This->lpbiPrev->biSizeImage = DIBWIDTHBYTES(*This->lpbiPrev) * This->lpbiPrev->biHeight; } /* get memory for format and picture */ size += This->lpbiPrev->biSizeImage; This->lpbiPrev = HeapReAlloc(GetProcessHeap(), 0, This->lpbiPrev, size); if (This->lpbiPrev == NULL) return AVIERR_MEMORY; This->lpPrev = DIBPTR(This->lpbiPrev); /* prepare codec also for decompression */ if (ICDecompressBegin(This->hic,This->lpbiOutput,This->lpbiPrev) != S_OK) return AVIERR_COMPRESSOR; } } else { /* format change -- check that's only the palette */ LPBITMAPINFOHEADER lpbi = format; if (lpbi->biSize != This->lpbiInput->biSize || lpbi->biWidth != This->lpbiInput->biWidth || lpbi->biHeight != This->lpbiInput->biHeight || lpbi->biBitCount != This->lpbiInput->biBitCount || lpbi->biPlanes != This->lpbiInput->biPlanes || lpbi->biCompression != This->lpbiInput->biCompression || lpbi->biClrUsed != This->lpbiInput->biClrUsed) return AVIERR_UNSUPPORTED; /* get new output format */ if (ICCompressGetFormat(This->hic, lpbi, This->lpbiOutput) < S_OK) return AVIERR_BADFORMAT; /* restart compression */ ICCompressEnd(This->hic); if (ICCompressBegin(This->hic, lpbi, This->lpbiOutput) != S_OK) return AVIERR_COMPRESSOR; /* check if we need to restart decompression also */ if (This->lKeyFrameEvery != 1 && (This->dwICMFlags & VIDCF_FASTTEMPORALC) == 0) { ICDecompressEnd(This->hic); if (ICDecompressGetFormat(This->hic,This->lpbiOutput,This->lpbiPrev) < S_OK) return AVIERR_COMPRESSOR; if (ICDecompressBegin(This->hic,This->lpbiOutput,This->lpbiPrev) != S_OK) return AVIERR_COMPRESSOR; } } /* tell nested stream the new format */ return IAVIStream_SetFormat(This->pStream, pos, This->lpbiOutput, This->cbOutput); } static HRESULT WINAPI ICMStream_fnRead(IAVIStream *iface, LONG start, LONG samples, LPVOID buffer, LONG buffersize, LPLONG bytesread, LPLONG samplesread) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); LPBITMAPINFOHEADER lpbi; TRACE("(%p,%d,%d,%p,%d,%p,%p)\n", iface, start, samples, buffer, buffersize, bytesread, samplesread); /* clear return parameters if given */ if (bytesread != NULL) *bytesread = 0; if (samplesread != NULL) *samplesread = 0; if (samples == 0) return AVIERR_OK; /* check parameters */ if (samples != 1 && (bytesread == NULL && samplesread == NULL)) return AVIERR_BADPARAM; if (samples == -1) /* read as much as we could */ samples = 1; if (This->pg == NULL) { HRESULT hr = AVIFILE_OpenGetFrame(This); if (FAILED(hr)) return hr; } /* compress or decompress? */ if (This->hic == NULL) { /* decompress */ lpbi = AVIStreamGetFrame(This->pg, start); if (lpbi == NULL) return AVIERR_MEMORY; if (buffer != NULL && buffersize > 0) { /* check buffersize */ if (buffersize < lpbi->biSizeImage) return AVIERR_BUFFERTOOSMALL; memcpy(buffer, DIBPTR(lpbi), lpbi->biSizeImage); } /* fill out return parameters if given */ if (bytesread != NULL) *bytesread = lpbi->biSizeImage; } else { /* compress */ if (This->lCurrent > start) AVIFILE_Reset(This); while (start > This->lCurrent) { HRESULT hr; lpbi = AVIStreamGetFrame(This->pg, ++This->lCurrent); if (lpbi == NULL) { AVIFILE_Reset(This); return AVIERR_MEMORY; } hr = AVIFILE_EncodeFrame(This, lpbi, DIBPTR(lpbi)); if (FAILED(hr)) { AVIFILE_Reset(This); return hr; } } if (buffer != NULL && buffersize > 0) { /* check buffersize */ if (This->lpbiCur->biSizeImage > buffersize) return AVIERR_BUFFERTOOSMALL; memcpy(buffer, This->lpCur, This->lpbiCur->biSizeImage); } /* fill out return parameters if given */ if (bytesread != NULL) *bytesread = This->lpbiCur->biSizeImage; } /* fill out return parameters if given */ if (samplesread != NULL) *samplesread = 1; return AVIERR_OK; } static HRESULT WINAPI ICMStream_fnWrite(IAVIStream *iface, LONG start, LONG samples, LPVOID buffer, LONG buffersize, DWORD flags, LPLONG sampwritten, LPLONG byteswritten) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); HRESULT hr; TRACE("(%p,%d,%d,%p,%d,0x%08X,%p,%p)\n", iface, start, samples, buffer, buffersize, flags, sampwritten, byteswritten); /* clear return parameters if given */ if (sampwritten != NULL) *sampwritten = 0; if (byteswritten != NULL) *byteswritten = 0; /* check parameters */ if (buffer == NULL && (buffersize > 0 || samples > 0)) return AVIERR_BADPARAM; if (This->sInfo.fccHandler == comptypeDIB) { /* only pass through */ flags |= AVIIF_KEYFRAME; return IAVIStream_Write(This->pStream, start, samples, buffer, buffersize, flags, sampwritten, byteswritten); } else { /* compress data before writing to pStream */ if (samples != 1 && (sampwritten == NULL && byteswritten == NULL)) return AVIERR_UNSUPPORTED; This->lCurrent = start; hr = AVIFILE_EncodeFrame(This, This->lpbiInput, buffer); if (FAILED(hr)) return hr; if (This->lLastKey == start) flags |= AVIIF_KEYFRAME; return IAVIStream_Write(This->pStream, start, samples, This->lpCur, This->lpbiCur->biSizeImage, flags, byteswritten, sampwritten); } } static HRESULT WINAPI ICMStream_fnDelete(IAVIStream *iface, LONG start, LONG samples) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); TRACE("(%p,%d,%d)\n", iface, start, samples); return IAVIStream_Delete(This->pStream, start, samples); } static HRESULT WINAPI ICMStream_fnReadData(IAVIStream *iface, DWORD fcc, LPVOID lp, LPLONG lpread) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); TRACE("(%p,0x%08X,%p,%p)\n", iface, fcc, lp, lpread); assert(This->pStream != NULL); return IAVIStream_ReadData(This->pStream, fcc, lp, lpread); } static HRESULT WINAPI ICMStream_fnWriteData(IAVIStream *iface, DWORD fcc, LPVOID lp, LONG size) { IAVIStreamImpl *This = impl_from_IAVIStream(iface); TRACE("(%p,0x%08x,%p,%d)\n", iface, fcc, lp, size); assert(This->pStream != NULL); return IAVIStream_WriteData(This->pStream, fcc, lp, size); } static HRESULT WINAPI ICMStream_fnSetInfo(IAVIStream *iface, LPAVISTREAMINFOW info, LONG infolen) { FIXME("(%p,%p,%d): stub\n", iface, info, infolen); return E_FAIL; } static const struct IAVIStreamVtbl iicmst = { ICMStream_fnQueryInterface, ICMStream_fnAddRef, ICMStream_fnRelease, ICMStream_fnCreate, ICMStream_fnInfo, ICMStream_fnFindSample, ICMStream_fnReadFormat, ICMStream_fnSetFormat, ICMStream_fnRead, ICMStream_fnWrite, ICMStream_fnDelete, ICMStream_fnReadData, ICMStream_fnWriteData, ICMStream_fnSetInfo }; HRESULT AVIFILE_CreateICMStream(REFIID riid, LPVOID *ppv) { IAVIStreamImpl *pstream; HRESULT hr; assert(riid != NULL && ppv != NULL); *ppv = NULL; pstream = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IAVIStreamImpl)); if (pstream == NULL) return AVIERR_MEMORY; pstream->IAVIStream_iface.lpVtbl = &iicmst; AVIFILE_Reset(pstream); hr = IAVIStream_QueryInterface(&pstream->IAVIStream_iface, riid, ppv); if (FAILED(hr)) HeapFree(GetProcessHeap(), 0, pstream); return hr; } /***********************************************************************/ static HRESULT AVIFILE_EncodeFrame(IAVIStreamImpl *This, LPBITMAPINFOHEADER lpbi, LPVOID lpBits) { DWORD dwMinQual, dwMaxQual, dwCurQual; DWORD dwRequest; DWORD icmFlags = 0; DWORD idxFlags = 0; BOOL bDecreasedQual = FALSE; BOOL doSizeCheck; BOOL noPrev; /* make lKeyFrameEvery and at start a keyframe */ if ((This->lKeyFrameEvery != 0 && (This->lCurrent - This->lLastKey) >= This->lKeyFrameEvery) || This->lCurrent == This->sInfo.dwStart) { idxFlags = AVIIF_KEYFRAME; icmFlags = ICCOMPRESS_KEYFRAME; } if (This->lKeyFrameEvery != 0) { if (This->lCurrent == This->sInfo.dwStart) { if (idxFlags & AVIIF_KEYFRAME) { /* allow keyframes to consume all unused bytes */ dwRequest = This->dwBytesPerFrame + This->dwUnusedBytes; This->dwUnusedBytes = 0; } else { /* for non-keyframes only allow some of the unused bytes to be consumed */ DWORD tmp1 = 0; DWORD tmp2; if (This->dwBytesPerFrame >= This->dwUnusedBytes) tmp1 = This->dwBytesPerFrame / This->lKeyFrameEvery; tmp2 = (This->dwUnusedBytes + tmp1) / This->lKeyFrameEvery; dwRequest = This->dwBytesPerFrame - tmp1 + tmp2; This->dwUnusedBytes -= tmp2; } } else dwRequest = MAX_FRAMESIZE; } else { /* only one keyframe at start desired */ if (This->lCurrent == This->sInfo.dwStart) { dwRequest = This->dwBytesPerFrame + This->dwUnusedBytes; This->dwUnusedBytes = 0; } else dwRequest = MAX_FRAMESIZE; } /* must we check for frame size to gain the requested * data rate or can we trust the codec? */ doSizeCheck = (dwRequest != 0 && ((This->dwICMFlags & (VIDCF_CRUNCH|VIDCF_QUALITY)) == 0)); dwMaxQual = dwCurQual = This->sInfo.dwQuality; dwMinQual = ICQUALITY_LOW; noPrev = TRUE; if ((icmFlags & ICCOMPRESS_KEYFRAME) == 0 && (This->dwICMFlags & VIDCF_FASTTEMPORALC) == 0) noPrev = FALSE; do { DWORD idxCkid = 0; DWORD res; res = ICCompress(This->hic,icmFlags,This->lpbiCur,This->lpCur,lpbi,lpBits, &idxCkid, &idxFlags, This->lCurrent, dwRequest, dwCurQual, noPrev ? NULL:This->lpbiPrev, noPrev ? NULL:This->lpPrev); if (res == ICERR_NEWPALETTE) { FIXME(": codec has changed palette -- unhandled!\n"); } else if (res != ICERR_OK) return AVIERR_COMPRESSOR; /* need to check for framesize */ if (! doSizeCheck) break; if (dwRequest >= This->lpbiCur->biSizeImage) { /* frame is smaller -- try to maximize quality */ if (dwMaxQual - dwCurQual > 10) { DWORD tmp = dwRequest / 8; if (tmp < MAX_FRAMESIZE_DIFF) tmp = MAX_FRAMESIZE_DIFF; if (tmp < dwRequest - This->lpbiCur->biSizeImage && bDecreasedQual) { tmp = dwCurQual; dwCurQual = (dwMinQual + dwMaxQual) / 2; dwMinQual = tmp; continue; } } else break; } else if (dwMaxQual - dwMinQual <= 1) { break; } else { dwMaxQual = dwCurQual; if (bDecreasedQual || dwCurQual == This->dwLastQuality) dwCurQual = (dwMinQual + dwMaxQual) / 2; else FIXME(": no new quality computed min=%u cur=%u max=%u last=%u\n", dwMinQual, dwCurQual, dwMaxQual, This->dwLastQuality); bDecreasedQual = TRUE; } } while (TRUE); /* remember some values */ This->dwLastQuality = dwCurQual; This->dwUnusedBytes = dwRequest - This->lpbiCur->biSizeImage; if (icmFlags & ICCOMPRESS_KEYFRAME) This->lLastKey = This->lCurrent; /* Does we manage previous frame? */ if (This->lpPrev != NULL && This->lKeyFrameEvery != 1) ICDecompress(This->hic, 0, This->lpbiCur, This->lpCur, This->lpbiPrev, This->lpPrev); return AVIERR_OK; } static HRESULT AVIFILE_OpenGetFrame(IAVIStreamImpl *This) { LPBITMAPINFOHEADER lpbi; DWORD size; /* pre-conditions */ assert(This != NULL); assert(This->pStream != NULL); assert(This->pg == NULL); This->pg = AVIStreamGetFrameOpen(This->pStream, NULL); if (This->pg == NULL) return AVIERR_ERROR; /* When we only decompress this is enough */ if (This->sInfo.fccHandler == comptypeDIB) return AVIERR_OK; assert(This->hic != NULL); assert(This->lpbiOutput == NULL); /* get input format */ lpbi = AVIStreamGetFrame(This->pg, This->sInfo.dwStart); if (lpbi == NULL) return AVIERR_MEMORY; /* get memory for output format */ size = ICCompressGetFormatSize(This->hic, lpbi); if ((LONG)size < (LONG)sizeof(BITMAPINFOHEADER)) return AVIERR_COMPRESSOR; This->lpbiOutput = HeapAlloc(GetProcessHeap(), 0, size); if (This->lpbiOutput == NULL) return AVIERR_MEMORY; This->cbOutput = size; if (ICCompressGetFormat(This->hic, lpbi, This->lpbiOutput) < S_OK) return AVIERR_BADFORMAT; /* update AVISTREAMINFO structure */ This->sInfo.rcFrame.right = This->sInfo.rcFrame.left + This->lpbiOutput->biWidth; This->sInfo.rcFrame.bottom = This->sInfo.rcFrame.top + This->lpbiOutput->biHeight; This->sInfo.dwSuggestedBufferSize = ICCompressGetSize(This->hic, lpbi, This->lpbiOutput); /* prepare codec for compression */ if (ICCompressBegin(This->hic, lpbi, This->lpbiOutput) != S_OK) return AVIERR_COMPRESSOR; /* allocate memory for current frame */ size += This->sInfo.dwSuggestedBufferSize; This->lpbiCur = HeapAlloc(GetProcessHeap(), 0, size); if (This->lpbiCur == NULL) return AVIERR_MEMORY; memcpy(This->lpbiCur, This->lpbiOutput, This->cbOutput); This->lpCur = DIBPTR(This->lpbiCur); /* allocate memory for last frame if needed */ if (This->lKeyFrameEvery != 1 && (This->dwICMFlags & VIDCF_FASTTEMPORALC) == 0) { size = ICDecompressGetFormatSize(This->hic, This->lpbiOutput); This->lpbiPrev = HeapAlloc(GetProcessHeap(), 0, size); if (This->lpbiPrev == NULL) return AVIERR_MEMORY; if (ICDecompressGetFormat(This->hic, This->lpbiOutput, This->lpbiPrev) < S_OK) return AVIERR_COMPRESSOR; if (This->lpbiPrev->biSizeImage == 0) { This->lpbiPrev->biSizeImage = DIBWIDTHBYTES(*This->lpbiPrev) * This->lpbiPrev->biHeight; } /* get memory for format and picture */ size += This->lpbiPrev->biSizeImage; This->lpbiPrev = HeapReAlloc(GetProcessHeap(), 0, This->lpbiPrev, size ); if (This->lpbiPrev == NULL) return AVIERR_MEMORY; This->lpPrev = DIBPTR(This->lpbiPrev); /* prepare codec also for decompression */ if (ICDecompressBegin(This->hic,This->lpbiOutput,This->lpbiPrev) != S_OK) return AVIERR_COMPRESSOR; } return AVIERR_OK; }