push 7f8c39dca3a5819e8ef115eebf7abed537de3a22
[wine/hacks.git] / dlls / quartz / avisplit.c
blob00673846297db0d40a8798cd1acee4f19aa87243
1 /*
2 * AVI Splitter Filter
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
22 /* FIXME:
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"
30 #include "pin.h"
32 #include "uuids.h"
33 #include "vfw.h"
34 #include "aviriff.h"
35 #include "vfwmsgs.h"
36 #include "amvideo.h"
38 #include "wine/unicode.h"
39 #include "wine/debug.h"
41 #include <math.h>
42 #include <assert.h>
44 #include "parser.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
56 DWORD dwSampleSize;
57 FLOAT fSamplesPerSec;
58 DWORD dwLength;
60 AVISTREAMHEADER streamheader;
61 DWORD entries;
62 AVISTDINDEX **stdindex;
63 DWORD frames;
64 } StreamData;
66 typedef struct AVISplitterImpl
68 ParserImpl Parser;
69 IMediaSample * pCurrentSample;
70 RIFFCHUNK CurrentChunk;
71 LONGLONG CurrentChunkOffset; /* in media time */
72 LONGLONG EndOfFile;
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;
78 DWORD offset;
80 StreamData *streams;
81 } AVISplitterImpl;
83 static HRESULT AVISplitter_NextChunk(LONGLONG * pllCurrentChunkOffset, RIFFCHUNK * pCurrentChunk, const REFERENCE_TIME * tStart, const REFERENCE_TIME * tStop, const BYTE * pbSrcStream, int inner)
85 if (inner)
86 *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFLIST));
87 else
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 */
97 else
98 memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), sizeof(RIFFCHUNK));
100 return S_OK;
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;
109 HRESULT hr;
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");
135 hr = S_OK;
136 goto skip;
139 memcpy(&This->CurrentChunk, pbSrcStream + offset, sizeof(RIFFCHUNK));
142 assert(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) < tStop);
144 while (bMoreData)
146 BYTE * pbDstStream;
147 long cbDstStream;
148 long chunk_remaining_bytes = 0;
149 long offset_src;
150 WORD streamId;
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);
157 else
158 offset_src = 0;
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");
164 case ckidAVIPADDING:
165 /* silently ignore */
166 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
167 bMoreData = FALSE;
168 continue;
169 case FOURCC_LIST:
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))
177 bMoreData = FALSE;
178 continue;
180 This->CurrentChunk = *(RIFFCHUNK*) (pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart));
181 offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
182 break;
184 else if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
185 bMoreData = FALSE;
186 continue;
187 default:
188 break;
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:
193 bSyncPoint = FALSE;
194 /* fall-through */
195 case cktypeDIBbits:
196 /* FIXME: check that pin is of type video */
197 break;
198 case cktypeWAVEbytes:
199 /* FIXME: check that pin is of type audio */
200 break;
201 case cktypePALchange:
202 FIXME("handle palette change\n");
203 break;
204 default:
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))
207 bMoreData = FALSE;
208 continue;
210 #endif
213 if (fcc[0] == 'i' && fcc[1] == 'x')
215 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
216 bMoreData = FALSE;
217 continue;
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);
225 hr = E_FAIL;
226 break;
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);
237 if (SUCCEEDED(hr))
239 hr = IMediaSample_SetActualDataLength(This->pCurrentSample, 0);
240 assert(hr == S_OK);
242 else
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))
247 bMoreData = FALSE;
248 continue;
252 hr = IMediaSample_GetPointer(This->pCurrentSample, &pbDstStream);
254 if (SUCCEEDED(hr))
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)
269 if (SUCCEEDED(hr))
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));
273 assert(hr == S_OK);
276 if (SUCCEEDED(hr))
278 REFERENCE_TIME tAviStart, tAviStop;
279 StreamData *stream = This->streams + streamId;
281 /* FIXME: hack */
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));
291 else
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));
295 else
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))
311 bMoreData = FALSE;
313 else
315 if (SUCCEEDED(hr))
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));
320 bMoreData = FALSE;
324 skip:
325 if (tStop >= This->EndOfFile)
327 int i;
329 TRACE("End of file reached\n");
331 for (i = 0; i < This->Parser.cStreams; i++)
333 IPin* ppin;
334 HRESULT hr;
336 TRACE("Send End Of Stream to output pin %d\n", i);
338 hr = IPin_ConnectedTo(This->Parser.ppPins[i+1], &ppin);
339 if (SUCCEEDED(hr))
341 hr = IPin_EndOfStream(ppin);
342 IPin_Release(ppin);
344 if (FAILED(hr))
346 ERR("%x\n", hr);
347 break;
351 /* Force the pullpin thread to stop */
352 hr = S_FALSE;
355 return hr;
358 static HRESULT AVISplitter_QueryAccept(LPVOID iface, const AM_MEDIA_TYPE * pmt)
360 if (IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream) && IsEqualIID(&pmt->subtype, &MEDIASUBTYPE_Avi))
361 return S_OK;
362 return S_FALSE;
365 static HRESULT AVISplitter_ProcessIndex(AVISplitterImpl *This, AVISTDINDEX **index, LONGLONG qwOffset, DWORD cb)
367 AVISTDINDEX *pIndex;
368 int x;
369 long rest;
371 *index = NULL;
372 if (cb < sizeof(AVISTDINDEX))
374 FIXME("size %u too small\n", cb);
375 return E_INVALIDARG;
378 pIndex = CoTaskMemAlloc(cb);
379 if (!pIndex)
380 return E_OUTOFMEMORY;
382 IAsyncReader_SyncRead(((PullPin *)This->Parser.ppPins[0])->pReader, qwOffset, cb, (BYTE *)pIndex);
383 pIndex = CoTaskMemRealloc(pIndex, pIndex->cb);
384 if (!pIndex)
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");
404 return E_INVALIDARG;
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");
416 *index = pIndex;
417 return S_OK;
420 static HRESULT AVISplitter_ProcessOldIndex(AVISplitterImpl *This)
422 ULONGLONG mov_pos = BYTES_FROM_MEDIATIME(This->CurrentChunkOffset) - sizeof(DWORD);
423 AVIOLDINDEX *pAviOldIndex = This->oldindex;
424 int relative = -1;
425 int x;
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 */
436 if (relative == -1)
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')
444 relative = FALSE;
446 if (relative)
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));
461 relative = -1;
463 else
464 TRACE("Scanned dwChunkId: %s\n", debugstr_an((char *)&temp2, 4));
466 else if (!relative)
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);
472 else break;
475 if (relative == -1)
477 FIXME("Dropping index: no idea whether it is relative or absolute\n");
478 CoTaskMemFree(This->oldindex);
479 This->oldindex = NULL;
481 else if (!relative)
482 This->offset = 0;
483 else
484 This->offset = (DWORD)mov_pos;
486 return S_OK;
489 static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE * pData, DWORD cb)
491 PIN_INFO piOutput;
492 const RIFFCHUNK * pChunk;
493 HRESULT hr;
494 AM_MEDIA_TYPE amt;
495 float fSamplesPerSec = 0.0f;
496 DWORD dwSampleSize = 0;
497 DWORD dwLength = 0;
498 ALLOCATOR_PROPERTIES props;
499 static const WCHAR wszStreamTemplate[] = {'S','t','r','e','a','m',' ','%','0','2','d',0};
500 StreamData *stream;
502 AVISTDINDEX **stdindex = NULL;
503 DWORD nstdindex = 0;
505 props.cbAlign = 1;
506 props.cbPrefix = 0;
507 props.cbBuffer = 0x20000;
508 props.cBuffers = 2;
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)
522 switch (pChunk->fcc)
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);
532 amt.pbFormat = NULL;
533 amt.cbFormat = 0;
535 switch (pStrHdr->fccType)
537 case streamtypeVIDEO:
538 amt.formattype = FORMAT_VideoInfo;
539 break;
540 case streamtypeAUDIO:
541 amt.formattype = FORMAT_WaveFormatEx;
542 break;
543 default:
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)
558 amt.lSampleSize = 1;
559 dwSampleSize = 1;
562 amt.bTemporalCompression = IsEqualGUID(&amt.majortype, &MEDIATYPE_Video); /* FIXME? */
563 dwSampleSize = pStrHdr->dwSampleSize;
564 dwLength = pStrHdr->dwLength;
565 if (!dwLength)
566 dwLength = This->AviHeader.dwTotalFrames;
568 if (pStrHdr->dwSuggestedBufferSize)
569 props.cbBuffer = pStrHdr->dwSuggestedBufferSize;
571 break;
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");
584 return E_FAIL;
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;
595 else
597 amt.cbFormat = pChunk->cb;
598 amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
599 CopyMemory(amt.pbFormat, (const BYTE *)(pChunk + 1), amt.cbFormat);
601 break;
602 case ckidSTREAMNAME:
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]));
606 break;
607 case ckidSTREAMHANDLERDATA:
608 FIXME("process stream handler data\n");
609 break;
610 case ckidAVIPADDING:
611 TRACE("JUNK chunk ignored\n");
612 break;
613 case ckidAVISUPERINDEX:
615 const AVISUPERINDEX *pIndex = (const AVISUPERINDEX *)pChunk;
616 int x;
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);
622 break;
625 if (nstdindex > 0)
627 ERR("Stream %d got more than 1 superindex?\n", This->Parser.cStreams);
628 break;
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");
649 break;
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);
658 ++nstdindex;
659 stdindex = CoTaskMemRealloc(stdindex, sizeof(*stdindex) * nstdindex);
660 AVISplitter_ProcessIndex(This, &stdindex[nstdindex-1], pIndex->aIndex[x].qwOffset, pIndex->aIndex[x].dwSize);
662 break;
664 default:
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);
689 return hr;
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)
701 switch (pChunk->fcc)
703 case ckidAVIEXTHEADER:
705 int x;
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);
712 break;
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;
719 break;
721 default:
722 FIXME("unknown chunk type \"%.04s\" ignored\n", (LPCSTR)&pChunk->fcc);
726 return S_OK;
729 static HRESULT AVISplitter_InitializeStreams(AVISplitterImpl *This)
731 int x;
733 if (This->oldindex)
735 DWORD nMax, n;
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));
751 continue;
754 if (This->streams[streamId].streamheader.dwSampleSize)
755 This->streams[streamId].frames += This->oldindex->aIndex[n].dwSize / This->streams[streamId].streamheader.dwSampleSize;
756 else
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;
781 /* WOEI! */
782 double fps;
783 int y;
784 DWORD64 frames = 0;
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));
810 return S_OK;
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;
819 HRESULT hr;
820 RIFFLIST list;
821 LONGLONG pos = 0; /* in bytes */
822 BYTE * pBuffer;
823 RIFFCHUNK * pCurrentChunk;
824 LONGLONG total, avail;
825 int x;
826 DWORD indexes;
828 AVISplitterImpl * pAviSplit = (AVISplitterImpl *)This->pin.pinInfo.pFilter;
830 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
831 pos += sizeof(list);
833 if (list.fcc != FOURCC_RIFF)
835 ERR("Input stream not a RIFF file\n");
836 return E_FAIL;
838 if (list.fccListType != formtypeAVI)
840 ERR("Input stream not an AVI RIFF file\n");
841 return E_FAIL;
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);
848 return E_FAIL;
850 if (list.fccListType != listtypeAVIHEADER)
852 ERR("Header list expected. Got: %.04s\n", (LPSTR)&list.fccListType);
853 return E_FAIL;
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))
863 RIFFLIST * pList;
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));
870 break;
871 case FOURCC_LIST:
872 pList = (RIFFLIST *)pCurrentChunk;
873 switch (pList->fccListType)
875 case ckidSTREAMLIST:
876 hr = AVISplitter_ProcessStreamList(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST));
877 break;
878 case ckidODML:
879 hr = AVISplitter_ProcessODML(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST));
880 break;
882 break;
883 case ckidAVIPADDING:
884 /* ignore */
885 break;
886 default:
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");
895 return E_FAIL;
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);
911 return E_FAIL;
913 if (list.fccListType != listtypeAVIMOVIE)
915 ERR("Expected AVI movie list, but got %.04s\n", (LPSTR)&list.fccListType);
916 return E_FAIL;
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 */
923 if (hr == S_OK)
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);
929 if (pos > total)
931 ERR("File smaller (%x%08x) then EndOfFile (%x%08x)\n", (DWORD)(total >> 32), (DWORD)total, (DWORD)(pAviSplit->EndOfFile >> 32), (DWORD)pAviSplit->EndOfFile);
932 return E_FAIL;
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);
950 if (hr == S_OK)
952 hr = AVISplitter_ProcessOldIndex(pAviSplit);
954 else
956 CoTaskMemFree(pAviSplit->oldindex);
957 pAviSplit->oldindex = NULL;
958 hr = S_OK;
964 indexes = 0;
965 for (x = 0; x < pAviSplit->Parser.cStreams; ++x)
966 if (pAviSplit->streams[x].entries)
967 ++indexes;
969 if (indexes)
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);
979 indexes = 0;
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");
988 hr = E_FAIL;
991 /* Now, set up the streams */
992 if (hr == S_OK)
993 hr = AVISplitter_InitializeStreams(pAviSplit);
995 if (hr != S_OK)
997 AVISplitter_Disconnect(pAviSplit);
998 return E_FAIL;
1001 TRACE("AVI File ok\n");
1003 return hr;
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;
1016 return S_OK;
1019 static HRESULT AVISplitter_Disconnect(LPVOID iface)
1021 AVISplitterImpl *This = iface;
1022 int x;
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)
1030 int i;
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;
1042 return S_OK;
1045 static const IBaseFilterVtbl AVISplitter_Vtbl =
1047 Parser_QueryInterface,
1048 Parser_AddRef,
1049 Parser_Release,
1050 Parser_GetClassID,
1051 Parser_Stop,
1052 Parser_Pause,
1053 Parser_Run,
1054 Parser_GetState,
1055 Parser_SetSyncSource,
1056 Parser_GetSyncSource,
1057 Parser_EnumPins,
1058 Parser_FindPin,
1059 Parser_QueryFilterInfo,
1060 Parser_JoinFilterGraph,
1061 Parser_QueryVendorInfo
1064 HRESULT AVISplitter_create(IUnknown * pUnkOuter, LPVOID * ppv)
1066 HRESULT hr;
1067 AVISplitterImpl * This;
1069 TRACE("(%p, %p)\n", pUnkOuter, ppv);
1071 *ppv = NULL;
1073 if (pUnkOuter)
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);
1085 if (FAILED(hr))
1086 return hr;
1088 *ppv = (LPVOID)This;
1090 return hr;