/**************************************************************************** * * AVISAVE.C * * routine for writing Standard AVI files * * AVISave() * * Copyright (c) 1992-1995 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. * ***************************************************************************/ #include #ifdef USE_ISVALIDINTERFACE #include #endif #include #include "avicmprs.h" #include "debug.h" #include #ifndef _WIN32 // DS!=SS nightmare #ifndef WINDLL #define WINDLL #define _WINDLL #define __WINDLL #endif #endif #include #ifdef UNICODE #include #endif //extern LONG FAR PASCAL muldiv32(LONG,LONG,LONG); /************************************************************************/ /* Auto-doc for the AVICOMPRESSOPTIONS structure. Make sure it matches */ /* the declarations in avifile.h !!! */ /************************************************************************/ /***************************************************************************** * @doc EXTERNAL AVICOMPRESSOPTIONS * * @types AVICOMPRESSOPTIONS | This structure contains information * about a stream and how it is to be compressed and saved. * This structure passes data to * (or which uses ). * * @field DWORD | fccType | Specifies a four-character code * indicating the stream type. The following * constants have been defined for the data commonly * found in AVI streams: * * @flag streamtypeAUDIO | Indicates an audio stream. * @flag streamtypeMIDI | Indicates a MIDI stream. * @flag streamtypeTEXT | Indicates a text stream. * @flag streamtypeVIDEO | Indicates a video stream. * * @field DWORD | fccHandler | For a video stream, specifies the * four-character code for the compressor handler that * will compress this stream when it is saved * (For example, mmioFOURCC('M','S','V','C')). * This member is not used for audio streams. * * @field DWORD | dwKeyFrameEvery | Specifies the maximum period * between key frames. This member is used only * if the AVICOMPRESSF_KEYFRAMES flag is set, otherwise * every frame is a key frame. * * @field DWORD | dwQuality | Specifies the quality value passed * to a video compressor. This member is not used for * an audio compressor. * * @field DWORD | dwBytesPerSecond | Specifies the data rate a video * compressor should use. This member is used only * if the AVICOMPRESSF_DATARATE flag is set. * * @field DWORD | dwFlags | Specifies the flags used for compression: * * @flag AVICOMPRESSF_INTERLEAVE | Indicates this stream is to be interleaved * every frames * with respect to the first stream. * * @flag AVICOMPRESSF_KEYFRAMES | Indicates this video stream * is to be saved with key frames at least * every frames. * By default, every frame will be a key frame. * * @flag AVICOMPRESSF_DATARATE | Indicates this video stream * is to be compressed with the data rate * specified in . * * @flag AVICOMPRESSF_VALID | Indicates this structure contains * valid data. If this flag is set, AVIFile uses the structure * data to set the default compression values for . * If an empty structure is passed and this flag is not set, * some defaults will be chosen. * * @field LPVOID | lpFormat | Specifies a pointer to a structure * defining the data format. For an audio stream, * this is an structure. * * @field DWORD | cbFormat | Specifies the size of the data referenced by * * * @field LPVOID | lpParms | Used internally to store compressor * specific data. * * @field DWORD | cbParms | Specifies the size of the data referenced by * * * @field DWORD | dwInterleaveEvery | Specifies how often * to interleave stream data with the data * from the first stream. Used only if the * AVICOMPRESSF_INTERLEAVE flag is set. * ***************************************************************************/ /******************************************************************* * @doc EXTERNAL AVISave * * @api LONG | AVISave | This function is used to save an AVI file. * * @parm LPCTSTR | szFile | Specifies a zero-terminated string * containing the name of the file to save. * * @parm CLSID FAR * | pclsidHandler | Specifies a pointer to the * file handler used to write the file. The file will * be created by calling using this handler. If * a handler is not specified, a default one is selected based * upon the file extension. * * @parm AVISAVECALLBACK | lpfnCallback | Specifies a far pointer to * a callback function for the save operation. * * @parm int | nStreams | Specifies the number of streams saved in the * the file. * * @parm PAVISTREAM | pavi | Specifies a pointer an AVI stream. * This parameter is paired with

. The parameter * pair can be repeated as a variable number of arguments. * * @parm LPAVICOMPRESSOPTIONS | lpOptions | Specifies a pointer to an * structure containing the compression * options for the stream referenced by

. * This parameter is paired with

. The parameter * pair can be repeated as a variable number of arguments. * * @parm .| . . | Additional streams can be appened * by including more

and

parameter pairs. * * @rdesc Returns AVIERR_OK if successful; otherwise it returns an error code. * * @comm This function saves an AVI sequence to the file * specified by

. The

and

parameters * define the streams saved. If saving more than one stream, * repeat the

and

parameter pair for * each additional stream. * * A callback function can be supplied in

to * display status information and let the user cancel the * save operation. The callback uses the following format: * * LONG FAR PASCAL SaveCallback(int nPercent) * * The

parameter specifies the percentage of the * file saved. * * The callback function should return AVIERR_OK if the * operation should continue and AVIERR_USERABORT if the * user wishes to abort the save operation. * * * @xref * *******************************************************************/ EXTERN_C HRESULT CDECL AVISave(LPCTSTR szFile, CLSID FAR *pclsidHandler, AVISAVECALLBACK lpfnCallback, int nStreams, PAVISTREAM pavi, LPAVICOMPRESSOPTIONS lpOptions, ... ) { PAVISTREAM FAR *apavi; LPAVICOMPRESSOPTIONS FAR *alpOptions; int i; HRESULT hr; // // We were passed arguments of the form PAVI, OPTIONS, PAVI, OPTIONS, etc. // for AVISaveV, we need to separate these into an array of PAVI's and // an array of LPAVICOMPRESSOPTIONS. // // !!!not only that, but we need to do it properly, using va_arg etc!!! va_list va; apavi = (PAVISTREAM FAR *)GlobalAllocPtr(GMEM_MOVEABLE, nStreams * sizeof(PAVISTREAM)); alpOptions = (LPAVICOMPRESSOPTIONS FAR *)GlobalAllocPtr(GMEM_MOVEABLE, nStreams * sizeof(LPAVICOMPRESSOPTIONS)); if (!apavi || !alpOptions) return ResultFromScode(AVIERR_MEMORY); // first two args are explicit if (nStreams) { apavi[0] = pavi; alpOptions[0] = lpOptions; } // do the rest by *portable* varargs methods va_start(va, lpOptions); for (i = 1; i < nStreams; i++) { apavi[i] = va_arg(va, PAVISTREAM); alpOptions[i] = va_arg(va, LPAVICOMPRESSOPTIONS); } va_end(va); hr = AVISaveV(szFile, pclsidHandler, lpfnCallback, nStreams, apavi, alpOptions); GlobalFreePtr(apavi); GlobalFreePtr(alpOptions); return hr; } #ifdef UNICODE //ansi thunk for AVISave - same, but calls AVISaveVA instead EXTERN_C HRESULT CDECL AVISaveA( LPCSTR szFile, CLSID FAR *pclsidHandler, AVISAVECALLBACK lpfnCallback, int nStreams, PAVISTREAM pavi, LPAVICOMPRESSOPTIONS lpOptions, ... ) { PAVISTREAM FAR *apavi; LPAVICOMPRESSOPTIONS FAR *alpOptions; int i; HRESULT hr; // // We were passed arguments of the form PAVI, OPTIONS, PAVI, OPTIONS, etc. // for AVISaveV, we need to separate these into an array of PAVI's and // an array of LPAVICOMPRESSOPTIONS. // // !!!not only that, but we need to do it properly, using va_arg etc!!! va_list va; apavi = (PAVISTREAM FAR *)GlobalAllocPtr(GMEM_MOVEABLE, nStreams * sizeof(PAVISTREAM)); alpOptions = (LPAVICOMPRESSOPTIONS FAR *)GlobalAllocPtr(GMEM_MOVEABLE, nStreams * sizeof(LPAVICOMPRESSOPTIONS)); if (!apavi || !alpOptions) return ResultFromScode(AVIERR_MEMORY); // first two args are explicit if (nStreams) { apavi[0] = pavi; alpOptions[0] = lpOptions; } // do the rest by *portable* varargs methods va_start(va, lpOptions); for (i = 1; i < nStreams; i++) { apavi[i] = va_arg(va, PAVISTREAM); alpOptions[i] = va_arg(va, LPAVICOMPRESSOPTIONS); } va_end(va); hr = AVISaveVA(szFile, pclsidHandler, lpfnCallback, nStreams, apavi, alpOptions); GlobalFreePtr(apavi); GlobalFreePtr(alpOptions); return hr; } #else #ifdef _WIN32 EXTERN_C HRESULT CDECL AVISaveW( LPCWSTR szFile, CLSID FAR *pclsidHandler, AVISAVECALLBACK lpfnCallback, int nStreams, PAVISTREAM pavi, LPAVICOMPRESSOPTIONS lpOptions, ... ) { return E_FAIL; } #endif #endif BOOL FAR PASCAL DummySaveCallback(int iProgress) { return FALSE; // do nothing, allow save to continue } #ifdef UNICODE // Ansi thunk for AVISaveV STDAPI AVISaveVA(LPCSTR szFile, CLSID FAR *pclsidHandler, AVISAVECALLBACK lpfnCallback, int nStreams, PAVISTREAM FAR * ppavi, LPAVICOMPRESSOPTIONS FAR * plpOptions) { // convert the filename, and then call AVISaveVW LPWSTR pW; int sz; HRESULT hr; sz = lstrlenA(szFile)+1; pW = (LPWSTR) (LocalAlloc(LPTR, sz * sizeof(WCHAR))); if (pW == NULL) { return ResultFromScode(AVIERR_MEMORY); } mbstowcs(pW, szFile, sz); hr = AVISaveVW(pW, pclsidHandler, lpfnCallback, nStreams, ppavi, plpOptions); LocalFree((HANDLE)pW); return hr; } #else #if _WIN32 STDAPI AVISaveVW(LPCWSTR szFile, CLSID FAR *pclsidHandler, AVISAVECALLBACK lpfnCallback, int nStreams, PAVISTREAM FAR * ppavi, LPAVICOMPRESSOPTIONS FAR * plpOptions) { return E_FAIL; } #endif #endif /************************************************************************** * @doc EXTERNAL AVIStreamTimeToSampleNoClip * * @api LONG | AVIStreamTimeToSampleNoClip | Converts from milliseconds to * samples. It is different from the regular API in that 1) it doesn't * clip to the size of the stream, and will return "theoretical" positions * past the start or end, and 2) it will always round UP because that's * what we want. * * @parm PAVISTREAM | pavi | Specifies a handle to an open stream. * * @parm LONG | lTime | Specifies the time in milliseconds. * * @devnote Currently, this doesn't call a handler function at all. * * @comm Samples typically correspond to audio samples or video frames. * Other stream types might support different formats than these. * @rdesc Returns the converted time, or -1 on error. * * @xref AVIStreamSampleToTime * *************************************************************************/ STDAPI_(LONG) AVIStreamTimeToSampleNoClip (PAVISTREAM pavi, LONG lTime) { AVISTREAMINFOW avistream; HRESULT hr; LONG lSample; // Invalid time if (lTime < 0) return -1; hr = pavi->lpVtbl->Info(pavi, &avistream, sizeof(avistream)); if (hr != NOERROR || avistream.dwScale == 0) { DPF("Error in AVIStreamTimeToSample!\n"); return lTime; } // This is likely to overflow if we're not careful for long AVIs // so keep the 1000 inside the brackets. lSample = muldivru32(lTime, avistream.dwRate, avistream.dwScale * 1000); return lSample; } // Converts from samples to milliseconds. // It is different from the regular API in that 1) it doesn't // clip to the size of the stream, and will return "theoretical" positions // past the start or end, and 2) it will always round DOWN because that's // what we want. STDAPI_(LONG) AVIStreamSampleToTimeNoClip (PAVISTREAM pavi, LONG lSample) { AVISTREAMINFOW avistream; HRESULT hr; hr = pavi->lpVtbl->Info(pavi, &avistream, sizeof(avistream)); if (hr != NOERROR || avistream.dwRate == 0) { DPF("Error in AVIStreamSampleToTime!\n"); return lSample; } // lSample * 1000 would overflow too easily return muldivrd32(lSample, avistream.dwScale * 1000, avistream.dwRate); } #define AVIStreamSampleToSampleNoClip(pavi1, pavi2, l) \ AVIStreamTimeToSampleNoClip(pavi1,AVIStreamSampleToTimeNoClip(pavi2, l)) /********************************************************************** * @doc EXTERNAL AVISaveV * * @api LONG | AVISaveV | This function is used to save an AVI file. * * @parm LPCTSTR | szFile | Specifies a zero-terminated string * containing the name of the file to save. * * @parm CLSID FAR * | pclsidHandler | Specifies a pointer to the * file handler used to write the file. The file will * be created by calling using this handler. If * a handler is not specified, a default one is selected based upon * the file extension. * * @parm AVISAVECALLBACK | lpfnCallback | Specifies a pointer to a callback * function used to display status information and let the use * cancel the save operation. * * @parm int | nStreams | Specifies the number of streams to save. * * @parm PAVISTREAM FAR * | ppavi | Specifies a pointer to an * array of pointers. The array uses one pointer * for each stream. * * @parm LPAVICOMPRESSOPTIONS FAR * | plpOptions | Specifies a pointer * to an array of pointers. The * uses one pointer for each stream. * * @rdesc Returns AVIERR_OK on success, an error code otherwise. * * @comm This function is equivalent to except * the streams are passed in an array instead of as a * variable number of arguments. ( is to * as is to .) * * @xref * ********************************************************************/ STDAPI AVISaveV(LPCTSTR szFile, CLSID FAR *pclsidHandler, AVISAVECALLBACK lpfnCallback, int nStreams, PAVISTREAM FAR * ppavi, LPAVICOMPRESSOPTIONS FAR * plpOptions) { int stream; MainAVIHeader hdrNew; PAVIFILE pfilesave = 0; HRESULT hr; AVISTREAMINFOW strhdr; AVIFILEINFOW finfo; LONG cbFormat; DWORD dwSamplesRead; LPVOID lpBuffer = 0; DWORD dwBufferSize; LONG l; DWORD dwSize; DWORD dwFlags; WORD cktype; LPBITMAPINFOHEADER lpbi; DWORD dwInterleaveEvery = 0; #define MAXSTREAMS 64 int iVideoStream = -1; PAVISTREAM apavi[MAXSTREAMS]; PAVISTREAM apaviNew[MAXSTREAMS]; LONG lDone[MAXSTREAMS]; LONG lInterval; if (nStreams > MAXSTREAMS) return ResultFromScode(AVIERR_INTERNAL); for (stream = 0; stream < nStreams; stream++) { apavi[stream] = NULL; apaviNew[stream] = NULL; } // // Open file and write out the main header // DPF("Creating new file\n"); hr = AVIFileOpen(&pfilesave, szFile, OF_CREATE | OF_WRITE | OF_SHARE_EXCLUSIVE, pclsidHandler); if (hr != 0) goto Error; AVIFileInfoW(pfilesave, &finfo, sizeof(finfo)); DPF("Creating compressed streams\n"); for (stream = 0; stream < nStreams; stream++) { #ifdef USE_ISVALIDINTERFACE if (!IsValidInterface(ppavi[stream])) { hr = ResultFromScode(AVIERR_INTERNAL); goto Error; } #endif hr = AVIStreamInfoW(ppavi[stream], &strhdr, sizeof(strhdr)); if (hr != AVIERR_OK) { DPF("Error from AVIStreamInfo!\n"); goto Error; } // Find the video stream.... if (strhdr.fccType == streamtypeVIDEO) { if (iVideoStream < 0) { iVideoStream = stream; } // Allow interleaving for any other type of stream } else { if (dwInterleaveEvery == 0) { // Should the interleave factor be in the options at all? if (plpOptions && plpOptions[stream] && plpOptions[stream]->dwFlags & AVICOMPRESSF_INTERLEAVE) dwInterleaveEvery = plpOptions[stream]->dwInterleaveEvery; } } apavi[stream] = NULL; if (plpOptions && plpOptions[stream] && (plpOptions[stream]->fccHandler || plpOptions[stream]->lpFormat)) { DWORD dwKeyFrameEvery = plpOptions[stream]->dwKeyFrameEvery; if (finfo.dwCaps & AVIFILECAPS_ALLKEYFRAMES) plpOptions[stream]->dwKeyFrameEvery = 1; // If they've given compression options for this stream, // use them.... hr = AVIMakeCompressedStream(&apavi[stream], ppavi[stream], plpOptions[stream], NULL); plpOptions[stream]->dwKeyFrameEvery = dwKeyFrameEvery; if (hr != 0) { DPF("AVISave: Failed to create compressed stream!\n"); apavi[stream] = NULL; goto Error; // !!! } else { hr = AVIStreamInfoW(apavi[stream], &strhdr, sizeof(strhdr)); if (hr != 0) { DPF("AVISave: Failed to create compressed stream!\n"); AVIStreamClose(apavi[stream]); apavi[stream] = NULL; goto Error; // !!! } } } if (apavi[stream] == NULL) { // otherwise just copy the stream over.... apavi[stream] = ppavi[stream]; AVIStreamAddRef(apavi[stream]); } lDone[stream] = AVIStreamStart(apavi[stream]); } // Put the video stream first, so interleaving will work. // !!! if (iVideoStream > 0) { PAVISTREAM p; p = apavi[iVideoStream]; apavi[iVideoStream] = apavi[0]; apavi[0] = p; iVideoStream = 0; } if (lpfnCallback == NULL) lpfnCallback = DummySaveCallback; /* pick a good buffer size and go for it.... */ dwBufferSize = 32768L; lpBuffer = GlobalAllocPtr(GMEM_MOVEABLE, dwBufferSize); if (!lpBuffer) { hr = ResultFromScode(AVIERR_MEMORY); goto Error; } // // Construct AVI file header // AVIStreamInfoW(apavi[0], &strhdr, sizeof(strhdr)); hdrNew.dwMicroSecPerFrame = muldiv32(1000000L, strhdr.dwScale, strhdr.dwRate); hdrNew.dwMaxBytesPerSec = 0; hdrNew.dwPaddingGranularity = 0; hdrNew.dwFlags = AVIF_HASINDEX; hdrNew.dwFlags &= ~(AVIF_ISINTERLEAVED | AVIF_WASCAPTUREFILE | AVIF_MUSTUSEINDEX); hdrNew.dwTotalFrames = strhdr.dwLength; hdrNew.dwInitialFrames = 0; hdrNew.dwStreams = nStreams; hdrNew.dwSuggestedBufferSize = 32768; if (iVideoStream >= 0) { cbFormat = dwBufferSize; hr = AVIStreamReadFormat(apavi[iVideoStream], AVIStreamStart(apavi[iVideoStream]), lpBuffer, &cbFormat); if (cbFormat < sizeof(BITMAPINFOHEADER)) { hr = ResultFromScode(AVIERR_INTERNAL); } if (hr != 0) { DPF("AVISave: Error from initial ReadFormat!\n"); goto Error; } lpbi = (LPBITMAPINFOHEADER) lpBuffer; hdrNew.dwWidth = lpbi->biWidth; hdrNew.dwHeight = lpbi->biHeight; lInterval = 1; } else { hdrNew.dwWidth = 0; hdrNew.dwHeight = 0; lInterval = AVIStreamTimeToSample(apavi[0], 500); } // // Loop through streams and write out stream header // for (stream = 0; stream < nStreams; stream++) { // DPF2("Making stream %d header LIST\n", stream); AVIStreamInfoW(apavi[stream], &strhdr, sizeof(strhdr)); strhdr.dwInitialFrames = 0; // If we're interleaving, skew all streams but video by 3/4 of a second // so their renderers won't starve if (dwInterleaveEvery > 0 && stream > 0) { if (strhdr.fccType != streamtypeVIDEO) { strhdr.dwInitialFrames = AVIStreamTimeToSample(apavi[0], 750); DPF("Stream %d has %lu initial frames\n", stream, strhdr.dwInitialFrames); } } // // Get stream format and write it out // cbFormat = dwBufferSize; hr = AVIStreamReadFormat(apavi[stream], AVIStreamStart(apavi[stream]), lpBuffer, &cbFormat); if (hr != AVIERR_OK) goto Error; // !!! Overflow? if (!cbFormat) { // !!! } hr = AVIFileCreateStreamW(pfilesave, &apaviNew[stream], &strhdr); #if 0 if (hr != AVIERR_OK || apaviNew[stream] == NULL) goto Error; #else // If we can't make a stream, continue with the other streams.... if (hr != AVIERR_OK || apaviNew[stream] == NULL) { int i; DPF("AVISave: Couldn't create stream in new file!\n"); AVIStreamClose(apavi[stream]); for (i = stream + 1; i < nStreams; i++) { apavi[stream] = apavi[stream + 1]; } --nStreams; --stream; continue; } #endif hr = AVIStreamSetFormat(apaviNew[stream], 0, lpBuffer, cbFormat); if (hr != AVIERR_OK) { DPF("Initial set format failed!\n"); goto Error; } cbFormat = dwBufferSize; hr = AVIStreamReadData(apavi[stream], ckidSTREAMHANDLERDATA, lpBuffer, &cbFormat); // !!! overflow? if (hr == AVIERR_OK && cbFormat) { /* ** Make the stream Data data chunk */ // DPF2("Making stream %ld Data data chunk\n", stream); hr = AVIStreamWriteData(apaviNew[stream], ckidSTREAMHANDLERDATA, lpBuffer, cbFormat); if (hr != AVIERR_OK) goto Error; } if (strhdr.dwInitialFrames > hdrNew.dwInitialFrames) hdrNew.dwInitialFrames = strhdr.dwInitialFrames; // This will round UP to the nearest video sample, which is what we want dwSize = AVIStreamSampleToSampleNoClip(apavi[0], apavi[stream], AVIStreamLength(apavi[stream])); if (dwSize > hdrNew.dwTotalFrames) hdrNew.dwTotalFrames = dwSize; // !!! Should call ReadExtra and WriteExtra to move over information! } if (nStreams <= 0) { DPF("No streams at all accepted by the file!\n"); goto Error; } // // We've written the header. Now, there are two possibilities: // // 1.) File is interleaved. We loop in time from beginning to end, // then loop through the streams and write out any data for the // current time. // // 2.) File is not interleaved. We loop through the streams and // write each one out separately. // if (dwInterleaveEvery > 0) { DPF("Saving interleaved: factor = %lu, intial = %lu, total = %lu\n", dwInterleaveEvery, hdrNew.dwInitialFrames, hdrNew.dwTotalFrames); if (dwInterleaveEvery == 1) { hdrNew.dwFlags |= AVIF_ISINTERLEAVED; AVIFileEndRecord(pfilesave); // Make first record.... } // // Interleaved case: loop from start to end... // for (l = - (LONG) hdrNew.dwInitialFrames; l < (LONG) hdrNew.dwTotalFrames; l += lInterval) { //DPF2("Writing data for frame #%ld/%lu\n", l, hdrNew.dwTotalFrames); // // Loop through all of the streams to see what needs to be // done at this time... // for (stream = 0; stream < nStreams; stream++) { LONG lPos; LONG lPosNext; LONG lStart; LONG lEnd; hr = AVIStreamInfoW(apaviNew[stream], &strhdr, sizeof(strhdr)); if (hr != AVIERR_OK) goto Error; if (l < - (LONG) strhdr.dwInitialFrames) continue; // !!! Better use of TWOCCs... if (strhdr.fccType == streamtypeAUDIO) cktype = cktypeWAVEbytes; else if (strhdr.fccType == streamtypeVIDEO) { if (strhdr.fccHandler == comptypeDIB) cktype = cktypeDIBbits; else cktype = cktypeDIBcompressed; } else cktype = aviTWOCC('x', 'x'); // // Time is based on the first stream: // Right now, we want to write out any data in the current // stream that lines up between time and in the // first stream. // lPos = l + strhdr.dwInitialFrames; lPosNext = lPos + lInterval; lStart = lDone[stream]; if (l >= (LONG) hdrNew.dwTotalFrames - lInterval) { // If this is going to be the last time through the // interleave loop, make sure everything gets written. lEnd = AVIStreamEnd(apavi[stream]); } else { // // Complication: to make other data come in bigger chunks, // we only write it out every once in a while. // We will interleave any non-video stream, not just audio. if (strhdr.fccType != streamtypeVIDEO && stream != 0) { if ((lPos % dwInterleaveEvery) != 0) continue; lPosNext = lPos + dwInterleaveEvery; } if (stream != 0) { // // Figure out the data for this stream that needs to be // written this time. Round UP so that the data goes // early in the file so the stream won't starve. // lEnd = AVIStreamSampleToSampleNoClip(apavi[stream], apavi[0], lPosNext); } else { lEnd = lPosNext; } lEnd = min(lEnd, AVIStreamEnd(apavi[stream])); } lDone[stream] = lEnd; //DPF2(" Stream %d: (%ld - %ld)\n", stream, lStart, lEnd); // // Loop until we've read all we want. // while (lEnd > lStart) { // !!! Right here, we should call AVIStreamGetFormat // and then call AVIStreamSetFormat on the new // streams. // !!! Whose job is it to tell if the format has really // changed? cbFormat = dwBufferSize; hr = AVIStreamReadFormat(apavi[stream], lStart, lpBuffer, &cbFormat); if (hr != AVIERR_OK) { DPF("AVIStreamReadFormat failed!\n"); goto Error; } hr = AVIStreamSetFormat(apaviNew[stream], lStart, lpBuffer, cbFormat); if (hr != AVIERR_OK) { // !!! Oh, well: we couldn't write the palette change... DPF("AVIStreamSetFormat failed!\n"); } ReadAgain0: cbFormat = dwBufferSize; dwSamplesRead = 0; hr = AVIStreamRead(apavi[stream], lStart, lEnd - lStart, lpBuffer, dwBufferSize, &dwSize, &dwSamplesRead); if (// dwSamplesRead == 0 && (GetScode(hr) == AVIERR_BUFFERTOOSMALL)) { // // The frame didn't fit in our buffer. // Make a bigger buffer. // dwBufferSize *= 2; DPF("Resizing buffer to be %lx bytes\n", dwBufferSize); lpBuffer = GlobalReAllocPtr(lpBuffer, dwBufferSize, GMEM_MOVEABLE); if (lpBuffer) goto ReadAgain0; hr = ResultFromScode(AVIERR_MEMORY); } if (hr != 0) { DPF("AVISave: Error %08lx reading stream %d, position %ld!\n", (DWORD) hr, stream, lStart); goto Error; } dwFlags = 0; if (AVIStreamFindSample(apavi[stream], lStart, FIND_KEY | FIND_PREV) == lStart) dwFlags |= AVIIF_KEYFRAME; hr = AVIStreamWrite(apaviNew[stream], -1, dwSamplesRead, lpBuffer, dwSize, // cktype, // !!! dwFlags, 0L, 0L); if (hr != AVIERR_OK) goto Error; lStart += dwSamplesRead; if ((LONG) dwSamplesRead != lEnd - lStart) { // DPF2(" %lu of %lu actually read....\n", dwSamplesRead, lEnd - lStart); } } } // // Mark the end of the frame, in case we're writing out // the "strict" interleaved format with LIST 'rec' chunks... // if (dwInterleaveEvery == 1) { hr = AVIFileEndRecord(pfilesave); if (hr != AVIERR_OK) { DPF("AVISave: Error from EndRecord!\n"); goto Error; } } // Give the application a chance to update status and the user // a chance to abort... if (lpfnCallback((int) muldiv32(l + hdrNew.dwInitialFrames, 100, hdrNew.dwInitialFrames + hdrNew.dwTotalFrames))) { hr = ResultFromScode(AVIERR_USERABORT); DPF("AVISave: Aborted!\n"); goto Error; } } } else { // // Non-interleaved case: loop through the streams and write // each one out by itself. // DPF("Saving non-interleaved.\n"); for (stream = 0; stream < nStreams; stream++) { if (lpfnCallback(MulDiv(stream, 100, nStreams))) { hr = ResultFromScode(AVIERR_USERABORT); goto Error; } AVIStreamInfoW(apavi[stream], &strhdr, sizeof(strhdr)); DPF("Saving stream %d: start=%lx, len=%lx\n", stream, strhdr.dwStart, strhdr.dwLength); // !!! Need better cktype handling.... if (strhdr.fccType == streamtypeAUDIO) cktype = cktypeWAVEbytes; else if (strhdr.fccType == streamtypeVIDEO) { if (strhdr.fccHandler == comptypeDIB) cktype = cktypeDIBbits; else cktype = cktypeDIBcompressed; } else cktype = aviTWOCC('x', 'x'); // // As usual, there are two possibilities: // // 1.) "wave-like" data, where lots of samples can be in // a single chunk. In this case, we write out big chunks // with many samples at a time. // // 2.) "video-like" data, where each sample is a different // size, and thus each must be written individually. // if (strhdr.dwSampleSize != 0) { /* It's wave-like data: lots of samples per chunk */ l = strhdr.dwStart; while (l < (LONG) strhdr.dwLength) { DWORD dwRead; // Make the format of the new stream // match the old one at every point.... // // !!! Whose job is it to tell if the format has really // changed? cbFormat = dwBufferSize; hr = AVIStreamReadFormat(apavi[stream], l, lpBuffer, &cbFormat); if (hr != AVIERR_OK) { DPF("AVIStreamReadFormat failed!\n"); goto Error; } hr = AVIStreamSetFormat(apaviNew[stream], l, lpBuffer, cbFormat); if (hr != AVIERR_OK) { DPF("AVIStreamSetFormat failed!\n"); // !!! Oh, well: we couldn't write the palette change... } // // Read some data... // ReadAgain1: dwSize = dwBufferSize; dwSamplesRead = 0; dwRead = min(dwBufferSize / strhdr.dwSampleSize, strhdr.dwLength - (DWORD) l); hr = AVIStreamRead(apavi[stream], l, dwRead, lpBuffer, dwBufferSize, &dwSize, &dwSamplesRead); if (// dwSamplesRead == 0 && (GetScode(hr) == AVIERR_BUFFERTOOSMALL)) { // // The frame didn't fit in our buffer. // Make a bigger buffer. // dwBufferSize *= 2; lpBuffer = GlobalReAllocPtr(lpBuffer, dwBufferSize, GMEM_MOVEABLE); if (lpBuffer) goto ReadAgain1; } // !!! Check if format has changed dwFlags = 0; // !!! KEYFRAME? DPF("Save: Read %lx/%lx samples at %lx\n", dwSamplesRead, dwRead, l); if (hr != AVIERR_OK) { DPF("Save: Read failed! (%08lx) pos=%lx, len=%lx\n", (DWORD) hr, l, dwRead); goto Error; } if (dwSamplesRead == 0) { DPF("Ack: Read zero samples!"); if (l + 1 == (LONG) strhdr.dwLength) { DPF("Pretending it's OK, since this was the last one...."); break; } hr = ResultFromScode(AVIERR_FILEREAD); goto Error; } l += dwSamplesRead; // // Write the data out... // hr = AVIStreamWrite(apaviNew[stream], -1, dwSamplesRead, lpBuffer, dwSize, // !!! cktype, // !!!TWOCCFromFOURCC(ckid), dwFlags, 0L, 0L); if (hr != AVIERR_OK) { DPF("AVIStreamWrite failed! (%08lx)\n", (DWORD) hr); goto Error; } if (lpfnCallback(MulDiv(stream, 100, nStreams) + (int) muldiv32(l, 100, nStreams * strhdr.dwLength))) { hr = ResultFromScode(AVIERR_USERABORT); goto Error; } } } else { /* It's video-like data: one sample (frame) per chunk */ for (l = strhdr.dwStart; l < (LONG) strhdr.dwLength; l++) { // !!! Right here, we should call AVIStreamGetFormat // and then call AVIStreamSetFormat on the new // streams. // !!! Whose job is it to tell if the format has really // changed? cbFormat = dwBufferSize; hr = AVIStreamReadFormat(apavi[stream], l, lpBuffer, &cbFormat); if (hr != AVIERR_OK) { DPF("AVIStreamReadFormat failed!\n"); goto Error; } hr = AVIStreamSetFormat(apaviNew[stream], l, lpBuffer, cbFormat); if (hr != AVIERR_OK) { // !!! Oh, well: we couldn't write the palette change... DPF("AVIStreamSetFormat failed!\n"); } ReadAgain: dwSize = dwBufferSize; /* Write out a single frame.... */ dwSamplesRead = 0; hr = AVIStreamRead(apavi[stream], l, 1, lpBuffer, dwBufferSize, &dwSize, &dwSamplesRead); // !!! Check if format has changed (palette change) if (// dwSamplesRead == 0 && (GetScode(hr) == AVIERR_BUFFERTOOSMALL)) { // // The frame didn't fit in our buffer. // Make a bigger buffer. // dwBufferSize *= 2; lpBuffer = GlobalReAllocPtr(lpBuffer, dwBufferSize, GMEM_MOVEABLE); if (lpBuffer) goto ReadAgain; } if (dwSamplesRead != 1 || hr != 0) { hr = ResultFromScode(AVIERR_FILEREAD); goto Error; } dwFlags = 0; // !!!! // // Check whether this should be marked a key frame. // // !!! shouldn't this be returned from AVIStreamRead()? // if (AVIStreamFindSample(apavi[stream], l, FIND_KEY | FIND_PREV) == l) dwFlags |= AVIIF_KEYFRAME; // // Write the chunk out. // hr = AVIStreamWrite(apaviNew[stream], -1, dwSamplesRead, lpBuffer, dwSize, // !!! cktype, // !!!TWOCCFromFOURCC(ckid), dwFlags, 0L, 0L); if (hr != AVIERR_OK) goto Error; // // Video frames can be big, so call back every time. // if (lpfnCallback(MulDiv(stream, 100, nStreams) + (int) muldiv32(l, 100, nStreams * strhdr.dwLength))) { hr = ResultFromScode(AVIERR_USERABORT); goto Error; } } } } } Error: // // We're done, one way or another. // /* Free buffer */ if (lpBuffer) { GlobalFreePtr(lpBuffer); } // If everything's OK so far, finish writing the file. // Close the file, free resources associated with writing it. if (pfilesave) { // Release all of our new streams for (stream = 0; stream < nStreams; stream++) { if (apaviNew[stream]) AVIStreamClose(apaviNew[stream]); } if (hr != AVIERR_OK) AVIFileClose(pfilesave); else { // !!! ACK: AVIFileClose doesn't return an error! How do I tell // if it worked? // !!! does this mean I need a Flush() call? /* hr = */ AVIFileClose(pfilesave); } } // Release all of our streams for (stream = 0; stream < nStreams; stream++) { if (apavi[stream]) AVIStreamClose(apavi[stream]); } if (hr != 0) { DPF("AVISave: Returning error %08lx\n", (DWORD) hr); } return hr; }