windows-nt/Source/XPSP1/NT/multimedia/media/avi/avicap.io/capio.c

1395 lines
44 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/****************************************************************************
*
* capio.c
*
* i/o routines for video capture
*
* Microsoft Video for Windows Sample Capture Class
*
* 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.
*
***************************************************************************/
//#define USE_AVIFILE 1
#define JMK_HACK_DONTWRITE TRUE
#define INC_OLE2
#pragma warning(disable:4103)
#include <windows.h>
#include <windowsx.h>
#include <win32.h>
#include <mmsystem.h>
#include <vfw.h>
#include <mmreg.h>
#include <mmddk.h>
#include "ivideo32.h"
#include "mmdebug.h"
#ifdef USE_ACM
#include <msacm.h>
#endif
#include "avicapi.h"
#include "time.h"
extern UINT GetSizeOfWaveFormat (LPWAVEFORMATEX lpwf);
STATICFN BOOL IndexVideo (LPCAPSTREAM lpcs, DWORD dwSize, BOOL bKeyFrame);
STATICFN BOOL IndexAudio (LPCAPSTREAM lpcs, DWORD dwSize);
#ifdef _DEBUG
#define DSTATUS(lpcs, sz) statusUpdateStatus(lpcs, IDS_CAP_INFO, (LPTSTR) TEXT(sz))
#else
#define DSTATUS(lpcs, sz)
#endif
//
// Define function variables for dynamically linking to the async IO
// completion routines on NT. This should allow the same code to run
// on Win95 which does not have these entry points.
//
HANDLE (WINAPI *pfnCreateIoCompletionPort)(
HANDLE FileHandle,
HANDLE ExistingCompletionPort,
DWORD CompletionKey,
DWORD NumberOfConcurrentThreads
);
BOOL (WINAPI *pfnGetQueuedCompletionStatus)(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytesTransferred,
LPDWORD lpCompletionKey,
LPOVERLAPPED *lpOverlapped,
DWORD dwMilliseconds
);
HINSTANCE hmodKernel; // Handle to loaded Kernel32.dll
#ifdef USE_AVIFILE
#include "capio.avf"
#else //---------------- ! using Avifile ----------------------------
// The following are anded with the size in the index
#define IS_AUDIO_CHUNK 0x80000000
#define IS_KEYFRAME_CHUNK 0x40000000
#define IS_DUMMY_CHUNK 0x20000000
#define IS_GRANULAR_CHUNK 0x10000000
#define INDEX_MASK (IS_AUDIO_CHUNK | IS_KEYFRAME_CHUNK | IS_DUMMY_CHUNK | IS_GRANULAR_CHUNK)
// Add an index entry for a video frame
// dwSize is the size of data ONLY, not including the chunk or junk
// Returns: TRUE if index space is not exhausted
//
STATICFN BOOL IndexVideo (LPCAPSTREAM lpcs, DWORD dwSize, BOOL bKeyFrame)
{
if (lpcs->dwIndex < lpcs->sCapParms.dwIndexSize) {
*lpcs->lpdwIndexEntry = dwSize | (bKeyFrame ? IS_KEYFRAME_CHUNK : 0);
++lpcs->lpdwIndexEntry;
++lpcs->dwIndex;
++lpcs->dwVideoChunkCount;
return TRUE;
}
dprintf("\n***WARNING*** Indexvideo space exhausted\n");
return FALSE;
}
// Add an index entry for an audio buffer
// dwSize is the size of data ONLY, not including the chunk or junk
// Returns: TRUE if index space is not exhausted
//
STATICFN BOOL IndexAudio (LPCAPSTREAM lpcs, DWORD dwSize)
{
if (lpcs->dwIndex < lpcs->sCapParms.dwIndexSize) {
*lpcs->lpdwIndexEntry = dwSize | IS_AUDIO_CHUNK;
++lpcs->lpdwIndexEntry;
++lpcs->dwIndex;
++lpcs->dwWaveChunkCount;
return TRUE;
}
dprintf("\n***WARNING*** Indexaudio space exhausted\n");
return FALSE;
}
DWORD CalcWaveBufferSize (LPCAPSTREAM lpcs)
{
DWORD dw;
if (!lpcs->lpWaveFormat)
return 0L;
// at least .5 second
dw = lpcs->lpWaveFormat->nAvgBytesPerSec / 2;
if (lpcs->sCapParms.wChunkGranularity) {
if (dw % lpcs->sCapParms.wChunkGranularity) {
dw += lpcs->sCapParms.wChunkGranularity -
dw % lpcs->sCapParms.wChunkGranularity;
}
}
dw = max ((1024L * 16), dw); // at least 16K
dw -= sizeof(RIFF);
dprintf("Wave buffer size = %ld", dw);
return dw;
}
/*
* AVIPreloadFat
*
* Force FAT for this file to be loaded into the FAT cache
*
*/
VOID WINAPI AVIPreloadFat (LPCAPSTREAM lpcs)
{
DWORD dw;
#ifdef CHICAGO
DWORD dwPos;
assert (lpcs->lpDropFrame);
// save the current file pointer then seek to the end of the file
//
dwPos = SetFilePointer (lpcs->hFile, 0, NULL, FILE_CURRENT);
dw = SetFilePointer (lpcs->hFile, 0, NULL, FILE_END);
if ((dw == (DWORD)-1) || (dw < lpcs->dwBytesPerSector)) {
// put the file pointer back to where it was
SetFilePointer (lpcs->hFile, dwPos, NULL, FILE_BEGIN);
return;
}
// read the last sector of the file, just to force
// the fat for the file to be loaded
//
ReadFile (lpcs->hFile, lpcs->lpDropFrame, lpcs->dwBytesPerSector, &dw, NULL);
// put the file pointer back to where it was
//
SetFilePointer (lpcs->hFile, dwPos, NULL, FILE_BEGIN);
#else
// Load all the FAT information. On NT this is sufficient for FAT
// files. On NTFS partitiions there is no way we can read in all the
// mapping information.
GetFileSize(lpcs->hFile, &dw);
#endif
}
#ifdef JMK_HACK_DONTWRITE
static BOOL bDontWrite;
#endif
// Write data to the capture file
// Returns: TRUE on a successful write
//
UINT NEAR PASCAL AVIWrite (
LPCAPSTREAM lpcs,
LPVOID pbuf,
DWORD dwSize,
UINT uIndex, // index of header for this buffer, -1 for step capture
UINT uType,
LPBOOL lpbPending)
{
DWORD dwWritten;
DWORD dwGran;
// the buffer must be sector aligned if using non-buffered IO
// and the size must be at least word aligned
// uIndex == -1 if this is a dummy frame write
// uIndex == Index into alpVideoHdr OR index in alpWaveHdr based on uType
//
assert (!lpcs->fUsingNonBufferedIO || (!((DWORD_PTR)pbuf & (lpcs->dwBytesPerSector - 1))));
assert (!(dwSize & 1));
assert (dwSize);
assert (*lpbPending == FALSE);
// if we are doing non-buffered io, we need to pad each write
// to an even multiple of sector size bytes, we do this by adding
// a junk riff chunk into the write buffer after dwSize bytes
//
dwGran = lpcs->sCapParms.wChunkGranularity;
if (lpcs->fUsingNonBufferedIO)
dwGran = max (lpcs->dwBytesPerSector,
(DWORD) lpcs->sCapParms.wChunkGranularity);
assert (dwGran);
if (dwSize % dwGran)
{
DWORD dwSizeT = dwGran - (dwSize % dwGran);
LPRIFF priff = (LPRIFF)((LPBYTE)pbuf + dwSize + (dwSize & 1));
if (dwSizeT < sizeof(RIFF))
dwSizeT += dwGran;
// add the junk riff chunk to the end of the buffer
//
priff->dwType = ckidAVIPADDING;
priff->dwSize = dwSizeT - sizeof(RIFF);
dwSize += dwSizeT;
}
#ifdef _DEBUG
if (dwSize)
{
volatile BYTE bt;
AuxDebugEx (8, DEBUGLINE "touch test of AviWrite buffer %08X\r\n", pbuf);
bt = ((LPBYTE)pbuf)[dwSize-1];
}
// List all of the RIFF chunks within the block being written
//
dwWritten = 0;
while (dwWritten < dwSize)
{
LPRIFF priff = (LPVOID)((LPBYTE)pbuf + dwWritten);
AuxDebugEx (4, DEBUGLINE "RIFF=%.4s size=%08X\r\n",
&priff->dwType, priff->dwSize);
dwWritten += priff->dwSize + sizeof(RIFF);
}
#endif
// BUGBUG, Remove the following line when done performance testing
#ifdef JMK_HACK_DONTWRITE
if (bDontWrite)
return 0;
#endif
if (lpcs->pAsync)
{
struct _avi_async * lpah = &lpcs->pAsync[lpcs->iLastAsync];
UINT iLastAsync;
// set iLastAsync to point to what lpcs->iLastAsync
// would be if we were to increment it. If we end up
// with an i/o that does not complete synchronously
// we will then update lpcs->iLastAsync so that we can
// remember to check for completion later
//
if ((iLastAsync = lpcs->iLastAsync+1) >= lpcs->iNumAsync)
iLastAsync = 0;
// is the async buffer that we are trying to use
// already in use?
//
if (iLastAsync == lpcs->iNextAsync) {
AuxDebugEx(1, DEBUGLINE "async buffer already in use\r\n");
return IDS_CAP_FILE_WRITE_ERROR;
}
assert (!lpah->uType);
// init the async buffer with the info that we will need
// to release the buffer when the io is complete
//
ZeroMemory (&lpah->ovl, sizeof(lpah->ovl));
if (uIndex == -1) {
// We want a synchronous write
assert (!(((DWORD_PTR)(lpcs->heSyncWrite))&1));
lpah->ovl.hEvent = (HANDLE)(((DWORD_PTR)lpcs->heSyncWrite) | 1);
// OR'ing hEvent with 1 prevents the IOCompletionPort being used
// ...and I know this sounds a bit tacky but this is what the
// docs actually say.
} else {
lpah->ovl.hEvent = 0;
}
lpah->ovl.Offset = lpcs->dwAsyncWriteOffset;
// attempt an async write. if WriteFile fails, we then
// need to check if it's a real failure, or just an instance
// of delayed completion. if delayed completion, we fill out
// the lpah structure so that we know what buffer to re-use
// when the io finally completes.
//
if ( ! WriteFile (lpcs->hFile, pbuf, dwSize, &dwWritten, &lpah->ovl))
{
UINT n = GetLastError();
if ((ERROR_IO_PENDING == n) || (ERROR_INVALID_HANDLE == n))
{
// if we are passed a index of -1, that means that
// this buffer is not associated with any entry in the
// header array. in this case, we must have the io complete
// before we return from this function.
//
if (uIndex == (UINT)-1)
{
AuxDebugEx(3, "Waiting for a block to write synchonously\n");
if ( ! GetOverlappedResult (lpcs->hFile, &lpah->ovl, &dwWritten, TRUE))
{
AuxDebugEx (1, DEBUGLINE "WriteFile failed %d\r\n", GetLastError());
return IDS_CAP_FILE_WRITE_ERROR;
}
}
else
{
// io is begun, but not yet completed. so setup info in
// the pending io array so that we can check later for completion
//
*lpbPending = TRUE;
lpah->uType = uType | ASYNCIOPENDING;
lpah->uIndex = uIndex;
AuxDebugEx(2, DEBUGLINE "IOPending... iLastAsync was %d, will be %d, uIndex=%d, Event=%d\r\n",lpcs->iLastAsync , iLastAsync, uIndex, lpah->ovl.hEvent);
lpcs->iLastAsync = iLastAsync;
}
}
else
{
AuxDebugEx (1, DEBUGLINE "WriteFile failed %d\r\n", GetLastError());
return IDS_CAP_FILE_WRITE_ERROR;
}
}
// we get to here only when the io succeeds or is pending
// so update the seek offset for use in the next write operation
//
lpcs->dwAsyncWriteOffset += dwSize;
}
else
{
// We are writing synchronously to the file
if (!WriteFile (lpcs->hFile, pbuf, dwSize, &dwWritten, NULL) ||
!(dwWritten == dwSize))
return IDS_CAP_FILE_WRITE_ERROR;
}
return 0;
}
/*
* CapFileInit
*
* Perform all initialization required to write a capture file.
*
* We take a slightly strange approach: We don't write
* out the header until we're done capturing. For now,
* we just seek 2K into the file, which is where all of
* the real data will go.
*
* When we're done, we'll come back and write out the header,
* because then we'll know all of the values we need.
*
* Also allocate and init the index.
*/
BOOL CapFileInit (LPCAPSTREAM lpcs)
{
LONG l;
LPBITMAPINFO lpBitsInfoOut; // Possibly compressed output format
DWORD dwOpenFlags;
// No special video format given -- use the default
lpBitsInfoOut = lpcs->CompVars.lpbiOut;
if (lpcs->CompVars.hic == NULL)
lpBitsInfoOut = lpcs->lpBitsInfo;
assert (lpcs->hmmio == NULL); // Should never have a file handle on entry
// if the capture file has not been set then set it now
if (!(*lpcs->achFile))
goto INIT_FILE_OPEN_ERROR;
// Get the Bytes per sector for the drive
{
DWORD dwSectorsPerCluster;
DWORD dwFreeClusters;
DWORD dwClusters;
TCHAR szFullPathName[MAX_PATH];
LPTSTR pszFilePart;
GetFullPathName (lpcs->achFile,
NUMELMS (szFullPathName),
szFullPathName,
&pszFilePart);
if (szFullPathName[1] == TEXT(':') && szFullPathName[2] == TEXT('\\')) {
szFullPathName[3] = TEXT('\0'); // Terminate after "x:\"
GetDiskFreeSpace (szFullPathName,
&dwSectorsPerCluster,
&lpcs->dwBytesPerSector,
&dwFreeClusters,
&dwClusters);
AuxDebugEx (3, DEBUGLINE "BytesPerSector=%d\r\n",
lpcs->dwBytesPerSector);
}
else {
// This handles cases where we do not have a "x:\" filename
// Principally this will be "\\server\name\path..."
lpcs->dwBytesPerSector = DEFAULT_BYTESPERSECTOR;
AuxDebugEx (3, DEBUGLINE "FullPath=%s\r\n", szFullPathName);
AuxDebugEx (3, DEBUGLINE "GetFullPath failed, Forcing dwBytesPerSector to %d\r\n",DEFAULT_BYTESPERSECTOR);
}
// bytes per sector must be non-zero and a power of two
//
assert (lpcs->dwBytesPerSector);
assert (!(lpcs->dwBytesPerSector & (lpcs->dwBytesPerSector-1)));
}
#ifdef ZERO_THE_FILE_FOR_TESTING
{
char c[64 * 1024];
DWORD dwSize;
DWORD dwBW;
// Open the file just to zero it
lpcs->hFile = CreateFile (lpcs->achFile,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (lpcs->hFile == INVALID_HANDLE_VALUE) {
lpcs->hFile = 0;
goto INIT_FILE_OPEN_ERROR;
}
ZeroMemory (c, sizeof(c));
SetFilePointer (lpcs->hFile, 0, NULL, FILE_BEGIN);
dwSize = GetFileSize (lpcs->hFile, NULL);
while (SetFilePointer (lpcs->hFile, 0, NULL, FILE_CURRENT) < dwSize)
WriteFile (lpcs->hFile, c, sizeof(c), &dwBW, NULL);
}
CloseHandle(lpcs->hFile); // Close the "normal" open
#endif
// We can use non-buffered I/O if the ChunkGranularity is
// a multiple of BytesPerSector. Better check that wChunkGranularity
// has indeed been set
if (0 == lpcs->sCapParms.wChunkGranularity)
lpcs->sCapParms.wChunkGranularity = lpcs->dwBytesPerSector;
dwOpenFlags = FILE_ATTRIBUTE_NORMAL;
lpcs->fUsingNonBufferedIO =
(lpcs->sCapParms.wChunkGranularity >= lpcs->dwBytesPerSector) &&
((lpcs->sCapParms.wChunkGranularity % lpcs->dwBytesPerSector) == 0) &&
(lpcs->CompVars.hic == NULL) &&
(!(lpcs->fCaptureFlags & CAP_fStepCapturingNow)) &&
(!(lpcs->fCaptureFlags & CAP_fFrameCapturingNow));
AuxDebugEx (3, DEBUGLINE "fUsingNonBufferedIO=%d\r\n", lpcs->fUsingNonBufferedIO);
// setup CreateFile flags based on whether we are using
// non-buffered io and/or overlapped io
//
if (lpcs->fUsingNonBufferedIO)
{
dwOpenFlags |= FILE_FLAG_NO_BUFFERING;
#ifdef CHICAGO
#define DOASYNCIO FALSE
#pragma message (SQUAWK "find a better way to set AsyncIO flag")
#else
#define DOASYNCIO TRUE
#endif
#ifdef CHICAGO
if (GetProfileIntA ("Avicap32", "AsyncIO", DOASYNCIO))
#else
if (!pfnCreateIoCompletionPort) {
hmodKernel = LoadLibrary(TEXT("kernel32"));
if (hmodKernel) {
#define IOCP (void *(__stdcall *)(void *,void *,unsigned long ,unsigned long ))
#define GQCS (int (__stdcall *)(void *,unsigned long *,unsigned long *,struct _OVERLAPPED ** ,unsigned long ))
pfnCreateIoCompletionPort = IOCP GetProcAddress(hmodKernel, "CreateIoCompletionPort");
pfnGetQueuedCompletionStatus = GQCS GetProcAddress(hmodKernel, "GetQueuedCompletionStatus");
if (!pfnCreateIoCompletionPort && !pfnGetQueuedCompletionStatus) {
pfnCreateIoCompletionPort = NULL;
pfnGetQueuedCompletionStatus = NULL;
FreeLibrary(hmodKernel);
}
}
}
DPF("CreateIoCompletionPort @%x", pfnCreateIoCompletionPort);
DPF("GetQueuedCompletionStatus @%x", pfnGetQueuedCompletionStatus);
// give a way to override the async default option.
if (!GetProfileIntA ("Avicap32", "AsyncIO", DOASYNCIO)
|| !pfnCreateIoCompletionPort) {
AuxDebugEx (2, DEBUGLINE "NOT doing Async IO\r\n");
} else
#endif
{
AuxDebugEx (3, DEBUGLINE "Doing Async IO\r\n");
dwOpenFlags |= FILE_FLAG_OVERLAPPED;
// We are requested to do async io. Allocate an array
// of async io headers and initialize the async io fields
// in the CAPSTREAM structure
//
{
UINT iNumAsync = NUMELMS(lpcs->alpVideoHdr) + NUMELMS(lpcs->alpWaveHdr) + 2;
// This is quite a lot of buffers. Perhaps we should limit
// ourselves to lpcs->iNumVideo and lpcs->iNumAudio EXCEPT
// these fields have not yet been set up. We would need
// to look in the cap stream structure to get the information.
// It is simpler to assume the maximum numbers.
// Set the offset for the first write to the file
lpcs->dwAsyncWriteOffset = lpcs->dwAVIHdrSize;
lpcs->iNextAsync = lpcs->iLastAsync = 0;
// Create the manual reset event for synchronous writing
if ((lpcs->heSyncWrite = CreateEvent(NULL, TRUE, FALSE, NULL))
&& (NULL != (lpcs->pAsync = (LPVOID)GlobalAllocPtr (GMEM_MOVEABLE | GMEM_ZEROINIT,
sizeof(*lpcs->pAsync) * iNumAsync)))) {
lpcs->iNumAsync = iNumAsync;
} else {
// cannot allocate the memory. Go synchronous
dprintf("Failed to allocate async buffers");
if (lpcs->heSyncWrite) {
CloseHandle(lpcs->heSyncWrite);
lpcs->heSyncWrite = 0;
}
dwOpenFlags &= ~(FILE_FLAG_OVERLAPPED);
}
}
}
}
// Open the capture file, using Non Buffered I/O
// if possible, given sector size, and buffer granularity
//
reopen:
lpcs->hFile = CreateFile (lpcs->achFile,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
dwOpenFlags,
NULL);
if (lpcs->hFile == INVALID_HANDLE_VALUE) {
lpcs->hFile = 0;
goto INIT_FILE_OPEN_ERROR;
}
#ifdef ASYNCIO_PORT
if (dwOpenFlags & FILE_FLAG_OVERLAPPED) {
lpcs->hCompletionPort = pfnCreateIoCompletionPort(lpcs->hFile, NULL, (DWORD)1, 0);
if (!lpcs->hCompletionPort) {
// if we cannot create the completion port, write synchronously.
dwOpenFlags &= ~FILE_FLAG_OVERLAPPED;
CloseHandle(lpcs->hFile);
GlobalFreePtr(lpcs->pAsync);
lpcs->iNumAsync=0;
if (lpcs->heSyncWrite) {
CloseHandle(lpcs->heSyncWrite);
lpcs->heSyncWrite = 0;
}
DPF("COULD NOT create the async completion port");
goto reopen;
} else {
DPF("Created the async completion port");
}
}
#endif
// BUGBUG, Remove the following line when done performance testing
#ifdef JMK_HACK_DONTWRITE
bDontWrite = GetProfileIntA("AVICAP32", "DontWrite", FALSE);
#endif
// Seek to a multiple of ChunkGranularity + AVIHEADERSIZE.
// This is the point at which we'll start writing
// Later, we'll come back and fill in the AVI header and index.
// l is zero for standard wave and video formats
l = (GetSizeOfWaveFormat ((LPWAVEFORMATEX) lpcs->lpWaveFormat) -
sizeof (PCMWAVEFORMAT)) +
(lpBitsInfoOut->bmiHeader.biSize -
sizeof (BITMAPINFOHEADER));
// (2K + size of wave and video stream headers) rounded to next 2K
lpcs->dwAVIHdrSize = AVI_HEADERSIZE +
(((lpcs->cbInfoChunks + l + lpcs->sCapParms.wChunkGranularity - 1)
/ lpcs->sCapParms.wChunkGranularity) * lpcs->sCapParms.wChunkGranularity);
// we should assert that AVI_HEADERSIZE is a multiple of wChunkGranularity
dprintf("AVIHdrSize = %ld", lpcs->dwAVIHdrSize);
SetFilePointer (lpcs->hFile, lpcs->dwAVIHdrSize, NULL, FILE_BEGIN);
if (lpcs->pAsync) {
lpcs->dwAsyncWriteOffset = lpcs->dwAVIHdrSize;
}
// do all Index allocations
if (!InitIndex (lpcs))
CloseHandle (lpcs->hFile), lpcs->hFile = 0;
lpcs->dwVideoChunkCount = 0;
lpcs->dwWaveChunkCount = 0;
INIT_FILE_OPEN_ERROR:
if (lpcs->hFile) {
return(TRUE);
}
if (lpcs->pAsync) {
GlobalFreePtr(lpcs->pAsync), lpcs->pAsync=NULL;
}
if (lpcs->heSyncWrite) {
CloseHandle(lpcs->heSyncWrite);
lpcs->heSyncWrite = 0;
}
return (FALSE);
}
///////////////////////////////////////////////////////////////////////////
// The index array is used to record the positions
// of every chunk in the RIFF (avi) file.
//
// what this array is:
//
// each entry contains the size of the data
// high order bits encode the type of data (audio / video)
// and whether the video chunk is a key frame, dropped frame, etc.
///////////////////////////////////////////////////////////////////////////
// Allocate the index table
// Returns: TRUE if index can be allocated
//
BOOL InitIndex (LPCAPSTREAM lpcs)
{
lpcs->dwIndex = 0;
// we assume that we have not already allocated an index
//
assert (lpcs->lpdwIndexStart == NULL);
// Limit index size between 1 minute at 30fps and 3 hours at 30fps
lpcs->sCapParms.dwIndexSize = max (lpcs->sCapParms.dwIndexSize, 1800);
lpcs->sCapParms.dwIndexSize = min (lpcs->sCapParms.dwIndexSize, 324000L);
dprintf("Max Index Size = %ld", lpcs->sCapParms.dwIndexSize);
if (lpcs->hIndex = GlobalAlloc (GMEM_MOVEABLE,
lpcs->sCapParms.dwIndexSize * sizeof (DWORD))) {
if (lpcs->lpdwIndexEntry =
lpcs->lpdwIndexStart = (LPDWORD)GlobalLock (lpcs->hIndex))
return TRUE; // Success
GlobalFree (lpcs->hIndex);
lpcs->hIndex = NULL;
}
lpcs->lpdwIndexStart = NULL;
return FALSE;
}
// Deallocate the index table
//
void FiniIndex (LPCAPSTREAM lpcs)
{
if (lpcs->hIndex) {
if (lpcs->lpdwIndexStart)
GlobalUnlock (lpcs->hIndex);
GlobalFree (lpcs->hIndex);
lpcs->hIndex = NULL;
}
lpcs->lpdwIndexStart = NULL;
}
// Write out the index at the end of the capture file.
// The single frame capture methods do not append
// JunkChunks! Audio chunks also now may have junk appended.
//
BOOL WriteIndex (LPCAPSTREAM lpcs, BOOL fJunkChunkWritten)
{
BOOL fChunkIsAudio;
BOOL fChunkIsKeyFrame;
BOOL fChunkIsDummy;
BOOL fChunkIsGranular;
DWORD dwIndex;
DWORD dw;
DWORD dwJunk;
DWORD off;
AVIINDEXENTRY avii;
MMCKINFO ck;
LPDWORD lpdw;
DWORD dwGran;
// determine which granularity (if any) to use
// when calculating junk appended
//
dwGran = 0;
if (fJunkChunkWritten)
{
dwGran = lpcs->sCapParms.wChunkGranularity;
if (lpcs->fUsingNonBufferedIO)
dwGran = max (lpcs->dwBytesPerSector, dwGran);
}
if (lpcs->dwIndex > lpcs->sCapParms.dwIndexSize)
return TRUE;
off = lpcs->dwAVIHdrSize;
ck.cksize = 0;
ck.ckid = ckidAVINEWINDEX;
ck.fccType = 0;
if (mmioCreateChunk(lpcs->hmmio,&ck,0)) {
dprintf("Failed to create chunk for index");
return FALSE;
}
lpdw = lpcs->lpdwIndexStart;
for (dwIndex= 0; dwIndex < lpcs->dwIndex; dwIndex++) {
dw = *lpdw++;
fChunkIsAudio = (BOOL) ((dw & IS_AUDIO_CHUNK) != 0);
fChunkIsKeyFrame = (BOOL) ((dw & IS_KEYFRAME_CHUNK) != 0);
fChunkIsDummy = (BOOL) ((dw & IS_DUMMY_CHUNK) != 0);
fChunkIsGranular = (BOOL) ((dw & IS_GRANULAR_CHUNK) != 0);
dw &= ~(INDEX_MASK);
if (fChunkIsAudio) {
avii.ckid = MAKEAVICKID(cktypeWAVEbytes, 1);
avii.dwFlags = 0;
} else {
avii.ckid = MAKEAVICKID(cktypeDIBbits, 0);
if (lpcs->lpBitsInfo->bmiHeader.biCompression == BI_RLE8)
avii.ckid = MAKEAVICKID(cktypeDIBcompressed, 0);
avii.dwFlags = fChunkIsKeyFrame ? AVIIF_KEYFRAME : 0;
}
avii.dwChunkLength = dw;
avii.dwChunkOffset = off;
if (mmioWrite(lpcs->hmmio, (LPVOID)&avii, sizeof(avii)) != sizeof(avii)) {
dprintf("Failed to write index chunk %d", dwIndex);
return FALSE;
}
dw += sizeof (RIFF);
// round to word boundary
//
dw += (dw & 1);
off += dw;
// If a Junk chunk was appended, move past it
//
if (fChunkIsGranular && dwGran && (off % dwGran)) {
dwJunk = dwGran - (off % dwGran);
if (dwJunk < sizeof (RIFF))
off += dwGran;
off += dwJunk;
}
}
if (mmioAscend(lpcs->hmmio, &ck, 0)){
dprintf("Failed to ascend at end of index writing");
return FALSE;
}
return TRUE;
}
/*
* AVIFileFini
*
* Write out the index, deallocate the index, and close the file.
*
*/
BOOL AVIFileFini (LPCAPSTREAM lpcs, BOOL fWroteJunkChunks, BOOL fAbort)
{
MMCKINFO ckRiff;
MMCKINFO ckList;
MMCKINFO ckStream;
MMCKINFO ck;
UINT ii;
DWORD dw;
AVIStreamHeader strhdr;
DWORD dwDataEnd;
BOOL fRet = TRUE;
RGBQUAD argbq[256];
MainAVIHeader aviHdr;
BOOL fSound;
LPBITMAPINFO lpBitsInfoOut; // Possibly compressed output format
// No special video format given -- use the default
//
lpBitsInfoOut = lpcs->lpBitsInfo;
#ifdef NEW_COMPMAN
if (lpcs->CompVars.hic != NULL)
lpBitsInfoOut = lpcs->CompVars.lpbiOut;
#endif
// if the capture file has not been opened, we have nothing to do
//
if (lpcs->hFile == 0)
return FALSE;
// save off the current seek position. this is the end of the capture
// data. then close the capture file, we will do the final work
// on the capture file using mmio & buffered io.
//
if (lpcs->pAsync)
dwDataEnd = lpcs->dwAsyncWriteOffset;
else
dwDataEnd = SetFilePointer (lpcs->hFile, 0, NULL, FILE_CURRENT);
CloseHandle (lpcs->hFile), lpcs->hFile = 0;
// if we had allocated space for async buffers, free them now
//
if (lpcs->pAsync)
{
GlobalFreePtr (lpcs->pAsync);
lpcs->pAsync = NULL;
lpcs->iNextAsync = lpcs->iLastAsync = lpcs->iNumAsync = 0;
}
// if we are aborting capture, we are done
lpcs->hmmio = mmioOpen(lpcs->achFile, NULL, MMIO_WRITE);
assert (lpcs->hmmio != NULL);
//
if (fAbort)
goto FileError;
if (!lpcs->dwWaveBytes)
fSound = FALSE;
else
fSound = lpcs->sCapParms.fCaptureAudio && (!(lpcs->fCaptureFlags & CAP_fFrameCapturingNow));
// Seek to beginning of file, so we can write the header.
mmioSeek(lpcs->hmmio, 0, SEEK_SET);
DSTATUS(lpcs, "Writing AVI header");
// Create RIFF/AVI chunk
ckRiff.cksize = 0;
ckRiff.fccType = formtypeAVI;
if (mmioCreateChunk(lpcs->hmmio,&ckRiff,MMIO_CREATERIFF))
goto FileError;
// Create header list
ckList.cksize = 0;
ckList.fccType = listtypeAVIHEADER;
if (mmioCreateChunk(lpcs->hmmio,&ckList,MMIO_CREATELIST))
goto FileError;
// Create AVI header chunk
ck.cksize = sizeof(MainAVIHeader);
ck.ckid = ckidAVIMAINHDR;
if (mmioCreateChunk(lpcs->hmmio,&ck,0))
goto FileError;
lpcs->dwAVIHdrPos = ck.dwDataOffset;
// Calculate AVI header info
//
ZeroMemory (&aviHdr, sizeof(aviHdr));
//
// Set the stream lengths based on the Master stream
//
#if 0 // stream length calc with unconditional audio master
aviHdr.dwMicroSecPerFrame = lpcs->sCapParms.dwRequestMicroSecPerFrame;
if (fSound && lpcs->dwVideoChunkCount) {
/* HACK HACK */
/* Set rate that was captured based on length of audio data */
aviHdr.dwMicroSecPerFrame = (DWORD) MulDiv ((LONG)lpcs->dwWaveBytes,
1000000,
(LONG)(lpcs->lpWaveFormat->nAvgBytesPerSec * lpcs->dwVideoChunkCount));
}
#else
// Init a value in case we're not capturing audio
aviHdr.dwMicroSecPerFrame = lpcs->sCapParms.dwRequestMicroSecPerFrame;
switch (lpcs->sCapParms.AVStreamMaster) {
case AVSTREAMMASTER_NONE:
break;
case AVSTREAMMASTER_AUDIO:
default:
// VFW 1.0 and 1.1 ALWAYS munged frame rate to match audio
// duration.
if (fSound && lpcs->sCapParms.fCaptureAudio && lpcs->dwVideoChunkCount) {
// Modify the video framerate based on audio duration
aviHdr.dwMicroSecPerFrame = (DWORD)
((double)lpcs->dwWaveBytes * 1000000. /
((double)lpcs->lpWaveFormat->nAvgBytesPerSec *
lpcs->dwVideoChunkCount + 0.5));
}
break;
}
#endif
lpcs->dwActualMicroSecPerFrame = aviHdr.dwMicroSecPerFrame;
aviHdr.dwMaxBytesPerSec = (DWORD) MulDiv (lpBitsInfoOut->bmiHeader.biSizeImage,
1000000,
lpcs->sCapParms.dwRequestMicroSecPerFrame) +
(fSound ? lpcs->lpWaveFormat->nAvgBytesPerSec : 0);
aviHdr.dwPaddingGranularity = 0L;
aviHdr.dwFlags = AVIF_WASCAPTUREFILE | AVIF_HASINDEX;
aviHdr.dwStreams = fSound ? 2 : 1;
aviHdr.dwTotalFrames = lpcs->dwVideoChunkCount;
aviHdr.dwInitialFrames = 0L;
aviHdr.dwSuggestedBufferSize = 0L;
aviHdr.dwWidth = lpBitsInfoOut->bmiHeader.biWidth;
aviHdr.dwHeight = lpBitsInfoOut->bmiHeader.biHeight;
#if 0 // unnecessary due to the ZeroMemory call above
aviHdr.dwReserved[0] = 0;
aviHdr.dwReserved[1] = 0;
aviHdr.dwReserved[2] = 0;
aviHdr.dwReserved[3] = 0;
#endif
//aviHdr.dwRate = 1000000L;
//aviHdr.dwScale = aviHdr.dwMicroSecPerFrame;
//aviHdr.dwStart = 0L;
//aviHdr.dwLength = lpcs->dwVideoChunkCount;
// Write AVI header info
if (mmioWrite(lpcs->hmmio, (LPBYTE)&aviHdr, sizeof(aviHdr)) != sizeof(aviHdr) ||
mmioAscend(lpcs->hmmio, &ck, 0))
goto FileError;
DSTATUS(lpcs, "Writing AVI Stream header");
// Create stream header list
ckStream.cksize = 0;
ckStream.fccType = listtypeSTREAMHEADER;
if (mmioCreateChunk(lpcs->hmmio,&ckStream,MMIO_CREATELIST))
goto FileError;
ZeroMemory (&strhdr, sizeof(strhdr));
strhdr.fccType = streamtypeVIDEO;
strhdr.fccHandler = lpBitsInfoOut->bmiHeader.biCompression;
#ifdef NEW_COMPMAN
if (lpcs->CompVars.hic)
strhdr.fccHandler = lpcs->CompVars.fccHandler;
#endif
// A bit of history...
// In VFW 1.0, we set fccHandler to 0 for BI_RLE8 formats
// as a kludge to make Mplayer and Videdit play the files.
// Just prior to 1.1 release, we found this broke Premiere,
// so now (after AVICAP beta is on Compuserve), we change the
// fccHandler to "MRLE". Just ask Todd...
// And now, at RC1, we change it again to "RLE ", Just ask Todd...
if (strhdr.fccHandler == BI_RLE8)
strhdr.fccHandler = mmioFOURCC('R', 'L', 'E', ' ');
//strhdr.dwFlags = 0L;
#ifdef NEW_COMPMAN
//strhdr.wPriority = 0L;
//strhdr.wLanguage = 0L;
#else
//strhdr.dwPriority = 0L;
#endif
//strhdr.dwInitialFrames = 0L;
strhdr.dwScale = aviHdr.dwMicroSecPerFrame;
strhdr.dwRate = 1000000L;
//strhdr.dwStart = 0L;
strhdr.dwLength = lpcs->dwVideoChunkCount; /* Needs to get filled in! */
strhdr.dwQuality = (DWORD) -1L; /* !!! ICQUALITY_DEFAULT */
//strhdr.dwSampleSize = 0L;
//
// Write stream header data
//
ck.ckid = ckidSTREAMHEADER;
if (mmioCreateChunk(lpcs->hmmio,&ck,0) ||
mmioWrite(lpcs->hmmio, (LPBYTE)&strhdr, sizeof(strhdr)) != sizeof(strhdr) ||
mmioAscend(lpcs->hmmio, &ck, 0))
goto FileError;
/*
** !!! dont write palette for full color?
*/
if (lpBitsInfoOut->bmiHeader.biBitCount > 8)
lpBitsInfoOut->bmiHeader.biClrUsed = 0;
/* Create DIB header chunk */
ck.cksize = lpBitsInfoOut->bmiHeader.biSize +
lpBitsInfoOut->bmiHeader.biClrUsed *
sizeof(RGBQUAD);
ck.ckid = ckidSTREAMFORMAT;
if (mmioCreateChunk(lpcs->hmmio,&ck,0))
goto FileError;
/* Write DIB header data */
if (mmioWrite(lpcs->hmmio, (LPBYTE)&lpBitsInfoOut->bmiHeader,
lpBitsInfoOut->bmiHeader.biSize) !=
(LONG) lpBitsInfoOut->bmiHeader.biSize)
goto FileError;
if (lpBitsInfoOut->bmiHeader.biClrUsed > 0) {
// Get Palette info
if ((ii = GetPaletteEntries(lpcs->hPalCurrent, 0,
(UINT) lpBitsInfoOut->bmiHeader.biClrUsed,
(LPPALETTEENTRY) argbq)) !=
(UINT)lpBitsInfoOut->bmiHeader.biClrUsed)
goto FileError;
// Reorder the palette from PALETTEENTRY order to RGBQUAD order
// by swapping the red and blue palette entries.
//for (ii = 0; ii < lpBitsInfoOut->bmiHeader.biClrUsed; ++ii)
while (ii--)
SWAPTYPE(argbq[ii].rgbRed, argbq[ii].rgbBlue, BYTE);
// Write Palette Info
dw = sizeof(RGBQUAD) * lpBitsInfoOut->bmiHeader.biClrUsed;
if (mmioWrite(lpcs->hmmio, (LPBYTE)argbq, dw) != (long)dw)
goto FileError;
}
if (mmioAscend(lpcs->hmmio, &ck, 0))
goto FileError;
// ADD FOURCC stuff here!!! for Video stream
// Ascend out of stream header
if (mmioAscend(lpcs->hmmio, &ckStream, 0))
goto FileError;
/* If sound is enabled, then write WAVE header */
if (fSound) {
/* Create stream header list */
ckStream.cksize = 0;
ckStream.fccType = listtypeSTREAMHEADER;
if (mmioCreateChunk(lpcs->hmmio,&ckStream,MMIO_CREATELIST))
goto FileError;
ZeroMemory (&strhdr, sizeof(strhdr));
strhdr.fccType = streamtypeAUDIO;
strhdr.fccHandler = 0L;
strhdr.dwFlags = 0L;
#ifdef NEW_COMPMAN
strhdr.wPriority = 0L;
strhdr.wLanguage = 0L;
#else
strhdr.dwPriority = 0L;
#endif
strhdr.dwInitialFrames = 0L;
strhdr.dwScale = lpcs->lpWaveFormat->nBlockAlign;
strhdr.dwRate = lpcs->lpWaveFormat->nAvgBytesPerSec;
strhdr.dwStart = 0L;
strhdr.dwLength = lpcs->dwWaveBytes /
lpcs->lpWaveFormat->nBlockAlign;
strhdr.dwQuality = (DWORD)-1L; /* !!! ICQUALITY_DEFAULT */
strhdr.dwSampleSize = lpcs->lpWaveFormat->nBlockAlign;
ck.ckid = ckidSTREAMHEADER;
if (mmioCreateChunk(lpcs->hmmio,&ck,0) ||
mmioWrite(lpcs->hmmio, (LPBYTE)&strhdr, sizeof(strhdr)) != sizeof(strhdr) ||
mmioAscend(lpcs->hmmio, &ck, 0))
goto FileError;
ck.cksize = (LONG) GetSizeOfWaveFormat ((LPWAVEFORMATEX) lpcs->lpWaveFormat);
ck.ckid = ckidSTREAMFORMAT;
if (mmioCreateChunk(lpcs->hmmio,&ck,0) ||
mmioWrite(lpcs->hmmio, (LPBYTE)lpcs->lpWaveFormat, ck.cksize) != (LONG) ck.cksize ||
mmioAscend(lpcs->hmmio, &ck, 0))
goto FileError;
/* Ascend out of stream header */
if (mmioAscend(lpcs->hmmio, &ckStream, 0))
goto FileError;
}
// ADD FOURCC stuff here!!! for entire file
DSTATUS(lpcs, "Writing Info chunks");
if (lpcs->lpInfoChunks) {
DSTATUS(lpcs, "Writing Info chunks");
if (mmioWrite (lpcs->hmmio, lpcs->lpInfoChunks, lpcs->cbInfoChunks) !=
lpcs->cbInfoChunks)
goto FileError;
}
/* ascend from the Header list */
if (mmioAscend(lpcs->hmmio, &ckList, 0))
goto FileError;
ck.ckid = ckidAVIPADDING;
if (mmioCreateChunk(lpcs->hmmio,&ck,0))
goto FileError;
// The data must begin at offset lpcs->dwAVIHdrSize.
// To create a valid RIFF file we must write the LIST/AVI chunk before
// this point. Hence we end the junk section at the end of the header
// leaving room for the LIST chunk header.
mmioSeek(lpcs->hmmio, lpcs->dwAVIHdrSize - 3 * sizeof(DWORD), SEEK_SET);
if (mmioAscend(lpcs->hmmio, &ck, 0))
goto FileError;
DSTATUS(lpcs, "Writing Movie LIST");
/* Start the movi list */
ckList.cksize = 0;
ckList.fccType = listtypeAVIMOVIE;
if (mmioCreateChunk(lpcs->hmmio,&ckList,MMIO_CREATELIST))
goto FileError;
// Force the chunk to end on the next word boundary
dprintf("IndexStartOffset = %8X\n", dwDataEnd);
mmioSeek(lpcs->hmmio, dwDataEnd + (dwDataEnd & 1L), SEEK_SET);
/* Ascend out of the movi list and the RIFF chunk so that */
/* the sizes can be fixed */
mmioAscend(lpcs->hmmio, &ckList, 0);
/*
** Now write index out!
*/
DSTATUS(lpcs, "Writing Index...");
WriteIndex(lpcs, fWroteJunkChunks);
lpcs->fFileCaptured = TRUE; // we got a good file, allow editing of it
goto Success;
FileError:
lpcs->fFileCaptured = fRet = FALSE; // bogus file - no editing allowed
Success:
DSTATUS(lpcs, "Freeing Index...");
FiniIndex (lpcs);
mmioAscend(lpcs->hmmio, &ckRiff, 0);
mmioSeek(lpcs->hmmio, 0, SEEK_END);
mmioFlush(lpcs->hmmio, 0);
// Close the file
mmioClose(lpcs->hmmio, 0);
lpcs->hmmio = NULL;
return fRet;
}
//
// Prepends dummy frame entries to the current valid video frame.
// Bumps the index, but does not actually trigger a write operation.
// nCount is a count of the number of frames to write
// Returns: TRUE on a successful write
BOOL WINAPI AVIWriteDummyFrames (
LPCAPSTREAM lpcs,
UINT nCount,
LPUINT lpuError,
LPBOOL lpbPending)
{
DWORD dwBytesToWrite;
DWORD dwType;
LPRIFF priff;
UINT jj;
*lpbPending = FALSE;
*lpuError = 0;
if ( ! nCount)
return TRUE;
// create a buffer full of dummy chunks to act as placeholders
// for the dropped frames
//
dwType = MAKEAVICKID(cktypeDIBbits, 0);
if (lpcs->lpBitsInfo->bmiHeader.biCompression == BI_RLE8)
dwType = MAKEAVICKID(cktypeDIBcompressed, 0);
// dont try to write more than 1 'sector' worth of dummy
// frames
//
dwBytesToWrite = nCount * sizeof(RIFF);
if (dwBytesToWrite > lpcs->dwBytesPerSector)
{
#ifdef DEBUG
UINT n = nCount;
#endif
dwBytesToWrite = lpcs->dwBytesPerSector;
#ifdef DEBUG
nCount = dwBytesToWrite / sizeof(RIFF);
assert(nCount*sizeof(RIFF) == dwBytesToWrite);
dprintf("Forced to reduce dummy frames from %d to %d", n, nCount);
#endif
}
// create index entries for the dummy chunks
//
for (jj = 0; jj < nCount-1; ++jj)
IndexVideo (lpcs, IS_DUMMY_CHUNK, FALSE);
IndexVideo (lpcs, IS_DUMMY_CHUNK | IS_GRANULAR_CHUNK, FALSE);
// fill in the drop frame buffer with dummy frames
//
priff = (LPRIFF)lpcs->lpDropFrame;
for (jj = 0; jj < nCount; ++jj, ++priff)
{
priff->dwSize = 0;
priff->dwType = dwType;
}
//
// cant use a single dummy frame buffer when we are doing async
// write because we cant write 'n' dummy frames to the buffer
// if it is currently already queued to an IO.
//
// perhaps several dummy frames? 1 frame, 2 frames, 3 frames, etc
// create dynamically?
//
// write out the dummy frames
//
AuxDebugEx (3, DEBUGLINE "DummyFrames Count=%d, ToWrite=%d\r\n",
nCount, dwBytesToWrite);
*lpuError = AVIWrite (lpcs,
lpcs->lpDropFrame,
dwBytesToWrite,
(UINT)-1, // force sync completion
ASYNC_BUF_DROP,
lpbPending);
return !(*lpuError);
}
// Writes compressed or uncompressed frames to the AVI file
// returns TRUE if no error, FALSE if end of file.
//
BOOL WINAPI AVIWriteVideoFrame (
LPCAPSTREAM lpcs,
LPBYTE lpData,
DWORD dwBytesUsed,
BOOL fKeyFrame,
UINT uIndex,
UINT nDropped,
LPUINT lpuError,
LPBOOL lpbPending)
{
DWORD dwBytesToWrite;
LPRIFF priff;
*lpuError = 0;
*lpbPending = FALSE;
if (!IndexVideo (lpcs,
dwBytesUsed | (nDropped ? 0 : IS_GRANULAR_CHUNK),
fKeyFrame))
return FALSE;
// adjust the size field of the RIFF chunk that preceeds the
// data to be written
//
priff = ((LPRIFF)lpData)-1;
priff->dwSize = dwBytesUsed;
dwBytesUsed += dwBytesUsed & 1;
dwBytesToWrite = dwBytesUsed + sizeof(RIFF);
if (nDropped)
{
UINT jj;
DWORD dwType;
// determine the 'type' of the dummy chunks
//
dwType = MAKEAVICKID(cktypeDIBbits, 0);
if (lpcs->lpBitsInfo->bmiHeader.biCompression == BI_RLE8)
dwType = MAKEAVICKID(cktypeDIBcompressed, 0);
// dont try to write more than 1 'sector' worth of dummy
// frames
//
if (nDropped > (lpcs->dwBytesPerSector / sizeof(RIFF)))
nDropped = lpcs->dwBytesPerSector / sizeof(RIFF);
// create index entries for the dummy chunks
//
for (jj = 0; jj < nDropped-1; ++jj)
IndexVideo (lpcs, IS_DUMMY_CHUNK, FALSE);
IndexVideo (lpcs, IS_DUMMY_CHUNK | IS_GRANULAR_CHUNK, FALSE);
// fill in the drop frame buffer with dummy frames
//
priff = (LPRIFF)(lpData + dwBytesToWrite - sizeof(RIFF));
for (jj = 0; jj < nDropped; ++jj, ++priff)
{
priff->dwSize = 0;
priff->dwType = dwType;
}
dwBytesToWrite += nDropped * sizeof(RIFF);
}
// AviWrite will write the data and create any trailing junk
// that is necessary
//
// write out the chunk, video data, and possibly the junk chunk
//
AuxDebugEx (3, DEBUGLINE "Calling AVIWrite - Video=%8x dw=%8x\r\n",
(LPBYTE)lpData - sizeof(RIFF), dwBytesToWrite);
*lpuError = AVIWrite (lpcs,
(LPBYTE)lpData - sizeof(RIFF),
dwBytesToWrite,
uIndex,
ASYNC_BUF_VIDEO,
lpbPending);
return !(*lpuError);
}
// New for Chicago, align audio buffers on wChunkGranularity boundaries!
//
BOOL WINAPI AVIWriteAudio (
LPCAPSTREAM lpcs,
LPWAVEHDR lpwh,
UINT uIndex,
LPUINT lpuError,
LPBOOL lpbPending)
{
DWORD dwBytesToWrite;
LPRIFF priff;
*lpuError = 0;
*lpbPending = FALSE;
// change the dwSize field in the RIFF chunk
priff = ((LPRIFF)lpwh->lpData) -1;
priff->dwSize = lpwh->dwBytesRecorded;
if ( ! IndexAudio (lpcs, lpwh->dwBytesRecorded | IS_GRANULAR_CHUNK))
return FALSE;
// update total bytes of wave audio recorded
//
lpcs->dwWaveBytes += lpwh->dwBytesRecorded;
// pad the data to be written to a WORD (16 bit) boundary
//
lpwh->dwBytesRecorded += lpwh->dwBytesRecorded & 1;
dwBytesToWrite = lpwh->dwBytesRecorded + sizeof(RIFF);
// write out the chunk, audio data, and possibly the junk chunk
AuxDebugEx (3, DEBUGLINE "Audio=%8x dw=%8x\r\n",
lpwh->lpData - sizeof(RIFF), dwBytesToWrite);
*lpuError = AVIWrite (lpcs,
lpwh->lpData - sizeof(RIFF),
dwBytesToWrite,
uIndex,
ASYNC_BUF_AUDIO,
lpbPending);
return !(*lpuError);
}
#endif //---------------- USE_AVIFILE ----------------------------