/**************************************************************************** * * 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 #include #include #include #include #include #include #include "ivideo32.h" #include "mmdebug.h" #ifdef USE_ACM #include #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 ----------------------------