DSEQF.CPP

/**************************************************************************** 
*
* DSEQF.CPP
*
* routines for reading DIB sequences
*
***************************************************************************/
/**************************************************************************
*
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
* PURPOSE.
*
* Copyright 1992 - 1998 Microsoft Corporation. All Rights Reserved.
*
**************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <ctype.h>
#include <vfw.h>

#include "handler.h"
#include "handler.rc"


static DWORD NEAR PASCAL dseqParseFileName(
LPSTR lpszFileName,
LPSTR lpszTemplate,
DWORD FAR * lpdwMaxValue);

#ifdef DEBUG
static void CDECL dprintf(LPSTR, ...);
#define DPF dprintf
#else
#define DPF ; / ## /
#endif

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

/* - - - - - - - - */

UINT uUseCount;
BOOL fLocked;

/* - - - - - - - - */

//
// External function called by the Class Factory to create an instance of
// the DIB sequence reader/writer
//
HRESULT CAVIFile::Create(
IUnknown FAR* pUnknownOuter,
const IID FAR& riid,
void FAR* FAR* ppv)
{
IUnknown FAR* pUnknown;
CAVIFile FAR* pAVIFile;
HRESULT hresult;

pAVIFile = new FAR CAVIFile(pUnknownOuter, &pUnknown);
if (!pAVIFile)
return ResultFromScode(E_OUTOFMEMORY);
hresult = pUnknown->QueryInterface(riid, ppv);
if (FAILED(GetScode(hresult)))
delete pAVIFile;
return hresult;
}

/* - - - - - - - - */

//
// Random C++ stuff: constructors & such...
//
CAVIFile::CAVIFile(
IUnknown FAR* pUnknownOuter,
IUnknown FAR* FAR* ppUnknown) :
m_Unknown(this),
m_AVIFile(this),
m_Persist(this),
#ifdef CUSTOMMARSHAL
m_Marshal(this),
#endif
m_AVIStream(this)
{
if (pUnknownOuter)
m_pUnknownOuter = pUnknownOuter;
else
m_pUnknownOuter = &m_Unknown;
*ppUnknown = &m_Unknown;
}

/* - - - - - - - - */

CAVIFile::CUnknownImpl::CUnknownImpl(
CAVIFile FAR* pAVIFile)
{
m_pAVIFile = pAVIFile;
m_refs = 0;
}

/* - - - - - - - - */

//
// This QueryInterface function allows a caller to move between the various
// interfaces the object presents
//
STDMETHODIMP CAVIFile::CUnknownImpl::QueryInterface(
const IID FAR& iid,
void FAR* FAR* ppv)
{
if (iid == IID_IUnknown)
*ppv = &m_pAVIFile->m_Unknown;
else if (iid == IID_IAVIFile)
*ppv = &m_pAVIFile->m_AVIFile;
else if (iid == IID_IAVIStream)
*ppv = &m_pAVIFile->m_AVIStream;
else if (iid == IID_IPersistFile)
*ppv = &m_pAVIFile->m_Persist;
#ifdef CUSTOMMARSHAL
else if (iid == IID_IMarshal)
*ppv = &m_pAVIFile->m_Marshal;
#endif
else
return ResultFromScode(E_NOINTERFACE);
AddRef();
return NULL;
}

/* - - - - - - - - */

STDMETHODIMP_(ULONG) CAVIFile::CUnknownImpl::AddRef()
{
uUseCount++;
return ++m_refs;
}

/* - - - - - - - - */

//
// All calls to AddRef, Release, QueryInterface for the file or stream
// functions are redirected to the Unknown implementation...
//
CAVIFile::CAVIFileImpl::CAVIFileImpl(
CAVIFile FAR* pAVIFile)
{
m_pAVIFile = pAVIFile;
}

/* - - - - - - - - */

CAVIFile::CAVIFileImpl::~CAVIFileImpl()
{
}

/* - - - - - - - - */

STDMETHODIMP CAVIFile::CAVIFileImpl::QueryInterface(
const IID FAR& iid,
void FAR* FAR* ppv)
{
return m_pAVIFile->m_pUnknownOuter->QueryInterface(iid, ppv);
}

/* - - - - - - - - */

STDMETHODIMP_(ULONG) CAVIFile::CAVIFileImpl::AddRef()
{
return m_pAVIFile->m_pUnknownOuter->AddRef();
}

/* - - - - - - - - */

STDMETHODIMP_(ULONG) CAVIFile::CAVIFileImpl::Release()
{
return m_pAVIFile->m_pUnknownOuter->Release();
}

/* - - - - - - - - */

CAVIFile::CAVIStreamImpl::CAVIStreamImpl(
CAVIFile FAR* pAVIFile)
{
m_pAVIFile = pAVIFile;
}

/* - - - - - - - - */

CAVIFile::CAVIStreamImpl::~CAVIStreamImpl()
{
}

/* - - - - - - - - */

STDMETHODIMP CAVIFile::CAVIStreamImpl::QueryInterface(
const IID FAR& iid,
void FAR* FAR* ppv)
{
return m_pAVIFile->m_pUnknownOuter->QueryInterface(iid, ppv);
}

/* - - - - - - - - */

STDMETHODIMP_(ULONG) CAVIFile::CAVIStreamImpl::AddRef()
{
return m_pAVIFile->m_pUnknownOuter->AddRef();
}

/* - - - - - - - - */

STDMETHODIMP_(ULONG) CAVIFile::CAVIStreamImpl::Release()
{
return m_pAVIFile->m_pUnknownOuter->Release();
}


// --- IPersistFile implementation --------------------------------------

CAVIFile::CPersistFileImpl::CPersistFileImpl(CAVIFile FAR* pAVIFile)
{
m_pAVIFile = pAVIFile;
}

STDMETHODIMP
CAVIFile::CPersistFileImpl::QueryInterface(REFIID riid, LPVOID FAR* ppv)
{
return m_pAVIFile->m_pUnknownOuter->QueryInterface(riid, ppv);
}

STDMETHODIMP_(ULONG)
CAVIFile::CPersistFileImpl::AddRef()
{
return m_pAVIFile->m_pUnknownOuter->AddRef();
}

STDMETHODIMP_(ULONG)
CAVIFile::CPersistFileImpl::Release()
{
return m_pAVIFile->m_pUnknownOuter->Release();
}

// *** IPersist methods ***
STDMETHODIMP
CAVIFile::CPersistFileImpl::GetClassID (LPCLSID lpClassID)
{
*lpClassID = CLSID_DIBSEQFileReader;

return NOERROR;
}

// *** IPersistFile methods ***
STDMETHODIMP
CAVIFile::CPersistFileImpl::IsDirty ()
{
if (m_pAVIFile->fDirty) {
return NOERROR;
} else {
return ResultFromScode(S_FALSE);
}
}

/* - - - - - - - - */

//
// This function takes the name of the first file in a DIB sequence, and
// returns a printf() specifier which can be used to create the names in
// the sequence, along with minimum and maximum values that can be used.
//
//
// Examples:
// lpszFileName = "FOO0047.DIB"
// -> lpszTemplate = "FOO%04d.DIB", dwMaxValue = 9999, return = 47
//
// lpszFileName = "TEST01.DIB"
// -> lpszTemplate = "TEST%01d.DIB", dwMaxValue = 9, return = 1
//
// lpszFileName = "TEST1.DIB"
// -> lpszTemplate = "TEST%d.DIB", dwMaxValue = 9999, return = 1
//
// lpszFileName = "SINGLE.DIB"
// -> lpszTemplate = "SINGLE.DIB", dwMaxValue = 0, return = 0
//
static DWORD NEAR PASCAL dseqParseFileName(
LPSTR lpszFileName,
LPSTR lpszTemplate,
DWORD FAR * lpdwMaxValue)
{
char achTemp[_MAX_PATH];
DWORD dwFirst;
WORD wFieldWidth;
DWORD dwMult;
BOOL fLeadingZero = FALSE;

LPSTR lp;
LPSTR lp2;
LPSTR lpExt;

/* Find end of string */
lp2 = lpszFileName;
lp = achTemp;
while (*lp2)
*lp++ = *lp2++;

*lp = '\0';

/* Make lp2 point at last character of base filename (w/o extension) */
/* Make lpExt point at the extension (without the dot) */
for (lp2 = lp; *lp2 != '.'; lp2--)
{
lpExt = lp2;
if ((lp2 == achTemp) || (*lp2 == '\\')
|| (*lp2 == ':') || (*lp2 == '!'))
{
/* There is no extension */
lp2 = lp;
lpExt = lp;
break;
}
}

lp2--;

// Count the number of numeric characters here....
dwFirst = 0;
wFieldWidth = 0;
dwMult = 1;
while (lp2 >= achTemp && (*lp2 >= '0') && (*lp2 <= '9')) {
fLeadingZero = (*lp2 == '0');
dwFirst += dwMult * (*(lp2--) - '0');
dwMult *= 10;
wFieldWidth++;
}

*lpdwMaxValue = dwMult - 1;

lp2++;
*lp2 = '\0';

// Make the format specifier....
if (wFieldWidth) {
if (fLeadingZero) {
wsprintf((LPSTR) lpszTemplate,"%s%%0%ulu.%s",
(LPSTR) achTemp, wFieldWidth,(LPSTR) lpExt);
} else {
wsprintf((LPSTR) lpszTemplate,"%s%%lu.%s",
(LPSTR) achTemp, (LPSTR) lpExt);
*lpdwMaxValue = 999999L;
// !!! This should really be based on the number of
// characters left after the base name....
}
} else
wsprintf((LPSTR) lpszTemplate,"%s.%s",
(LPSTR) achTemp, (LPSTR) lpExt);
DPF("First = %lu, Width = %u, Template = '%s'\n",dwFirst, wFieldWidth, lpszTemplate);

return dwFirst;
}

/* - - - - - - - - */

#define SLASH(c) ((c) == '/' || (c) == '\\')

/*--------------------------------------------------------------+
| FileName - return a pointer to the filename part of szPath |
| with no preceding path. |
+--------------------------------------------------------------*/
LPSTR FAR FileName(
LPCSTR lszPath)
{
LPCSTR lszCur;

for (lszCur = lszPath + lstrlen(lszPath); lszCur > lszPath && !SLASH(*lszCur) && *lszCur != ':';)
lszCur = AnsiPrev(lszPath, lszCur);
if (lszCur == lszPath)
return (LPSTR)lszCur;
else
return (LPSTR)(lszCur + 1);
}

/* - - - - - - - - */

//
// "Open" a DIB sequence, by parsing the filename and counting the number
// of frames actually present....
//
STDMETHODIMP
CAVIFile::CPersistFileImpl::Load (LPCOLESTR szFile, DWORD mode)
{
CAVIFile FAR *p = m_pAVIFile;
UINT ui;
char ach[80];
char szFileA[MAX_PATH];

p->mode = mode;

//
// Parse the filename
//
wsprintf(szFileA, "%ls", szFile);
p->dwFirstFrame = dseqParseFileName(szFileA,
p->achFilenameTemplate,
&p->dwMaxValue);

//
// Initialize the variables that keep track of what frame is cached
//
p->lCurFrame = -1;
p->lpFrame = NULL;
p->cbFrame = 0;
p->cbFrameBuffer = 0;
p->lpFormat = NULL;
p->cbFormat = 0;
p->cbFormatBuffer = 0;

//
// Build a stream header....
//
p->sinfo.fccType = streamtypeVIDEO;
p->sinfo.fccHandler = 0;
p->sinfo.dwFlags = 0;
p->sinfo.wPriority = 0;
p->sinfo.wLanguage = 0;
p->sinfo.dwInitialFrames = 0;
p->sinfo.dwScale = 1;
p->sinfo.dwRate = 15;
p->sinfo.dwStart = 0;
p->sinfo.dwLength = 0;
p->sinfo.dwSuggestedBufferSize = 0;
p->sinfo.dwSampleSize = 0;

LoadString(ghModule, IDS_STREAMNAME, ach, sizeof(ach));
{
char TempFileName[80];
char TempName[80];
wsprintf(TempFileName, "%ls", szFile);
wsprintf(TempName, ach, FileName(TempFileName));
wsprintfW(p->sinfo.szName, L"%hs", TempName);
}

//
// ... and a file header.
//
_fmemset(&p->finfo, 0, sizeof(p->finfo));
p->finfo.dwRate = 15;
p->finfo.dwScale = 1;
p->finfo.dwStreams = 1;
p->finfo.dwWidth = 0;
p->finfo.dwHeight = 0;
LoadString(ghModule, IDS_FILETYPE,
p->finfo.szFileType,
sizeof(p->finfo.szFileType));

p->finfo.dwCaps = AVIFILECAPS_CANREAD |
AVIFILECAPS_CANWRITE |
AVIFILECAPS_ALLKEYFRAMES;

if (mode & OF_CREATE) {
//
// They're creating a "new" sequence
//
p->fStreamPresent = FALSE;
} else {
char ach[_MAX_PATH];
OFSTRUCT of;
DWORD dwFrame;
HRESULT hr;

//
// They're opening an existing sequence, so we have to actually
// count how many files are present
//
p->fStreamPresent = TRUE;

ui = SetErrorMode(SEM_NOOPENFILEERRORBOX);

for (dwFrame = 0; TRUE; dwFrame++) {
if (dwFrame > p->dwMaxValue)
break;

wsprintf(ach,p->achFilenameTemplate, dwFrame + p->dwFirstFrame);

// DPF("DIBSEQ: Checking frame %lu from '%s'\n",dwFrame,(LPSTR) ach);

/****************************************************************************/
/* DOS share has a bug. If the file we're testing for existence is open */
/* already by someone else, we have to give it the same flag for SHARE as */
/* the other person is using. So we have to try both on and off. Only one */
/* of these will return TRUE but if one of them does, the file exists. Also*/
/* we have to turn off the system model error box for share violations. */
/****************************************************************************/

if (OpenFile((LPSTR)ach, &of, OF_EXIST) == HFILE_ERROR &&
OpenFile((LPSTR)ach, &of, OF_EXIST | OF_SHARE_DENY_NONE) ==
HFILE_ERROR)
break;
}

SetErrorMode(ui);

if (dwFrame == 0)
goto error;

//
// Fix up the length in the header structures
//
p->sinfo.dwLength = dwFrame;
p->finfo.dwLength = dwFrame;

//
// Load the first frame, so we'll be ready...
//
hr = p->LoadFrame(0);

if (FAILED(GetScode(hr)))
return hr;

p->finfo.dwSuggestedBufferSize = p->cbFrame;
p->sinfo.dwSuggestedBufferSize = p->cbFrame;

p->finfo.dwWidth = ((LPBITMAPINFOHEADER) p->lpFormat)->biWidth;
p->finfo.dwHeight = ((LPBITMAPINFOHEADER) p->lpFormat)->biHeight;

p->sinfo.dwFlags = AVISTREAMINFO_FORMATCHANGES;

SetRect(&p->sinfo.rcFrame,
0, 0, (int) p->finfo.dwWidth, (int) p->finfo.dwHeight);
}

//
// all done return success.
//
return ResultFromScode(0); // success

error:
return ResultFromScode(AVIERR_FILEREAD);


}


STDMETHODIMP
CAVIFile::CPersistFileImpl::Save (LPCOLESTR lpszFileName, BOOL fRemember)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP
CAVIFile::CPersistFileImpl::SaveCompleted (LPCOLESTR lpszFileName)
{
return NOERROR;
}

STDMETHODIMP
CAVIFile::CPersistFileImpl::GetCurFile (LPOLESTR FAR * lplpszFileName)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}



// -------------------- IAVIFile Implementation-----------------------


//
// The GetStream method returns an interface pointer to the video stream,
// assuming one exists.
//
STDMETHODIMP CAVIFile::CAVIFileImpl::GetStream(
PAVISTREAM FAR * ppavi,
DWORD fccType,
LONG lParam)
{
CAVIFile FAR * p = m_pAVIFile;
int iStreamWant;

iStreamWant = (int)lParam;

if (!p->fStreamPresent)
return ResultFromScode(-1);

// We only support one stream
if (lParam != 0)
return ResultFromScode(-1);

// We only support a video stream
if (fccType && fccType != streamtypeVIDEO)
return ResultFromScode(-1);

//
// Be sure to keep the reference count up to date...
//
AddRef();

*ppavi = (PAVISTREAM) &(p->m_AVIStream);
return ResultFromScode(AVIERR_OK);
}


//
// If they opened the file with the OF_CREATE flag, they will use this
// method to create the video stream.
//
STDMETHODIMP CAVIFile::CAVIFileImpl::CreateStream(
PAVISTREAM FAR *ppstream,
AVISTREAMINFOW FAR *psi
)
{
CAVIFile FAR * p = m_pAVIFile;

// If the stream was already there, we fail.
if (p->fStreamPresent)
return ResultFromScode(AVIERR_UNSUPPORTED);

p->sinfo = *psi;
p->sinfo.dwLength = 0;

*ppstream = (PAVISTREAM) &(p->m_AVIStream);
p->fStreamPresent = TRUE;

// Keep the reference count correct
AddRef();

return ResultFromScode(AVIERR_OK);
}


STDMETHODIMP CAVIFile::CAVIFileImpl::WriteData(
DWORD ckid,
LPVOID lpData,
LONG cbData)
{
CAVIFile FAR * p = m_pAVIFile;

return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP CAVIFile::CAVIFileImpl::ReadData(
DWORD ckid,
LPVOID lpData,
LONG FAR *lpcbData)
{
CAVIFile FAR * p = m_pAVIFile;

return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP CAVIFile::CAVIFileImpl::EndRecord(void)
{
return ResultFromScode(AVIERR_OK);
}


STDMETHODIMP CAVIFile::CAVIFileImpl::Info(AVIFILEINFOW FAR * pfi, LONG lSize)
{
CAVIFile FAR * p = m_pAVIFile;

hmemcpy(pfi, &p->finfo, min(lSize,sizeof(p->finfo)));
return 0;
}



STDMETHODIMP CAVIFile::CAVIStreamImpl::Create(
LONG lParam1,
LONG lParam2)
{
return ResultFromScode(AVIERR_UNSUPPORTED);
}


//
// Returns where the last key frame before the given frame is.
//
// For now, we assume each DIB is a key frame.
//
STDMETHODIMP_(LONG) CAVIFile::CAVIStreamImpl::FindSample(
LONG lPos,
LONG lFlags)
{
CAVIFile FAR * p = m_pAVIFile;

// some minimal error checking....
if (lPos < 0 || lPos >= (LONG) p->sinfo.dwLength)
return -1;

// !!! Can we really assume every frame is non-empty and a key frame?
// !!! Who knows where format changes are? Let's assume everywhere!

return lPos;
}

#define WIDTHBYTES(i) ((unsigned)((i+31)&(~31))/8) /* ULONG aligned ! */
#define DIBWIDTHBYTES(bi) (int)WIDTHBYTES((int)(bi).biWidth * (int)(bi).biBitCount)
#define BFT_BITMAP 0x4d42 /* 'BM' */

//
// Helper function to load a given frame into our cache.
//
// This is where the actual work is done; all other functions just return
// the current format or frame out of the cache.
//
HRESULT NEAR PASCAL CAVIFile::LoadFrame(
LONG lPos)
{
char ach[_MAX_PATH];
HMMIO hmmio;
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;
SCODE sc = 0;
UINT ui;

//
// Check if we've already loaded this frame...
//
if (lPos == lCurFrame)
return 0;

//
// Build the filename by printing using our template
//
wsprintf(ach, achFilenameTemplate, dwFirstFrame + lPos);

// No system error box, please.
ui = SetErrorMode(SEM_NOOPENFILEERRORBOX);

//
// Go try to read the frame... Because of SHARE we have to try
// opening it two different ways.
//
hmmio = mmioOpen(ach, NULL, MMIO_READ | OF_SHARE_DENY_WRITE);
if (!hmmio) {
hmmio = mmioOpen(ach, NULL, MMIO_READ);
if (!hmmio)
return ResultFromScode(AVIERR_FILEOPEN);
}

SetErrorMode(ui);

//
// Read the BitmapFileHeader...
//
if (mmioRead(hmmio, (LPSTR) &bfh, sizeof(bfh)) != sizeof(bfh)) {
sc = AVIERR_FILEREAD;
goto error;
}

if (bfh.bfType != BFT_BITMAP) {
sc = AVIERR_BADFORMAT;
goto error;
}

//
// Read the BitmapInfoHeader...
//
if (mmioRead(hmmio, (LPSTR) &bih, sizeof(bih)) != sizeof(bih)) {
sc = AVIERR_FILEREAD;
goto error;
}

if (bih.biSize < sizeof(bih)) {
sc = AVIERR_BADFORMAT;
goto error;
}

// Check that the width and height match....
if ((finfo.dwWidth && finfo.dwWidth != (DWORD) bih.biWidth) ||
(finfo.dwHeight && finfo.dwHeight != (DWORD) bih.biHeight)) {
sc = AVIERR_BADFORMAT;
goto error;
}

// Fix up some fields in the header...
if (bih.biSizeImage == 0) {
bih.biSizeImage = DIBWIDTHBYTES(bih) * bih.biHeight;
}

if (bih.biClrUsed == 0 && bih.biBitCount <= 8 && bih.biCompression <= BI_RLE8)
bih.biClrUsed = 1 << bih.biBitCount;

cbFormat = bih.biSize + bih.biClrUsed * sizeof(RGBQUAD);

// Allocate space for the format
if (cbFormat > cbFormatBuffer) {
if (lpFormat) {
GlobalFreePtr(lpFormat);
lpFormat = 0;
cbFormatBuffer = 0;
}

lpFormat = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_DDESHARE, cbFormat);
if (!lpFormat) {
sc = AVIERR_MEMORY;
goto error;
}

cbFormatBuffer = cbFormat;
}

*((LPBITMAPINFOHEADER) lpFormat) = bih;

// If the format is bigger than a BITMAPINFOHEADER, read the rest....
if (cbFormat > sizeof(bih)) {
if (mmioRead(hmmio, (LPSTR) lpFormat + sizeof(bih),
cbFormat - (LONG)sizeof(bih))
!= cbFormat - (LONG)sizeof(bih))
{
sc = AVIERR_FILEREAD;
goto error;
}
}

//
// Allocate enough space to read the frame in...
//
if (bih.biSizeImage > (DWORD) cbFrameBuffer) {
if (lpFrame) {
GlobalFreePtr(lpFrame);
lpFrame = 0;
cbFrameBuffer = 0;
}

lpFrame = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_DDESHARE, bih.biSizeImage);
if (!lpFrame) {
sc = AVIERR_MEMORY;
goto error;
}

cbFrameBuffer = bih.biSizeImage;
}

cbFrame = bih.biSizeImage;

//
// and actually read the frame....
//
if (mmioRead(hmmio, (LPSTR) lpFrame, cbFrame) != cbFrame) {
sc = AVIERR_FILEREAD;
goto error;
}

lCurFrame = lPos;

error:
mmioClose(hmmio, 0);

return ResultFromScode(sc);
}

//
// The ReadFormat method returns the format of the specified frame....
//
STDMETHODIMP CAVIFile::CAVIStreamImpl::ReadFormat(
LONG lPos,
LPVOID lpFormat,
LONG FAR *lpcbFormat)
{
CAVIFile FAR * p = m_pAVIFile;
HRESULT hr;

//
// Try to get the correct frame
//
hr = p->LoadFrame(lPos);

if (hr != 0)
return hr;

// No buffer to fill in, this means return the size needed.
if (lpFormat == NULL || *lpcbFormat == 0) {
*lpcbFormat = p->cbFormat;
return 0;
}

//
// and return as much of the format as will fit.
//
hmemcpy(lpFormat, p->lpFormat, min(*lpcbFormat, p->cbFormat));
*lpcbFormat = p->cbFormat;
return 0;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////


STDMETHODIMP CAVIFile::CAVIStreamImpl::Info(AVISTREAMINFOW FAR * psi, LONG lSize)
{
CAVIFile FAR * p = m_pAVIFile;

hmemcpy(psi,&p->sinfo, min(lSize,sizeof(p->sinfo)));
// return sizeof(p->sinfo);
return 0;
}

STDMETHODIMP_(ULONG) CAVIFile::CUnknownImpl::Release()
{
CAVIFile FAR * p = m_pAVIFile;

uUseCount--;
if (!--m_refs) {
LONG lRet = AVIERR_OK;

if (p->fDirty) {
}


goto success;

success:
if (p->lpFormat)
GlobalFreePtr(p->lpFormat);

p->lpFormat = NULL;
p->cbFormat = 0;

if (p->lpFrame)
GlobalFreePtr(p->lpFrame);

p->lpFrame = NULL;
p->cbFrame = 0;

delete this;
return 0;
}
return m_refs;
}


///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////


STDMETHODIMP CAVIFile::CAVIStreamImpl::Read(
LONG lStart,
LONG lSamples,
LPVOID lpBuffer,
LONG cbBuffer,
LONG FAR * plBytes,
LONG FAR * plSamples)
{
CAVIFile FAR * p = m_pAVIFile;
HRESULT hr;

if (lStart < 0 || lStart >= (LONG) p->sinfo.dwLength) {
if (plBytes)
*plBytes = 0;
if (plSamples)
*plSamples = 0;
return 0;
}

// We always read one frame at a time...
lSamples = 1;

// Load it into the cache....
hr = p->LoadFrame(lStart);

if (hr != 0)
return hr;

//
// a NULL buffer means return the size buffer needed to read
// the given sample.
//
if (lpBuffer == NULL || cbBuffer == 0) {
if (plBytes)
*plBytes = p->cbFrame;
if (plSamples)
*plSamples = lSamples;
return 0;
}

//
// They didn't give us enough space for the frame, so complain
//
if (cbBuffer < p->cbFrame) {
if (plBytes)
*plBytes = p->cbFrame;

return ResultFromScode(AVIERR_BUFFERTOOSMALL);
}

//
// Copy the frame into the caller's buffer
//
hmemcpy(lpBuffer, p->lpFrame, p->cbFrame);

//
// success return number of bytes and number of samples read
//
if (plBytes)
*plBytes = p->cbFrame;

if (plSamples)
*plSamples = lSamples;

return ResultFromScode(AVIERR_OK);
}


STDMETHODIMP CAVIFile::CAVIStreamImpl::SetFormat(
LONG lPos,
LPVOID lpFormat,
LONG cbFormat)
{
CAVIFile FAR * p = m_pAVIFile;

// Keep track of the format....
p->cbFormat = cbFormat;
p->lpFormat = (LPVOID) GlobalAllocPtr(GMEM_MOVEABLE | GMEM_DDESHARE, cbFormat);

if (p->lpFormat == NULL)
return ResultFromScode(AVIERR_MEMORY);

hmemcpy(p->lpFormat, lpFormat, cbFormat);

p->finfo.dwWidth = ((LPBITMAPINFOHEADER) p->lpFormat)->biWidth;
p->finfo.dwHeight = ((LPBITMAPINFOHEADER) p->lpFormat)->biHeight;

SetRect(&p->sinfo.rcFrame,
0, 0, (int) p->finfo.dwWidth, (int) p->finfo.dwHeight);

return 0L;
}

//
// Helper function to save a single frame
//
HRESULT NEAR PASCAL CAVIFile::WriteFrame(
LONG lPos,
LPVOID lp,
LONG cb)
{
char ach[_MAX_PATH];
HMMIO hmmio;
BITMAPFILEHEADER bfh;

//
// If they're overwriting the cached frame, invalidate the cache

// 
if (lPos == lCurFrame)
lCurFrame = -1;


//
// Build the filename to write to
//
wsprintf(ach, achFilenameTemplate, dwFirstFrame + lPos);

// and write it.
hmmio = mmioOpen(ach, NULL, MMIO_WRITE | MMIO_CREATE | OF_SHARE_EXCLUSIVE);

if (!hmmio)
return ResultFromScode(AVIERR_FILEOPEN);

//
// Write the BitmapFileHeader
//
bfh.bfType = BFT_BITMAP;
bfh.bfOffBits = sizeof(bfh) + cbFormat;
bfh.bfSize = bfh.bfOffBits + cb;

if (mmioWrite(hmmio, (LPSTR) &bfh, sizeof(bfh)) != sizeof(bfh)) {
error:
mmioClose(hmmio, 0);
return ResultFromScode(AVIERR_FILEWRITE);
}

((LPBITMAPINFOHEADER) lpFormat)->biSizeImage = cb;

//
// Write the DIB format
//
if (mmioWrite(hmmio, (LPSTR) lpFormat, cbFormat) != cbFormat)
goto error;

//
// Write the data
//
if (mmioWrite(hmmio, (LPSTR) lp, cb) != cb)
goto error;

//
// Flush things so that we can be sure everything is written out
//
if (mmioFlush(hmmio, 0) != 0)
goto error;

mmioClose(hmmio, 0);

return 0;
}

STDMETHODIMP CAVIFile::CAVIStreamImpl::Write(
LONG lStart,
LONG lSamples,
LPVOID lpData,
LONG cbData,
DWORD dwFlags,
LONG FAR *plSampWritten,
LONG FAR *plBytesWritten)
{
CAVIFile FAR * p = m_pAVIFile;
HRESULT hr;

if ((p->mode & (OF_WRITE | OF_READWRITE)) == 0)
return ResultFromScode(AVIERR_READONLY);

if (p->lpFormat == NULL)
return ResultFromScode(AVIERR_UNSUPPORTED);

// < 0 means "at end"
if (lStart < 0)
lStart = p->sinfo.dwStart + p->sinfo.dwLength;

if (lStart > (LONG) (p->sinfo.dwStart + p->sinfo.dwLength))
return ResultFromScode(AVIERR_BADPARAM);

// !!! Die if we've reached the limit of our numbers....
if ((DWORD) lStart + p->dwFirstFrame > p->dwMaxValue)
return ResultFromScode(AVIERR_FILEWRITE);

if (lSamples != 1)
return ResultFromScode(AVIERR_BADPARAM);

// only allow key frames!
if (!(dwFlags & AVIIF_KEYFRAME)) {
DPF("Tried to write a non-key frame to a DIB sequence!\n");
return ResultFromScode(AVIERR_UNSUPPORTED);
}

hr = p->WriteFrame(lStart, lpData, cbData);

if (hr != AVIERR_OK)
return hr;

p->fDirty = TRUE;

p->sinfo.dwLength =
max((LONG) p->sinfo.dwLength,
lStart + lSamples);

p->finfo.dwLength = p->sinfo.dwLength;

p->finfo.dwSuggestedBufferSize =
max(p->finfo.dwSuggestedBufferSize, (DWORD) cbData);
p->sinfo.dwSuggestedBufferSize =
p->finfo.dwSuggestedBufferSize;

if (plSampWritten)
*plSampWritten = lSamples;

if (plBytesWritten)
*plBytesWritten = cbData;

return ResultFromScode(AVIERR_OK);
}

// these both are for saving. we don't support saving

STDMETHODIMP
CAVIFile::CAVIFileImpl::DeleteStream(DWORD fccType, LONG lParam)
{
return ResultFromScode(E_FAIL);
}

STDMETHODIMP
CAVIFile::CAVIStreamImpl::SetInfo(
AVISTREAMINFOW FAR * lpInfo,
LONG cbInfo
)
{
return ResultFromScode(E_FAIL);
}


STDMETHODIMP CAVIFile::CAVIStreamImpl::Delete(
LONG lStart,
LONG lSamples)
{
CAVIFile FAR * p = m_pAVIFile;

return ResultFromScode(AVIERR_UNSUPPORTED);
}


// Should these just map to Read/WriteData? !!!

STDMETHODIMP CAVIFile::CAVIStreamImpl::ReadData(
DWORD fcc,
LPVOID lp,
LONG FAR *lpcb)
{
CAVIFile FAR * p = m_pAVIFile;

return ResultFromScode(AVIERR_UNSUPPORTED);
}

STDMETHODIMP CAVIFile::CAVIStreamImpl::WriteData(
DWORD fcc,
LPVOID lp,
LONG cb)
{
CAVIFile FAR * p = m_pAVIFile;

return ResultFromScode(AVIERR_UNSUPPORTED);
}


#ifdef CUSTOMMARSHAL
// The code below supports custom marshalling.

// !!! Need good explanation here!

CAVIFile::CMarshalImpl::CMarshalImpl(
CAVIFile FAR* pAVIFile)
{
m_pAVIFile = pAVIFile;
}

/* - - - - - - - - */

STDMETHODIMP CAVIFile::CMarshalImpl::QueryInterface(
const IID FAR& iid,
void FAR* FAR* ppv)
{
return m_pAVIFile->m_pUnknownOuter->QueryInterface(iid, ppv);
}

/* - - - - - - - - */

STDMETHODIMP_(ULONG) CAVIFile::CMarshalImpl::AddRef()
{
return m_pAVIFile->m_pUnknownOuter->AddRef();
}

/* - - - - - - - - */

STDMETHODIMP_(ULONG) CAVIFile::CMarshalImpl::Release()
{
return m_pAVIFile->m_pUnknownOuter->Release();
}

// *** IMarshal methods ***
STDMETHODIMP CAVIFile::CMarshalImpl::GetUnmarshalClass (
THIS_ REFIID riid, LPVOID pv,
DWORD dwDestContext, LPVOID pvDestContext,
DWORD mshlflags, LPCLSID pCid)
{
HRESULT hr = NOERROR;

DPF("UnMarshalClass called\n");

if (dwDestContext != MSHCTX_LOCAL) {
LPMARSHAL pMarshal;

DPF("Marshal context is %lu: delegating...\n", dwDestContext);

hr = CoGetStandardMarshal(riid, NULL, dwDestContext, pvDestContext, mshlflags,
&pMarshal);

if (hr != NOERROR)
return hr;

hr = pMarshal->GetUnmarshalClass(riid, pv, dwDestContext, pvDestContext,
mshlflags, pCid);

pMarshal->Release();

return hr;
}

*pCid = CLSID_AVISimpleUnMarshal;

return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::GetMarshalSizeMax (
THIS_ REFIID riid, LPVOID pv,
DWORD dwDestContext, LPVOID pvDestContext,
DWORD mshlflags, LPDWORD pSize)
{
HRESULT hr = NOERROR;
IUnknown FAR * pUnk = &m_pAVIFile->m_Unknown;

if (dwDestContext != MSHCTX_LOCAL) {
LPMARSHAL pMarshal;

hr = CoGetStandardMarshal(riid, NULL, dwDestContext, pvDestContext, mshlflags,
&pMarshal);

if (hr != NOERROR)
return hr;

hr = pMarshal->GetMarshalSizeMax(riid, pv, dwDestContext, pvDestContext,
mshlflags, pSize);

pMarshal->Release();

return hr;
}

*pSize = sizeof(pUnk);

return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::MarshalInterface (
THIS_ LPSTREAM pStm, REFIID riid,
LPVOID pv, DWORD dwDestContext, LPVOID pvDestContext,
DWORD mshlflags)
{
HRESULT hr = NOERROR;
IUnknown FAR * pUnk = &m_pAVIFile->m_Unknown;

DPF("MarshalInterface called\n");

if (dwDestContext != MSHCTX_LOCAL) {
LPMARSHAL pMarshal;

DPF("Marshal context is %lu: delegating...\n", dwDestContext);

hr = CoGetStandardMarshal(riid, NULL, dwDestContext, pvDestContext, mshlflags,
&pMarshal);

if (hr != NOERROR)
return hr;

hr = pMarshal->MarshalInterface(pStm, riid, pv, dwDestContext, pvDestContext,
mshlflags);

pMarshal->Release();

return hr;
}


if ((riid != IID_IAVIStream && riid != IID_IAVIFile && riid != IID_IUnknown))
return ResultFromScode(E_INVALIDARG);

if ((hr = pStm->Write(&pUnk, sizeof(pUnk), NULL)) == NOERROR)
AddRef();

DPF("Returns %lx\n", (DWORD) (LPVOID) hr);
return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::UnmarshalInterface (
THIS_ LPSTREAM pStm, REFIID riid,
LPVOID FAR* ppv)
{
HRESULT hr = ResultFromScode(E_FAIL);

DPF("UnMarshalInterface called!!!\n");
return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::ReleaseMarshalData (
THIS_ LPSTREAM pStm)
{
HRESULT hr = NOERROR;
IUnknown FAR * pUnk;

hr = pStm->Read(&pUnk,sizeof(pUnk),NULL);
if (hr == NOERROR)
pUnk->Release();

return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::DisconnectObject (
THIS_ DWORD dwReserved)
{
HRESULT hr = NOERROR;

return hr;
}
#endif

/*****************************************************************************
*
* dprintf() is called by the DPF macro if DEBUG is defined at compile time.
*
* The messages will be send to COM1: like any debug message. To
* enable debug output, add the following to WIN.INI :
*
* [debug]
* ICSAMPLE=1
*
****************************************************************************/

#ifdef DEBUG

#define MODNAME "DSEQFILE"
static BOOL fDebug = -1;

static void cdecl dprintf(
LPSTR szFormat, ...)
{
char ach[128];

va_list va;
if (fDebug == -1)
fDebug = GetProfileIntA("Debug",MODNAME, FALSE);

if (!fDebug)
return;

va_start(va, szFormat);
if (szFormat[0] == '!')
ach[0]=0, szFormat++;
else
lstrcpyA(ach, MODNAME ": ");

wvsprintfA(ach+lstrlenA(ach),szFormat,(LPSTR)va);
va_end(va);
// lstrcatA(ach, "\r\r\n");

OutputDebugStringA(ach);
}

#endif