push 6fe5edf8439c19d3885814583531c2f2b1495177
[wine/hacks.git] / dlls / quartz / avisplit.c
blob89c02d45e0be2b1d0254c6b4cdcdb3b683393d64
1 /*
2 * AVI Splitter Filter
4 * Copyright 2003 Robert Shearman
5 * Copyright 2004-2005 Christian Costa
6 * Copyright 2008 Maarten Lankhorst
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 /* FIXME:
23 * - we don't do anything with indices yet (we could use them when seeking)
24 * - we don't support multiple RIFF sections (i.e. large AVI files > 2Gb)
25 * - Memory leaks, and lots of them
28 #include "quartz_private.h"
29 #include "control_private.h"
30 #include "pin.h"
32 #include "uuids.h"
33 #include "vfw.h"
34 #include "aviriff.h"
35 #include "vfwmsgs.h"
36 #include "amvideo.h"
38 #include "wine/unicode.h"
39 #include "wine/debug.h"
41 #include <math.h>
42 #include <assert.h>
44 #include "parser.h"
46 #define TWOCCFromFOURCC(fcc) HIWORD(fcc)
48 /* four character codes used in AVI files */
49 #define ckidINFO mmioFOURCC('I','N','F','O')
50 #define ckidREC mmioFOURCC('R','E','C',' ')
52 WINE_DEFAULT_DEBUG_CHANNEL(quartz);
54 typedef struct StreamData
56 DWORD dwSampleSize;
57 FLOAT fSamplesPerSec;
58 DWORD dwLength;
60 AVISTREAMHEADER streamheader;
61 DWORD entries;
62 AVISTDINDEX **stdindex;
63 DWORD frames;
64 } StreamData;
66 typedef struct AVISplitterImpl
68 ParserImpl Parser;
69 IMediaSample * pCurrentSample;
70 RIFFCHUNK CurrentChunk;
71 LONGLONG CurrentChunkOffset; /* in media time */
72 LONGLONG EndOfFile;
73 AVIMAINHEADER AviHeader;
74 AVIEXTHEADER ExtHeader;
76 /* TODO: Handle old style index, probably by creating an opendml style new index from it for within StreamData */
77 AVIOLDINDEX *oldindex;
78 DWORD offset;
80 StreamData *streams;
81 } AVISplitterImpl;
83 static HRESULT AVISplitter_NextChunk(LONGLONG * pllCurrentChunkOffset, RIFFCHUNK * pCurrentChunk, const REFERENCE_TIME * tStart, const REFERENCE_TIME * tStop, const BYTE * pbSrcStream, int inner)
85 if (inner)
86 *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFLIST));
87 else
88 *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK) + RIFFROUND(pCurrentChunk->cb));
90 if (*pllCurrentChunkOffset >= *tStop)
91 return S_FALSE; /* no more data - we couldn't even get the next chunk header! */
92 else if (*pllCurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) >= *tStop)
94 memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), (DWORD)BYTES_FROM_MEDIATIME(*tStop - *pllCurrentChunkOffset));
95 return S_FALSE; /* no more data */
97 else
98 memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), sizeof(RIFFCHUNK));
100 return S_OK;
103 static HRESULT AVISplitter_Sample(LPVOID iface, IMediaSample * pSample)
105 AVISplitterImpl *This = (AVISplitterImpl *)iface;
106 LPBYTE pbSrcStream = NULL;
107 long cbSrcStream = 0;
108 REFERENCE_TIME tStart, tStop;
109 HRESULT hr;
110 BOOL bMoreData = TRUE;
112 hr = IMediaSample_GetPointer(pSample, &pbSrcStream);
114 hr = IMediaSample_GetTime(pSample, &tStart, &tStop);
116 cbSrcStream = IMediaSample_GetActualDataLength(pSample);
118 /* trace removed for performance reasons */
119 /* TRACE("(%p)\n", pSample); */
121 assert(BYTES_FROM_MEDIATIME(tStop - tStart) == cbSrcStream);
123 if (This->CurrentChunkOffset <= tStart && This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) > tStart)
125 DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(tStart - This->CurrentChunkOffset);
126 assert(offset <= sizeof(RIFFCHUNK));
127 memcpy((BYTE *)&This->CurrentChunk + offset, pbSrcStream, sizeof(RIFFCHUNK) - offset);
129 else if (This->CurrentChunkOffset > tStart)
131 DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart);
132 if (offset >= (DWORD)cbSrcStream)
134 FIXME("large offset\n");
135 hr = S_OK;
136 goto skip;
139 memcpy(&This->CurrentChunk, pbSrcStream + offset, sizeof(RIFFCHUNK));
142 assert(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) < tStop);
144 while (bMoreData)
146 BYTE * pbDstStream;
147 long cbDstStream;
148 long chunk_remaining_bytes = 0;
149 long offset_src;
150 WORD streamId;
151 Parser_OutputPin * pOutputPin;
152 BOOL bSyncPoint = TRUE;
153 BYTE *fcc = (BYTE *)&This->CurrentChunk.fcc;
155 if (This->CurrentChunkOffset >= tStart)
156 offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
157 else
158 offset_src = 0;
160 switch (This->CurrentChunk.fcc)
162 case ckidAVIOLDINDEX: /* Should not be popping up here! */
163 ERR("There should be no index in the stream data!\n");
164 case ckidAVIPADDING:
165 /* silently ignore */
166 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
167 bMoreData = FALSE;
168 continue;
169 case FOURCC_LIST:
170 /* We only handle the 'rec ' list which contains the stream data */
171 if ((*(DWORD*)(pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart) + sizeof(RIFFCHUNK))) == ckidREC)
173 /* FIXME: We only advanced to the first chunk inside the list without keeping track that we are in it.
174 * This is not clean and the parser should be improved for that but it is enough for most AVI files. */
175 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, TRUE))
177 bMoreData = FALSE;
178 continue;
180 This->CurrentChunk = *(RIFFCHUNK*) (pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart));
181 offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
182 break;
184 else if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
185 bMoreData = FALSE;
186 continue;
187 default:
188 break;
189 #if 0 /* According to the AVI specs, a stream data chunk should be ABXX where AB is the stream number and X means don't care */
190 switch (TWOCCFromFOURCC(This->CurrentChunk.fcc))
192 case cktypeDIBcompressed:
193 bSyncPoint = FALSE;
194 /* fall-through */
195 case cktypeDIBbits:
196 /* FIXME: check that pin is of type video */
197 break;
198 case cktypeWAVEbytes:
199 /* FIXME: check that pin is of type audio */
200 break;
201 case cktypePALchange:
202 FIXME("handle palette change\n");
203 break;
204 default:
205 FIXME("Skipping unknown chunk type: %s at file offset 0x%x\n", debugstr_an((LPSTR)&This->CurrentChunk.fcc, 4), (DWORD)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset));
206 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
207 bMoreData = FALSE;
208 continue;
210 #endif
213 if (fcc[0] == 'i' && fcc[1] == 'x')
215 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
216 bMoreData = FALSE;
217 continue;
220 streamId = StreamFromFOURCC(This->CurrentChunk.fcc);
222 if (streamId > This->Parser.cStreams)
224 ERR("Corrupted AVI file (contains stream id (%s) %d, but supposed to only have %d streams)\n", debugstr_an((char *)&This->CurrentChunk.fcc, 4), streamId, This->Parser.cStreams);
225 hr = E_FAIL;
226 break;
229 pOutputPin = (Parser_OutputPin *)This->Parser.ppPins[streamId + 1];
231 if (!This->pCurrentSample)
233 /* cache media sample until it is ready to be despatched
234 * (i.e. we reach the end of the chunk) */
235 hr = OutputPin_GetDeliveryBuffer(&pOutputPin->pin, &This->pCurrentSample, NULL, NULL, 0);
237 if (SUCCEEDED(hr))
239 hr = IMediaSample_SetActualDataLength(This->pCurrentSample, 0);
240 assert(hr == S_OK);
242 else
244 TRACE("Skipping sending sample for stream %02d due to error (%x)\n", streamId, hr);
245 This->pCurrentSample = NULL;
246 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
247 bMoreData = FALSE;
248 continue;
252 hr = IMediaSample_GetPointer(This->pCurrentSample, &pbDstStream);
254 if (SUCCEEDED(hr))
256 cbDstStream = IMediaSample_GetSize(This->pCurrentSample);
258 chunk_remaining_bytes = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(This->CurrentChunk.cb + sizeof(RIFFCHUNK)) - tStart) - offset_src;
260 assert(chunk_remaining_bytes >= 0);
261 assert(chunk_remaining_bytes <= cbDstStream - IMediaSample_GetActualDataLength(This->pCurrentSample));
263 /* trace removed for performance reasons */
264 /* TRACE("chunk_remaining_bytes: 0x%x, cbSrcStream: 0x%x, offset_src: 0x%x\n", chunk_remaining_bytes, cbSrcStream, offset_src); */
267 if (chunk_remaining_bytes <= cbSrcStream - offset_src)
269 if (SUCCEEDED(hr))
271 memcpy(pbDstStream + IMediaSample_GetActualDataLength(This->pCurrentSample), pbSrcStream + offset_src, chunk_remaining_bytes);
272 hr = IMediaSample_SetActualDataLength(This->pCurrentSample, chunk_remaining_bytes + IMediaSample_GetActualDataLength(This->pCurrentSample));
273 assert(hr == S_OK);
276 if (SUCCEEDED(hr))
278 REFERENCE_TIME tAviStart, tAviStop;
279 StreamData *stream = This->streams + streamId;
281 /* FIXME: hack */
282 if (pOutputPin->dwSamplesProcessed == 0)
283 IMediaSample_SetDiscontinuity(This->pCurrentSample, TRUE);
285 IMediaSample_SetSyncPoint(This->pCurrentSample, bSyncPoint);
287 pOutputPin->dwSamplesProcessed++;
289 if (stream->dwSampleSize)
290 tAviStart = (LONGLONG)ceil(10000000.0 * (float)(pOutputPin->dwSamplesProcessed - 1) * (float)IMediaSample_GetActualDataLength(This->pCurrentSample) / ((float)stream->dwSampleSize * stream->fSamplesPerSec));
291 else
292 tAviStart = (LONGLONG)ceil(10000000.0 * (float)(pOutputPin->dwSamplesProcessed - 1) / (float)stream->fSamplesPerSec);
293 if (stream->dwSampleSize)
294 tAviStop = (LONGLONG)ceil(10000000.0 * (float)pOutputPin->dwSamplesProcessed * (float)IMediaSample_GetActualDataLength(This->pCurrentSample) / ((float)stream->dwSampleSize * stream->fSamplesPerSec));
295 else
296 tAviStop = (LONGLONG)ceil(10000000.0 * (float)pOutputPin->dwSamplesProcessed / (float)stream->fSamplesPerSec);
298 IMediaSample_SetTime(This->pCurrentSample, &tAviStart, &tAviStop);
300 hr = OutputPin_SendSample(&pOutputPin->pin, This->pCurrentSample);
301 if (hr != S_OK && hr != VFW_E_NOT_CONNECTED)
302 ERR("Error sending sample (%x)\n", hr);
305 if (This->pCurrentSample)
306 IMediaSample_Release(This->pCurrentSample);
308 This->pCurrentSample = NULL;
310 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
311 bMoreData = FALSE;
313 else
315 if (SUCCEEDED(hr))
317 memcpy(pbDstStream + IMediaSample_GetActualDataLength(This->pCurrentSample), pbSrcStream + offset_src, cbSrcStream - offset_src);
318 IMediaSample_SetActualDataLength(This->pCurrentSample, cbSrcStream - offset_src + IMediaSample_GetActualDataLength(This->pCurrentSample));
320 bMoreData = FALSE;
324 skip:
325 if (tStop >= This->EndOfFile)
327 int i;
329 TRACE("End of file reached\n");
331 for (i = 0; i < This->Parser.cStreams; i++)
333 IPin* ppin;
334 HRESULT hr;
336 TRACE("Send End Of Stream to output pin %d\n", i);
338 hr = IPin_ConnectedTo(This->Parser.ppPins[i+1], &ppin);
339 if (SUCCEEDED(hr))
341 hr = IPin_EndOfStream(ppin);
342 IPin_Release(ppin);
344 if (FAILED(hr))
346 ERR("%x\n", hr);
347 break;
351 /* Force the pullpin thread to stop */
352 hr = S_FALSE;
355 return hr;
358 static HRESULT AVISplitter_QueryAccept(LPVOID iface, const AM_MEDIA_TYPE * pmt)
360 if (IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream) && IsEqualIID(&pmt->subtype, &MEDIASUBTYPE_Avi))
361 return S_OK;
362 return S_FALSE;
365 static HRESULT AVISplitter_ProcessIndex(AVISplitterImpl *This, AVISTDINDEX **index, LONGLONG qwOffset, DWORD cb)
367 AVISTDINDEX *pIndex;
368 int x;
369 long rest;
371 *index = NULL;
372 if (cb < sizeof(AVISTDINDEX))
374 FIXME("size %u too small\n", cb);
375 return E_INVALIDARG;
378 pIndex = CoTaskMemAlloc(cb);
379 if (!pIndex)
380 return E_OUTOFMEMORY;
382 IAsyncReader_SyncRead(((PullPin *)This->Parser.ppPins[0])->pReader, qwOffset, cb, (BYTE *)pIndex);
383 pIndex = CoTaskMemRealloc(pIndex, pIndex->cb);
384 if (!pIndex)
385 return E_OUTOFMEMORY;
387 IAsyncReader_SyncRead(((PullPin *)This->Parser.ppPins[0])->pReader, qwOffset, pIndex->cb, (BYTE *)pIndex);
388 rest = pIndex->cb - sizeof(AVISUPERINDEX) + sizeof(RIFFCHUNK) + sizeof(pIndex->aIndex[0]) * ANYSIZE_ARRAY;
390 TRACE("wLongsPerEntry: %hd\n", pIndex->wLongsPerEntry);
391 TRACE("bIndexSubType: %hd\n", pIndex->bIndexSubType);
392 TRACE("bIndexType: %hd\n", pIndex->bIndexType);
393 TRACE("nEntriesInUse: %u\n", pIndex->nEntriesInUse);
394 TRACE("dwChunkId: %.4s\n", (char *)&pIndex->dwChunkId);
395 TRACE("qwBaseOffset: %x%08x\n", (DWORD)(pIndex->qwBaseOffset >> 32), (DWORD)pIndex->qwBaseOffset);
396 TRACE("dwReserved_3: %u\n", pIndex->dwReserved_3);
398 if (pIndex->bIndexType != AVI_INDEX_OF_CHUNKS
399 || pIndex->wLongsPerEntry != 2
400 || rest < (pIndex->nEntriesInUse * sizeof(DWORD) * pIndex->wLongsPerEntry)
401 || (pIndex->bIndexSubType != AVI_INDEX_SUB_DEFAULT))
403 FIXME("Invalid index chunk encountered\n");
404 return E_INVALIDARG;
407 for (x = 0; x < pIndex->nEntriesInUse; ++x)
409 BOOL keyframe = !(pIndex->aIndex[x].dwOffset >> 31);
410 DWORDLONG offset = pIndex->qwBaseOffset + (pIndex->aIndex[x].dwOffset & ~(1<<31));
411 TRACE("dwOffset: %x%08x\n", (DWORD)(offset >> 32), (DWORD)offset);
412 TRACE("dwSize: %u\n", pIndex->aIndex[x].dwSize);
413 TRACE("Frame is a keyframe: %s\n", keyframe ? "yes" : "no");
416 *index = pIndex;
417 return S_OK;
420 static HRESULT AVISplitter_ProcessOldIndex(AVISplitterImpl *This)
422 ULONGLONG mov_pos = BYTES_FROM_MEDIATIME(This->CurrentChunkOffset) - sizeof(DWORD);
423 AVIOLDINDEX *pAviOldIndex = This->oldindex;
424 int relative = -1;
425 int x;
427 for (x = 0; x < pAviOldIndex->cb / sizeof(pAviOldIndex->aIndex[0]); ++x)
429 DWORD temp, temp2 = 0, offset, chunkid;
430 PullPin *pin = This->Parser.pInputPin;
432 offset = pAviOldIndex->aIndex[x].dwOffset;
433 chunkid = pAviOldIndex->aIndex[x].dwChunkId;
435 /* Only scan once, or else this will take too long */
436 if (relative == -1)
438 IAsyncReader_SyncRead(pin->pReader, offset, sizeof(DWORD), (BYTE *)&temp);
439 relative = (chunkid != temp);
441 if (chunkid == mmioFOURCC('7','F','x','x')
442 && ((char *)&temp)[0] == 'i' && ((char *)&temp)[1] == 'x')
443 relative = FALSE;
445 if (relative)
447 if (offset + mov_pos < BYTES_FROM_MEDIATIME(This->EndOfFile))
448 IAsyncReader_SyncRead(pin->pReader, offset + mov_pos, sizeof(DWORD), (BYTE *)&temp2);
450 if (chunkid == mmioFOURCC('7','F','x','x')
451 && ((char *)&temp2)[0] == 'i' && ((char *)&temp2)[1] == 'x')
453 /* Do nothing, all is great */
455 else if (temp2 != chunkid)
457 ERR("Faulty index or bug in handling: Wanted FCC: %s, Abs FCC: %s (@ %x), Rel FCC: %s (@ %.0x%08x)\n",
458 debugstr_an((char *)&chunkid, 4), debugstr_an((char *)&temp, 4), offset,
459 debugstr_an((char *)&temp2, 4), (DWORD)((mov_pos + offset) >> 32), (DWORD)(mov_pos + offset));
460 relative = -1;
465 TRACE("Scanned dwChunkId: %s\n", debugstr_an((char *)&temp, 4));
466 TRACE("dwChunkId: %.4s\n", (char *)&chunkid);
467 TRACE("dwFlags: %08x\n", pAviOldIndex->aIndex[x].dwFlags);
468 TRACE("dwOffset (%s): %08x\n", relative ? "relative" : "absolute", offset);
469 TRACE("dwSize: %08x\n", pAviOldIndex->aIndex[x].dwSize);
472 if (relative == -1)
474 FIXME("Dropping index: no idea whether it is relative or absolute\n");
475 CoTaskMemFree(This->oldindex);
476 This->oldindex = NULL;
478 else if (!relative)
479 This->offset = 0;
480 else
481 This->offset = (DWORD)mov_pos;
483 return S_OK;
486 static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE * pData, DWORD cb)
488 PIN_INFO piOutput;
489 const RIFFCHUNK * pChunk;
490 HRESULT hr;
491 AM_MEDIA_TYPE amt;
492 float fSamplesPerSec = 0.0f;
493 DWORD dwSampleSize = 0;
494 DWORD dwLength = 0;
495 ALLOCATOR_PROPERTIES props;
496 static const WCHAR wszStreamTemplate[] = {'S','t','r','e','a','m',' ','%','0','2','d',0};
497 StreamData *stream;
499 AVISTDINDEX **stdindex = NULL;
500 DWORD nstdindex = 0;
502 props.cbAlign = 1;
503 props.cbPrefix = 0;
504 props.cbBuffer = 0x20000;
505 props.cBuffers = 2;
507 ZeroMemory(&amt, sizeof(amt));
508 piOutput.dir = PINDIR_OUTPUT;
509 piOutput.pFilter = (IBaseFilter *)This;
510 wsprintfW(piOutput.achName, wszStreamTemplate, This->Parser.cStreams);
511 This->streams = CoTaskMemRealloc(This->streams, sizeof(StreamData) * (This->Parser.cStreams+1));
512 stream = This->streams + This->Parser.cStreams;
514 for (pChunk = (const RIFFCHUNK *)pData;
515 ((const BYTE *)pChunk >= pData) && ((const BYTE *)pChunk + sizeof(RIFFCHUNK) < pData + cb) && (pChunk->cb > 0);
516 pChunk = (const RIFFCHUNK *)((const BYTE*)pChunk + sizeof(RIFFCHUNK) + pChunk->cb)
519 switch (pChunk->fcc)
521 case ckidSTREAMHEADER:
523 const AVISTREAMHEADER * pStrHdr = (const AVISTREAMHEADER *)pChunk;
524 TRACE("processing stream header\n");
525 stream->streamheader = *pStrHdr;
527 fSamplesPerSec = (float)pStrHdr->dwRate / (float)pStrHdr->dwScale;
529 switch (pStrHdr->fccType)
531 case streamtypeVIDEO:
532 amt.formattype = FORMAT_VideoInfo;
533 amt.pbFormat = NULL;
534 amt.cbFormat = 0;
535 break;
536 case streamtypeAUDIO:
537 amt.formattype = FORMAT_WaveFormatEx;
538 break;
539 default:
540 FIXME("fccType %.4s not handled yet\n", (char *)&pStrHdr->fccType);
541 amt.formattype = FORMAT_None;
543 amt.majortype = MEDIATYPE_Video;
544 amt.majortype.Data1 = pStrHdr->fccType;
545 amt.subtype = MEDIATYPE_Video;
546 amt.subtype.Data1 = pStrHdr->fccHandler;
547 TRACE("Subtype FCC: %.04s\n", (LPCSTR)&pStrHdr->fccHandler);
548 amt.lSampleSize = pStrHdr->dwSampleSize;
549 amt.bFixedSizeSamples = (amt.lSampleSize != 0);
551 /* FIXME: Is this right? */
552 if (!amt.lSampleSize)
554 amt.lSampleSize = 1;
555 dwSampleSize = 1;
558 amt.bTemporalCompression = IsEqualGUID(&amt.majortype, &MEDIATYPE_Video); /* FIXME? */
559 dwSampleSize = pStrHdr->dwSampleSize;
560 dwLength = pStrHdr->dwLength;
561 if (!dwLength)
562 dwLength = This->AviHeader.dwTotalFrames;
564 if (pStrHdr->dwSuggestedBufferSize)
565 props.cbBuffer = pStrHdr->dwSuggestedBufferSize;
567 break;
569 case ckidSTREAMFORMAT:
570 TRACE("processing stream format data\n");
571 if (IsEqualIID(&amt.formattype, &FORMAT_VideoInfo))
573 VIDEOINFOHEADER * pvi;
574 /* biCompression member appears to override the value in the stream header.
575 * i.e. the stream header can say something completely contradictory to what
576 * is in the BITMAPINFOHEADER! */
577 if (pChunk->cb < sizeof(BITMAPINFOHEADER))
579 ERR("Not enough bytes for BITMAPINFOHEADER\n");
580 return E_FAIL;
582 amt.cbFormat = sizeof(VIDEOINFOHEADER) - sizeof(BITMAPINFOHEADER) + pChunk->cb;
583 amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
584 ZeroMemory(amt.pbFormat, amt.cbFormat);
585 pvi = (VIDEOINFOHEADER *)amt.pbFormat;
586 pvi->AvgTimePerFrame = (LONGLONG)(10000000.0 / fSamplesPerSec);
587 CopyMemory(&pvi->bmiHeader, (const BYTE *)(pChunk + 1), pChunk->cb);
588 if (pvi->bmiHeader.biCompression)
589 amt.subtype.Data1 = pvi->bmiHeader.biCompression;
591 else
593 amt.cbFormat = pChunk->cb;
594 amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
595 CopyMemory(amt.pbFormat, (const BYTE *)(pChunk + 1), amt.cbFormat);
597 break;
598 case ckidSTREAMNAME:
599 TRACE("processing stream name\n");
600 /* FIXME: this doesn't exactly match native version (we omit the "##)" prefix), but hey... */
601 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)(pChunk + 1), pChunk->cb, piOutput.achName, sizeof(piOutput.achName) / sizeof(piOutput.achName[0]));
602 break;
603 case ckidSTREAMHANDLERDATA:
604 FIXME("process stream handler data\n");
605 break;
606 case ckidAVIPADDING:
607 TRACE("JUNK chunk ignored\n");
608 break;
609 case ckidAVISUPERINDEX:
611 const AVISUPERINDEX *pIndex = (const AVISUPERINDEX *)pChunk;
612 int x;
613 long rest = pIndex->cb - sizeof(AVISUPERINDEX) + sizeof(RIFFCHUNK) + sizeof(pIndex->aIndex[0]) * ANYSIZE_ARRAY;
615 if (pIndex->cb < sizeof(AVISUPERINDEX) - sizeof(RIFFCHUNK))
617 FIXME("size %u\n", pIndex->cb);
618 break;
621 if (nstdindex > 0)
623 ERR("Stream %d got more then 1 superindex?\n", This->Parser.cStreams);
624 break;
627 TRACE("wLongsPerEntry: %hd\n", pIndex->wLongsPerEntry);
628 TRACE("bIndexSubType: %hd\n", pIndex->bIndexSubType);
629 TRACE("bIndexType: %hd\n", pIndex->bIndexType);
630 TRACE("nEntriesInUse: %u\n", pIndex->nEntriesInUse);
631 TRACE("dwChunkId: %.4s\n", (char *)&pIndex->dwChunkId);
632 if (pIndex->dwReserved[0])
633 TRACE("dwReserved[0]: %u\n", pIndex->dwReserved[0]);
634 if (pIndex->dwReserved[2])
635 TRACE("dwReserved[1]: %u\n", pIndex->dwReserved[1]);
636 if (pIndex->dwReserved[2])
637 TRACE("dwReserved[2]: %u\n", pIndex->dwReserved[2]);
639 if (pIndex->bIndexType != AVI_INDEX_OF_INDEXES
640 || pIndex->wLongsPerEntry != 4
641 || rest < (pIndex->nEntriesInUse * sizeof(DWORD) * pIndex->wLongsPerEntry)
642 || (pIndex->bIndexSubType != AVI_INDEX_SUB_2FIELD && pIndex->bIndexSubType != AVI_INDEX_SUB_DEFAULT))
644 FIXME("Invalid index chunk encountered\n");
645 break;
648 for (x = 0; x < pIndex->nEntriesInUse; ++x)
650 TRACE("qwOffset: %x%08x\n", (DWORD)(pIndex->aIndex[x].qwOffset >> 32), (DWORD)pIndex->aIndex[x].qwOffset);
651 TRACE("dwSize: %u\n", pIndex->aIndex[x].dwSize);
652 TRACE("dwDuration: %u (unreliable)\n", pIndex->aIndex[x].dwDuration);
654 ++nstdindex;
655 stdindex = CoTaskMemRealloc(stdindex, sizeof(*stdindex) * nstdindex);
656 AVISplitter_ProcessIndex(This, &stdindex[nstdindex-1], pIndex->aIndex[x].qwOffset, pIndex->aIndex[x].dwSize);
658 break;
660 default:
661 FIXME("unknown chunk type \"%.04s\" ignored\n", (LPCSTR)&pChunk->fcc);
665 if (IsEqualGUID(&amt.formattype, &FORMAT_WaveFormatEx))
667 amt.subtype = MEDIATYPE_Video;
668 amt.subtype.Data1 = ((WAVEFORMATEX *)amt.pbFormat)->wFormatTag;
671 dump_AM_MEDIA_TYPE(&amt);
672 TRACE("fSamplesPerSec = %f\n", (double)fSamplesPerSec);
673 TRACE("dwSampleSize = %x\n", dwSampleSize);
674 TRACE("dwLength = %x\n", dwLength);
676 stream->fSamplesPerSec = fSamplesPerSec;
677 stream->dwSampleSize = dwSampleSize;
678 stream->dwLength = dwLength; /* TODO: Use this for mediaseeking */
679 stream->entries = nstdindex;
680 stream->stdindex = stdindex;
682 hr = Parser_AddPin(&(This->Parser), &piOutput, &props, &amt);
683 CoTaskMemFree(amt.pbFormat);
685 return hr;
688 static HRESULT AVISplitter_ProcessODML(AVISplitterImpl * This, const BYTE * pData, DWORD cb)
690 const RIFFCHUNK * pChunk;
692 for (pChunk = (const RIFFCHUNK *)pData;
693 ((const BYTE *)pChunk >= pData) && ((const BYTE *)pChunk + sizeof(RIFFCHUNK) < pData + cb) && (pChunk->cb > 0);
694 pChunk = (const RIFFCHUNK *)((const BYTE*)pChunk + sizeof(RIFFCHUNK) + pChunk->cb)
697 switch (pChunk->fcc)
699 case ckidAVIEXTHEADER:
701 int x;
702 const AVIEXTHEADER * pExtHdr = (const AVIEXTHEADER *)pChunk;
704 TRACE("processing extension header\n");
705 if (pExtHdr->cb != sizeof(AVIEXTHEADER) - sizeof(RIFFCHUNK))
707 FIXME("Size: %u\n", pExtHdr->cb);
708 break;
710 TRACE("dwGrandFrames: %u\n", pExtHdr->dwGrandFrames);
711 for (x = 0; x < 61; ++x)
712 if (pExtHdr->dwFuture[x])
713 FIXME("dwFuture[%i] = %u (0x%08x)\n", x, pExtHdr->dwFuture[x], pExtHdr->dwFuture[x]);
714 This->ExtHeader = *pExtHdr;
715 break;
717 default:
718 FIXME("unknown chunk type \"%.04s\" ignored\n", (LPCSTR)&pChunk->fcc);
722 return S_OK;
725 static HRESULT AVISplitter_InitializeStreams(AVISplitterImpl *This)
727 int x;
729 if (This->oldindex)
731 DWORD nMax, n;
733 for (x = 0; x < This->Parser.cStreams; ++x)
735 This->streams[x].frames = 0;
738 nMax = This->oldindex->cb / sizeof(This->oldindex->aIndex[0]);
740 /* Ok, maybe this is more an excercize to see if I interpret everything correctly or not, but that is useful for now */
741 for (n = 0; n < nMax; ++n)
743 DWORD streamId = StreamFromFOURCC(This->oldindex->aIndex[n].dwChunkId);
744 if (streamId >= This->Parser.cStreams)
746 FIXME("Stream id %s ignored\n", debugstr_an((char*)&This->oldindex->aIndex[n].dwChunkId, 4));
747 continue;
750 if (This->streams[streamId].streamheader.dwSampleSize)
751 This->streams[streamId].frames += This->oldindex->aIndex[n].dwSize / This->streams[streamId].streamheader.dwSampleSize;
752 else
753 ++This->streams[streamId].frames;
756 for (x = 0; x < This->Parser.cStreams; ++x)
758 if ((DWORD)This->streams[x].frames != This->streams[x].streamheader.dwLength)
760 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 else if (!This->streams[0].entries)
767 for (x = 0; x < This->Parser.cStreams; ++x)
769 This->streams[x].frames = This->streams[x].streamheader.dwLength;
773 /* Not much here yet */
774 for (x = 0; x < This->Parser.cStreams; ++x)
776 StreamData *stream = This->streams + x;
777 /* WOEI! */
778 double fps;
779 int y;
780 DWORD64 frames = 0;
782 fps = (double)stream->streamheader.dwRate / (float)stream->streamheader.dwScale;
783 if (stream->stdindex)
785 for (y = 0; y < stream->entries; ++y)
787 frames += stream->stdindex[y]->nEntriesInUse;
790 else frames = stream->frames;
792 frames *= stream->streamheader.dwScale;
793 /* Keep accuracy as high as possible for duration */
794 This->Parser.mediaSeeking.llDuration = frames * 10000000;
795 This->Parser.mediaSeeking.llDuration /= stream->streamheader.dwRate;
796 This->Parser.mediaSeeking.llStop = This->Parser.mediaSeeking.llDuration;
797 This->Parser.mediaSeeking.llCurrent = 0;
799 frames /= stream->streamheader.dwRate;
801 TRACE("fps: %f\n", fps);
802 TRACE("Duration: %d days, %d hours, %d minutes and %d seconds\n", (DWORD)(frames / 86400),
803 (DWORD)((frames % 86400) / 3600), (DWORD)((frames % 3600) / 60), (DWORD)(frames % 60));
806 return S_OK;
809 static HRESULT AVISplitter_Disconnect(LPVOID iface);
811 /* FIXME: fix leaks on failure here */
812 static HRESULT AVISplitter_InputPin_PreConnect(IPin * iface, IPin * pConnectPin)
814 PullPin *This = (PullPin *)iface;
815 HRESULT hr;
816 RIFFLIST list;
817 LONGLONG pos = 0; /* in bytes */
818 BYTE * pBuffer;
819 RIFFCHUNK * pCurrentChunk;
820 LONGLONG total, avail;
821 int x;
822 DWORD indexes;
824 AVISplitterImpl * pAviSplit = (AVISplitterImpl *)This->pin.pinInfo.pFilter;
826 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
827 pos += sizeof(list);
829 if (list.fcc != FOURCC_RIFF)
831 ERR("Input stream not a RIFF file\n");
832 return E_FAIL;
834 if (list.fccListType != formtypeAVI)
836 ERR("Input stream not an AVI RIFF file\n");
837 return E_FAIL;
840 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
841 if (list.fcc != FOURCC_LIST)
843 ERR("Expected LIST chunk, but got %.04s\n", (LPSTR)&list.fcc);
844 return E_FAIL;
846 if (list.fccListType != listtypeAVIHEADER)
848 ERR("Header list expected. Got: %.04s\n", (LPSTR)&list.fccListType);
849 return E_FAIL;
852 pBuffer = HeapAlloc(GetProcessHeap(), 0, list.cb - sizeof(RIFFLIST) + sizeof(RIFFCHUNK));
853 hr = IAsyncReader_SyncRead(This->pReader, pos + sizeof(list), list.cb - sizeof(RIFFLIST) + sizeof(RIFFCHUNK), pBuffer);
855 pAviSplit->AviHeader.cb = 0;
857 for (pCurrentChunk = (RIFFCHUNK *)pBuffer; (BYTE *)pCurrentChunk + sizeof(*pCurrentChunk) < pBuffer + list.cb; pCurrentChunk = (RIFFCHUNK *)(((BYTE *)pCurrentChunk) + sizeof(*pCurrentChunk) + pCurrentChunk->cb))
859 RIFFLIST * pList;
861 switch (pCurrentChunk->fcc)
863 case ckidMAINAVIHEADER:
864 /* AVIMAINHEADER includes the structure that is pCurrentChunk at the moment */
865 memcpy(&pAviSplit->AviHeader, pCurrentChunk, sizeof(pAviSplit->AviHeader));
866 break;
867 case FOURCC_LIST:
868 pList = (RIFFLIST *)pCurrentChunk;
869 switch (pList->fccListType)
871 case ckidSTREAMLIST:
872 hr = AVISplitter_ProcessStreamList(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST));
873 break;
874 case ckidODML:
875 hr = AVISplitter_ProcessODML(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST));
876 break;
878 break;
879 case ckidAVIPADDING:
880 /* ignore */
881 break;
882 default:
883 FIXME("unrecognised header list type: %.04s\n", (LPSTR)&pCurrentChunk->fcc);
886 HeapFree(GetProcessHeap(), 0, pBuffer);
888 if (pAviSplit->AviHeader.cb != sizeof(pAviSplit->AviHeader) - sizeof(RIFFCHUNK))
890 ERR("Avi Header wrong size!\n");
891 return E_FAIL;
894 pos += sizeof(RIFFCHUNK) + list.cb;
895 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
897 while (list.fcc == ckidAVIPADDING || (list.fcc == FOURCC_LIST && list.fccListType == ckidINFO))
899 pos += sizeof(RIFFCHUNK) + list.cb;
901 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
904 if (list.fcc != FOURCC_LIST)
906 ERR("Expected LIST, but got %.04s\n", (LPSTR)&list.fcc);
907 return E_FAIL;
909 if (list.fccListType != listtypeAVIMOVIE)
911 ERR("Expected AVI movie list, but got %.04s\n", (LPSTR)&list.fccListType);
912 return E_FAIL;
915 IAsyncReader_Length(This->pReader, &total, &avail);
917 /* FIXME: AVIX files are added ("eXtended") beyond the "AVI " length, and thus won't be played here */
918 if (hr == S_OK)
920 This->rtStart = pAviSplit->CurrentChunkOffset = MEDIATIME_FROM_BYTES(pos + sizeof(RIFFLIST));
921 pos += list.cb + sizeof(RIFFCHUNK);
923 pAviSplit->EndOfFile = This->rtStop = MEDIATIME_FROM_BYTES(pos);
924 if (pos > total)
926 ERR("File smaller (%x%08x) then EndOfFile (%x%08x)\n", (DWORD)(total >> 32), (DWORD)total, (DWORD)(pAviSplit->EndOfFile >> 32), (DWORD)pAviSplit->EndOfFile);
927 return E_FAIL;
930 hr = IAsyncReader_SyncRead(This->pReader, BYTES_FROM_MEDIATIME(pAviSplit->CurrentChunkOffset), sizeof(pAviSplit->CurrentChunk), (BYTE *)&pAviSplit->CurrentChunk);
933 /* Now peek into the idx1 index, if available */
934 if (hr == S_OK && (total - pos) > sizeof(RIFFCHUNK))
936 memset(&list, 0, sizeof(list));
938 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
939 if (list.fcc == ckidAVIOLDINDEX)
941 pAviSplit->oldindex = CoTaskMemRealloc(pAviSplit->oldindex, list.cb + sizeof(RIFFCHUNK));
942 if (pAviSplit->oldindex)
944 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(RIFFCHUNK) + list.cb, (BYTE *)pAviSplit->oldindex);
945 if (hr == S_OK)
947 hr = AVISplitter_ProcessOldIndex(pAviSplit);
949 else
951 CoTaskMemFree(pAviSplit->oldindex);
952 pAviSplit->oldindex = NULL;
953 hr = S_OK;
959 indexes = 0;
960 for (x = 0; x < pAviSplit->Parser.cStreams; ++x)
961 if (pAviSplit->streams[x].entries)
962 ++indexes;
964 if (indexes)
966 CoTaskMemFree(pAviSplit->oldindex);
967 pAviSplit->oldindex = NULL;
968 if (indexes < pAviSplit->Parser.cStreams)
970 /* This error could possible be survived by switching to old type index,
971 * but I would rather find out why it doesn't find everything here
973 ERR("%d indexes expected, but only have %d\n", indexes, pAviSplit->Parser.cStreams);
974 indexes = 0;
977 else if (!indexes && pAviSplit->oldindex)
978 indexes = pAviSplit->Parser.cStreams;
980 if (!indexes && pAviSplit->AviHeader.dwFlags & AVIF_MUSTUSEINDEX)
982 FIXME("No usable index was found!\n");
983 hr = E_FAIL;
986 /* Now, set up the streams */
987 if (hr == S_OK)
988 hr = AVISplitter_InitializeStreams(pAviSplit);
990 if (hr != S_OK)
992 AVISplitter_Disconnect(pAviSplit);
993 return E_FAIL;
996 TRACE("AVI File ok\n");
998 return hr;
1001 static HRESULT AVISplitter_Cleanup(LPVOID iface)
1003 AVISplitterImpl *This = (AVISplitterImpl*)iface;
1005 TRACE("(%p)->()\n", This);
1007 if (This->pCurrentSample)
1008 IMediaSample_Release(This->pCurrentSample);
1009 This->pCurrentSample = NULL;
1011 return S_OK;
1014 static HRESULT AVISplitter_Disconnect(LPVOID iface)
1016 AVISplitterImpl *This = iface;
1017 int x;
1019 /* TODO: Remove other memory that's allocated during connect */
1020 CoTaskMemFree(This->oldindex);
1021 This->oldindex = NULL;
1023 for (x = 0; x < This->Parser.cStreams; ++x)
1025 int i;
1027 StreamData *stream = &This->streams[x];
1029 for (i = 0; i < stream->entries; ++i)
1030 CoTaskMemFree(stream->stdindex[i]);
1032 CoTaskMemFree(stream->stdindex);
1034 CoTaskMemFree(This->streams);
1035 This->streams = NULL;
1037 return S_OK;
1040 HRESULT AVISplitter_create(IUnknown * pUnkOuter, LPVOID * ppv)
1042 HRESULT hr;
1043 AVISplitterImpl * This;
1045 TRACE("(%p, %p)\n", pUnkOuter, ppv);
1047 *ppv = NULL;
1049 if (pUnkOuter)
1050 return CLASS_E_NOAGGREGATION;
1052 /* Note: This memory is managed by the transform filter once created */
1053 This = CoTaskMemAlloc(sizeof(AVISplitterImpl));
1055 This->pCurrentSample = NULL;
1056 This->streams = NULL;
1057 This->oldindex = NULL;
1059 hr = Parser_Create(&(This->Parser), &CLSID_AviSplitter, AVISplitter_Sample, AVISplitter_QueryAccept, AVISplitter_InputPin_PreConnect, AVISplitter_Cleanup, AVISplitter_Disconnect, NULL, NULL, NULL);
1061 if (FAILED(hr))
1062 return hr;
1064 *ppv = (LPVOID)This;
1066 return hr;