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