| /* |
| * 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_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 to 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) { |
| /* for keyframes allow to consume all unused bytes */ |
| dwRequest = This->dwBytesPerFrame + This->dwUnusedBytes; |
| This->dwUnusedBytes = 0; |
| } else { |
| /* for non-keyframes only allow something 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 framesize to gain requested |
| * datarate or could we trust 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; |
| } |