mshtml: Added IHTMLElement::put_className implementation.
[wine] / dlls / quartz / avisplit.c
1 /*
2  * AVI Splitter Filter
3  *
4  * Copyright 2003 Robert Shearman
5  * Copyright 2004-2005 Christian Costa
6  * Copyright 2008 Maarten Lankhorst
7  *
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.
12  *
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.
17  *
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
21  */
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
26  */
27
28 #include "quartz_private.h"
29 #include "control_private.h"
30 #include "pin.h"
31
32 #include "uuids.h"
33 #include "vfw.h"
34 #include "aviriff.h"
35 #include "vfwmsgs.h"
36 #include "amvideo.h"
37
38 #include "wine/unicode.h"
39 #include "wine/debug.h"
40
41 #include <math.h>
42 #include <assert.h>
43
44 #include "parser.h"
45
46 #define TWOCCFromFOURCC(fcc) HIWORD(fcc)
47
48 /* four character codes used in AVI files */
49 #define ckidINFO       mmioFOURCC('I','N','F','O')
50 #define ckidREC        mmioFOURCC('R','E','C',' ')
51
52 WINE_DEFAULT_DEBUG_CHANNEL(quartz);
53
54 typedef struct StreamData
55 {
56     DWORD dwSampleSize;
57     FLOAT fSamplesPerSec;
58     DWORD dwLength;
59
60     AVISTREAMHEADER streamheader;
61     DWORD entries;
62     AVISTDINDEX **stdindex;
63     DWORD frames;
64 } StreamData;
65
66 typedef struct AVISplitterImpl
67 {
68     ParserImpl Parser;
69     IMediaSample * pCurrentSample;
70     RIFFCHUNK CurrentChunk;
71     LONGLONG CurrentChunkOffset; /* in media time */
72     LONGLONG EndOfFile;
73     AVIMAINHEADER AviHeader;
74     AVIEXTHEADER ExtHeader;
75
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;
79
80     StreamData *streams;
81 } AVISplitterImpl;
82
83 static HRESULT AVISplitter_NextChunk(LONGLONG * pllCurrentChunkOffset, RIFFCHUNK * pCurrentChunk, const REFERENCE_TIME * tStart, const REFERENCE_TIME * tStop, const BYTE * pbSrcStream, int inner)
84 {
85     if (inner)
86         *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFLIST));
87     else
88         *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK) + RIFFROUND(pCurrentChunk->cb));
89     
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)
93     {
94         memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), (DWORD)BYTES_FROM_MEDIATIME(*tStop - *pllCurrentChunkOffset));
95         return S_FALSE; /* no more data */
96     }
97     else
98         memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), sizeof(RIFFCHUNK));
99
100     return S_OK;
101 }
102
103 static HRESULT AVISplitter_Sample(LPVOID iface, IMediaSample * pSample, DWORD_PTR cookie)
104 {
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;
111
112     hr = IMediaSample_GetPointer(pSample, &pbSrcStream);
113
114     hr = IMediaSample_GetTime(pSample, &tStart, &tStop);
115
116     cbSrcStream = IMediaSample_GetActualDataLength(pSample);
117
118     /* trace removed for performance reasons */
119     /* TRACE("(%p)\n", pSample); */
120
121     assert(BYTES_FROM_MEDIATIME(tStop - tStart) == cbSrcStream);
122
123     if (This->CurrentChunkOffset <= tStart && This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) > tStart)
124     {
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);
128     }
129     else if (This->CurrentChunkOffset > tStart)
130     {
131         DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart);
132         if (offset >= (DWORD)cbSrcStream)
133         {
134             FIXME("large offset\n");
135             hr = S_OK;
136             goto skip;
137         }
138
139         memcpy(&This->CurrentChunk, pbSrcStream + offset, sizeof(RIFFCHUNK));
140     }
141
142     assert(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) < tStop);
143
144     while (bMoreData)
145     {
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;
154
155         if (This->CurrentChunkOffset >= tStart)
156             offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
157         else
158             offset_src = 0;
159
160         switch (This->CurrentChunk.fcc)
161         {
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)
172             {
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))
176                 {
177                     bMoreData = FALSE;
178                     continue;
179                 }
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;
183             }
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))
191             {
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;
209             }
210 #endif
211         }
212
213         if (fcc[0] == 'i' && fcc[1] == 'x')
214         {
215             if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
216                 bMoreData = FALSE;
217             continue;
218         }
219
220         streamId = StreamFromFOURCC(This->CurrentChunk.fcc);
221
222         if (streamId > This->Parser.cStreams)
223         {
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;
227         }
228
229         pOutputPin = (Parser_OutputPin *)This->Parser.ppPins[streamId + 1];
230
231         if (!This->pCurrentSample)
232         {
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);
236
237             if (SUCCEEDED(hr))
238             {
239                 hr = IMediaSample_SetActualDataLength(This->pCurrentSample, 0);
240                 assert(hr == S_OK);
241             }
242             else
243             {
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;
249             }
250         }
251
252         hr = IMediaSample_GetPointer(This->pCurrentSample, &pbDstStream);
253
254         if (SUCCEEDED(hr))
255         {
256             cbDstStream = IMediaSample_GetSize(This->pCurrentSample);
257
258             chunk_remaining_bytes = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(This->CurrentChunk.cb + sizeof(RIFFCHUNK)) - tStart) - offset_src;
259         
260             assert(chunk_remaining_bytes >= 0);
261             assert(chunk_remaining_bytes <= cbDstStream - IMediaSample_GetActualDataLength(This->pCurrentSample));
262
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); */
265         }
266
267         if (chunk_remaining_bytes <= cbSrcStream - offset_src)
268         {
269             if (SUCCEEDED(hr))
270             {
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);
274             }
275
276             if (SUCCEEDED(hr))
277             {
278                 REFERENCE_TIME tAviStart, tAviStop;
279                 StreamData *stream = This->streams + streamId;
280
281                 /* FIXME: hack */
282                 if (pOutputPin->dwSamplesProcessed == 0)
283                     IMediaSample_SetDiscontinuity(This->pCurrentSample, TRUE);
284
285                 IMediaSample_SetSyncPoint(This->pCurrentSample, bSyncPoint);
286
287                 pOutputPin->dwSamplesProcessed++;
288
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);
297
298                 IMediaSample_SetTime(This->pCurrentSample, &tAviStart, &tAviStop);
299
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);
303             }
304
305             if (This->pCurrentSample)
306                 IMediaSample_Release(This->pCurrentSample);
307             
308             This->pCurrentSample = NULL;
309
310             if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
311                 bMoreData = FALSE;
312         }
313         else
314         {
315             if (SUCCEEDED(hr))
316             {
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));
319             }
320             bMoreData = FALSE;
321         }
322     }
323
324 skip:
325     if (tStop >= This->EndOfFile)
326     {
327         int i;
328
329         TRACE("End of file reached\n");
330
331         for (i = 0; i < This->Parser.cStreams; i++)
332         {
333             IPin* ppin;
334             HRESULT hr;
335
336             TRACE("Send End Of Stream to output pin %d\n", i);
337
338             hr = IPin_ConnectedTo(This->Parser.ppPins[i+1], &ppin);
339             if (SUCCEEDED(hr))
340             {
341                 hr = IPin_EndOfStream(ppin);
342                 IPin_Release(ppin);
343             }
344             if (FAILED(hr))
345             {
346                 ERR("%x\n", hr);
347                 break;
348             }
349         }
350
351         /* Force the pullpin thread to stop */
352         hr = S_FALSE;
353     }
354
355     return hr;
356 }
357
358 static HRESULT AVISplitter_QueryAccept(LPVOID iface, const AM_MEDIA_TYPE * pmt)
359 {
360     if (IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream) && IsEqualIID(&pmt->subtype, &MEDIASUBTYPE_Avi))
361         return S_OK;
362     return S_FALSE;
363 }
364
365 static HRESULT AVISplitter_ProcessIndex(AVISplitterImpl *This, AVISTDINDEX **index, LONGLONG qwOffset, DWORD cb)
366 {
367     AVISTDINDEX *pIndex;
368     int x;
369     long rest;
370
371     *index = NULL;
372     if (cb < sizeof(AVISTDINDEX))
373     {
374         FIXME("size %u too small\n", cb);
375         return E_INVALIDARG;
376     }
377
378     pIndex = CoTaskMemAlloc(cb);
379     if (!pIndex)
380         return E_OUTOFMEMORY;
381
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;
386
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;
389
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);
397
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))
402     {
403         FIXME("Invalid index chunk encountered\n");
404         return E_INVALIDARG;
405     }
406
407     for (x = 0; x < pIndex->nEntriesInUse; ++x)
408     {
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");
414     }
415
416     *index = pIndex;
417     return S_OK;
418 }
419
420 static HRESULT AVISplitter_ProcessOldIndex(AVISplitterImpl *This)
421 {
422     ULONGLONG mov_pos = BYTES_FROM_MEDIATIME(This->CurrentChunkOffset) - sizeof(DWORD);
423     AVIOLDINDEX *pAviOldIndex = This->oldindex;
424     int relative = -1;
425     int x;
426
427     for (x = 0; x < pAviOldIndex->cb / sizeof(pAviOldIndex->aIndex[0]); ++x)
428     {
429         DWORD temp, temp2 = 0, offset, chunkid;
430         PullPin *pin = This->Parser.pInputPin;
431
432         offset = pAviOldIndex->aIndex[x].dwOffset;
433         chunkid = pAviOldIndex->aIndex[x].dwChunkId;
434
435         /* Only scan once, or else this will take too long */
436         if (relative == -1)
437         {
438             IAsyncReader_SyncRead(pin->pReader, offset, sizeof(DWORD), (BYTE *)&temp);
439             relative = (chunkid != temp);
440
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;
445
446             if (relative)
447             {
448                 if (offset + mov_pos < BYTES_FROM_MEDIATIME(This->EndOfFile))
449                     IAsyncReader_SyncRead(pin->pReader, offset + mov_pos, sizeof(DWORD), (BYTE *)&temp2);
450
451                 if (chunkid == mmioFOURCC('7','F','x','x')
452                     && ((char *)&temp2)[0] == 'i' && ((char *)&temp2)[1] == 'x')
453                 {
454                     /* Do nothing, all is great */
455                 }
456                 else if (temp2 != chunkid)
457                 {
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;
462                 }
463                 else
464                     TRACE("Scanned dwChunkId: %s\n", debugstr_an((char *)&temp2, 4));
465             }
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);
471         }
472         else break;
473     }
474
475     if (relative == -1)
476     {
477         FIXME("Dropping index: no idea whether it is relative or absolute\n");
478         CoTaskMemFree(This->oldindex);
479         This->oldindex = NULL;
480     }
481     else if (!relative)
482         This->offset = 0;
483     else
484         This->offset = (DWORD)mov_pos;
485
486     return S_OK;
487 }
488
489 static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE * pData, DWORD cb)
490 {
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;
501
502     AVISTDINDEX **stdindex = NULL;
503     DWORD nstdindex = 0;
504
505     props.cbAlign = 1;
506     props.cbPrefix = 0;
507     props.cbBuffer = 0x20000;
508     props.cBuffers = 2;
509
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;
516
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)     
520         )
521     {
522         switch (pChunk->fcc)
523         {
524         case ckidSTREAMHEADER:
525             {
526                 const AVISTREAMHEADER * pStrHdr = (const AVISTREAMHEADER *)pChunk;
527                 TRACE("processing stream header\n");
528                 stream->streamheader = *pStrHdr;
529
530                 fSamplesPerSec = (float)pStrHdr->dwRate / (float)pStrHdr->dwScale;
531                 CoTaskMemFree(amt.pbFormat);
532                 amt.pbFormat = NULL;
533                 amt.cbFormat = 0;
534
535                 switch (pStrHdr->fccType)
536                 {
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;
546                 }
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);
554
555                 /* FIXME: Is this right? */
556                 if (!amt.lSampleSize)
557                 {
558                     amt.lSampleSize = 1;
559                     dwSampleSize = 1;
560                 }
561
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;
567
568                 if (pStrHdr->dwSuggestedBufferSize)
569                     props.cbBuffer = pStrHdr->dwSuggestedBufferSize;
570
571                 break;
572             }
573         case ckidSTREAMFORMAT:
574             TRACE("processing stream format data\n");
575             if (IsEqualIID(&amt.formattype, &FORMAT_VideoInfo))
576             {
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))
582                 {
583                     ERR("Not enough bytes for BITMAPINFOHEADER\n");
584                     return E_FAIL;
585                 }
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;
594             }
595             else
596             {
597                 amt.cbFormat = pChunk->cb;
598                 amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
599                 CopyMemory(amt.pbFormat, (const BYTE *)(pChunk + 1), amt.cbFormat);
600             }
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:
614         {
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;
618
619             if (pIndex->cb < sizeof(AVISUPERINDEX) - sizeof(RIFFCHUNK))
620             {
621                 FIXME("size %u\n", pIndex->cb);
622                 break;
623             }
624
625             if (nstdindex > 0)
626             {
627                 ERR("Stream %d got more than 1 superindex?\n", This->Parser.cStreams);
628                 break;
629             }
630
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]);
642
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))
647             {
648                 FIXME("Invalid index chunk encountered\n");
649                 break;
650             }
651
652             for (x = 0; x < pIndex->nEntriesInUse; ++x)
653             {
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);
657
658                 ++nstdindex;
659                 stdindex = CoTaskMemRealloc(stdindex, sizeof(*stdindex) * nstdindex);
660                 AVISplitter_ProcessIndex(This, &stdindex[nstdindex-1], pIndex->aIndex[x].qwOffset, pIndex->aIndex[x].dwSize);
661             }
662             break;
663         }
664         default:
665             FIXME("unknown chunk type \"%.04s\" ignored\n", (LPCSTR)&pChunk->fcc);
666         }
667     }
668
669     if (IsEqualGUID(&amt.formattype, &FORMAT_WaveFormatEx))
670     {
671         amt.subtype = MEDIATYPE_Video;
672         amt.subtype.Data1 = ((WAVEFORMATEX *)amt.pbFormat)->wFormatTag;
673     }
674
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);
679
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;
685
686     hr = Parser_AddPin(&(This->Parser), &piOutput, &props, &amt);
687     CoTaskMemFree(amt.pbFormat);
688
689     return hr;
690 }
691
692 static HRESULT AVISplitter_ProcessODML(AVISplitterImpl * This, const BYTE * pData, DWORD cb)
693 {
694     const RIFFCHUNK * pChunk;
695
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)
699         )
700     {
701         switch (pChunk->fcc)
702         {
703         case ckidAVIEXTHEADER:
704             {
705                 int x;
706                 const AVIEXTHEADER * pExtHdr = (const AVIEXTHEADER *)pChunk;
707
708                 TRACE("processing extension header\n");
709                 if (pExtHdr->cb != sizeof(AVIEXTHEADER) - sizeof(RIFFCHUNK))
710                 {
711                     FIXME("Size: %u\n", pExtHdr->cb);
712                     break;
713                 }
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;
720             }
721         default:
722             FIXME("unknown chunk type \"%.04s\" ignored\n", (LPCSTR)&pChunk->fcc);
723         }
724     }
725
726     return S_OK;
727 }
728
729 static HRESULT AVISplitter_InitializeStreams(AVISplitterImpl *This)
730 {
731     int x;
732
733     if (This->oldindex)
734     {
735         DWORD nMax, n;
736
737         for (x = 0; x < This->Parser.cStreams; ++x)
738         {
739             This->streams[x].frames = 0;
740         }
741
742         nMax = This->oldindex->cb / sizeof(This->oldindex->aIndex[0]);
743
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)
746         {
747             DWORD streamId = StreamFromFOURCC(This->oldindex->aIndex[n].dwChunkId);
748             if (streamId >= This->Parser.cStreams)
749             {
750                 FIXME("Stream id %s ignored\n", debugstr_an((char*)&This->oldindex->aIndex[n].dwChunkId, 4));
751                 continue;
752             }
753
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;
758         }
759
760         for (x = 0; x < This->Parser.cStreams; ++x)
761         {
762             if ((DWORD)This->streams[x].frames != This->streams[x].streamheader.dwLength)
763             {
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);
765             }
766         }
767
768     }
769     else if (!This->streams[0].entries)
770     {
771         for (x = 0; x < This->Parser.cStreams; ++x)
772         {
773             This->streams[x].frames = This->streams[x].streamheader.dwLength;
774         }
775     }
776
777     /* Not much here yet */
778     for (x = 0; x < This->Parser.cStreams; ++x)
779     {
780         StreamData *stream = This->streams + x;
781         /* WOEI! */
782         double fps;
783         int y;
784         DWORD64 frames = 0;
785
786         fps = (double)stream->streamheader.dwRate / (float)stream->streamheader.dwScale;
787         if (stream->stdindex)
788         {
789             for (y = 0; y < stream->entries; ++y)
790             {
791                 frames += stream->stdindex[y]->nEntriesInUse;
792             }
793         }
794         else frames = stream->frames;
795
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;
802
803         frames /= stream->streamheader.dwRate;
804
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));
808     }
809
810     return S_OK;
811 }
812
813 static HRESULT AVISplitter_Disconnect(LPVOID iface);
814
815 /* FIXME: fix leaks on failure here */
816 static HRESULT AVISplitter_InputPin_PreConnect(IPin * iface, IPin * pConnectPin, ALLOCATOR_PROPERTIES *props)
817 {
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;
827
828     AVISplitterImpl * pAviSplit = (AVISplitterImpl *)This->pin.pinInfo.pFilter;
829
830     hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
831     pos += sizeof(list);
832
833     if (list.fcc != FOURCC_RIFF)
834     {
835         ERR("Input stream not a RIFF file\n");
836         return E_FAIL;
837     }
838     if (list.fccListType != formtypeAVI)
839     {
840         ERR("Input stream not an AVI RIFF file\n");
841         return E_FAIL;
842     }
843
844     hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
845     if (list.fcc != FOURCC_LIST)
846     {
847         ERR("Expected LIST chunk, but got %.04s\n", (LPSTR)&list.fcc);
848         return E_FAIL;
849     }
850     if (list.fccListType != listtypeAVIHEADER)
851     {
852         ERR("Header list expected. Got: %.04s\n", (LPSTR)&list.fccListType);
853         return E_FAIL;
854     }
855
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);
858
859     pAviSplit->AviHeader.cb = 0;
860
861     for (pCurrentChunk = (RIFFCHUNK *)pBuffer; (BYTE *)pCurrentChunk + sizeof(*pCurrentChunk) < pBuffer + list.cb; pCurrentChunk = (RIFFCHUNK *)(((BYTE *)pCurrentChunk) + sizeof(*pCurrentChunk) + pCurrentChunk->cb))
862     {
863         RIFFLIST * pList;
864
865         switch (pCurrentChunk->fcc)
866         {
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)
874             {
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;
881             }
882             break;
883         case ckidAVIPADDING:
884             /* ignore */
885             break;
886         default:
887             FIXME("unrecognised header list type: %.04s\n", (LPSTR)&pCurrentChunk->fcc);
888         }
889     }
890     HeapFree(GetProcessHeap(), 0, pBuffer);
891
892     if (pAviSplit->AviHeader.cb != sizeof(pAviSplit->AviHeader) - sizeof(RIFFCHUNK))
893     {
894         ERR("Avi Header wrong size!\n");
895         return E_FAIL;
896     }
897
898     pos += sizeof(RIFFCHUNK) + list.cb;
899     hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
900
901     while (list.fcc == ckidAVIPADDING || (list.fcc == FOURCC_LIST && list.fccListType == ckidINFO))
902     {
903         pos += sizeof(RIFFCHUNK) + list.cb;
904
905         hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
906     }
907
908     if (list.fcc != FOURCC_LIST)
909     {
910         ERR("Expected LIST, but got %.04s\n", (LPSTR)&list.fcc);
911         return E_FAIL;
912     }
913     if (list.fccListType != listtypeAVIMOVIE)
914     {
915         ERR("Expected AVI movie list, but got %.04s\n", (LPSTR)&list.fccListType);
916         return E_FAIL;
917     }
918
919     IAsyncReader_Length(This->pReader, &total, &avail);
920
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)
924     {
925         This->rtStart = pAviSplit->CurrentChunkOffset = MEDIATIME_FROM_BYTES(pos + sizeof(RIFFLIST));
926         pos += list.cb + sizeof(RIFFCHUNK);
927
928         pAviSplit->EndOfFile = This->rtStop = MEDIATIME_FROM_BYTES(pos);
929         if (pos > total)
930         {
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;
933         }
934
935         hr = IAsyncReader_SyncRead(This->pReader, BYTES_FROM_MEDIATIME(pAviSplit->CurrentChunkOffset), sizeof(pAviSplit->CurrentChunk), (BYTE *)&pAviSplit->CurrentChunk);
936     }
937
938     /* Now peek into the idx1 index, if available */
939     if (hr == S_OK && (total - pos) > sizeof(RIFFCHUNK))
940     {
941         memset(&list, 0, sizeof(list));
942
943         hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
944         if (list.fcc == ckidAVIOLDINDEX)
945         {
946             pAviSplit->oldindex = CoTaskMemRealloc(pAviSplit->oldindex, list.cb + sizeof(RIFFCHUNK));
947             if (pAviSplit->oldindex)
948             {
949                 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(RIFFCHUNK) + list.cb, (BYTE *)pAviSplit->oldindex);
950                 if (hr == S_OK)
951                 {
952                     hr = AVISplitter_ProcessOldIndex(pAviSplit);
953                 }
954                 else
955                 {
956                     CoTaskMemFree(pAviSplit->oldindex);
957                     pAviSplit->oldindex = NULL;
958                     hr = S_OK;
959                 }
960             }
961         }
962     }
963
964     indexes = 0;
965     for (x = 0; x < pAviSplit->Parser.cStreams; ++x)
966         if (pAviSplit->streams[x].entries)
967             ++indexes;
968
969     if (indexes)
970     {
971         CoTaskMemFree(pAviSplit->oldindex);
972         pAviSplit->oldindex = NULL;
973         if (indexes < pAviSplit->Parser.cStreams)
974         {
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
977              */
978             ERR("%d indexes expected, but only have %d\n", indexes, pAviSplit->Parser.cStreams);
979             indexes = 0;
980         }
981     }
982     else if (!indexes && pAviSplit->oldindex)
983         indexes = pAviSplit->Parser.cStreams;
984
985     if (!indexes && pAviSplit->AviHeader.dwFlags & AVIF_MUSTUSEINDEX)
986     {
987         FIXME("No usable index was found!\n");
988         hr = E_FAIL;
989     }
990
991     /* Now, set up the streams */
992     if (hr == S_OK)
993         hr = AVISplitter_InitializeStreams(pAviSplit);
994
995     if (hr != S_OK)
996     {
997         AVISplitter_Disconnect(pAviSplit);
998         return E_FAIL;
999     }
1000
1001     TRACE("AVI File ok\n");
1002
1003     return hr;
1004 }
1005
1006 static HRESULT AVISplitter_Cleanup(LPVOID iface)
1007 {
1008     AVISplitterImpl *This = (AVISplitterImpl*)iface;
1009
1010     TRACE("(%p)->()\n", This);
1011
1012     if (This->pCurrentSample)
1013         IMediaSample_Release(This->pCurrentSample);
1014     This->pCurrentSample = NULL;
1015
1016     return S_OK;
1017 }
1018
1019 static HRESULT AVISplitter_Disconnect(LPVOID iface)
1020 {
1021     AVISplitterImpl *This = iface;
1022     int x;
1023
1024     /* TODO: Remove other memory that's allocated during connect */
1025     CoTaskMemFree(This->oldindex);
1026     This->oldindex = NULL;
1027
1028     for (x = 0; x < This->Parser.cStreams; ++x)
1029     {
1030         int i;
1031
1032         StreamData *stream = &This->streams[x];
1033
1034         for (i = 0; i < stream->entries; ++i)
1035             CoTaskMemFree(stream->stdindex[i]);
1036
1037         CoTaskMemFree(stream->stdindex);
1038     }
1039     CoTaskMemFree(This->streams);
1040     This->streams = NULL;
1041
1042     return S_OK;
1043 }
1044
1045 static const IBaseFilterVtbl AVISplitter_Vtbl =
1046 {
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
1062 };
1063
1064 HRESULT AVISplitter_create(IUnknown * pUnkOuter, LPVOID * ppv)
1065 {
1066     HRESULT hr;
1067     AVISplitterImpl * This;
1068
1069     TRACE("(%p, %p)\n", pUnkOuter, ppv);
1070
1071     *ppv = NULL;
1072
1073     if (pUnkOuter)
1074         return CLASS_E_NOAGGREGATION;
1075
1076     /* Note: This memory is managed by the transform filter once created */
1077     This = CoTaskMemAlloc(sizeof(AVISplitterImpl));
1078
1079     This->pCurrentSample = NULL;
1080     This->streams = NULL;
1081     This->oldindex = NULL;
1082
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);
1084
1085     if (FAILED(hr))
1086         return hr;
1087
1088     *ppv = (LPVOID)This;
1089
1090     return hr;
1091 }