quartz: Allow parser filters to implement their own seeking methods.
[wine] / dlls / quartz / avisplit.c
1 /*
2  * AVI Splitter Filter
3  *
4  * Copyright 2003 Robert Shearman
5  * Copyright 2004-2005 Christian Costa
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 /* FIXME:
22  * - we don't do anything with indices yet (we could use them when seeking)
23  * - we don't support multiple RIFF sections (i.e. large AVI files > 2Gb)
24  */
25
26 #include "quartz_private.h"
27 #include "control_private.h"
28 #include "pin.h"
29
30 #include "uuids.h"
31 #include "aviriff.h"
32 #include "vfwmsgs.h"
33 #include "amvideo.h"
34
35 #include "fourcc.h"
36
37 #include "wine/unicode.h"
38 #include "wine/debug.h"
39
40 #include <math.h>
41 #include <assert.h>
42
43 #include "parser.h"
44
45 WINE_DEFAULT_DEBUG_CHANNEL(quartz);
46
47 typedef struct StreamData
48 {
49     DWORD dwSampleSize;
50     FLOAT fSamplesPerSec;
51     DWORD dwLength;
52 } StreamData;
53
54 typedef struct AVISplitterImpl
55 {
56     ParserImpl Parser;
57     IMediaSample * pCurrentSample;
58     RIFFCHUNK CurrentChunk;
59     LONGLONG CurrentChunkOffset; /* in media time */
60     LONGLONG EndOfFile;
61     AVIMAINHEADER AviHeader;
62     StreamData *streams;
63 } AVISplitterImpl;
64
65 static HRESULT AVISplitter_NextChunk(LONGLONG * pllCurrentChunkOffset, RIFFCHUNK * pCurrentChunk, const REFERENCE_TIME * tStart, const REFERENCE_TIME * tStop, const BYTE * pbSrcStream, int inner)
66 {
67     if (inner)
68         *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFLIST));
69     else
70         *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK) + RIFFROUND(pCurrentChunk->cb));
71     
72     if (*pllCurrentChunkOffset >= *tStop)
73         return S_FALSE; /* no more data - we couldn't even get the next chunk header! */
74     else if (*pllCurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) >= *tStop)
75     {
76         memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), (DWORD)BYTES_FROM_MEDIATIME(*tStop - *pllCurrentChunkOffset));
77         return S_FALSE; /* no more data */
78     }
79     else
80         memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), sizeof(RIFFCHUNK));
81
82     return S_OK;
83 }
84
85 static HRESULT AVISplitter_Sample(LPVOID iface, IMediaSample * pSample)
86 {
87     AVISplitterImpl *This = (AVISplitterImpl *)iface;
88     LPBYTE pbSrcStream = NULL;
89     long cbSrcStream = 0;
90     REFERENCE_TIME tStart, tStop;
91     HRESULT hr;
92     BOOL bMoreData = TRUE;
93
94     hr = IMediaSample_GetPointer(pSample, &pbSrcStream);
95
96     hr = IMediaSample_GetTime(pSample, &tStart, &tStop);
97
98     cbSrcStream = IMediaSample_GetActualDataLength(pSample);
99
100     /* trace removed for performance reasons */
101     /* TRACE("(%p)\n", pSample); */
102
103     assert(BYTES_FROM_MEDIATIME(tStop - tStart) == cbSrcStream);
104
105     if (This->CurrentChunkOffset <= tStart && This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) > tStart)
106     {
107         DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(tStart - This->CurrentChunkOffset);
108         assert(offset <= sizeof(RIFFCHUNK));
109         memcpy((BYTE *)&This->CurrentChunk + offset, pbSrcStream, sizeof(RIFFCHUNK) - offset);
110     }
111     else if (This->CurrentChunkOffset > tStart)
112     {
113         DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart);
114         if (offset >= (DWORD)cbSrcStream)
115         {
116             FIXME("large offset\n");
117             hr = S_OK;
118             goto skip;
119         }
120
121         memcpy(&This->CurrentChunk, pbSrcStream + offset, sizeof(RIFFCHUNK));
122     }
123
124     assert(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) < tStop);
125
126     while (bMoreData)
127     {
128         BYTE * pbDstStream;
129         long cbDstStream;
130         long chunk_remaining_bytes = 0;
131         long offset_src;
132         WORD streamId;
133         Parser_OutputPin * pOutputPin;
134         BOOL bSyncPoint = TRUE;
135
136         if (This->CurrentChunkOffset >= tStart)
137             offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
138         else
139             offset_src = 0;
140
141         switch (This->CurrentChunk.fcc)
142         {
143         case ckidJUNK:
144         case aviFCC('i','d','x','1'): /* Index is not handled */
145             /* silently ignore */
146             if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
147                 bMoreData = FALSE;
148             continue;
149         case ckidLIST:
150             /* We only handle the 'rec ' list which contains the stream data */
151             if ((*(DWORD*)(pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart) + sizeof(RIFFCHUNK))) == aviFCC('r','e','c',' '))
152             {
153                 /* FIXME: We only advanced to the first chunk inside the list without keeping track that we are in it.
154                  *        This is not clean and the parser should be improved for that but it is enough for most AVI files. */
155                 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, TRUE))
156                 {
157                     bMoreData = FALSE;
158                     continue;
159                 }
160                 This->CurrentChunk = *(RIFFCHUNK*) (pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart));
161                 offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
162                 break;
163             }
164             else if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
165                 bMoreData = FALSE;
166             continue;
167         default:
168             break;
169 #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 */
170             switch (TWOCCFromFOURCC(This->CurrentChunk.fcc))
171             {
172             case cktypeDIBcompressed:
173                 bSyncPoint = FALSE;
174                 /* fall-through */
175             case cktypeDIBbits:
176                 /* FIXME: check that pin is of type video */
177                 break;
178             case cktypeWAVEbytes:
179                 /* FIXME: check that pin is of type audio */
180                 break;
181             case cktypePALchange:
182                 FIXME("handle palette change\n");
183                 break;
184             default:
185                 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));
186                 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
187                     bMoreData = FALSE;
188                 continue;
189             }
190 #endif
191         }
192
193         streamId = StreamFromFOURCC(This->CurrentChunk.fcc);
194
195         if (streamId > This->Parser.cStreams)
196         {
197             ERR("Corrupted AVI file (contains stream id %d, but supposed to only have %d streams)\n", streamId, This->Parser.cStreams);
198             hr = E_FAIL;
199             break;
200         }
201
202         pOutputPin = (Parser_OutputPin *)This->Parser.ppPins[streamId + 1];
203
204         if (!This->pCurrentSample)
205         {
206             /* cache media sample until it is ready to be despatched
207              * (i.e. we reach the end of the chunk) */
208             hr = OutputPin_GetDeliveryBuffer(&pOutputPin->pin, &This->pCurrentSample, NULL, NULL, 0);
209
210             if (SUCCEEDED(hr))
211             {
212                 hr = IMediaSample_SetActualDataLength(This->pCurrentSample, 0);
213                 assert(hr == S_OK);
214             }
215             else
216             {
217                 TRACE("Skipping sending sample for stream %02d due to error (%x)\n", streamId, hr);
218                 This->pCurrentSample = NULL;
219                 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
220                     bMoreData = FALSE;
221                 continue;
222             }
223         }
224
225         hr = IMediaSample_GetPointer(This->pCurrentSample, &pbDstStream);
226
227         if (SUCCEEDED(hr))
228         {
229             cbDstStream = IMediaSample_GetSize(This->pCurrentSample);
230
231             chunk_remaining_bytes = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(This->CurrentChunk.cb + sizeof(RIFFCHUNK)) - tStart) - offset_src;
232         
233             assert(chunk_remaining_bytes >= 0);
234             assert(chunk_remaining_bytes <= cbDstStream - IMediaSample_GetActualDataLength(This->pCurrentSample));
235
236             /* trace removed for performance reasons */
237 /*          TRACE("chunk_remaining_bytes: 0x%x, cbSrcStream: 0x%x, offset_src: 0x%x\n", chunk_remaining_bytes, cbSrcStream, offset_src); */
238         }
239
240         if (chunk_remaining_bytes <= cbSrcStream - offset_src)
241         {
242             if (SUCCEEDED(hr))
243             {
244                 memcpy(pbDstStream + IMediaSample_GetActualDataLength(This->pCurrentSample), pbSrcStream + offset_src, chunk_remaining_bytes);
245                 hr = IMediaSample_SetActualDataLength(This->pCurrentSample, chunk_remaining_bytes + IMediaSample_GetActualDataLength(This->pCurrentSample));
246                 assert(hr == S_OK);
247             }
248
249             if (SUCCEEDED(hr))
250             {
251                 REFERENCE_TIME tAviStart, tAviStop;
252                 StreamData *stream = This->streams + streamId;
253
254                 /* FIXME: hack */
255                 if (pOutputPin->dwSamplesProcessed == 0)
256                     IMediaSample_SetDiscontinuity(This->pCurrentSample, TRUE);
257
258                 IMediaSample_SetSyncPoint(This->pCurrentSample, bSyncPoint);
259
260                 pOutputPin->dwSamplesProcessed++;
261
262                 if (stream->dwSampleSize)
263                     tAviStart = (LONGLONG)ceil(10000000.0 * (float)(pOutputPin->dwSamplesProcessed - 1) * (float)IMediaSample_GetActualDataLength(This->pCurrentSample) / ((float)stream->dwSampleSize * stream->fSamplesPerSec));
264                 else
265                     tAviStart = (LONGLONG)ceil(10000000.0 * (float)(pOutputPin->dwSamplesProcessed - 1) / (float)stream->fSamplesPerSec);
266                 if (stream->dwSampleSize)
267                     tAviStop = (LONGLONG)ceil(10000000.0 * (float)pOutputPin->dwSamplesProcessed * (float)IMediaSample_GetActualDataLength(This->pCurrentSample) / ((float)stream->dwSampleSize * stream->fSamplesPerSec));
268                 else
269                     tAviStop = (LONGLONG)ceil(10000000.0 * (float)pOutputPin->dwSamplesProcessed / (float)stream->fSamplesPerSec);
270
271                 IMediaSample_SetTime(This->pCurrentSample, &tAviStart, &tAviStop);
272
273                 hr = OutputPin_SendSample(&pOutputPin->pin, This->pCurrentSample);
274                 if (hr != S_OK && hr != VFW_E_NOT_CONNECTED)
275                     ERR("Error sending sample (%x)\n", hr);
276             }
277
278             if (This->pCurrentSample)
279                 IMediaSample_Release(This->pCurrentSample);
280             
281             This->pCurrentSample = NULL;
282
283             if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
284                 bMoreData = FALSE;
285         }
286         else
287         {
288             if (SUCCEEDED(hr))
289             {
290                 memcpy(pbDstStream + IMediaSample_GetActualDataLength(This->pCurrentSample), pbSrcStream + offset_src, cbSrcStream - offset_src);
291                 IMediaSample_SetActualDataLength(This->pCurrentSample, cbSrcStream - offset_src + IMediaSample_GetActualDataLength(This->pCurrentSample));
292             }
293             bMoreData = FALSE;
294         }
295     }
296
297 skip:
298     if (tStop >= This->EndOfFile)
299     {
300         int i;
301
302         TRACE("End of file reached\n");
303
304         for (i = 0; i < This->Parser.cStreams; i++)
305         {
306             IPin* ppin;
307             HRESULT hr;
308
309             TRACE("Send End Of Stream to output pin %d\n", i);
310
311             hr = IPin_ConnectedTo(This->Parser.ppPins[i+1], &ppin);
312             if (SUCCEEDED(hr))
313             {
314                 hr = IPin_EndOfStream(ppin);
315                 IPin_Release(ppin);
316             }
317             if (FAILED(hr))
318             {
319                 ERR("%x\n", hr);
320                 break;
321             }
322         }
323
324         /* Force the pullpin thread to stop */
325         hr = S_FALSE;
326     }
327
328     return hr;
329 }
330
331 static HRESULT AVISplitter_QueryAccept(LPVOID iface, const AM_MEDIA_TYPE * pmt)
332 {
333     if (IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream) && IsEqualIID(&pmt->subtype, &MEDIASUBTYPE_Avi))
334         return S_OK;
335     return S_FALSE;
336 }
337
338 static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE * pData, DWORD cb)
339 {
340     PIN_INFO piOutput;
341     const RIFFCHUNK * pChunk;
342     HRESULT hr;
343     AM_MEDIA_TYPE amt;
344     float fSamplesPerSec = 0.0f;
345     DWORD dwSampleSize = 0;
346     DWORD dwLength = 0;
347     ALLOCATOR_PROPERTIES props;
348     static const WCHAR wszStreamTemplate[] = {'S','t','r','e','a','m',' ','%','0','2','d',0};
349     StreamData *stream;
350
351     props.cbAlign = 1;
352     props.cbPrefix = 0;
353     props.cbBuffer = 0x20000;
354     props.cBuffers = 2;
355     
356     ZeroMemory(&amt, sizeof(amt));
357     piOutput.dir = PINDIR_OUTPUT;
358     piOutput.pFilter = (IBaseFilter *)This;
359     wsprintfW(piOutput.achName, wszStreamTemplate, This->Parser.cStreams);
360
361     for (pChunk = (const RIFFCHUNK *)pData; 
362          ((const BYTE *)pChunk >= pData) && ((const BYTE *)pChunk + sizeof(RIFFCHUNK) < pData + cb) && (pChunk->cb > 0); 
363          pChunk = (const RIFFCHUNK *)((const BYTE*)pChunk + sizeof(RIFFCHUNK) + pChunk->cb)     
364         )
365     {
366         switch (pChunk->fcc)
367         {
368         case ckidSTREAMHEADER:
369             {
370                 const AVISTREAMHEADER * pStrHdr = (const AVISTREAMHEADER *)pChunk;
371                 TRACE("processing stream header\n");
372
373                 fSamplesPerSec = (float)pStrHdr->dwRate / (float)pStrHdr->dwScale;
374
375                 switch (pStrHdr->fccType)
376                 {
377                 case streamtypeVIDEO:
378                     amt.formattype = FORMAT_VideoInfo;
379                     amt.pbFormat = NULL;
380                     amt.cbFormat = 0;
381                     break;
382                 case streamtypeAUDIO:
383                     amt.formattype = FORMAT_WaveFormatEx;
384                     break;
385                 default:
386                     amt.formattype = FORMAT_None;
387                 }
388                 amt.majortype = MEDIATYPE_Video;
389                 amt.majortype.Data1 = pStrHdr->fccType;
390                 amt.subtype = MEDIATYPE_Video;
391                 amt.subtype.Data1 = pStrHdr->fccHandler;
392                 TRACE("Subtype FCC: %.04s\n", (LPCSTR)&pStrHdr->fccHandler);
393                 amt.lSampleSize = pStrHdr->dwSampleSize;
394                 amt.bFixedSizeSamples = (amt.lSampleSize != 0);
395
396                 /* FIXME: Is this right? */
397                 if (!amt.lSampleSize)
398                 {
399                     amt.lSampleSize = 1;
400                     dwSampleSize = 1;
401                 }
402
403                 amt.bTemporalCompression = IsEqualGUID(&amt.majortype, &MEDIATYPE_Video); /* FIXME? */
404                 dwSampleSize = pStrHdr->dwSampleSize;
405                 dwLength = pStrHdr->dwLength;
406                 if (!dwLength)
407                     dwLength = This->AviHeader.dwTotalFrames;
408
409                 if (pStrHdr->dwSuggestedBufferSize)
410                     props.cbBuffer = pStrHdr->dwSuggestedBufferSize;
411
412                 break;
413             }
414         case ckidSTREAMFORMAT:
415             TRACE("processing stream format data\n");
416             if (IsEqualIID(&amt.formattype, &FORMAT_VideoInfo))
417             {
418                 VIDEOINFOHEADER * pvi;
419                 /* biCompression member appears to override the value in the stream header.
420                  * i.e. the stream header can say something completely contradictory to what
421                  * is in the BITMAPINFOHEADER! */
422                 if (pChunk->cb < sizeof(BITMAPINFOHEADER))
423                 {
424                     ERR("Not enough bytes for BITMAPINFOHEADER\n");
425                     return E_FAIL;
426                 }
427                 amt.cbFormat = sizeof(VIDEOINFOHEADER) - sizeof(BITMAPINFOHEADER) + pChunk->cb;
428                 amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
429                 ZeroMemory(amt.pbFormat, amt.cbFormat);
430                 pvi = (VIDEOINFOHEADER *)amt.pbFormat;
431                 pvi->AvgTimePerFrame = (LONGLONG)(10000000.0 / fSamplesPerSec);
432                 CopyMemory(&pvi->bmiHeader, (const BYTE *)(pChunk + 1), pChunk->cb);
433                 if (pvi->bmiHeader.biCompression)
434                     amt.subtype.Data1 = pvi->bmiHeader.biCompression;
435             }
436             else
437             {
438                 amt.cbFormat = pChunk->cb;
439                 amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
440                 CopyMemory(amt.pbFormat, (const BYTE *)(pChunk + 1), amt.cbFormat);
441             }
442             break;
443         case ckidSTREAMNAME:
444             TRACE("processing stream name\n");
445             /* FIXME: this doesn't exactly match native version (we omit the "##)" prefix), but hey... */
446             MultiByteToWideChar(CP_ACP, 0, (LPCSTR)(pChunk + 1), pChunk->cb, piOutput.achName, sizeof(piOutput.achName) / sizeof(piOutput.achName[0]));
447             break;
448         case ckidSTREAMHANDLERDATA:
449             FIXME("process stream handler data\n");
450             break;
451         case ckidJUNK:
452             TRACE("JUNK chunk ignored\n");
453             break;
454         default:
455             FIXME("unknown chunk type \"%.04s\" ignored\n", (LPCSTR)&pChunk->fcc);
456         }
457     }
458
459     if (IsEqualGUID(&amt.formattype, &FORMAT_WaveFormatEx))
460     {
461         amt.subtype = MEDIATYPE_Video;
462         amt.subtype.Data1 = ((WAVEFORMATEX *)amt.pbFormat)->wFormatTag;
463     }
464
465     dump_AM_MEDIA_TYPE(&amt);
466     TRACE("fSamplesPerSec = %f\n", (double)fSamplesPerSec);
467     TRACE("dwSampleSize = %x\n", dwSampleSize);
468     TRACE("dwLength = %x\n", dwLength);
469     This->streams = CoTaskMemRealloc(This->streams, sizeof(StreamData) * (This->Parser.cStreams+1));
470     stream = This->streams + This->Parser.cStreams;
471     stream->fSamplesPerSec = fSamplesPerSec;
472     stream->dwSampleSize = dwSampleSize;
473     stream->dwLength = dwLength; /* TODO: Use this for mediaseeking */
474
475     hr = Parser_AddPin(&(This->Parser), &piOutput, &props, &amt);
476
477     return hr;
478 }
479
480 /* FIXME: fix leaks on failure here */
481 static HRESULT AVISplitter_InputPin_PreConnect(IPin * iface, IPin * pConnectPin)
482 {
483     PullPin *This = (PullPin *)iface;
484     HRESULT hr;
485     RIFFLIST list;
486     LONGLONG pos = 0; /* in bytes */
487     BYTE * pBuffer;
488     RIFFCHUNK * pCurrentChunk;
489     AVISplitterImpl * pAviSplit = (AVISplitterImpl *)This->pin.pinInfo.pFilter;
490
491     hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
492     pos += sizeof(list);
493
494     if (list.fcc != ckidRIFF)
495     {
496         ERR("Input stream not a RIFF file\n");
497         return E_FAIL;
498     }
499     if (list.fccListType != ckidAVI)
500     {
501         ERR("Input stream not an AVI RIFF file\n");
502         return E_FAIL;
503     }
504
505     hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
506     if (list.fcc != ckidLIST)
507     {
508         ERR("Expected LIST chunk, but got %.04s\n", (LPSTR)&list.fcc);
509         return E_FAIL;
510     }
511     if (list.fccListType != ckidHEADERLIST)
512     {
513         ERR("Header list expected. Got: %.04s\n", (LPSTR)&list.fccListType);
514         return E_FAIL;
515     }
516
517     pBuffer = HeapAlloc(GetProcessHeap(), 0, list.cb - sizeof(RIFFLIST) + sizeof(RIFFCHUNK));
518     hr = IAsyncReader_SyncRead(This->pReader, pos + sizeof(list), list.cb - sizeof(RIFFLIST) + sizeof(RIFFCHUNK), pBuffer);
519
520     pAviSplit->AviHeader.cb = 0;
521
522     for (pCurrentChunk = (RIFFCHUNK *)pBuffer; (BYTE *)pCurrentChunk + sizeof(*pCurrentChunk) < pBuffer + list.cb; pCurrentChunk = (RIFFCHUNK *)(((BYTE *)pCurrentChunk) + sizeof(*pCurrentChunk) + pCurrentChunk->cb))
523     {
524         RIFFLIST * pList;
525
526         switch (pCurrentChunk->fcc)
527         {
528         case ckidMAINAVIHEADER:
529             /* AVIMAINHEADER includes the structure that is pCurrentChunk at the moment */
530             memcpy(&pAviSplit->AviHeader, pCurrentChunk, sizeof(pAviSplit->AviHeader));
531             break;
532         case ckidLIST:
533             pList = (RIFFLIST *)pCurrentChunk;
534             switch (pList->fccListType)
535             {
536             case ckidSTREAMLIST:
537                 hr = AVISplitter_ProcessStreamList(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST));
538                 break;
539             case ckidODML:
540                 FIXME("process ODML header\n");
541                 break;
542             }
543             break;
544         case ckidJUNK:
545             /* ignore */
546             break;
547         default:
548             FIXME("unrecognised header list type: %.04s\n", (LPSTR)&pCurrentChunk->fcc);
549         }
550     }
551     HeapFree(GetProcessHeap(), 0, pBuffer);
552
553     if (pAviSplit->AviHeader.cb != sizeof(pAviSplit->AviHeader) - sizeof(RIFFCHUNK))
554     {
555         ERR("Avi Header wrong size!\n");
556         return E_FAIL;
557     }
558
559     pos += sizeof(RIFFCHUNK) + list.cb;
560     hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
561
562     while (list.fcc == ckidJUNK || (list.fcc == ckidLIST && list.fccListType == ckidINFO))
563     {
564         pos += sizeof(RIFFCHUNK) + list.cb;
565         hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
566     }
567
568     if (list.fcc != ckidLIST)
569     {
570         ERR("Expected LIST, but got %.04s\n", (LPSTR)&list.fcc);
571         return E_FAIL;
572     }
573     if (list.fccListType != ckidAVIMOVIE)
574     {
575         ERR("Expected AVI movie list, but got %.04s\n", (LPSTR)&list.fccListType);
576         return E_FAIL;
577     }
578
579     if (hr == S_OK)
580     {
581         pAviSplit->CurrentChunkOffset = MEDIATIME_FROM_BYTES(pos + sizeof(RIFFLIST));
582         pAviSplit->EndOfFile = MEDIATIME_FROM_BYTES(pos + list.cb + sizeof(RIFFLIST));
583         hr = IAsyncReader_SyncRead(This->pReader, BYTES_FROM_MEDIATIME(pAviSplit->CurrentChunkOffset), sizeof(pAviSplit->CurrentChunk), (BYTE *)&pAviSplit->CurrentChunk);
584     }
585
586     if (hr != S_OK)
587         return E_FAIL;
588
589     TRACE("AVI File ok\n");
590
591     return hr;
592 }
593
594 static HRESULT AVISplitter_Cleanup(LPVOID iface)
595 {
596     AVISplitterImpl *This = (AVISplitterImpl*)iface;
597
598     TRACE("(%p)->()\n", This);
599
600     if (This->pCurrentSample)
601         IMediaSample_Release(This->pCurrentSample);
602     This->pCurrentSample = NULL;
603
604     return S_OK;
605 }
606
607 HRESULT AVISplitter_create(IUnknown * pUnkOuter, LPVOID * ppv)
608 {
609     HRESULT hr;
610     AVISplitterImpl * This;
611
612     TRACE("(%p, %p)\n", pUnkOuter, ppv);
613
614     *ppv = NULL;
615
616     if (pUnkOuter)
617         return CLASS_E_NOAGGREGATION;
618
619     /* Note: This memory is managed by the transform filter once created */
620     This = CoTaskMemAlloc(sizeof(AVISplitterImpl));
621
622     This->pCurrentSample = NULL;
623     This->streams = NULL;
624
625     hr = Parser_Create(&(This->Parser), &CLSID_AviSplitter, AVISplitter_Sample, AVISplitter_QueryAccept, AVISplitter_InputPin_PreConnect, AVISplitter_Cleanup, NULL, NULL, NULL);
626
627     if (FAILED(hr))
628         return hr;
629
630     *ppv = (LPVOID)This;
631
632     return hr;
633 }