4 * Copyright 2003 Robert Shearman
5 * Copyright 2004-2005 Christian Costa
6 * Copyright 2008 Maarten Lankhorst
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * - we don't do anything with indices yet (we could use them when seeking)
24 * - we don't support multiple RIFF sections (i.e. large AVI files > 2Gb)
25 * - Memory leaks, and lots of them
28 #include "quartz_private.h"
29 #include "control_private.h"
38 #include "wine/unicode.h"
39 #include "wine/debug.h"
46 #define TWOCCFromFOURCC(fcc) HIWORD(fcc)
48 /* four character codes used in AVI files */
49 #define ckidINFO mmioFOURCC('I','N','F','O')
50 #define ckidREC mmioFOURCC('R','E','C',' ')
52 WINE_DEFAULT_DEBUG_CHANNEL(quartz);
54 typedef struct StreamData
60 AVISTREAMHEADER streamheader;
62 AVISTDINDEX **stdindex;
66 typedef struct AVISplitterImpl
69 IMediaSample * pCurrentSample;
70 RIFFCHUNK CurrentChunk;
71 LONGLONG CurrentChunkOffset; /* in media time */
73 AVIMAINHEADER AviHeader;
74 AVIEXTHEADER ExtHeader;
76 /* TODO: Handle old style index, probably by creating an opendml style new index from it for within StreamData */
77 AVIOLDINDEX *oldindex;
83 static HRESULT AVISplitter_NextChunk(LONGLONG * pllCurrentChunkOffset, RIFFCHUNK * pCurrentChunk, const REFERENCE_TIME * tStart, const REFERENCE_TIME * tStop, const BYTE * pbSrcStream, int inner)
86 *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFLIST));
88 *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK) + RIFFROUND(pCurrentChunk->cb));
90 if (*pllCurrentChunkOffset >= *tStop)
91 return S_FALSE; /* no more data - we couldn't even get the next chunk header! */
92 else if (*pllCurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) >= *tStop)
94 memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), (DWORD)BYTES_FROM_MEDIATIME(*tStop - *pllCurrentChunkOffset));
95 return S_FALSE; /* no more data */
98 memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), sizeof(RIFFCHUNK));
103 static HRESULT AVISplitter_Sample(LPVOID iface, IMediaSample * pSample, DWORD_PTR cookie)
105 AVISplitterImpl *This = (AVISplitterImpl *)iface;
106 LPBYTE pbSrcStream = NULL;
107 long cbSrcStream = 0;
108 REFERENCE_TIME tStart, tStop;
110 BOOL bMoreData = TRUE;
112 hr = IMediaSample_GetPointer(pSample, &pbSrcStream);
114 hr = IMediaSample_GetTime(pSample, &tStart, &tStop);
116 cbSrcStream = IMediaSample_GetActualDataLength(pSample);
118 /* trace removed for performance reasons */
119 /* TRACE("(%p)\n", pSample); */
121 assert(BYTES_FROM_MEDIATIME(tStop - tStart) == cbSrcStream);
123 if (This->CurrentChunkOffset <= tStart && This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) > tStart)
125 DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(tStart - This->CurrentChunkOffset);
126 assert(offset <= sizeof(RIFFCHUNK));
127 memcpy((BYTE *)&This->CurrentChunk + offset, pbSrcStream, sizeof(RIFFCHUNK) - offset);
129 else if (This->CurrentChunkOffset > tStart)
131 DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart);
132 if (offset >= (DWORD)cbSrcStream)
134 FIXME("large offset\n");
139 memcpy(&This->CurrentChunk, pbSrcStream + offset, sizeof(RIFFCHUNK));
142 assert(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) < tStop);
148 long chunk_remaining_bytes = 0;
151 Parser_OutputPin * pOutputPin;
152 BOOL bSyncPoint = TRUE;
153 BYTE *fcc = (BYTE *)&This->CurrentChunk.fcc;
155 if (This->CurrentChunkOffset >= tStart)
156 offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
160 switch (This->CurrentChunk.fcc)
162 case ckidAVIOLDINDEX: /* Should not be popping up here! */
163 ERR("There should be no index in the stream data!\n");
165 /* silently ignore */
166 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
170 /* We only handle the 'rec ' list which contains the stream data */
171 if ((*(DWORD*)(pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart) + sizeof(RIFFCHUNK))) == ckidREC)
173 /* FIXME: We only advanced to the first chunk inside the list without keeping track that we are in it.
174 * This is not clean and the parser should be improved for that but it is enough for most AVI files. */
175 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, TRUE))
180 This->CurrentChunk = *(RIFFCHUNK*) (pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart));
181 offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
184 else if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
189 #if 0 /* According to the AVI specs, a stream data chunk should be ABXX where AB is the stream number and X means don't care */
190 switch (TWOCCFromFOURCC(This->CurrentChunk.fcc))
192 case cktypeDIBcompressed:
196 /* FIXME: check that pin is of type video */
198 case cktypeWAVEbytes:
199 /* FIXME: check that pin is of type audio */
201 case cktypePALchange:
202 FIXME("handle palette change\n");
205 FIXME("Skipping unknown chunk type: %s at file offset 0x%x\n", debugstr_an((LPSTR)&This->CurrentChunk.fcc, 4), (DWORD)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset));
206 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
213 if (fcc[0] == 'i' && fcc[1] == 'x')
215 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
220 streamId = StreamFromFOURCC(This->CurrentChunk.fcc);
222 if (streamId > This->Parser.cStreams)
224 ERR("Corrupted AVI file (contains stream id (%s) %d, but supposed to only have %d streams)\n", debugstr_an((char *)&This->CurrentChunk.fcc, 4), streamId, This->Parser.cStreams);
229 pOutputPin = (Parser_OutputPin *)This->Parser.ppPins[streamId + 1];
231 if (!This->pCurrentSample)
233 /* cache media sample until it is ready to be despatched
234 * (i.e. we reach the end of the chunk) */
235 hr = OutputPin_GetDeliveryBuffer(&pOutputPin->pin, &This->pCurrentSample, NULL, NULL, 0);
239 hr = IMediaSample_SetActualDataLength(This->pCurrentSample, 0);
244 TRACE("Skipping sending sample for stream %02d due to error (%x)\n", streamId, hr);
245 This->pCurrentSample = NULL;
246 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
252 hr = IMediaSample_GetPointer(This->pCurrentSample, &pbDstStream);
256 cbDstStream = IMediaSample_GetSize(This->pCurrentSample);
258 chunk_remaining_bytes = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(This->CurrentChunk.cb + sizeof(RIFFCHUNK)) - tStart) - offset_src;
260 assert(chunk_remaining_bytes >= 0);
261 assert(chunk_remaining_bytes <= cbDstStream - IMediaSample_GetActualDataLength(This->pCurrentSample));
263 /* trace removed for performance reasons */
264 /* TRACE("chunk_remaining_bytes: 0x%x, cbSrcStream: 0x%x, offset_src: 0x%x\n", chunk_remaining_bytes, cbSrcStream, offset_src); */
267 if (chunk_remaining_bytes <= cbSrcStream - offset_src)
271 memcpy(pbDstStream + IMediaSample_GetActualDataLength(This->pCurrentSample), pbSrcStream + offset_src, chunk_remaining_bytes);
272 hr = IMediaSample_SetActualDataLength(This->pCurrentSample, chunk_remaining_bytes + IMediaSample_GetActualDataLength(This->pCurrentSample));
278 REFERENCE_TIME tAviStart, tAviStop;
279 StreamData *stream = This->streams + streamId;
282 if (pOutputPin->dwSamplesProcessed == 0)
283 IMediaSample_SetDiscontinuity(This->pCurrentSample, TRUE);
285 IMediaSample_SetSyncPoint(This->pCurrentSample, bSyncPoint);
287 pOutputPin->dwSamplesProcessed++;
289 if (stream->dwSampleSize)
290 tAviStart = (LONGLONG)ceil(10000000.0 * (float)(pOutputPin->dwSamplesProcessed - 1) * (float)IMediaSample_GetActualDataLength(This->pCurrentSample) / ((float)stream->dwSampleSize * stream->fSamplesPerSec));
292 tAviStart = (LONGLONG)ceil(10000000.0 * (float)(pOutputPin->dwSamplesProcessed - 1) / (float)stream->fSamplesPerSec);
293 if (stream->dwSampleSize)
294 tAviStop = (LONGLONG)ceil(10000000.0 * (float)pOutputPin->dwSamplesProcessed * (float)IMediaSample_GetActualDataLength(This->pCurrentSample) / ((float)stream->dwSampleSize * stream->fSamplesPerSec));
296 tAviStop = (LONGLONG)ceil(10000000.0 * (float)pOutputPin->dwSamplesProcessed / (float)stream->fSamplesPerSec);
298 IMediaSample_SetTime(This->pCurrentSample, &tAviStart, &tAviStop);
300 hr = OutputPin_SendSample(&pOutputPin->pin, This->pCurrentSample);
301 if (hr != S_OK && hr != VFW_E_NOT_CONNECTED)
302 ERR("Error sending sample (%x)\n", hr);
305 if (This->pCurrentSample)
306 IMediaSample_Release(This->pCurrentSample);
308 This->pCurrentSample = NULL;
310 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
317 memcpy(pbDstStream + IMediaSample_GetActualDataLength(This->pCurrentSample), pbSrcStream + offset_src, cbSrcStream - offset_src);
318 IMediaSample_SetActualDataLength(This->pCurrentSample, cbSrcStream - offset_src + IMediaSample_GetActualDataLength(This->pCurrentSample));
325 if (tStop >= This->EndOfFile)
329 TRACE("End of file reached\n");
331 for (i = 0; i < This->Parser.cStreams; i++)
336 TRACE("Send End Of Stream to output pin %d\n", i);
338 hr = IPin_ConnectedTo(This->Parser.ppPins[i+1], &ppin);
341 hr = IPin_EndOfStream(ppin);
351 /* Force the pullpin thread to stop */
358 static HRESULT AVISplitter_QueryAccept(LPVOID iface, const AM_MEDIA_TYPE * pmt)
360 if (IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream) && IsEqualIID(&pmt->subtype, &MEDIASUBTYPE_Avi))
365 static HRESULT AVISplitter_ProcessIndex(AVISplitterImpl *This, AVISTDINDEX **index, LONGLONG qwOffset, DWORD cb)
372 if (cb < sizeof(AVISTDINDEX))
374 FIXME("size %u too small\n", cb);
378 pIndex = CoTaskMemAlloc(cb);
380 return E_OUTOFMEMORY;
382 IAsyncReader_SyncRead(((PullPin *)This->Parser.ppPins[0])->pReader, qwOffset, cb, (BYTE *)pIndex);
383 pIndex = CoTaskMemRealloc(pIndex, pIndex->cb);
385 return E_OUTOFMEMORY;
387 IAsyncReader_SyncRead(((PullPin *)This->Parser.ppPins[0])->pReader, qwOffset, pIndex->cb, (BYTE *)pIndex);
388 rest = pIndex->cb - sizeof(AVISUPERINDEX) + sizeof(RIFFCHUNK) + sizeof(pIndex->aIndex[0]) * ANYSIZE_ARRAY;
390 TRACE("wLongsPerEntry: %hd\n", pIndex->wLongsPerEntry);
391 TRACE("bIndexSubType: %hd\n", pIndex->bIndexSubType);
392 TRACE("bIndexType: %hd\n", pIndex->bIndexType);
393 TRACE("nEntriesInUse: %u\n", pIndex->nEntriesInUse);
394 TRACE("dwChunkId: %.4s\n", (char *)&pIndex->dwChunkId);
395 TRACE("qwBaseOffset: %x%08x\n", (DWORD)(pIndex->qwBaseOffset >> 32), (DWORD)pIndex->qwBaseOffset);
396 TRACE("dwReserved_3: %u\n", pIndex->dwReserved_3);
398 if (pIndex->bIndexType != AVI_INDEX_OF_CHUNKS
399 || pIndex->wLongsPerEntry != 2
400 || rest < (pIndex->nEntriesInUse * sizeof(DWORD) * pIndex->wLongsPerEntry)
401 || (pIndex->bIndexSubType != AVI_INDEX_SUB_DEFAULT))
403 FIXME("Invalid index chunk encountered\n");
407 for (x = 0; x < pIndex->nEntriesInUse; ++x)
409 BOOL keyframe = !(pIndex->aIndex[x].dwOffset >> 31);
410 DWORDLONG offset = pIndex->qwBaseOffset + (pIndex->aIndex[x].dwOffset & ~(1<<31));
411 TRACE("dwOffset: %x%08x\n", (DWORD)(offset >> 32), (DWORD)offset);
412 TRACE("dwSize: %u\n", pIndex->aIndex[x].dwSize);
413 TRACE("Frame is a keyframe: %s\n", keyframe ? "yes" : "no");
420 static HRESULT AVISplitter_ProcessOldIndex(AVISplitterImpl *This)
422 ULONGLONG mov_pos = BYTES_FROM_MEDIATIME(This->CurrentChunkOffset) - sizeof(DWORD);
423 AVIOLDINDEX *pAviOldIndex = This->oldindex;
427 for (x = 0; x < pAviOldIndex->cb / sizeof(pAviOldIndex->aIndex[0]); ++x)
429 DWORD temp, temp2 = 0, offset, chunkid;
430 PullPin *pin = This->Parser.pInputPin;
432 offset = pAviOldIndex->aIndex[x].dwOffset;
433 chunkid = pAviOldIndex->aIndex[x].dwChunkId;
435 /* Only scan once, or else this will take too long */
438 IAsyncReader_SyncRead(pin->pReader, offset, sizeof(DWORD), (BYTE *)&temp);
439 relative = (chunkid != temp);
441 TRACE("dwChunkId: %.4s\n", (char *)&chunkid);
442 if (chunkid == mmioFOURCC('7','F','x','x')
443 && ((char *)&temp)[0] == 'i' && ((char *)&temp)[1] == 'x')
448 if (offset + mov_pos < BYTES_FROM_MEDIATIME(This->EndOfFile))
449 IAsyncReader_SyncRead(pin->pReader, offset + mov_pos, sizeof(DWORD), (BYTE *)&temp2);
451 if (chunkid == mmioFOURCC('7','F','x','x')
452 && ((char *)&temp2)[0] == 'i' && ((char *)&temp2)[1] == 'x')
454 /* Do nothing, all is great */
456 else if (temp2 != chunkid)
458 ERR("Faulty index or bug in handling: Wanted FCC: %s, Abs FCC: %s (@ %x), Rel FCC: %s (@ %.0x%08x)\n",
459 debugstr_an((char *)&chunkid, 4), debugstr_an((char *)&temp, 4), offset,
460 debugstr_an((char *)&temp2, 4), (DWORD)((mov_pos + offset) >> 32), (DWORD)(mov_pos + offset));
464 TRACE("Scanned dwChunkId: %s\n", debugstr_an((char *)&temp2, 4));
467 TRACE("Scanned dwChunkId: %s\n", debugstr_an((char *)&temp, 4));
468 TRACE("dwFlags: %08x\n", pAviOldIndex->aIndex[x].dwFlags);
469 TRACE("dwOffset (%s): %08x\n", relative ? "relative" : "absolute", offset);
470 TRACE("dwSize: %08x\n", pAviOldIndex->aIndex[x].dwSize);
477 FIXME("Dropping index: no idea whether it is relative or absolute\n");
478 CoTaskMemFree(This->oldindex);
479 This->oldindex = NULL;
484 This->offset = (DWORD)mov_pos;
489 static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE * pData, DWORD cb)
492 const RIFFCHUNK * pChunk;
495 float fSamplesPerSec = 0.0f;
496 DWORD dwSampleSize = 0;
498 ALLOCATOR_PROPERTIES props;
499 static const WCHAR wszStreamTemplate[] = {'S','t','r','e','a','m',' ','%','0','2','d',0};
502 AVISTDINDEX **stdindex = NULL;
507 props.cbBuffer = 0x20000;
510 ZeroMemory(&amt, sizeof(amt));
511 piOutput.dir = PINDIR_OUTPUT;
512 piOutput.pFilter = (IBaseFilter *)This;
513 wsprintfW(piOutput.achName, wszStreamTemplate, This->Parser.cStreams);
514 This->streams = CoTaskMemRealloc(This->streams, sizeof(StreamData) * (This->Parser.cStreams+1));
515 stream = This->streams + This->Parser.cStreams;
517 for (pChunk = (const RIFFCHUNK *)pData;
518 ((const BYTE *)pChunk >= pData) && ((const BYTE *)pChunk + sizeof(RIFFCHUNK) < pData + cb) && (pChunk->cb > 0);
519 pChunk = (const RIFFCHUNK *)((const BYTE*)pChunk + sizeof(RIFFCHUNK) + pChunk->cb)
524 case ckidSTREAMHEADER:
526 const AVISTREAMHEADER * pStrHdr = (const AVISTREAMHEADER *)pChunk;
527 TRACE("processing stream header\n");
528 stream->streamheader = *pStrHdr;
530 fSamplesPerSec = (float)pStrHdr->dwRate / (float)pStrHdr->dwScale;
531 CoTaskMemFree(amt.pbFormat);
535 switch (pStrHdr->fccType)
537 case streamtypeVIDEO:
538 amt.formattype = FORMAT_VideoInfo;
540 case streamtypeAUDIO:
541 amt.formattype = FORMAT_WaveFormatEx;
544 FIXME("fccType %.4s not handled yet\n", (char *)&pStrHdr->fccType);
545 amt.formattype = FORMAT_None;
547 amt.majortype = MEDIATYPE_Video;
548 amt.majortype.Data1 = pStrHdr->fccType;
549 amt.subtype = MEDIATYPE_Video;
550 amt.subtype.Data1 = pStrHdr->fccHandler;
551 TRACE("Subtype FCC: %.04s\n", (LPCSTR)&pStrHdr->fccHandler);
552 amt.lSampleSize = pStrHdr->dwSampleSize;
553 amt.bFixedSizeSamples = (amt.lSampleSize != 0);
555 /* FIXME: Is this right? */
556 if (!amt.lSampleSize)
562 amt.bTemporalCompression = IsEqualGUID(&amt.majortype, &MEDIATYPE_Video); /* FIXME? */
563 dwSampleSize = pStrHdr->dwSampleSize;
564 dwLength = pStrHdr->dwLength;
566 dwLength = This->AviHeader.dwTotalFrames;
568 if (pStrHdr->dwSuggestedBufferSize)
569 props.cbBuffer = pStrHdr->dwSuggestedBufferSize;
573 case ckidSTREAMFORMAT:
574 TRACE("processing stream format data\n");
575 if (IsEqualIID(&amt.formattype, &FORMAT_VideoInfo))
577 VIDEOINFOHEADER * pvi;
578 /* biCompression member appears to override the value in the stream header.
579 * i.e. the stream header can say something completely contradictory to what
580 * is in the BITMAPINFOHEADER! */
581 if (pChunk->cb < sizeof(BITMAPINFOHEADER))
583 ERR("Not enough bytes for BITMAPINFOHEADER\n");
586 amt.cbFormat = sizeof(VIDEOINFOHEADER) - sizeof(BITMAPINFOHEADER) + pChunk->cb;
587 amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
588 ZeroMemory(amt.pbFormat, amt.cbFormat);
589 pvi = (VIDEOINFOHEADER *)amt.pbFormat;
590 pvi->AvgTimePerFrame = (LONGLONG)(10000000.0 / fSamplesPerSec);
591 CopyMemory(&pvi->bmiHeader, (const BYTE *)(pChunk + 1), pChunk->cb);
592 if (pvi->bmiHeader.biCompression)
593 amt.subtype.Data1 = pvi->bmiHeader.biCompression;
597 amt.cbFormat = pChunk->cb;
598 amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
599 CopyMemory(amt.pbFormat, (const BYTE *)(pChunk + 1), amt.cbFormat);
603 TRACE("processing stream name\n");
604 /* FIXME: this doesn't exactly match native version (we omit the "##)" prefix), but hey... */
605 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)(pChunk + 1), pChunk->cb, piOutput.achName, sizeof(piOutput.achName) / sizeof(piOutput.achName[0]));
607 case ckidSTREAMHANDLERDATA:
608 FIXME("process stream handler data\n");
611 TRACE("JUNK chunk ignored\n");
613 case ckidAVISUPERINDEX:
615 const AVISUPERINDEX *pIndex = (const AVISUPERINDEX *)pChunk;
617 long rest = pIndex->cb - sizeof(AVISUPERINDEX) + sizeof(RIFFCHUNK) + sizeof(pIndex->aIndex[0]) * ANYSIZE_ARRAY;
619 if (pIndex->cb < sizeof(AVISUPERINDEX) - sizeof(RIFFCHUNK))
621 FIXME("size %u\n", pIndex->cb);
627 ERR("Stream %d got more then 1 superindex?\n", This->Parser.cStreams);
631 TRACE("wLongsPerEntry: %hd\n", pIndex->wLongsPerEntry);
632 TRACE("bIndexSubType: %hd\n", pIndex->bIndexSubType);
633 TRACE("bIndexType: %hd\n", pIndex->bIndexType);
634 TRACE("nEntriesInUse: %u\n", pIndex->nEntriesInUse);
635 TRACE("dwChunkId: %.4s\n", (char *)&pIndex->dwChunkId);
636 if (pIndex->dwReserved[0])
637 TRACE("dwReserved[0]: %u\n", pIndex->dwReserved[0]);
638 if (pIndex->dwReserved[2])
639 TRACE("dwReserved[1]: %u\n", pIndex->dwReserved[1]);
640 if (pIndex->dwReserved[2])
641 TRACE("dwReserved[2]: %u\n", pIndex->dwReserved[2]);
643 if (pIndex->bIndexType != AVI_INDEX_OF_INDEXES
644 || pIndex->wLongsPerEntry != 4
645 || rest < (pIndex->nEntriesInUse * sizeof(DWORD) * pIndex->wLongsPerEntry)
646 || (pIndex->bIndexSubType != AVI_INDEX_SUB_2FIELD && pIndex->bIndexSubType != AVI_INDEX_SUB_DEFAULT))
648 FIXME("Invalid index chunk encountered\n");
652 for (x = 0; x < pIndex->nEntriesInUse; ++x)
654 TRACE("qwOffset: %x%08x\n", (DWORD)(pIndex->aIndex[x].qwOffset >> 32), (DWORD)pIndex->aIndex[x].qwOffset);
655 TRACE("dwSize: %u\n", pIndex->aIndex[x].dwSize);
656 TRACE("dwDuration: %u (unreliable)\n", pIndex->aIndex[x].dwDuration);
659 stdindex = CoTaskMemRealloc(stdindex, sizeof(*stdindex) * nstdindex);
660 AVISplitter_ProcessIndex(This, &stdindex[nstdindex-1], pIndex->aIndex[x].qwOffset, pIndex->aIndex[x].dwSize);
665 FIXME("unknown chunk type \"%.04s\" ignored\n", (LPCSTR)&pChunk->fcc);
669 if (IsEqualGUID(&amt.formattype, &FORMAT_WaveFormatEx))
671 amt.subtype = MEDIATYPE_Video;
672 amt.subtype.Data1 = ((WAVEFORMATEX *)amt.pbFormat)->wFormatTag;
675 dump_AM_MEDIA_TYPE(&amt);
676 TRACE("fSamplesPerSec = %f\n", (double)fSamplesPerSec);
677 TRACE("dwSampleSize = %x\n", dwSampleSize);
678 TRACE("dwLength = %x\n", dwLength);
680 stream->fSamplesPerSec = fSamplesPerSec;
681 stream->dwSampleSize = dwSampleSize;
682 stream->dwLength = dwLength; /* TODO: Use this for mediaseeking */
683 stream->entries = nstdindex;
684 stream->stdindex = stdindex;
686 hr = Parser_AddPin(&(This->Parser), &piOutput, &props, &amt);
687 CoTaskMemFree(amt.pbFormat);
692 static HRESULT AVISplitter_ProcessODML(AVISplitterImpl * This, const BYTE * pData, DWORD cb)
694 const RIFFCHUNK * pChunk;
696 for (pChunk = (const RIFFCHUNK *)pData;
697 ((const BYTE *)pChunk >= pData) && ((const BYTE *)pChunk + sizeof(RIFFCHUNK) < pData + cb) && (pChunk->cb > 0);
698 pChunk = (const RIFFCHUNK *)((const BYTE*)pChunk + sizeof(RIFFCHUNK) + pChunk->cb)
703 case ckidAVIEXTHEADER:
706 const AVIEXTHEADER * pExtHdr = (const AVIEXTHEADER *)pChunk;
708 TRACE("processing extension header\n");
709 if (pExtHdr->cb != sizeof(AVIEXTHEADER) - sizeof(RIFFCHUNK))
711 FIXME("Size: %u\n", pExtHdr->cb);
714 TRACE("dwGrandFrames: %u\n", pExtHdr->dwGrandFrames);
715 for (x = 0; x < 61; ++x)
716 if (pExtHdr->dwFuture[x])
717 FIXME("dwFuture[%i] = %u (0x%08x)\n", x, pExtHdr->dwFuture[x], pExtHdr->dwFuture[x]);
718 This->ExtHeader = *pExtHdr;
722 FIXME("unknown chunk type \"%.04s\" ignored\n", (LPCSTR)&pChunk->fcc);
729 static HRESULT AVISplitter_InitializeStreams(AVISplitterImpl *This)
737 for (x = 0; x < This->Parser.cStreams; ++x)
739 This->streams[x].frames = 0;
742 nMax = This->oldindex->cb / sizeof(This->oldindex->aIndex[0]);
744 /* Ok, maybe this is more of an excercise to see if I interpret everything correctly or not, but that is useful for now. */
745 for (n = 0; n < nMax; ++n)
747 DWORD streamId = StreamFromFOURCC(This->oldindex->aIndex[n].dwChunkId);
748 if (streamId >= This->Parser.cStreams)
750 FIXME("Stream id %s ignored\n", debugstr_an((char*)&This->oldindex->aIndex[n].dwChunkId, 4));
754 if (This->streams[streamId].streamheader.dwSampleSize)
755 This->streams[streamId].frames += This->oldindex->aIndex[n].dwSize / This->streams[streamId].streamheader.dwSampleSize;
757 ++This->streams[streamId].frames;
760 for (x = 0; x < This->Parser.cStreams; ++x)
762 if ((DWORD)This->streams[x].frames != This->streams[x].streamheader.dwLength)
764 FIXME("stream %u: frames found: %u, frames meant to be found: %u\n", x, (DWORD)This->streams[x].frames, This->streams[x].streamheader.dwLength);
769 else if (!This->streams[0].entries)
771 for (x = 0; x < This->Parser.cStreams; ++x)
773 This->streams[x].frames = This->streams[x].streamheader.dwLength;
777 /* Not much here yet */
778 for (x = 0; x < This->Parser.cStreams; ++x)
780 StreamData *stream = This->streams + x;
786 fps = (double)stream->streamheader.dwRate / (float)stream->streamheader.dwScale;
787 if (stream->stdindex)
789 for (y = 0; y < stream->entries; ++y)
791 frames += stream->stdindex[y]->nEntriesInUse;
794 else frames = stream->frames;
796 frames *= stream->streamheader.dwScale;
797 /* Keep accuracy as high as possible for duration */
798 This->Parser.mediaSeeking.llDuration = frames * 10000000;
799 This->Parser.mediaSeeking.llDuration /= stream->streamheader.dwRate;
800 This->Parser.mediaSeeking.llStop = This->Parser.mediaSeeking.llDuration;
801 This->Parser.mediaSeeking.llCurrent = 0;
803 frames /= stream->streamheader.dwRate;
805 TRACE("fps: %f\n", fps);
806 TRACE("Duration: %d days, %d hours, %d minutes and %d seconds\n", (DWORD)(frames / 86400),
807 (DWORD)((frames % 86400) / 3600), (DWORD)((frames % 3600) / 60), (DWORD)(frames % 60));
813 static HRESULT AVISplitter_Disconnect(LPVOID iface);
815 /* FIXME: fix leaks on failure here */
816 static HRESULT AVISplitter_InputPin_PreConnect(IPin * iface, IPin * pConnectPin, ALLOCATOR_PROPERTIES *props)
818 PullPin *This = (PullPin *)iface;
821 LONGLONG pos = 0; /* in bytes */
823 RIFFCHUNK * pCurrentChunk;
824 LONGLONG total, avail;
828 AVISplitterImpl * pAviSplit = (AVISplitterImpl *)This->pin.pinInfo.pFilter;
830 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
833 if (list.fcc != FOURCC_RIFF)
835 ERR("Input stream not a RIFF file\n");
838 if (list.fccListType != formtypeAVI)
840 ERR("Input stream not an AVI RIFF file\n");
844 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
845 if (list.fcc != FOURCC_LIST)
847 ERR("Expected LIST chunk, but got %.04s\n", (LPSTR)&list.fcc);
850 if (list.fccListType != listtypeAVIHEADER)
852 ERR("Header list expected. Got: %.04s\n", (LPSTR)&list.fccListType);
856 pBuffer = HeapAlloc(GetProcessHeap(), 0, list.cb - sizeof(RIFFLIST) + sizeof(RIFFCHUNK));
857 hr = IAsyncReader_SyncRead(This->pReader, pos + sizeof(list), list.cb - sizeof(RIFFLIST) + sizeof(RIFFCHUNK), pBuffer);
859 pAviSplit->AviHeader.cb = 0;
861 for (pCurrentChunk = (RIFFCHUNK *)pBuffer; (BYTE *)pCurrentChunk + sizeof(*pCurrentChunk) < pBuffer + list.cb; pCurrentChunk = (RIFFCHUNK *)(((BYTE *)pCurrentChunk) + sizeof(*pCurrentChunk) + pCurrentChunk->cb))
865 switch (pCurrentChunk->fcc)
867 case ckidMAINAVIHEADER:
868 /* AVIMAINHEADER includes the structure that is pCurrentChunk at the moment */
869 memcpy(&pAviSplit->AviHeader, pCurrentChunk, sizeof(pAviSplit->AviHeader));
872 pList = (RIFFLIST *)pCurrentChunk;
873 switch (pList->fccListType)
876 hr = AVISplitter_ProcessStreamList(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST));
879 hr = AVISplitter_ProcessODML(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST));
887 FIXME("unrecognised header list type: %.04s\n", (LPSTR)&pCurrentChunk->fcc);
890 HeapFree(GetProcessHeap(), 0, pBuffer);
892 if (pAviSplit->AviHeader.cb != sizeof(pAviSplit->AviHeader) - sizeof(RIFFCHUNK))
894 ERR("Avi Header wrong size!\n");
898 pos += sizeof(RIFFCHUNK) + list.cb;
899 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
901 while (list.fcc == ckidAVIPADDING || (list.fcc == FOURCC_LIST && list.fccListType == ckidINFO))
903 pos += sizeof(RIFFCHUNK) + list.cb;
905 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
908 if (list.fcc != FOURCC_LIST)
910 ERR("Expected LIST, but got %.04s\n", (LPSTR)&list.fcc);
913 if (list.fccListType != listtypeAVIMOVIE)
915 ERR("Expected AVI movie list, but got %.04s\n", (LPSTR)&list.fccListType);
919 IAsyncReader_Length(This->pReader, &total, &avail);
921 /* FIXME: AVIX files are extended beyond the FOURCC chunk "AVI ", and thus won't be played here,
922 * once I get one of the files I'll try to fix it */
925 This->rtStart = pAviSplit->CurrentChunkOffset = MEDIATIME_FROM_BYTES(pos + sizeof(RIFFLIST));
926 pos += list.cb + sizeof(RIFFCHUNK);
928 pAviSplit->EndOfFile = This->rtStop = MEDIATIME_FROM_BYTES(pos);
931 ERR("File smaller (%x%08x) then EndOfFile (%x%08x)\n", (DWORD)(total >> 32), (DWORD)total, (DWORD)(pAviSplit->EndOfFile >> 32), (DWORD)pAviSplit->EndOfFile);
935 hr = IAsyncReader_SyncRead(This->pReader, BYTES_FROM_MEDIATIME(pAviSplit->CurrentChunkOffset), sizeof(pAviSplit->CurrentChunk), (BYTE *)&pAviSplit->CurrentChunk);
938 /* Now peek into the idx1 index, if available */
939 if (hr == S_OK && (total - pos) > sizeof(RIFFCHUNK))
941 memset(&list, 0, sizeof(list));
943 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
944 if (list.fcc == ckidAVIOLDINDEX)
946 pAviSplit->oldindex = CoTaskMemRealloc(pAviSplit->oldindex, list.cb + sizeof(RIFFCHUNK));
947 if (pAviSplit->oldindex)
949 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(RIFFCHUNK) + list.cb, (BYTE *)pAviSplit->oldindex);
952 hr = AVISplitter_ProcessOldIndex(pAviSplit);
956 CoTaskMemFree(pAviSplit->oldindex);
957 pAviSplit->oldindex = NULL;
965 for (x = 0; x < pAviSplit->Parser.cStreams; ++x)
966 if (pAviSplit->streams[x].entries)
971 CoTaskMemFree(pAviSplit->oldindex);
972 pAviSplit->oldindex = NULL;
973 if (indexes < pAviSplit->Parser.cStreams)
975 /* This error could possible be survived by switching to old type index,
976 * but I would rather find out why it doesn't find everything here
978 ERR("%d indexes expected, but only have %d\n", indexes, pAviSplit->Parser.cStreams);
982 else if (!indexes && pAviSplit->oldindex)
983 indexes = pAviSplit->Parser.cStreams;
985 if (!indexes && pAviSplit->AviHeader.dwFlags & AVIF_MUSTUSEINDEX)
987 FIXME("No usable index was found!\n");
991 /* Now, set up the streams */
993 hr = AVISplitter_InitializeStreams(pAviSplit);
997 AVISplitter_Disconnect(pAviSplit);
1001 TRACE("AVI File ok\n");
1006 static HRESULT AVISplitter_Cleanup(LPVOID iface)
1008 AVISplitterImpl *This = (AVISplitterImpl*)iface;
1010 TRACE("(%p)->()\n", This);
1012 if (This->pCurrentSample)
1013 IMediaSample_Release(This->pCurrentSample);
1014 This->pCurrentSample = NULL;
1019 static HRESULT AVISplitter_Disconnect(LPVOID iface)
1021 AVISplitterImpl *This = iface;
1024 /* TODO: Remove other memory that's allocated during connect */
1025 CoTaskMemFree(This->oldindex);
1026 This->oldindex = NULL;
1028 for (x = 0; x < This->Parser.cStreams; ++x)
1032 StreamData *stream = &This->streams[x];
1034 for (i = 0; i < stream->entries; ++i)
1035 CoTaskMemFree(stream->stdindex[i]);
1037 CoTaskMemFree(stream->stdindex);
1039 CoTaskMemFree(This->streams);
1040 This->streams = NULL;
1045 static const IBaseFilterVtbl AVISplitter_Vtbl =
1047 Parser_QueryInterface,
1055 Parser_SetSyncSource,
1056 Parser_GetSyncSource,
1059 Parser_QueryFilterInfo,
1060 Parser_JoinFilterGraph,
1061 Parser_QueryVendorInfo
1064 HRESULT AVISplitter_create(IUnknown * pUnkOuter, LPVOID * ppv)
1067 AVISplitterImpl * This;
1069 TRACE("(%p, %p)\n", pUnkOuter, ppv);
1074 return CLASS_E_NOAGGREGATION;
1076 /* Note: This memory is managed by the transform filter once created */
1077 This = CoTaskMemAlloc(sizeof(AVISplitterImpl));
1079 This->pCurrentSample = NULL;
1080 This->streams = NULL;
1081 This->oldindex = NULL;
1083 hr = Parser_Create(&(This->Parser), &AVISplitter_Vtbl, &CLSID_AviSplitter, AVISplitter_Sample, AVISplitter_QueryAccept, AVISplitter_InputPin_PreConnect, AVISplitter_Cleanup, AVISplitter_Disconnect, NULL, NULL, NULL, NULL, NULL);
1088 *ppv = (LPVOID)This;