/****************************************************************************** Copyright (C) Microsoft Corporation 1985-1995. All rights reserved. AVIIDX.C - AVI Index stuff *****************************************************************************/ #include // Win16/32 porting #include #include "aviidx.h" #ifdef AVIIDX_READONLY #include "common.h" // for DEBUG #else #include "debug.h" // for DEBUG #endif /*************************************************************************** ***************************************************************************/ #define INDEXALLOC 512 #define STACK _based(_segname("_STACK")) /*************************************************************************** ***************************************************************************/ // // used by SearchIndex() to return where a sample is // typedef struct { LONG lx; // index position LONG lPos; // position in samples. LONG lSize; // size in samples. LONG lOffset; // file offset. LONG lLength; // size in bytes. } IDXPOS; /*************************************************************************** * * @doc INTERNAL * * @api PAVIINDEX | IndexAddFileIndex * * add a bunch of entries from a AVIFILE index to the index. * ***************************************************************************/ EXTERN_C PAVIINDEX IndexAddFileIndex(PAVIINDEX px, AVIINDEXENTRY _huge *pidx, LONG cnt, LONG lAdjust, BOOL fRle) { LONG lx; LONG l; LONG lxRec; DWORD ckid; UINT stream; DWORD offset; DWORD length; UINT flags; Assert(px); Assert(pidx); if (px == NULL || pidx == NULL) return NULL; Assert(sizeof(AVIINDEXENTRY) > sizeof(AVIIDX)); // // grow the index if needed. // if (px->nIndex + cnt > px->nIndexSize) { LONG grow = px->nIndex + cnt - px->nIndexSize; LPVOID p; if (grow < INDEXALLOC) grow = INDEXALLOC; p = (LPVOID)GlobalReAllocPtr(px,sizeof(AVIINDEX) + (px->nIndexSize + grow) * sizeof(AVIIDX), GMEM_MOVEABLE | GMEM_SHARE); if (!p) return NULL; px = (PAVIINDEX)p; px->nIndexSize += grow; } for (lxRec=-1,l=0; l < cnt; l++,pidx++) { lx = px->nIndex + l; // // adjust the offset to be absolute // offset = pidx->dwChunkOffset + lAdjust; length = pidx->dwChunkLength; ckid = pidx->ckid; stream = StreamFromFOURCC(ckid); flags = 0; if (ckid == listtypeAVIRECORD) stream = STREAM_REC; if (ckid == listtypeAVIRECORD) lxRec = lx; // // handle over flows in a "sane" way. // if (offset >= MAX_OFFSET) break; if (stream >= MAX_STREAM) break; if (length >= MAX_LENGTH) length = MAX_LENGTH-1; if (pidx->dwFlags & AVIIF_KEYFRAME) flags |= IDX_KEY; else flags |= IDX_NONKEY; // // length == 0 samples are not real // if (length == 0) flags &= ~(IDX_NONKEY|IDX_KEY); // // mark palette changes // if (TWOCCFromFOURCC(ckid) == cktypePALchange) { flags |= IDX_PAL; flags &= ~(IDX_NONKEY|IDX_KEY); } // // fix up bogus index's by adding any missing KEYFRAME // bits. ie this only applies for RLE files. // if (fRle && length > 0 && TWOCCFromFOURCC(ckid) == cktypeDIBbits) flags |= IDX_KEY; // // do we need to support these? // if (fRle && TWOCCFromFOURCC(ckid) == aviTWOCC('d', 'x')) flags |= IDX_HALF; // // audio is always a key. // if (TWOCCFromFOURCC(ckid) == cktypeWAVEbytes) flags |= IDX_KEY|IDX_NONKEY; //hack to get audio back! // // make sure records are marked as contining a key // //if (lxRec > 0 && (flags & IDX_KEY)) // IndexSetKey(px, lxRec); IndexSetFlags(px,lx,flags); IndexSetOffset(px,lx,offset); IndexSetLength(px,lx,length); IndexSetStream(px,lx,stream); } cnt = l; px->nIndex += cnt; return px; } /*************************************************************************** ***************************************************************************/ static LONG FAR PASCAL mmioReadProc(HMMIO hmmio, LONG lSeek, LONG lRead, LPVOID lpBuffer) { if (mmioSeek(hmmio, lSeek, SEEK_SET) == -1) return -1; if (mmioRead(hmmio, (HPSTR)lpBuffer, lRead) != lRead) return -1; return lRead; } /*************************************************************************** ***************************************************************************/ static LONG FAR PASCAL mmioWriteProc(HMMIO hmmio, LONG lSeek, LONG lWrite, LPVOID lpBuffer) { if (mmioSeek(hmmio, lSeek, SEEK_SET) == -1) return -1; if (mmioWrite(hmmio, (HPSTR)lpBuffer, lWrite) != lWrite) return -1; return lWrite; } /*************************************************************************** * * @doc INTERNAL * * @api PSTREAMINDEX | MakeStreamIndex * * makes a STREAMINDEX structure that will be used later to read/find * samples in a stream. * ***************************************************************************/ EXTERN_C PSTREAMINDEX MakeStreamIndex(PAVIINDEX px, UINT stream, LONG lStart, LONG lSampleSize, HANDLE hFile, STREAMIOPROC ReadProc, STREAMIOPROC WriteProc) { LONG lPos; LONG lx; PSTREAMINDEX psx; Assert(px); if (px == NULL) return NULL; psx = (PSTREAMINDEX)LocalAlloc(LPTR, sizeof(STREAMINDEX)); if (psx == NULL) return NULL; //!!! fixed length sample streams should never have this if (lSampleSize != 0 && lStart < 0) { #ifdef DEBUG //AssertSz(0, "Audio streams should not have initial frames"); #endif lStart = 0; } psx->px = px; psx->lStart = lStart; psx->lSampleSize = lSampleSize; psx->lMaxSampleSize = 0; psx->stream = stream; psx->flags = 0; psx->lStart = lStart; psx->lxStart = IndexFirst(px, stream); psx->lPos = lStart; psx->lx = psx->lxStart; psx->lFrames = 0; psx->lKeyFrames = 0; psx->lPalFrames = 0; psx->lNulFrames = 0; psx->hFile = hFile; if (ReadProc == NULL) psx->Read = (STREAMIOPROC)mmioReadProc; else psx->Read = ReadProc; if (WriteProc == NULL) psx->Write = (STREAMIOPROC)mmioWriteProc; else psx->Write = WriteProc; lPos = lStart; for (lx = psx->lxStart; lx >= 0 && lx < px->nIndex; lx=IndexNext(px, lx, 0)) { if (psx->lMaxSampleSize < IndexLength(px, lx)) psx->lMaxSampleSize = IndexLength(px, lx); // // make sure the start sample is a key frame (unless it's wave data!) // if (lPos == 0 || (lPos >= 0 && lPos == psx->lStart)) { if ((IndexFlags(px, lx) & (IDX_KEY|IDX_NONKEY)) != (IDX_KEY|IDX_NONKEY)) { IndexSetKey(px, lx); } } // // make sure sample size is correct // if (psx->lSampleSize && ((IndexLength(px, lx) % lSampleSize) != 0)) { DPF("!!! Bad chunk size found: force sample size to 0???\n"); // psx->lSampleSize = 0; !!! turned off because of possible // partial audio chunks at file's end..... } // // or all the flags together so we can see what a stream has. // psx->flags |= IndexFlags(px, lx); // // check for all key frames. // if (IndexFlags(px, lx) & IDX_KEY) psx->lKeyFrames++; // // check for all palette changes // if (IndexFlags(px, lx) & IDX_PAL) psx->lPalFrames++; // // check for empty frames // if (IndexLength(px, lx) == 0) psx->lNulFrames++; // // advance the position // if (!(IndexFlags(px,lx) & IDX_NOTIME)) { if (lSampleSize) lPos += IndexLength(px, lx) / lSampleSize; else lPos++; } psx->lFrames++; } // // correct the length // psx->lEnd = lPos; DPF("MakeStreamIndex stream=#%d lStart=%ld, lEnd=%ld\n", stream, psx->lStart, psx->lEnd); DPF(" lFrames = %ld, lKeys = %ld, lPals = %ld, lEmpty = %ld\n", psx->lFrames, psx->lKeyFrames, psx->lPalFrames, psx->lNulFrames); return psx; } #ifndef AVIIDX_READONLY /*************************************************************************** * * @doc INTERNAL * * @api PAVIINDEX | IndexGetFileIndex * * make a file index out of a in memory index * ***************************************************************************/ EXTERN_C LONG IndexGetFileIndex(PAVIINDEX px, LONG l, LONG cnt, PAVIINDEXENTRY pidx, LONG lAdjust) { LONG lx; DWORD ckid; UINT stream; DWORD offset; DWORD length; UINT flags; DWORD dwFlags; Assert(pidx); Assert(px); if (pidx == NULL || px == NULL) return NULL; Assert(sizeof(AVIINDEXENTRY) > sizeof(AVIIDX)); for (lx=l; lx < px->nIndex && lx < l+cnt; lx++) { // // adjust the offset to be relative // offset = IndexOffset(px,lx) + lAdjust; length = IndexLength(px,lx); stream = IndexStream(px,lx); flags = IndexFlags(px, lx); if (length == MAX_LENGTH-1) { } ckid = MAKEAVICKID(0, stream); dwFlags = 0; // // set the flags, there are only a few flags in file index's // AVIIF_KEYFRAME, AVIIF_LIST, AVIIF_NOTIME // if (flags & IDX_KEY) dwFlags |= AVIIF_KEYFRAME; if (flags & IDX_PAL) dwFlags |= AVIIF_NOTIME; if (stream == STREAM_REC) dwFlags |= AVIIF_LIST; // // now figure out the ckid // if (stream == STREAM_REC) ckid = listtypeAVIRECORD; else if ((flags & (IDX_KEY|IDX_NONKEY)) == (IDX_KEY|IDX_NONKEY)) ckid |= MAKELONG(0, aviTWOCC('w', 'b')); else if (flags & IDX_PAL) ckid |= MAKELONG(0, aviTWOCC('p', 'c')); else if (flags & IDX_HALF) ckid |= MAKELONG(0, aviTWOCC('d', 'x')); else if (flags & IDX_KEY) ckid |= MAKELONG(0, aviTWOCC('d', 'b')); else ckid |= MAKELONG(0, aviTWOCC('d', 'c')); // // set the info // pidx->dwChunkOffset = offset; pidx->dwChunkLength = length; pidx->dwFlags = dwFlags; pidx->ckid = ckid; pidx++; } return lx - l; // return count copied } /*************************************************************************** * * @doc INTERNAL * * @api PAVIINDEX | IndexCreate | make a index. * ***************************************************************************/ EXTERN_C PAVIINDEX IndexCreate(void) { PAVIINDEX px; px = (PAVIINDEX)GlobalAllocPtr(GHND | GMEM_SHARE, sizeof(AVIINDEX) + INDEXALLOC * sizeof(AVIIDX)); if (px == NULL) return NULL; px->nIndex = 0; // index size px->nIndexSize = INDEXALLOC; // allocated size return px; } #endif // AVIIDX_READONLY /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api LONG | IndexFirst | returns the first index entry for a stream * * @rdesc returns the first index entry, -1 for error * ***************************************************************************/ EXTERN_C LONG IndexFirst(PAVIINDEX px, UINT stream) { LONG l; Assert(px); for (l=0; lnIndex; l++) { if (IndexStream(px, l) == stream) return l; } return ERR_IDX; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api LONG | IndexNext | go forward in a index * ***************************************************************************/ EXTERN_C LONG IndexNext(PAVIINDEX px, LONG l, UINT f) { WORD bStream; Assert(px); if (l < 0 || l >= px->nIndex) return ERR_IDX; bStream = IndexStream(px, l); for (l++; lnIndex; l++) { if (IndexStream(px, l) != bStream) continue; if (!f || (IndexFlags(px, l) & f)) return l; } return ERR_IDX; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api LONG | IndexPrev | step backward in a stream * ***************************************************************************/ EXTERN_C LONG IndexPrev(PAVIINDEX px, LONG l, UINT f) { WORD bStream; Assert(px); if (l < 0 || l >= px->nIndex) return ERR_IDX; bStream = IndexStream(px, l); for (l--; l>=0; l--) { if (IndexStream(px, l) != bStream) continue; if (!f || (IndexFlags(px, l) & f)) return l; } return ERR_IDX; } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// INLINE BOOL StreamNext(PSTREAMINDEX psx, LONG FAR& l, LONG FAR& lPos, UINT flags) { BYTE bStream = (BYTE) psx->stream; LONG lSampleSize = psx->lSampleSize; LONG lSave = l; LONG lPosSave = lPos; PAVIINDEX px = psx->px; Assert(px && l >= 0 && l < px->nIndex); if (lSampleSize == 0) { lPos += 1; l++; for (; lnIndex; l++) { if (IndexStream(px, l) != bStream) continue; if (!flags || (IndexFlags(px, l) & flags)) return TRUE; if (!(IndexFlags(px, l) & IDX_NOTIME)) lPos += 1; } } else { lPos += IndexLength(px, l) / lSampleSize; l++; for (; lnIndex; l++) { if (IndexStream(px, l) != bStream) continue; if (!flags || (IndexFlags(px, l) & flags)) return TRUE; lPos += IndexLength(px, l) / lSampleSize; } } lPos = lPosSave; l = lSave; return FALSE; } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// INLINE BOOL StreamPrev(PSTREAMINDEX psx, LONG FAR& l, LONG FAR& lPos, UINT flags) { BYTE bStream = (BYTE) psx->stream; LONG lSampleSize = psx->lSampleSize; LONG lSave = l; LONG lPosSave = lPos; PAVIINDEX px = psx->px; Assert(px && l >= 0 && l < px->nIndex); if (lSampleSize == 0) { for (l--;l>=0;l--) { if (IndexStream(px, l) != bStream) continue; if (!(IndexFlags(px, l) & IDX_NOTIME)) lPos -= 1; if (!flags || (IndexFlags(px, l) & flags)) return TRUE; } } else { for (l--;l>=0;l--) { if (IndexStream(px, l) != bStream) continue; lPos -= IndexLength(px, l) / lSampleSize; if (!flags || (IndexFlags(px, l) & flags)) return TRUE; } } lPos = lPosSave; l = lSave; return FALSE; } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// static LONG SearchIndex(PSTREAMINDEX psx,LONG lPos,UINT uFlags,IDXPOS FAR *pos) { LONG l; LONG lScan; LONG lFound; LONG lFoundPos; LONG lLen; UINT flags; PAVIINDEX px = psx->px; Assert(psx); Assert(psx->px); if (psx == NULL) return ERR_POS; if (lPos < psx->lStart) return ERR_POS; if (lPos >= psx->lEnd) return ERR_POS; // // figure out where to start in the index. // if (psx->lx != -1) { lScan = psx->lPos; l = psx->lx; } else { DPF3("Starting index search at begining\n"); lScan = psx->lStart; for (l=0; lnIndex; l++) if (IndexStream(px, l) == (UINT)psx->stream) break; } Assert(l >= 0 && l < px->nIndex); Assert(IndexStream(px, l) == psx->stream); #ifdef DEBUG if (!(uFlags & FIND_DIR)) uFlags |= FIND_PREV; switch (uFlags & (FIND_TYPE|FIND_DIR)) { case FIND_NEXT|FIND_KEY: DPF3("SearchIndex(%d): %ld next key, start=%ld",psx->stream, lPos, lScan); break; case FIND_NEXT|FIND_ANY: DPF3("SearchIndex(%d): %ld next any, start=%ld",psx->stream, lPos, lScan); break; case FIND_NEXT|FIND_FORMAT: DPF3("SearchIndex(%d): %ld next fmt, start=%ld",psx->stream, lPos, lScan); break; case FIND_NEXT: DPF3("SearchIndex(%d): %ld next , start=%ld",psx->stream, lPos, lScan); break; case FIND_PREV|FIND_KEY: DPF3("SearchIndex(%d): %ld prev key, start=%ld",psx->stream, lPos, lScan); break; case FIND_PREV|FIND_ANY: DPF3("SearchIndex(%d): %ld prev any, start=%ld",psx->stream, lPos, lScan); break; case FIND_PREV|FIND_FORMAT: DPF3("SearchIndex(%d): %ld prev fmt, start=%ld",psx->stream, lPos, lScan); break; case FIND_PREV: DPF3("SearchIndex(%d): %ld prev , start=%ld",psx->stream, lPos, lScan); break; } LONG time = timeGetTime(); #endif lLen = psx->lSampleSize == 0 ? 1 : IndexLength(px, l) / psx->lSampleSize; if (lScan+lLen <= lPos) { // // search forward for this position // while (lScan <= lPos) { lFound = l; lFoundPos = lScan; if (lScan == lPos) break; if (!StreamNext(psx, l, lScan, IDX_KEY|IDX_NONKEY)) break; } if ((lScan > lPos) && !(uFlags & FIND_NEXT)) { lScan = lFoundPos; l = lFound; } } else if (lScan > lPos) { // // search backward for this position // while (lScan >= lPos) { lFound = l; lFoundPos = lScan; if (lScan == lPos) break; if (!StreamPrev(psx, l, lScan, IDX_KEY|IDX_NONKEY)) break; } if (uFlags & FIND_NEXT) { lScan = lFoundPos; l = lFound; } } else { Assert(lScan <= lPos && lPos < lScan+lLen); } Assert(l >= 0 && l < px->nIndex); Assert(IndexStream(px, l) == psx->stream); // // cache what we found. // psx->lx = l; psx->lPos = lScan; if (uFlags & FIND_TYPE) { switch (uFlags & FIND_TYPE) { case FIND_ANY: flags = IDX_KEY|IDX_NONKEY; break; case FIND_FORMAT: flags = IDX_PAL; break; case FIND_KEY: flags = IDX_KEY; break; } if (!(IndexFlags(px, l) & flags)) { if (!(uFlags & FIND_NEXT)) { if (!StreamPrev(psx, l, lScan, flags)) { DPF3("!, EOI, time = %ld\n", timeGetTime() - time); return ERR_POS; } } else { if (!StreamNext(psx, l, lScan, flags)) { DPF3("!, EOI, time = %ld\n", timeGetTime() - time); return ERR_POS; } } } Assert(l >= 0 && l < px->nIndex); Assert(IndexStream(px, l) == psx->stream); Assert(IndexFlags(px, l) & flags); } Assert(lScan >= psx->lStart && lScan < psx->lEnd); DPF3("!, found %ld, time = %ld\n", lScan, timeGetTime() - time); if (pos == NULL) return lScan; if (psx->lSampleSize != 0) { lLen = IndexLength(px, l); if (lLen == MAX_LENGTH-1) lLen = 0x7FFFFFFF; if (psx->lSampleSize > 1) lLen /= psx->lSampleSize; } else { lLen = 1; } pos->lx = l; pos->lPos = lScan; pos->lSize = lLen; pos->lOffset = IndexOffset(px, l); pos->lLength = IndexLength(px, l); // // if the FIND_TYPE is not one of FIND_ANY, FIND_KEY, FIND_FORMAT // make sure we realy found the wanted sample. // if ((uFlags & FIND_TYPE) == 0) { if (lPos < lScan || lPos >= lScan+lLen) { pos->lOffset = -1; pos->lLength = 0; pos->lSize = 0; pos->lPos = lPos; } else if (psx->lSampleSize > 0) { pos->lOffset += (lPos - lScan) * psx->lSampleSize; pos->lLength -= (lPos - lScan) * psx->lSampleSize; pos->lSize -= (lPos - lScan); pos->lPos = lPos; } } return pos->lPos; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api LONG | FindSample | find a sample in a stream * ***************************************************************************/ EXTERN_C LONG StreamFindSample(PSTREAMINDEX psx,LONG lPos,UINT uFlags) { Assert(psx); Assert(psx->px); if (lPos < psx->lStart) return ERR_POS; if (lPos >= psx->lEnd) return ERR_POS; if ((uFlags & FIND_RET) == FIND_POS) { switch (uFlags & FIND_TYPE) { case FIND_FORMAT: if (psx->lPalFrames == 0) { if ((uFlags & FIND_NEXT) && lPos > psx->lStart) return ERR_POS; else return psx->lStart; } break; case FIND_ANY: if (psx->lNulFrames == 0) { return lPos; } break; case FIND_KEY: if (psx->lKeyFrames == psx->lFrames) { return lPos; } break; default: return lPos; } return SearchIndex(psx, lPos, uFlags, NULL); } else { IDXPOS pos; if (SearchIndex(psx, lPos, uFlags, &pos) == ERR_POS) return ERR_POS; switch (uFlags & FIND_RET) { case FIND_POS: return pos.lPos; case FIND_OFFSET: return pos.lOffset + 8; case FIND_LENGTH: return pos.lLength; case FIND_SIZE: return pos.lSize; case FIND_INDEX: return pos.lx; } } return ERR_POS; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api LONG | StreamRead | read from a stream * ***************************************************************************/ EXTERN_C LONG StreamRead( PSTREAMINDEX psx, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer) { LONG lBytes; LONG lSampleSize; LONG lSeek; LONG lRead; IDXPOS pos; Assert(psx); Assert(psx->px); Assert(psx->hFile); Assert(psx->Read); if (lStart < psx->lStart) return -1; if (lStart >= psx->lEnd) return -1; //DPF("%cst: %d : %d\n", psx->stream ? '\t':' ', psx->stream, lStart); // // find nearest chunk // if (SearchIndex(psx, lStart, FIND_PREV, &pos) == ERR_POS) return -1; // // only continue if the sample we want is in here. // if (lStart < pos.lPos || lStart >= pos.lPos + pos.lSize) return 0; // // if they give us a NULL buffer dummy up the cbBuffer so we return // what we would have read if we had enough room // if (lpBuffer == NULL && cbBuffer == 0 && lSamples != 0) cbBuffer = 0x7FFFFFFF; if (lSampleSize = psx->lSampleSize) { // If they wanted to read/write only a "convenient amount", // pretend the buffer is only large enough to hold the // rest of this chunk. if (lSamples == -1l) cbBuffer = min(cbBuffer, pos.lLength); /* Fixed-length samples, if lSamples is zero, just fill the buffer. */ if (lSamples > 0) lSamples = min(lSamples, cbBuffer / lSampleSize); else lSamples = cbBuffer / lSampleSize; lBytes = lSamples * lSampleSize; } else { lBytes = pos.lLength; } if (lpBuffer == NULL) return lBytes; if (cbBuffer < lBytes) return -1; // buffer is too small #define WORDALIGN(x) ((x) + ((x) & 1)) if (lSampleSize == 0) { DWORD adw[2]; psx->Read(psx->hFile, pos.lOffset, sizeof(adw), adw); if (StreamFromFOURCC(adw[0]) != psx->stream) { Assert(0); } else { Assert(WORDALIGN(adw[1]) == WORDALIGN((DWORD)pos.lLength)); pos.lLength = adw[1]; // !!! Make netware video work! lBytes = pos.lLength; } } else { #ifdef DEBUG IDXPOS x; DWORD adw[2]; SearchIndex(psx, lStart, FIND_PREV|FIND_ANY, &x); psx->Read(psx->hFile, x.lOffset, sizeof(adw), adw); Assert(StreamFromFOURCC(adw[0]) == psx->stream); Assert(WORDALIGN(adw[1]) == WORDALIGN((DWORD)x.lLength)); #endif } cbBuffer = lBytes; lBytes = 0; while (cbBuffer > 0) { lSeek = pos.lOffset + 8; lRead = min(pos.lLength,cbBuffer); if (lRead <= 0) { DPF3("!!!! lRead <= 0 in AVIStreamRead\n"); break; } DPF3("StreamRead: %ld bytes @%ld\n", lRead, lSeek); if (psx->Read(psx->hFile, lSeek, lRead, lpBuffer) != lRead) return -1; lBytes += lRead; cbBuffer -= lRead; if (cbBuffer > 0) { if (lSampleSize == 0) { DPF("%ld bytes to read, but sample size is 0!\n", cbBuffer); break; } lpBuffer = (LPVOID) (((BYTE _huge *)lpBuffer) + lRead); lStart += lRead / lSampleSize; lStart = SearchIndex(psx, lStart, FIND_PREV, &pos); if (lStart == ERR_POS) break; } } // // success return number of bytes read // return lBytes; } /*************************************************************************** * * @doc INTERNAL MCIAVI * * @api LONG | StreamWrite | write to a stream * ***************************************************************************/ EXTERN_C LONG StreamWrite( PSTREAMINDEX psx, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer) { return -1; }