/****************************************************************************** Copyright (C) Microsoft Corporation 1985-1991. All rights reserved. Title: aviopen.c - open a AVI file *****************************************************************************/ #include "graphic.h" #ifdef WIN32 #include #endif #ifdef USEAVIFILE #include DEFINE_AVIGUID(IID_IAVIFile, 0x00020020, 0, 0); DEFINE_AVIGUID(IID_IAVIStream, 0x00020021, 0, 0); #endif #define comptypeNONE mmioFOURCC('N','O','N','E') // // special error to use AVIFile to open this file. // #define AVIERR_NOT_AVIFILE 4242 // // if this is defined we will always use AVIFILE.DLL, except for // 1:1 interleaved files. // #define USE_AVIFILE_FOR_NON_INT /*************************************************************************** * ***************************************************************************/ BOOL FAR PASCAL mciaviCloseFile(NPMCIGRAPHIC npMCI); BOOL FAR PASCAL mciaviOpenFile(NPMCIGRAPHIC npMCI); BOOL NEAR PASCAL InitStream(NPMCIGRAPHIC npMCI, STREAMINFO *psi); BOOL NEAR PASCAL InitVideoStream(NPMCIGRAPHIC npMCI, STREAMINFO *psi); BOOL NEAR PASCAL InitAudioStream(NPMCIGRAPHIC npMCI, STREAMINFO *psi); BOOL NEAR PASCAL InitOtherStream(NPMCIGRAPHIC npMCI, STREAMINFO *psi); void NEAR PASCAL CloseStream(NPMCIGRAPHIC npMCI, STREAMINFO *psi); BOOL NEAR PASCAL OpenFileInit(NPMCIGRAPHIC npMCI); BOOL NEAR PASCAL ParseNewHeader(NPMCIGRAPHIC npMCI); BOOL NEAR PASCAL OpenRiffAVIFile(NPMCIGRAPHIC npMCI); BOOL NEAR PASCAL OpenWithAVIFile(NPMCIGRAPHIC npMCI); BOOL NEAR PASCAL OpenInterface(NPMCIGRAPHIC npMCI); BOOL NEAR PASCAL OpenAVIFile(NPMCIGRAPHIC npMCI, IAVIFile FAR *pf); BOOL NEAR PASCAL OpenAVIStream(NPMCIGRAPHIC npMCI, int stream, IAVIStream FAR *pf); static BOOL NEAR PASCAL IsRectBogus(LPRECT prc); static LONG NEAR PASCAL atol(char *sz); #ifdef WIN32 #define GetFileDriveType GetDriveType #else static UINT NEAR PASCAL GetFileDriveType(LPSTR szPath); #endif #ifndef WIN32 SZCODE szOLENLSDLL[] = "OLE2NLS.DLL"; SZCODE szOLENLSAPI[] = "GetUserDefaultLangID"; #endif /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | mciaviOpenFile | Open an AVI file. * the filename we are to open is passed to npMCI->szFileName. * * @parm NPMCIGRAPHIC | npMCI | Pointer to instance data. * * @rdesc TRUE means OK, otherwise mci error in dwTaskError * ***************************************************************************/ BOOL FAR PASCAL mciaviOpenFile(NPMCIGRAPHIC npMCI) { // // mciaviOpenFile should not be called with a file open! // Assert(npMCI->streams == 0); Assert(npMCI->hmmio == NULL); Assert(npMCI->hpIndex == NULL); Assert(!(npMCI->dwFlags & ( MCIAVI_NOTINTERLEAVED | MCIAVI_ANIMATEPALETTE | MCIAVI_CANDRAW | MCIAVI_HASINDEX))); // // !!!support open new // if (npMCI->szFilename[0] == '\0') { } // // what media is this file coming from, will be important later // when we play. // if (npMCI->szFilename[0] == '@') npMCI->uDriveType = DRIVE_INTERFACE; else npMCI->uDriveType = GetFileDriveType(npMCI->szFilename); #ifdef DEBUG switch (npMCI->uDriveType) { case DRIVE_CDROM: DOUT2("Media is a CD-ROM\n"); break; case DRIVE_REMOTE: DOUT2("Media is a Network\n"); break; case DRIVE_FIXED: DOUT2("Media is a Hard disk\n"); break; case DRIVE_REMOVABLE: DOUT2("Media is a floppy disk\n"); break; case DRIVE_INTERFACE: DOUT2("Media is OLE COM Interface\n"); break; default: DPF(("Unknown Media type %d\n", npMCI->uDriveType)); break; } #endif #ifdef USEAVIFILE // // if the "filename" is of the form: '@########' then we assume we // have been pased a interface pointer of some sort. // if (npMCI->szFilename[0] == '@' && OpenInterface(npMCI)) goto DoneOpening; // !!! This will open even AVI files this way! if ((npMCI->dwOptionFlags & MCIAVIO_USEAVIFILE) && OpenWithAVIFile(npMCI)) goto DoneOpening; #endif if (!OpenRiffAVIFile(npMCI)) { // // unable to open RIFF file, if it was because it was // not a AVI file, then give AVIFile a try. // if (npMCI->dwTaskError != AVIERR_NOT_AVIFILE) goto error; #ifdef USEAVIFILE npMCI->dwTaskError = 0; if (!OpenWithAVIFile(npMCI)) #endif goto error; } DoneOpening: if (OpenFileInit(npMCI)) { npMCI->dwTaskError = 0; return TRUE; } error: mciaviCloseFile(npMCI); if (npMCI->dwTaskError == 0) npMCI->dwTaskError = MCIERR_INVALID_FILE; return FALSE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | OpenFileInit | called after a file is opened to init things * * @parm NPMCIGRAPHIC | npMCI | Pointer to instance data. * * @rdesc TRUE means OK, otherwise mci error in dwTaskError * ***************************************************************************/ BOOL NEAR PASCAL OpenFileInit(NPMCIGRAPHIC npMCI) { int i; RECT rc; // // lets make sure we opened something. // if (npMCI->streams == 0) return FALSE; if (npMCI->nVideoStreams + npMCI->nAudioStreams + npMCI->nOtherStreams == 0) return FALSE; if (npMCI->nVideoStreams == 0) npMCI->dwFlags &= ~MCIAVI_SHOWVIDEO; if (npMCI->nAudioStreams == 0) npMCI->dwFlags &= ~MCIAVI_PLAYAUDIO; if (npMCI->nAudioStreams > 1) { UINT wLang; int stream; #ifndef WIN32 UINT (WINAPI * GetUserDefaultLangID)(void); UINT u; HANDLE hdll; u = SetErrorMode(SEM_NOOPENFILEERRORBOX); hdll = LoadLibrary(szOLENLSDLL); SetErrorMode(u); if ((UINT)hdll > (UINT)HINSTANCE_ERROR) { if ((FARPROC) GetUserDefaultLangID = GetProcAddress(hdll, szOLENLSAPI)) { #endif wLang = GetUserDefaultLangID(); #ifndef WIN32 } FreeLibrary(hdll); } else wLang = 0; #endif DPF(("Current language: %x\n", wLang)); if (wLang > 0) { for (stream = 0; stream < npMCI->streams; stream++) { if (SH(stream).fccType == streamtypeAUDIO) { if (!(SH(stream).dwFlags & STREAM_ENABLED)) continue; if (SH(stream).wLanguage == wLang) { npMCI->nAudioStream = stream; npMCI->psiAudio = SI(stream); break; } } } } } if (npMCI->dwFlags & MCIAVI_NOTINTERLEAVED) { npMCI->wEarlyRecords = npMCI->wEarlyVideo; } else { npMCI->wEarlyRecords = max(npMCI->wEarlyVideo, npMCI->wEarlyAudio); } if (npMCI->wEarlyRecords == 0 && !(npMCI->dwFlags & MCIAVI_NOTINTERLEAVED)) { DPF(("Interleaved file with no audio skew?\n")); npMCI->dwFlags |= MCIAVI_NOTINTERLEAVED; } if (npMCI->dwFlags & MCIAVI_ANIMATEPALETTE) { DPF(("This AVI file has palette changes.\n")); if (npMCI->nVideoStreams > 1) { npMCI->dwFlags &= ~MCIAVI_ANIMATEPALETTE; DPF(("...But we are going to ignore them?\n")); } } // // this must be set // if (npMCI->dwSuggestedBufferSize == 0) { for (i=0; istreams; i++) npMCI->dwSuggestedBufferSize = max(SH(i).dwSuggestedBufferSize,npMCI->dwSuggestedBufferSize); } // // check all fields in the main header // if (npMCI->dwScale == 0 || npMCI->dwRate == 0) { } ////will be set when header parsed ////npMCI->dwMicroSecPerFrame = muldiv32(npMCI->dwScale, 1000000, npMCI->dwRate); npMCI->dwPlayMicroSecPerFrame = npMCI->dwMicroSecPerFrame; #define COMMON_SCALE 10000 // // convert the rate/scale into something that is normalized to 1000 // npMCI->dwRate = muldiv32(npMCI->dwRate, COMMON_SCALE, npMCI->dwScale); npMCI->dwScale = COMMON_SCALE; // // walk all streams and fix them up. // for (i=0; istreams; i++) { STREAMINFO *psi = SI(i); LONG lStart; LONG lEnd; // // convert the rate/scale into something that is normalized to 1000 // psi->sh.dwRate = muldiv32(psi->sh.dwRate, COMMON_SCALE, psi->sh.dwScale); psi->sh.dwScale = COMMON_SCALE; // // trim any streams that hang over the movie. // lStart = MovieToStream(psi, 0); lEnd = MovieToStream(psi, npMCI->lFrames); if ((LONG)(psi->sh.dwStart + psi->sh.dwLength) > lEnd) { DPF(("Stream #%d is too long, was %ld now %ld\n", i, psi->sh.dwLength, lEnd - psi->sh.dwStart)); psi->sh.dwLength = lEnd - psi->sh.dwStart; } } // // fix up the movie rect // if (IsRectEmpty(&npMCI->rcMovie)) { DPF2(("Movie rect is empty\n")); SetRectEmpty(&rc); for (i=0; istreams; i++) UnionRect(&rc,&rc,&SH(i).rcFrame); npMCI->rcMovie = rc; } rc = npMCI->rcMovie; // // always read the index, so we can skip frames even on CD! // ReadIndex(npMCI); DPF(("Key frames are every (on average): %ld frames (%ld ms)\n",npMCI->dwKeyFrameInfo, MovieToTime(npMCI->dwKeyFrameInfo))); // force things to happen, in case we're re-loading SetRectEmpty(&npMCI->rcSource); SetRectEmpty(&npMCI->rcDest); /* this will call DrawDibBegin() ... */ DevicePut(npMCI, &rc, MCI_DGV_PUT_SOURCE); /* * also set the dest rect. This should be done * by the WM_SIZE message sent during SetWindowToDefaultSize. * On NT, the WM_SIZE message is not sent synchronously since it * is an inter-thread sendmessage (the winproc is on the original thread * whereas we are currently running on the avi thread). The winproc * thread may well not get the WM_SIZE message until much too late, so * set the dest rect here. Note: don't use ResetDestRect since that * also relies on the window size, which is not set yet. */ /* double frame size of destination if zoom by 2 */ if (npMCI->dwOptionFlags & MCIAVIO_ZOOMBY2) SetRect(&rc, 0, 0, rc.right*2, rc.bottom*2); DevicePut(npMCI, &rc, MCI_DGV_PUT_DESTINATION); // // size the window and things. // SetWindowToDefaultSize(npMCI); DrawBegin(npMCI, NULL); return TRUE; } #ifdef USEAVIFILE /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | OpenWithAVIFile | Open an file using AVIFile * * @parm NPMCIGRAPHIC | npMCI | Pointer to instance data. * * @rdesc TRUE means OK, otherwise mci error in dwTaskError * ***************************************************************************/ BOOL NEAR PASCAL OpenWithAVIFile(NPMCIGRAPHIC npMCI) { IAVIFile FAR *pf = NULL; if (!InitAVIFile(npMCI)) return FALSE; AVIFileOpen(&pf, npMCI->szFilename, MMIO_READ, 0); if (pf == NULL) { npMCI->dwTaskError = MCIERR_INVALID_FILE; return FALSE; } if (!OpenAVIFile(npMCI, pf)) { mciaviCloseFile(npMCI); pf->lpVtbl->Release(pf); return FALSE; } return TRUE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | OpenInterface | Open an interface pointer * * @parm NPMCIGRAPHIC | npMCI | Pointer to instance data. * * @rdesc TRUE means OK, otherwise mci error in dwTaskError * ***************************************************************************/ BOOL NEAR PASCAL OpenInterface(NPMCIGRAPHIC npMCI) { IUnknown FAR *p; IAVIFile FAR *pf=NULL; IAVIStream FAR *ps=NULL; if (!InitAVIFile(npMCI)) return FALSE; if (npMCI->szFilename[0] != '@') return FALSE; #ifdef UNICODE p = (IUnknown FAR *)wcstol(npMCI->szFilename+1, NULL, 10); #else p = (IUnknown FAR *)atol(npMCI->szFilename+1); #endif if (!IsValidInterface(p)) return FALSE; #ifndef WIN32 //!!!we need to do the PSP stuff? or will the TASK stuff in //!!!COMPOBJ mess us up? { extern void FAR SetPSP(UINT psp); SetPSP(npMCI->pspParent); } #endif p->lpVtbl->QueryInterface(p, &IID_IAVIFile, (LPVOID FAR *)&pf); if (pf != NULL) { if (OpenAVIFile(npMCI, pf)) return TRUE; pf->lpVtbl->Release(pf); } p->lpVtbl->QueryInterface(p, &IID_IAVIStream, (LPVOID FAR *)&ps); if (ps != NULL) { AVIMakeFileFromStreams(&pf, 1, &ps); ps->lpVtbl->Release(ps); if (pf == NULL) return FALSE; if (OpenAVIFile(npMCI, pf)) return TRUE; pf->lpVtbl->Release(pf); return FALSE; } return FALSE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | OpenAVIFile | Open an a AVIFile object * * NOTE we do not do call AddRef() we assume we dont need to. * * @parm NPMCIGRAPHIC | npMCI | Pointer to instance data. * * @rdesc TRUE means OK, otherwise mci error in dwTaskError * ***************************************************************************/ BOOL NEAR PASCAL OpenAVIFile(NPMCIGRAPHIC npMCI, IAVIFile FAR *pf) { AVIFILEINFO info; HRESULT hr; IAVIStream FAR *ps; STREAMINFO *psi; int i; Assert(npMCI->pf == NULL); _fmemset(&info, 0, sizeof(info)); hr = AVIFileInfo(pf, &info, sizeof(info)); if (FAILED(GetScode(hr))) { npMCI->dwTaskError = MCIERR_INVALID_FILE; return FALSE; } DPF(("OpenAVIFile: %s\n", (LPSTR)info.szFileType)); // // get rid of bad files // if (info.dwStreams == 0 || info.dwStreams > 255 || info.dwLength == 0) { npMCI->dwTaskError = MCIERR_INVALID_FILE; return FALSE; } // // make a copy of the VTable, for later use // npMCI->pf = pf; ////npMCI->vt = *pf->lpVtbl; npMCI->dwFlags |= MCIAVI_HASINDEX; npMCI->dwMicroSecPerFrame = muldiv32(info.dwScale, 1000000, info.dwRate); npMCI->lFrames = (LONG)info.dwLength; npMCI->dwRate = info.dwRate; npMCI->dwScale = info.dwScale; npMCI->streams = (int)info.dwStreams; npMCI->dwBytesPerSec = info.dwMaxBytesPerSec; npMCI->dwSuggestedBufferSize = info.dwSuggestedBufferSize + 2*sizeof(DWORD); SetRect(&npMCI->rcMovie,0,0,(int)info.dwWidth,(int)info.dwHeight); npMCI->paStreamInfo = (STREAMINFO*) LocalAlloc(LPTR,npMCI->streams * sizeof(STREAMINFO)); if (npMCI->paStreamInfo == NULL) { npMCI->dwTaskError = MCIERR_OUT_OF_MEMORY; npMCI->pf = NULL; return FALSE; } for (i = 0; i < npMCI->streams; i++) { ps = NULL; AVIFileGetStream(pf, &ps, 0, i); if (ps == NULL) { npMCI->dwTaskError = MCIERR_INVALID_FILE; npMCI->pf = NULL; return FALSE; } if (!OpenAVIStream(npMCI, i, ps)) DPF(("Error opening stream %d!\n", i)); if (npMCI->dwTaskError) { npMCI->pf = NULL; return FALSE; } } // // compute the key frames every value // // do this by finding the key frame average over the first few frames. // #define NFRAMES 250 if (psi = npMCI->psiVideo) { LONG l; int nKeyFrames=0; for (l=0; lps, psi->sh.dwStart+l, FIND_PREV|FIND_KEY) == l) nKeyFrames++; } if (nKeyFrames > 1) npMCI->dwKeyFrameInfo = (DWORD)((NFRAMES + nKeyFrames/2)/nKeyFrames); else npMCI->dwKeyFrameInfo = 0; } return TRUE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | OpenAVIStream | Open an a AVIStream object * * @parm NPMCIGRAPHIC | npMCI | Pointer to instance data. * * @rdesc TRUE means OK, otherwise mci error in dwTaskError * ***************************************************************************/ BOOL NEAR PASCAL OpenAVIStream(NPMCIGRAPHIC npMCI, int stream, IAVIStream FAR *ps) { STREAMINFO* psi; AVISTREAMINFO info; HRESULT hr; _fmemset(&info, 0, sizeof(info)); hr = AVIStreamInfo(ps, &info, sizeof(info)); if (FAILED(GetScode(hr))) { npMCI->dwTaskError = MCIERR_INVALID_FILE; return FALSE; } DPF(("OpenAVIStream(%d) %4.4s:%4.4s %s\n", stream, (LPSTR)&info.fccType, (LPSTR)&info.fccHandler, (LPSTR)info.szName)); // // init the STREAMINFO from the IAVIStream // psi = SI(stream); psi->ps = ps; // save interface ////psi->vt = *ps->lpVtbl; // save VTable !!!needed? psi->sh.fccType = info.fccType; psi->sh.fccHandler = info.fccHandler; psi->sh.dwFlags = info.dwFlags; psi->sh.wPriority = info.wPriority; psi->sh.wLanguage = info.wLanguage; psi->sh.dwInitialFrames = 0; // info.dwInitialFrames; psi->sh.dwScale = info.dwScale; psi->sh.dwRate = info.dwRate; psi->sh.dwStart = info.dwStart; psi->sh.dwLength = info.dwLength; psi->sh.dwSuggestedBufferSize = info.dwSuggestedBufferSize; psi->sh.dwQuality = info.dwQuality; psi->sh.dwSampleSize = info.dwSampleSize; psi->sh.rcFrame = info.rcFrame; DPF0(("OpenAVIStream: #%d, rc [%d %d %d %d]\n", stream, info.rcFrame)); // // get the format of the stream. // AVIStreamFormatSize(ps, 0, &psi->cbFormat); psi->lpFormat = GlobalAllocPtr(GMEM_MOVEABLE, psi->cbFormat); if (!psi->lpFormat) { npMCI->dwTaskError = MCIERR_OUT_OF_MEMORY; return FALSE; } AVIStreamReadFormat(psi->ps, 0, psi->lpFormat, &psi->cbFormat); // // get the extra data for the stream. // AVIStreamReadData(psi->ps,ckidSTREAMHANDLERDATA, NULL, &psi->cbData); if (psi->cbData > 0) { psi->lpData = GlobalAllocPtr(GMEM_MOVEABLE, psi->cbData); if (!psi->lpData) { npMCI->dwTaskError = MCIERR_OUT_OF_MEMORY; return FALSE; } AVIStreamReadData(psi->ps, ckidSTREAMHANDLERDATA, NULL, &psi->cbData); } return InitStream(npMCI, psi); } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | InitAVIFile | called to RTL to AVIFILE.DLL * * @rdesc TRUE means OK, otherwise error * ***************************************************************************/ #ifdef WIN32 // For the moment the 16 bit and 32 bit AVIFILE DLLs share the same // name. If the history of NT is to be repeated this will change. SZCODE szAVIFILE[] = TEXT("AVIFILE.DLL"); SZCODE szCOMPOBJ[] = TEXT("COMPOB32"); #else SZCODE szAVIFILE[] = "AVIFILE.DLL"; SZCODE szCOMPOBJ[] = "COMPOBJ"; #endif // On NT the entry points will NOT be unicode strings, as there is // no unicode version of GetProcAddress. BUT SZCODE generate UNICODE... SZCODEA szAVIFileInit[] = "AVIFileInit"; SZCODEA szAVIFileExit[] = "AVIFileExit"; SZCODEA szIsValidInterface[] = "IsValidInterface"; SZCODEA szAVIMakeFileFromStreams[] = "AVIMakeFileFromStreams"; SZCODEA szAVIStreamBeginStreaming[] = "AVIStreamBeginStreaming"; SZCODEA szAVIStreamEndStreaming[] = "AVIStreamEndStreaming"; #ifdef UNICODE // There has GOT to be a neat way of combining macros so that we can // assign AVIFileOpenA/W to the name string definining the entry point, // and still get avifilex.h to get AVIFileOpen function calls replaced by // using the function variable. SZCODEA szAVIFileOpen[] = "AVIFileOpenW"; #else SZCODEA szAVIFileOpen[] = "AVIFileOpen"; #endif BOOL FAR InitAVIFile(NPMCIGRAPHIC npMCI) { UINT u; if (hdllAVIFILE == (HMODULE)-1) return FALSE; if (hdllAVIFILE == NULL) { u = SetErrorMode(SEM_NOOPENFILEERRORBOX); hdllAVIFILE = LoadLibrary(szAVIFILE); SetErrorMode(u); #ifndef WIN32 if ((UINT)hdllAVIFILE <= (UINT)HINSTANCE_ERROR) hdllAVIFILE = NULL; #endif if (hdllAVIFILE == NULL) { hdllAVIFILE = (HMODULE)-1; return FALSE; } hdllCOMPOBJ = GetModuleHandle(szCOMPOBJ); (FARPROC)XIsValidInterface = GetProcAddress(hdllCOMPOBJ, szIsValidInterface); Assert(hdllCOMPOBJ != NULL); Assert(XIsValidInterface != NULL); (FARPROC)XAVIFileInit = GetProcAddress(hdllAVIFILE, szAVIFileInit); (FARPROC)XAVIFileExit = GetProcAddress(hdllAVIFILE, szAVIFileExit); (FARPROC)XAVIFileOpen = GetProcAddress(hdllAVIFILE, szAVIFileOpen); (FARPROC)XAVIMakeFileFromStreams = GetProcAddress(hdllAVIFILE, szAVIMakeFileFromStreams); (FARPROC)XAVIStreamBeginStreaming = GetProcAddress(hdllAVIFILE, szAVIStreamBeginStreaming); (FARPROC)XAVIStreamEndStreaming = GetProcAddress(hdllAVIFILE, szAVIStreamEndStreaming); Assert(XAVIFileInit != NULL); Assert(XAVIFileExit != NULL); Assert(XAVIFileOpen != NULL); Assert(XAVIMakeFileFromStreams != NULL); } // // we need to call AVIFileInit() and AVIFileExit() for each task that // is using AVIFile or things will not work right. // if (!(npMCI->dwFlags & MCIAVI_USING_AVIFILE)) { npMCI->dwFlags |= MCIAVI_USING_AVIFILE; AVIFileInit(); uAVIFILE++; } return TRUE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | FreeAVIFile | called to un-RTL to AVIFILE.DLL * * @rdesc TRUE means OK, otherwise error * ***************************************************************************/ BOOL FAR FreeAVIFile(NPMCIGRAPHIC npMCI) { if (!(npMCI->dwFlags & MCIAVI_USING_AVIFILE)) return FALSE; Assert(hdllAVIFILE != (HMODULE)-1 && hdllAVIFILE != NULL); // free this tasks use of AVIFile AVIFileExit(); // if no more people using AVIFile lets the DLLs go. Assert(uAVIFILE > 0); uAVIFILE--; if (uAVIFILE == 0) { FreeLibrary(hdllAVIFILE); hdllAVIFILE = NULL; hdllCOMPOBJ = NULL; } } #endif /* USEAVIFILE */ /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | OpenRiffAVIFile | Open an RIFF AVI file * * @parm NPMCIGRAPHIC | npMCI | Pointer to instance data. * * @rdesc TRUE means OK, otherwise mci error in dwTaskError * ***************************************************************************/ BOOL NEAR PASCAL OpenRiffAVIFile(NPMCIGRAPHIC npMCI) { HMMIO hmmio; HANDLE h = NULL; BOOL fRet = TRUE; MMIOINFO mmioInfo; MMCKINFO ckRIFF; MMCKINFO ckLIST; MMCKINFO ckRECORD; _fmemset(&mmioInfo, 0, sizeof(MMIOINFO)); mmioInfo.htask = (HANDLE)npMCI->hCallingTask; hmmio = mmioOpen(npMCI->szFilename, &mmioInfo, MMIO_READ | MMIO_DENYWRITE); if (hmmio == NULL) hmmio = mmioOpen(npMCI->szFilename, &mmioInfo, MMIO_READ); if (!hmmio) { switch (mmioInfo.wErrorRet) { case MMIOERR_OUTOFMEMORY: npMCI->dwTaskError = MCIERR_OUT_OF_MEMORY; break; case MMIOERR_FILENOTFOUND: case MMIOERR_CANNOTOPEN: default: npMCI->dwTaskError = MCIERR_FILE_NOT_FOUND; break; } fRet = FALSE; goto exit; } npMCI->hmmio = hmmio; /* ** Descend into RIFF file */ if (mmioDescend(hmmio, &ckRIFF, NULL, 0) != 0) { npMCI->dwTaskError = MCIERR_INVALID_FILE; goto ERROR_BADFILE; } /* * check for a 'QuickTime AVI' file, a QuickTime AVI file is a * QuickTime public movie with a AVI file in the 'mdat' atom. */ if (ckRIFF.cksize == mmioFOURCC('m','d','a','t')) { DPF(("File is a QuickTime public movie\n")); /* * now the 'mdat' atom better be a RIFF/AVI or we cant handle it. */ if (mmioDescend(hmmio, &ckRIFF, NULL, 0) != 0) { npMCI->dwTaskError = MCIERR_INVALID_FILE; goto ERROR_BADFILE; } } /* Make sure it's a RIFF file */ if (ckRIFF.ckid != FOURCC_RIFF) { npMCI->dwTaskError = MCIERR_INVALID_FILE; goto ERROR_NOTAVIFILE; } /* Otherwise, it should be an AVI file */ if (ckRIFF.fccType != formtypeAVI) { npMCI->dwTaskError = MCIERR_INVALID_FILE; goto ERROR_NOTAVIFILE; } /* ** Descend into header LIST */ ckLIST.fccType = listtypeAVIHEADER; if (mmioDescend(hmmio, &ckLIST, &ckRIFF, MMIO_FINDLIST) != 0) { npMCI->dwTaskError = MCIERR_INVALID_FILE; goto ERROR_BADFILE; } /* Leave space at end of buffer for pad word */ h = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, ckLIST.cksize - sizeof(DWORD) + sizeof(DWORD)); if (!h) { npMCI->dwTaskError = MCIERR_OUT_OF_MEMORY; return FALSE; } npMCI->lp = npMCI->lpBuffer = (LPSTR) GlobalLock(h); DPF(("Reading header list: %lu bytes.\n", ckLIST.cksize - sizeof(DWORD))); if (mmioRead(hmmio, npMCI->lp, ckLIST.cksize - sizeof(DWORD)) != (LONG) (ckLIST.cksize - sizeof(DWORD))) { npMCI->dwTaskError = MCIERR_FILE_READ; goto ERROR_BADFILE; } #ifdef USE_AVIFILE_FOR_NON_INT // // we check here for AVI RIFF files we dont want to handle with our // built in code, and want to pass on to AVIFILE.DLL // // we handle the following files: // // interleaved // // we pass on the following files to AVIFILE.DLL // // non-interleaved // // pretty simple right now, just interleaved non-interlaved // but could get as complex as you want. // { MainAVIHeader FAR * lpHdr; lpHdr = (MainAVIHeader FAR *)((BYTE FAR *)npMCI->lp + 8); if (!(lpHdr->dwFlags & AVIF_ISINTERLEAVED) || lpHdr->dwInitialFrames == 0) { DOUT("File is not interleaved, giving it to AVIFILE.DLL\n"); goto ERROR_NOTAVIFILE; } // // ok now we have a 1:1 interleved file. // // always use our code on a CD-ROM, but on other media... // switch (npMCI->uDriveType) { case DRIVE_CDROM: break; case DRIVE_REMOTE: case DRIVE_FIXED: case DRIVE_REMOVABLE: break; default: break; } } #endif if (PEEK_DWORD() == ckidAVIMAINHDR) { if (!ParseNewHeader(npMCI)) goto ERROR_BADFILE; } else { npMCI->dwTaskError = MCIERR_INVALID_FILE; goto ERROR_BADFILE; } /* Ascend out of header LIST */ if (mmioAscend(hmmio, &ckLIST, 0) != 0) { npMCI->dwTaskError = MCIERR_FILE_READ; goto ERROR_BADFILE; } /* Initially, no frame has been drawn */ npMCI->lFrameDrawn = (- (LONG) npMCI->wEarlyRecords) - 1; /* ** Descend into big 'Movie LIST' */ ckLIST.fccType = listtypeAVIMOVIE; if (mmioDescend(hmmio, &ckLIST, &ckRIFF, MMIO_FINDLIST) != 0) { npMCI->dwTaskError = MCIERR_INVALID_FILE; goto ERROR_BADFILE; } npMCI->dwMovieListOffset = ckLIST.dwDataOffset; /* Calculate end of 'movi' list, in case we need to read the index */ npMCI->dwBigListEnd = ckLIST.dwDataOffset + ckLIST.cksize + (ckLIST.cksize & 1); /* ** Descend into header of first chunk */ if (mmioDescend(hmmio, &ckRECORD, &ckLIST, 0) != 0) { npMCI->dwTaskError = MCIERR_INVALID_FILE; goto ERROR_BADFILE; } npMCI->dwFirstRecordType = ckRECORD.ckid; npMCI->dwFirstRecordSize = ckRECORD.cksize + 2 * sizeof(DWORD); npMCI->dwFirstRecordPosition = mmioSeek(hmmio, 0, SEEK_CUR); if (mmioAscend(hmmio, &ckRECORD, 0) != 0) { npMCI->dwTaskError = MCIERR_FILE_READ; goto ERROR_BADFILE; } #ifdef DEBUG DPF2(("First record (%4.4s) 0x%lx bytes at position 0x%lx.\n", (LPSTR)&npMCI->dwFirstRecordType, npMCI->dwFirstRecordSize, npMCI->dwFirstRecordPosition)); if (npMCI->dwFirstRecordPosition & 0x7ff) { DPF(("!!\n")); DPF(("!! This file is not properly aligned to a 2K boundary.\n")); DPF(("!! It may not play well from CD-ROM.\n")); DPF(("!!\n")); } #endif exit: if (!fRet) mciaviCloseFile(npMCI); if (h) { npMCI->lpBuffer = NULL; npMCI->dwBufferSize = 0L; GlobalUnlock(h); GlobalFree(h); } return fRet; ERROR_NOTAVIFILE: npMCI->dwTaskError = AVIERR_NOT_AVIFILE; // mark as not a AVI file ERROR_BADFILE: fRet = FALSE; goto exit; } /*************************************************************************** * @doc INTERNAL MCIAVI * * @api BOOL | ParseNewHeader | 'nuf said * ***************************************************************************/ BOOL NEAR PASCAL ParseNewHeader(NPMCIGRAPHIC npMCI) { DWORD dwHeaderSize; MainAVIHeader FAR * lpHdr; int stream; if (GET_DWORD() != ckidAVIMAINHDR) { goto FileError; } dwHeaderSize = GET_DWORD(); /* Skip size */ /* Now, we're pointing at the actual header */ lpHdr = (MainAVIHeader FAR *) npMCI->lp; npMCI->lFrames = (LONG)lpHdr->dwTotalFrames; npMCI->dwMicroSecPerFrame = lpHdr->dwMicroSecPerFrame; npMCI->dwRate = 1000000; npMCI->dwScale = npMCI->dwMicroSecPerFrame; /* Reject some bad values */ if (!lpHdr->dwStreams || lpHdr->dwStreams > 255 || !npMCI->lFrames) { goto FileError; } npMCI->streams = (int) lpHdr->dwStreams; npMCI->dwBytesPerSec = lpHdr->dwMaxBytesPerSec; npMCI->wEarlyRecords = (UINT) lpHdr->dwInitialFrames; npMCI->dwSuggestedBufferSize = lpHdr->dwSuggestedBufferSize; SetRect(&npMCI->rcMovie,0,0,(int)lpHdr->dwWidth,(int)lpHdr->dwHeight); npMCI->dwFlags |= MCIAVI_HASINDEX; if (!(lpHdr->dwFlags & AVIF_ISINTERLEAVED)) { DPF(("File is not interleaved.\n")); npMCI->dwFlags |= MCIAVI_NOTINTERLEAVED; } SKIP_BYTES(dwHeaderSize); /* Skip rest of chunk */ npMCI->paStreamInfo = (STREAMINFO NEAR *) LocalAlloc(LPTR, npMCI->streams * sizeof(STREAMINFO)); // !!! error check for (stream = 0; stream < npMCI->streams; stream++) { AVIStreamHeader FAR * lpStream; HPSTR hpNextChunk; STREAMINFO * psi = &npMCI->paStreamInfo[stream]; if (GET_DWORD() != FOURCC_LIST) { goto FileError; } dwHeaderSize = GET_DWORD(); /* Skip size */ hpNextChunk = npMCI->lp + (dwHeaderSize + (dwHeaderSize & 1)); if (GET_DWORD() != listtypeSTREAMHEADER) { goto FileError; } /* Now, we're at the begging of the stream's header chunks. */ if (GET_DWORD() != ckidSTREAMHEADER) { goto FileError; } dwHeaderSize = GET_DWORD(); /* Skip size */ /* Now, we're pointing at the stream header */ lpStream = (AVIStreamHeader FAR *) npMCI->lp; hmemcpy(&psi->sh, lpStream, min(dwHeaderSize, sizeof(psi->sh))); // // reject files with more than one video stream. // if (psi->sh.fccType == streamtypeVIDEO && npMCI->nVideoStreams >= 1) { DPF(("File has multiple video streams, giving it to AVIFILE.DLL\n")); goto DontHandleThisFile; } SKIP_BYTES(dwHeaderSize); /* Read the format */ if (GET_DWORD() != ckidSTREAMFORMAT) { goto FileError; } dwHeaderSize = GET_DWORD(); /* Skip size */ if (dwHeaderSize > 16384L) { goto FileError; } psi->cbFormat = dwHeaderSize; psi->lpFormat = GlobalAllocPtr(GHND,dwHeaderSize); if (!psi->lpFormat) { npMCI->dwTaskError = MCIERR_OUT_OF_MEMORY; return FALSE; } hmemcpy(psi->lpFormat, npMCI->lp, dwHeaderSize); SKIP_BYTES(dwHeaderSize); if (PEEK_DWORD() == ckidSTREAMHANDLERDATA) { GET_DWORD(); dwHeaderSize = GET_DWORD(); /* Skip size */ psi->cbData = dwHeaderSize; psi->lpData = GlobalAllocPtr(GHND,dwHeaderSize); if (!psi->lpData) { npMCI->dwTaskError = MCIERR_OUT_OF_MEMORY; return FALSE; } hmemcpy(psi->lpData, npMCI->lp, dwHeaderSize); /* Skip to end of Data chunk */ SKIP_BYTES(dwHeaderSize); } else { psi->cbData = 0; psi->lpData = NULL; } InitStream(npMCI, psi); npMCI->lp = hpNextChunk; } return TRUE; FileError: npMCI->dwTaskError = MCIERR_INVALID_FILE; return FALSE; DontHandleThisFile: npMCI->dwTaskError = AVIERR_NOT_AVIFILE; return FALSE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | mciaviCloseFile | Close an AVI file. * * @parm NPMCIGRAPHIC | npMCI | Pointer to instance data. * * @rdesc TRUE means OK, otherwise mci error in dwTaskError * ***************************************************************************/ BOOL FAR PASCAL mciaviCloseFile (NPMCIGRAPHIC npMCI) { if (!npMCI) return FALSE; #ifdef DEBUG npMCI->mciid = MCIIDX; #endif if (npMCI->lpMMIOBuffer) { GlobalFreePtr(npMCI->lpMMIOBuffer); npMCI->lpMMIOBuffer = NULL; } npMCI->hicDraw = NULL; if (npMCI->hicDrawDefault) { if (npMCI->hicDrawDefault != (HIC) -1) ICClose(npMCI->hicDrawDefault); npMCI->hicDrawDefault = NULL; } if (npMCI->hicDrawFull) { if (npMCI->hicDrawFull != (HIC) -1) ICClose(npMCI->hicDrawFull); npMCI->hicDrawFull = NULL; } if (npMCI->hicDecompress) { // !!! What if we never began it? ICDecompressEnd(npMCI->hicDecompress); ICClose(npMCI->hicDecompress); npMCI->hicDecompress = NULL; } if (npMCI->hicInternal) { ICClose(npMCI->hicInternal); npMCI->hicInternal = NULL; } if (npMCI->hicInternalFull) { ICClose(npMCI->hicInternalFull); npMCI->hicInternalFull = NULL; } if (npMCI->hmmio) { mmioClose(npMCI->hmmio, 0); npMCI->hmmio = NULL; } if (npMCI->hmmioAudio) { mmioClose(npMCI->hmmioAudio, 0); npMCI->hmmioAudio = NULL; } if (npMCI->pWF) { LocalFree((HANDLE) npMCI->pWF); npMCI->pWF = NULL; } if (npMCI->pbiFormat) { GlobalFreePtr(npMCI->pbiFormat); npMCI->pbiFormat = NULL; } // if (npMCI->hpal) { // DeleteObject(npMCI->hpal); // npMCI->hpal = NULL; // } if (npMCI->hpDecompress) { GlobalFreePtr(npMCI->hpDecompress); npMCI->hpDecompress = NULL; } if (npMCI->hpIndex) { GlobalFreePtr(npMCI->hpIndex); npMCI->hpIndex = NULL; } if (npMCI->hpFrameIndex) { GlobalFreePtr(npMCI->hpFrameIndex); //!!!NTBUG not same pointer! npMCI->hpFrameIndex = NULL; } if (npMCI->pVolumeTable) { LocalFree((HLOCAL)npMCI->pVolumeTable); npMCI->pVolumeTable = NULL; } #ifdef USEAVIFILE if (npMCI->pf) { AVIFileClose(npMCI->pf); npMCI->pf = NULL; } #endif if (npMCI->paStreamInfo) { int i; for (i = 0; i < npMCI->streams; i++) CloseStream(npMCI, &npMCI->paStreamInfo[i]); LocalFree((HLOCAL)npMCI->paStreamInfo); npMCI->paStreamInfo = NULL; } npMCI->streams = 0; npMCI->nAudioStreams = 0; npMCI->nVideoStreams = 0; npMCI->nErrorStreams = 0; npMCI->nOtherStreams = 0; npMCI->wEarlyVideo = 0; npMCI->wEarlyAudio = 0; npMCI->wEarlyRecords = 0; //!!! I bet we need to clear more npMCI->dwFlags &= ~(MCIAVI_NOTINTERLEAVED | MCIAVI_ANIMATEPALETTE | MCIAVI_CANDRAW | MCIAVI_HASINDEX); return TRUE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | CloseStream | Close an StreamAVI file. * ***************************************************************************/ void NEAR PASCAL CloseStream(NPMCIGRAPHIC npMCI, STREAMINFO *psi) { psi->dwFlags &= ~STREAM_ENABLED; ////psi->sh.fccType = 0; ////psi->sh.fccHandler = 0; if (psi->lpFormat) GlobalFreePtr(psi->lpFormat); psi->lpFormat = NULL; if (psi->lpData) GlobalFreePtr(psi->lpData); psi->lpData = NULL; if (psi->hicDraw) ICClose(psi->hicDraw); psi->hicDraw = NULL; #ifdef USEAVIFILE if (psi->ps) AVIStreamClose(psi->ps); psi->ps = NULL; #endif } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | InitStream | initialize a stream * ***************************************************************************/ BOOL NEAR PASCAL InitStream(NPMCIGRAPHIC npMCI, STREAMINFO *psi) { BOOL f; // // set flags // if (psi->sh.dwFlags & AVISF_VIDEO_PALCHANGES) psi->dwFlags |= STREAM_PALCHANGES; psi->lStart = (LONG)psi->sh.dwStart; psi->lEnd = (LONG)psi->sh.dwStart + psi->sh.dwLength; if (psi->sh.fccType == streamtypeVIDEO && !(npMCI->dwFlags & MCIAVI_NOTINTERLEAVED)) psi->lStart -= (LONG)psi->sh.dwInitialFrames; switch(psi->sh.fccType) { case streamtypeVIDEO: f = InitVideoStream(npMCI, psi); break; case streamtypeAUDIO: f = InitAudioStream(npMCI, psi); break; default: f = InitOtherStream(npMCI, psi); break; } if (!f) { psi->dwFlags |= STREAM_ERROR; npMCI->nErrorStreams++; CloseStream(npMCI, psi); } // // disable the stream if the file header says to // if (psi->sh.dwFlags & AVISF_DISABLED) { psi->dwFlags &= ~STREAM_ENABLED; } return f; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | InitVideoStream | initialize a video stream * ***************************************************************************/ BOOL NEAR PASCAL InitVideoStream(NPMCIGRAPHIC npMCI, STREAMINFO *psi) { LPBITMAPINFOHEADER lpbi; int stream = psi - npMCI->paStreamInfo; npMCI->wEarlyVideo = (UINT)psi->sh.dwInitialFrames; if (psi->sh.dwFlags & AVISF_VIDEO_PALCHANGES) { //!!! is this right. npMCI->dwFlags |= MCIAVI_ANIMATEPALETTE; } if (IsRectBogus(&psi->sh.rcFrame)) { DPF(("BOGUS Stream rectangle [%d %d %d %d]\n", psi->sh.rcFrame)); SetRectEmpty(&psi->sh.rcFrame); } // In case the rectangle is totally wrong, chop it down to size.... // !!! What if the user _wants_ a zero-size RECT? IntersectRect(&psi->sh.rcFrame, &psi->sh.rcFrame, &npMCI->rcMovie); if (IsRectEmpty(&psi->sh.rcFrame)) { DPF(("Video stream rect is empty, correcting\n")); SetRect(&psi->sh.rcFrame, 0, 0, (int)((LPBITMAPINFOHEADER)psi->lpFormat)->biWidth, (int)((LPBITMAPINFOHEADER)psi->lpFormat)->biHeight); } // // make sure the biCompression is right for RLE files. // lpbi = (LPBITMAPINFOHEADER)psi->lpFormat; if (psi->sh.fccHandler == 0) { if (lpbi->biCompression == 0) psi->sh.fccHandler = comptypeDIB; if (lpbi->biCompression == BI_RLE8 && lpbi->biBitCount == 8) psi->sh.fccHandler = comptypeRLE; if (lpbi->biCompression > 256) psi->sh.fccHandler = lpbi->biCompression; } if (lpbi->biCompression <= BI_RLE8 && lpbi->biBitCount == 8) { if (psi->sh.fccHandler == comptypeRLE0 || psi->sh.fccHandler == comptypeRLE) lpbi->biCompression = BI_RLE8; // Assuming a DIB handler has RGB data will blow up files that have RLE data. // Unfortunately, VidEdit writes out files like this. // if (psi->sh.fccHandler == comptypeDIB) // lpbi->biCompression = BI_RGB; } // // make sure the color table is set to the right size // if (lpbi->biClrUsed == 0 && lpbi->biBitCount <= 8) lpbi->biClrUsed = (1 << (int)lpbi->biBitCount); // // try to open draw handler // if (psi->sh.fccHandler) { psi->hicDraw = ICDrawOpen(psi->sh.fccType,psi->sh.fccHandler,psi->lpFormat); if (psi->hicDraw) DPF(("Opened draw handler %4.4s:%4.4s\n", (LPSTR)&psi->sh.fccType,(LPSTR)&psi->sh.fccHandler)); } // // one video stream is the master, he controls the palette etc // for lack of a better default the first video stream will // become the master. // if (npMCI->pbiFormat == NULL) { npMCI->nVideoStream = stream; npMCI->psiVideo = psi; npMCI->pbiFormat = (LPBITMAPINFOHEADER) GlobalAllocPtr(GMEM_MOVEABLE, psi->cbFormat); if (!npMCI->pbiFormat) { npMCI->dwTaskError = MCIERR_OUT_OF_MEMORY; return FALSE; } // // copy the entire format over // hmemcpy(npMCI->pbiFormat,psi->lpFormat,psi->cbFormat); npMCI->bih = *npMCI->pbiFormat; npMCI->bih.biSize = sizeof(BITMAPINFOHEADER); npMCI->bih.biCompression = BI_RGB; if (npMCI->bih.biClrUsed) { /* save the original colors. */ hmemcpy(npMCI->argb, (LPBYTE)npMCI->pbiFormat + npMCI->pbiFormat->biSize, (int)npMCI->bih.biClrUsed * sizeof(RGBQUAD)); hmemcpy(npMCI->argbOriginal, (LPSTR) npMCI->pbiFormat + npMCI->pbiFormat->biSize, (int)npMCI->bih.biClrUsed * sizeof(RGBQUAD)); } // // now open the decompressor, try fastdecompress if it will do it. // npMCI->hicDecompress = ICLocate(ICTYPE_VIDEO,psi->sh.fccHandler, psi->lpFormat,NULL,ICMODE_FASTDECOMPRESS); // fast decompress may not be supported if (npMCI->hicDecompress == NULL) { npMCI->hicDecompress = ICDecompressOpen(ICTYPE_VIDEO, psi->sh.fccHandler,psi->lpFormat,NULL); } // // set any state data. // if (npMCI->hicDecompress && psi->cbData) { ICSetState(npMCI->hicDecompress, psi->lpData, psi->cbData); } if (psi->hicDraw == NULL && npMCI->hicDecompress == NULL && psi->sh.fccHandler != comptypeRLE0 && psi->sh.fccHandler != comptypeNONE && psi->sh.fccHandler != comptypeDIB && psi->sh.fccHandler != comptypeRLE && psi->sh.fccHandler != 0) { DPF(("Unable to open compressor '%4.4ls'!!!\n", (LPSTR) &psi->sh.fccHandler)); npMCI->nVideoStream = -1; npMCI->psiVideo = NULL; GlobalFreePtr(npMCI->pbiFormat); npMCI->pbiFormat = NULL; // // we would like to return a custom, error but MCI will not // find the error string because it has unloaded us (because // the open failed), so we return a bogus generic error. // if (npMCI->streams == 1) // this is the only stream npMCI->dwTaskError = MMSYSERR_NODRIVER; // MCIERR_AVI_NOCOMPRESSOR; return FALSE; // cant load this video stream } } else { // // this is not the default video stream find a draw handler that // can deal with the stream. // // // try VIDS.DRAW // // if that fails open a draw handler not-specific to the format // if (psi->hicDraw == NULL) { psi->hicDraw = ICOpen(psi->sh.fccType,FOURCC_AVIDraw,ICMODE_DRAW); if (psi->hicDraw) DOUT("Opened draw handler VIDS.DRAW\n"); if (psi->hicDraw && ICDrawQuery(psi->hicDraw,psi->lpFormat) != ICERR_OK) { DOUT("Closing VIDS.DRAW because it cant handle this format"); ICClose(psi->hicDraw); psi->hicDraw = NULL; } } // // if that fails open our internal handler. // if (psi->hicDraw == NULL) { psi->hicDraw = ICOpenFunction(psi->sh.fccType, FOURCC_AVIDraw,ICMODE_DRAW,(FARPROC)ICAVIDrawProc); if (psi->hicDraw) DOUT("Opened Internal draw handler\n"); } } npMCI->dwFlags |= MCIAVI_NEEDDRAWBEGIN; psi->dwFlags |= STREAM_VIDEO; // is a video stream psi->dwFlags |= STREAM_ENABLED; npMCI->nVideoStreams++; return TRUE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | InitAudioStream | initialize a audio stream * ***************************************************************************/ BOOL NEAR PASCAL InitAudioStream(NPMCIGRAPHIC npMCI, STREAMINFO *psi) { int stream = psi - npMCI->paStreamInfo; LPWAVEFORMAT pwf; npMCI->wEarlyAudio = (UINT)psi->sh.dwInitialFrames; pwf = (LPWAVEFORMAT)psi->lpFormat; if (pwf->nChannels == 0 || pwf->nSamplesPerSec == 0) { return FALSE; } if (pwf->wFormatTag == WAVE_FORMAT_PCM) { pwf->nBlockAlign = pwf->nChannels * ((((LPPCMWAVEFORMAT)pwf)->wBitsPerSample + 7) / 8); pwf->nAvgBytesPerSec = pwf->nBlockAlign * pwf->nSamplesPerSec; } psi->sh.dwSampleSize = pwf->nBlockAlign; psi->dwFlags |= STREAM_AUDIO; // audio stream psi->dwFlags |= STREAM_ENABLED; // enabled by default. // // make sure dwRate and dwScale are right // dwRate/dwScale should be blocks/sec // Assert(muldiv32(pwf->nAvgBytesPerSec,1000,pwf->nBlockAlign) == muldiv32(psi->sh.dwRate, 1000, psi->sh.dwScale)); // // just to be safe set these ourself to the right value. // psi->sh.dwRate = pwf->nAvgBytesPerSec; psi->sh.dwScale = pwf->nBlockAlign; // // only one audio stream can be active at once // for lack of a better default the first audio stream will // become the active one. // if (npMCI->nAudioStreams == 0) { npMCI->nAudioStream = stream; npMCI->psiAudio = psi; } npMCI->nAudioStreams++; return TRUE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | InitOtherStream | initialize a random stream * ***************************************************************************/ BOOL NEAR PASCAL InitOtherStream(NPMCIGRAPHIC npMCI, STREAMINFO *psi) { int stream = psi - npMCI->paStreamInfo; /* Open the specified video compressor */ psi->hicDraw = ICDrawOpen(psi->sh.fccType,psi->sh.fccHandler,psi->lpFormat); if (psi->hicDraw == NULL) { DPF(("Unable to play stream!\n")); return FALSE; } if (psi->cbData > 0) { ICSetState(psi->hicDraw, psi->lpData, psi->cbData); } psi->dwFlags |= STREAM_ENABLED; ////psi->dwFlags |= STREAM_OTHER; npMCI->nOtherStreams++; return TRUE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | CleanIndex | This function cleans up the index loaded by * ReadIndex() it does the following when cleaning up the index: * * converts all offsets to be absolute * * converts "alpha" format index's into new format. * * computes the max buffer size needed to read this file. * ***************************************************************************/ static BOOL NEAR CleanIndex(NPMCIGRAPHIC npMCI) { LONG lScan; AVIINDEXENTRY FAR * px; AVIINDEXENTRY FAR * pxRec=NULL; DWORD lIndexAdjust; Assert(npMCI->hpIndex != NULL); px = (AVIINDEXENTRY FAR *)npMCI->hpIndex; #ifdef ALPHAFILES if (npMCI->dwFlags & MCIAVI_USINGALPHAFORMAT) lIndexAdjust = 0; else #endif if (// (avihdr.dwFlags & AVIF_MUSTUSEINDEX) || (px->dwChunkOffset < 100)) lIndexAdjust = npMCI->dwMovieListOffset; else lIndexAdjust = (npMCI->dwMovieListOffset + sizeof(DWORD)) - px->dwChunkOffset; //!!! only compute this for the video stream! (or interleaved...) npMCI->dwSuggestedBufferSize = 0; // lets get this exactly right DPF(("Adjusting index by %ld bytes....\n", lIndexAdjust)); /* Can we do anything to see if the index is valid? */ for (lScan = 0; lScan < (LONG)npMCI->macIndex; lScan++, ++((AVIINDEXENTRY _huge *)px)) { DWORD ckid; // // adjust the offset to be absolute // px->dwChunkOffset += lIndexAdjust; // get ckid ckid = px->ckid; // // make sure the buffer size is right, ignore audio chunks because // they are either in a 'rec' or we will be reading them into // internal buffers not the main buffer.365 // if (((npMCI->dwFlags & MCIAVI_NOTINTERLEAVED) || ckid == listtypeAVIRECORD) && TWOCCFromFOURCC(ckid) != cktypeWAVEbytes) { if (px->dwChunkLength + 8 > npMCI->dwSuggestedBufferSize) npMCI->dwSuggestedBufferSize = px->dwChunkLength + 12; } #ifdef ALPHAFILES // // convert a "old" index to a new index // if (npMCI->dwFlags & MCIAVI_USINGALPHAFORMAT) { switch(ckid) { case ckidDIBbits: px->dwFlags |= AVIIF_KEYFRAME; px->ckid = MAKEAVICKID(cktypeDIBbits, 0); break; case ckidDIBcompressed: px->ckid = MAKEAVICKID(cktypeDIBcompressed, 0); break; case ckidDIBhalfframe: px->ckid = MAKEAVICKID(cktypeDIBhalf, 0); break; case ckidPALchange: px->ckid = MAKEAVICKID(cktypePALchange, 0); break; case ckidWAVEbytes: px->ckid = MAKEAVICKID(cktypeWAVEbytes, 1); break; case ckidWAVEsilence: px->ckid = MAKEAVICKID(cktypeWAVEsilence, 1); break; case ckidAVIPADDING: case ckidOLDPADDING: px->ckid = ckidAVIPADDING; break; } ckid = px->ckid; } #endif // // do special things with the video stream. // if (StreamFromFOURCC(ckid) == (UINT)npMCI->nVideoStream) { // // fix up bogus index's by adding any missing AVIIF_KEYFRAME // bits. ie this only applies for RLE files. // if (TWOCCFromFOURCC(ckid) == cktypeDIBbits && VIDFMT(npMCI->nVideoStream)->biCompression <= BI_RLE8) px->dwFlags |= AVIIF_KEYFRAME; // // for video streams, make sure the palette changes are marked // as a no time chunk // if (TWOCCFromFOURCC(ckid) == cktypePALchange) px->dwFlags |= AVIIF_NOTIME/*|AVIIF_PALCHANGE*/; // // make sure the 'REC ' list has the right flags. // if (pxRec) { if ((px->dwFlags & AVIIF_KEYFRAME) != (pxRec->dwFlags & AVIIF_KEYFRAME)) { // Record list does not have correct flags pxRec->dwFlags &= ~AVIIF_KEYFRAME; pxRec->dwFlags |= (px->dwFlags & AVIIF_KEYFRAME); } } } if (ckid == listtypeAVIRECORD) { pxRec = px; if (npMCI->dwFlags & MCIAVI_NOTINTERLEAVED) { DPF(("Non interleaved file with a 'REC ' in it?\n")); npMCI->wEarlyRecords = max(npMCI->wEarlyVideo, npMCI->wEarlyAudio); if (npMCI->wEarlyRecords > 0) { DPF(("Interlaved file with bad header\n")); npMCI->dwFlags &= ~MCIAVI_NOTINTERLEAVED; } } } } return TRUE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | MakeFrameIndex | makes the frame index * * the frame index is a array of AVIFRAMEINDEX entries, one for each * frame in the movie. using the frame index we can easily find * a given frame, along with it's keyframe and palette. * ***************************************************************************/ static BOOL NEAR MakeFrameIndex(NPMCIGRAPHIC npMCI) { LONG nFrames; LONG iFrameStart; LONG iFrame; LONG iKeyFrame; LONG nKeyFrames; LONG iScan; LONG iNewIndex; LONG iPalette; BOOL fInterleaved; DWORD ckid; STREAMINFO *psi; AVIINDEXENTRY _huge * pNewIndex; AVIINDEXENTRY _huge * pIndexEntry; AVIFRAMEINDEX _huge * pFrameIndex; if (npMCI->nVideoStreams == 0) return TRUE; if (npMCI->hpFrameIndex != NULL) return TRUE; psi = npMCI->psiVideo; Assert(psi != NULL); fInterleaved = !(npMCI->dwFlags & MCIAVI_NOTINTERLEAVED); if (fInterleaved && muldiv32(npMCI->dwRate, 1000, npMCI->dwScale) != muldiv32(psi->sh.dwRate, 1000, psi->sh.dwScale)) { // // master video stream should match the movie rate! // AssertSz(FALSE, "Video stream differnet rate than movie"); npMCI->dwRate = psi->sh.dwRate; npMCI->dwScale = psi->sh.dwScale; } if (fInterleaved) iFrameStart = -(LONG)npMCI->wEarlyRecords; else iFrameStart = -(LONG)npMCI->wEarlyVideo; nFrames = npMCI->lFrames - iFrameStart; npMCI->hpFrameIndex = (LPVOID)GlobalAllocPtr(GMEM_SHARE|GHND, (DWORD)(nFrames+1) * sizeof(AVIFRAMEINDEX)); if (npMCI->hpFrameIndex == NULL) { DPF(("Couldn't allocate memory for frame index!\n")); return FALSE; } // // do this so we can just index the array with the frame number // (positive or neg) // npMCI->hpFrameIndex += (-iFrameStart); pFrameIndex = npMCI->hpFrameIndex; iFrame = iFrameStart; iKeyFrame = -(LONG)npMCI->wEarlyVideo; // iFrameStart; iNewIndex = 0; iPalette = -1; // first palette nKeyFrames= 0; #ifdef USEAVIFILE if (npMCI->pf) { PAVISTREAM ps = SI(npMCI->nVideoStream)->ps; for (iFrame = 0; iFrame < npMCI->lFrames; iFrame++) { LONG iKey; iKey = AVIStreamFindSample(ps,iFrame,FIND_PREV|FIND_KEY); iPalette = AVIStreamFindSample(ps,iFrame,FIND_PREV|FIND_FORMAT); if (iKey != -1) iKeyFrame = iKey; if (iPalette == -1) iPalette = 0; pFrameIndex[iFrame].iPrevKey = (UINT)(iFrame - iKeyFrame); pFrameIndex[iFrame].iNextKey = 0; pFrameIndex[iFrame].iPalette = (WORD)iPalette; pFrameIndex[iFrame].dwOffset = 0; pFrameIndex[iFrame].dwLength = 0; Assert(iPalette <= 0xFFFF); if (iFrame - iKeyFrame > 0xFFFF) { //!!! we need to set a flag! //!!! we need to throw out the index! AssertSz(FALSE, "File has too few key frames"); pFrameIndex[iFrame].iPrevKey = 0; } } goto ack; } #endif Assert(npMCI->hpIndex != NULL); Assert(npMCI->macIndex != 0L); pNewIndex = npMCI->hpIndex; pIndexEntry = npMCI->hpIndex; for (iScan = 0; iScan < (LONG)npMCI->macIndex; iScan++, pIndexEntry++) { ckid = pIndexEntry->ckid; // // check for palette changes. // if (StreamFromFOURCC(ckid) == (UINT)npMCI->nVideoStream && TWOCCFromFOURCC(ckid) == cktypePALchange) { iPalette = iNewIndex; pNewIndex[iNewIndex++] = *pIndexEntry; if (fInterleaved) pFrameIndex[iFrame-1].iPalette = (WORD)iPalette; } // // remove the video stream from the master index // if ((ckid != listtypeAVIRECORD) && (StreamFromFOURCC(ckid) != (UINT)npMCI->nVideoStream)) { pNewIndex[iNewIndex++] = *pIndexEntry; } // // in interleaved files a "frame" happens every list record // // in non-interleaved files a "frame" happens every piece of // data in the video stream (except no time chunks) // if (fInterleaved) { if (ckid != listtypeAVIRECORD) continue; } else { if ((StreamFromFOURCC(ckid) != (UINT)npMCI->nVideoStream) || (pIndexEntry->dwFlags & AVIIF_NOTIME)) continue; } AssertSz(iFrame < npMCI->lFrames,"Too many frames in index!"); if (iFrame >= npMCI->lFrames) { break; } if (pIndexEntry->dwFlags & AVIIF_KEYFRAME) { iKeyFrame = iFrame; nKeyFrames++; } pFrameIndex[iFrame].iPrevKey = (UINT)(iFrame - iKeyFrame); pFrameIndex[iFrame].iNextKey = 0; pFrameIndex[iFrame].iPalette = (WORD)iPalette; pFrameIndex[iFrame].dwOffset = pIndexEntry->dwChunkOffset; pFrameIndex[iFrame].dwLength = pIndexEntry->dwChunkLength; if (fInterleaved) pFrameIndex[iFrame].dwOffset += 3 * sizeof(DWORD); Assert(iPalette <= 0xFFFF); if (iFrame - iKeyFrame > 0xFFFF) { //!!! we need to set a flag! //!!! we need to throw out the index! AssertSz(FALSE, "File has too few key frames"); pFrameIndex[iFrame].iPrevKey = 0; } iFrame++; } ack: // // iFrame better equal npMCI->lFrames // Assert(iFrame == npMCI->lFrames); if (iFrame < npMCI->lFrames) npMCI->lFrames = iFrame; // // make a "dummy" last frame // pFrameIndex[iFrame].iPrevKey = (UINT)(iFrame - iKeyFrame); pFrameIndex[iFrame].iNextKey = 0; pFrameIndex[iFrame].iPalette = (WORD)iPalette; pFrameIndex[iFrame].dwOffset = 0; pFrameIndex[iFrame].dwLength = 0; // // compute the key frames every value // if (nKeyFrames) { if (nKeyFrames > 1) npMCI->dwKeyFrameInfo = (DWORD)((nFrames + nKeyFrames/2)/nKeyFrames); else npMCI->dwKeyFrameInfo = 0; } // // now go through the index, and fix all the iNextKey fields // pFrameIndex = npMCI->hpFrameIndex; ////iKeyFrame = npMCI->lFrames; //!!! what should this be set to? zero? for (iFrame = npMCI->lFrames; iFrame>=iFrameStart; iFrame--) { if (pFrameIndex[iFrame].iPrevKey == 0) iKeyFrame = iFrame; if (iKeyFrame >= iFrame) pFrameIndex[iFrame].iNextKey = (UINT)(iKeyFrame - iFrame); else pFrameIndex[iFrame].iNextKey = 0xFFFF; // way far away if (iKeyFrame - iFrame > 0xFFFF) { //!!! we need to set a flag! //!!! we need to throw out the index! AssertSz(FALSE, "File has too few key frames"); pFrameIndex[iFrame].iNextKey = 0; } } // // we dont need the index, if we are using AVIFile or // we have a interleaved file. when the file is interleaved // we never do random access (except for palette changes) // // !!!this is not true, we need the index iff we have a audio only // file or we play a interleaved file real slow. // if (npMCI->pf /* || (fInterleaved && !(npMCI->dwFlags & MCIAVI_ANIMATEPALETTE))*/ ) { DOUT("The Master index must go!\n"); iNewIndex = 0; } // // now re-alloc the master index down to size. // // !!! do we even need the master index anymore, for interleaved files? // DPF(("Master index was %ld entries now %ld\n",npMCI->macIndex, iNewIndex)); npMCI->macIndex = iNewIndex; if (iNewIndex > 0) { npMCI->hpIndex = (AVIINDEXENTRY _huge *) GlobalReAllocPtr(npMCI->hpIndex, (LONG)iNewIndex * sizeof(AVIINDEXENTRY), GMEM_MOVEABLE | GMEM_SHARE); Assert(npMCI->hpIndex != NULL); } else { if (npMCI->hpIndex) GlobalFreePtr(npMCI->hpIndex); npMCI->hpIndex = NULL; } return TRUE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | ReadIndex | Read the index into npMCI->hpIndex. Should * only be called if the HASINDEX flag is set. * * @parm NPMCIGRAPHIC | npMCI | Pointer to instance data * * @rdesc TRUE means no errors, false means unable to read index. * ***************************************************************************/ BOOL FAR PASCAL ReadIndex(NPMCIGRAPHIC npMCI) { MMCKINFO ck; DWORD dwOldPos; if (npMCI->hpIndex || npMCI->hpFrameIndex) return TRUE; if (!(npMCI->dwFlags & MCIAVI_HASINDEX)) return FALSE; if (npMCI->pf) { MakeFrameIndex(npMCI); return TRUE; } #if 0 if (GetCurrentTask() != npMCI->hTask) { /* this function is called (from GraphicStatus) when * possibly playing - so we have to suspend play while we read * the index. */ TEMPORARYSTATE ts; if (StopTemporarily(npMCI, &ts) == 0) { mciaviTaskMessage(npMCI, TASKREADINDEX); RestartAgain(npMCI, &ts); return (npMCI->hpIndex != NULL); } return(FALSE); } #else if (GetCurrentTask() != npMCI->hTask) return FALSE; #endif dwOldPos = mmioSeek(npMCI->hmmio, 0, SEEK_CUR); DPF(("Reading index: starting from %lx\n", npMCI->dwBigListEnd)); if (mmioSeek(npMCI->hmmio, npMCI->dwBigListEnd, SEEK_SET) == -1) { IndexReadError: DPF(("Error reading index!\n")); npMCI->dwFlags &= ~(MCIAVI_HASINDEX); mmioSeek(npMCI->hmmio, dwOldPos, SEEK_SET); return FALSE; } ck.ckid = ckidAVINEWINDEX; if (mmioDescend(npMCI->hmmio, &ck, NULL, MMIO_FINDCHUNK) != 0) { goto IndexReadError; } /* A zero-size index isn't much good. */ if (ck.cksize == 0) goto IndexReadError; npMCI->macIndex = ck.cksize / sizeof(AVIINDEXENTRY); npMCI->hpIndex = (AVIINDEXENTRY _huge *) GlobalAllocPtr(GMEM_SHARE | GMEM_MOVEABLE, ck.cksize); if (!npMCI->hpIndex) { DPF(("Insufficient memory to read index.\n")); goto IndexReadError; } #ifndef WIN32 Assert(OFFSETOF(npMCI->hpIndex) == 0); #endif if (mmioRead(npMCI->hmmio, (HPSTR) npMCI->hpIndex, ck.cksize) != (LONG) ck.cksize) { Assert(0); goto IndexReadError; } CleanIndex(npMCI); MakeFrameIndex(npMCI); ////should we do this for audio? remove video data? ////MakeStreamIndex(npMCI, ???); mmioSeek(npMCI->hmmio, dwOldPos, SEEK_SET); return TRUE; } /*************************************************************************** * @doc INTERNAL MCIAVI * * @api BOOL | IsRectBogus | 'nuf said * ***************************************************************************/ static BOOL NEAR PASCAL IsRectBogus(LPRECT prc) { if (prc->right - prc->left <= 0 || prc->bottom - prc->top <= 0 || prc->bottom <= 0 || prc->right <= 0) return TRUE; else return FALSE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api LONG | atol | local version of atol * ***************************************************************************/ static LONG NEAR PASCAL atol(char *sz) { LONG l = 0; while (*sz && *sz >= '0' && *sz <= '9') l = l*10 + *sz++ - '0'; return l; } #ifndef WIN32 /*-------------------------------------------------------------------------- * * IsCDROMDrive() - * * Purpose: Return non-zero if a RAM drive * * wDrive drive index (0=A, 1=B, ...) * * return TRUE/FALSE *-------------------------------------------------------------------------*/ #pragma optimize("", off) static BOOL NEAR PASCAL IsCDROMDrive(UINT wDrive) { BOOL f; _asm { mov ax, 1500h /* first test for presence of MSCDEX */ xor bx, bx int 2fh mov ax, bx /* MSCDEX is not there if bx is still zero */ or ax, ax /* ...so return FALSE from this function */ jz no_mscdex mov ax, 150bh /* MSCDEX driver check API */ mov cx, wDrive /* ...cx is drive index */ int 2fh no_mscdex: mov f,ax } return f; } #pragma optimize("", on) /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | IsNetFile | is the passed file on a network drive? * ***************************************************************************/ static BOOL NEAR PASCAL IsNetFile(LPTSTR szFile) { OFSTRUCT of; if (OpenFile(szFile, &of, OF_PARSE) == -1) return FALSE; AnsiUpper(of.szPathName); if (of.szPathName[0] == '\\' && of.szPathName[1] == '\\') return TRUE; if (of.szPathName[0] == '/' && of.szPathName[1] == '/') return TRUE; if (of.szPathName[1] == ':' && GetDriveType(of.szPathName[0] - 'A') == DRIVE_REMOTE && !IsCDROMDrive(of.szPathName[0] - 'A')) return TRUE; return FALSE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api BOOL | IsCDROMFile | is the passed file on a CD-ROM drive? * ***************************************************************************/ static BOOL NEAR PASCAL IsCDROMFile(LPTSTR szFile) { OFSTRUCT of; if (OpenFile(szFile, &of, OF_PARSE) == -1) return FALSE; AnsiUpper(of.szPathName); if (of.szPathName[0] == '\\' && of.szPathName[1] == '\\') return FALSE; if (of.szPathName[0] == '/' && of.szPathName[1] == '/') return FALSE; if (of.szPathName[1] == ':' && GetDriveType(of.szPathName[0] - 'A') == DRIVE_REMOTE && IsCDROMDrive(of.szPathName[0] - 'A')) return TRUE; return FALSE; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api UINT | GetFileDriveType | return drive type given a file * * DRIVE_CDROM * DRIVE_REMOTE * DRIVE_FIXED * DRIVE_REMOVABLE * ***************************************************************************/ static UINT NEAR PASCAL GetFileDriveType(LPSTR szPath) { if (IsCDROMFile(szPath)) return DRIVE_CDROM; if (IsNetFile(szPath)) return DRIVE_REMOTE; if (szPath[1] == ':') return GetDriveType(szPath[0] - 'A'); return 0; } #endif