4 * Copyright 2003 Robert Shearman
5 * Copyright 2004-2005 Christian Costa
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.
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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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)
26 #include "quartz_private.h"
27 #include "control_private.h"
38 #include "wine/unicode.h"
39 #include "wine/debug.h"
46 WINE_DEFAULT_DEBUG_CHANNEL(quartz);
48 typedef struct AVISplitterImpl
51 IMediaSample * pCurrentSample;
52 RIFFCHUNK CurrentChunk;
53 LONGLONG CurrentChunkOffset; /* in media time */
55 AVIMAINHEADER AviHeader;
58 static HRESULT AVISplitter_NextChunk(LONGLONG * pllCurrentChunkOffset, RIFFCHUNK * pCurrentChunk, const REFERENCE_TIME * tStart, const REFERENCE_TIME * tStop, const BYTE * pbSrcStream)
60 *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK) + RIFFROUND(pCurrentChunk->cb));
62 if (*pllCurrentChunkOffset > *tStop)
63 return S_FALSE; /* no more data - we couldn't even get the next chunk header! */
64 else if (*pllCurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) >= *tStop)
66 memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), (DWORD)BYTES_FROM_MEDIATIME(*tStop - *pllCurrentChunkOffset));
67 return S_FALSE; /* no more data */
70 memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), sizeof(RIFFCHUNK));
75 static HRESULT AVISplitter_Sample(LPVOID iface, IMediaSample * pSample)
77 AVISplitterImpl *This = (AVISplitterImpl *)iface;
78 LPBYTE pbSrcStream = NULL;
80 REFERENCE_TIME tStart, tStop;
82 BOOL bMoreData = TRUE;
84 hr = IMediaSample_GetPointer(pSample, &pbSrcStream);
86 hr = IMediaSample_GetTime(pSample, &tStart, &tStop);
88 cbSrcStream = IMediaSample_GetActualDataLength(pSample);
90 /* trace removed for performance reasons */
91 /* TRACE("(%p)\n", pSample); */
93 assert(BYTES_FROM_MEDIATIME(tStop - tStart) == cbSrcStream);
95 if (This->CurrentChunkOffset <= tStart && This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) > tStart)
97 DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(tStart - This->CurrentChunkOffset);
98 assert(offset <= sizeof(RIFFCHUNK));
99 memcpy((BYTE *)&This->CurrentChunk + offset, pbSrcStream, sizeof(RIFFCHUNK) - offset);
101 else if (This->CurrentChunkOffset > tStart)
103 DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart);
104 if (offset >= (DWORD)cbSrcStream)
106 FIXME("large offset\n");
110 memcpy(&This->CurrentChunk, pbSrcStream + offset, sizeof(RIFFCHUNK));
113 assert(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) < tStop);
119 long chunk_remaining_bytes = 0;
122 Parser_OutputPin * pOutputPin;
123 BOOL bSyncPoint = TRUE;
125 if (This->CurrentChunkOffset >= tStart)
126 offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
130 switch (This->CurrentChunk.fcc)
133 case aviFCC('i','d','x','1'): /* Index is not handled */
134 /* silently ignore */
135 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream))
139 /* We only handle the 'rec ' list which contains the stream data */
140 if ((*(DWORD*)(pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart) + sizeof(RIFFCHUNK))) == aviFCC('r','e','c',' '))
142 /* FIXME: We only advanced to the first chunk inside the list without keeping track that we are in it.
143 * This is not clean and the parser should be improved for that but it is enough for most AVI files. */
144 This->CurrentChunkOffset = MEDIATIME_FROM_BYTES(BYTES_FROM_MEDIATIME(This->CurrentChunkOffset) + sizeof(RIFFLIST));
145 This->CurrentChunk = *(RIFFCHUNK*) (pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart));
146 offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
149 else if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream))
154 #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 */
155 switch (TWOCCFromFOURCC(This->CurrentChunk.fcc))
157 case cktypeDIBcompressed:
161 /* FIXME: check that pin is of type video */
163 case cktypeWAVEbytes:
164 /* FIXME: check that pin is of type audio */
166 case cktypePALchange:
167 FIXME("handle palette change\n");
170 FIXME("Skipping unknown chunk type: %s at file offset 0x%lx\n", debugstr_an((LPSTR)&This->CurrentChunk.fcc, 4), (DWORD)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset));
171 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream))
178 streamId = StreamFromFOURCC(This->CurrentChunk.fcc);
180 if (streamId > This->Parser.cStreams)
182 ERR("Corrupted AVI file (contains stream id %d, but supposed to only have %ld streams)\n", streamId, This->Parser.cStreams);
186 pOutputPin = (Parser_OutputPin *)This->Parser.ppPins[streamId + 1];
188 if (!This->pCurrentSample)
190 /* cache media sample until it is ready to be despatched
191 * (i.e. we reach the end of the chunk) */
192 hr = OutputPin_GetDeliveryBuffer(&pOutputPin->pin, &This->pCurrentSample, NULL, NULL, 0);
196 hr = IMediaSample_SetActualDataLength(This->pCurrentSample, 0);
201 TRACE("Skipping sending sample for stream %02d due to error (%lx)\n", streamId, hr);
202 This->pCurrentSample = NULL;
203 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream))
209 hr = IMediaSample_GetPointer(This->pCurrentSample, &pbDstStream);
213 cbDstStream = IMediaSample_GetSize(This->pCurrentSample);
215 chunk_remaining_bytes = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(This->CurrentChunk.cb + sizeof(RIFFCHUNK)) - tStart) - offset_src;
217 assert(chunk_remaining_bytes >= 0);
218 assert(chunk_remaining_bytes <= cbDstStream - IMediaSample_GetActualDataLength(This->pCurrentSample));
220 /* trace removed for performance reasons */
221 /* TRACE("chunk_remaining_bytes: 0x%lx, cbSrcStream: 0x%lx, offset_src: 0x%lx\n", chunk_remaining_bytes, cbSrcStream, offset_src); */
224 if (chunk_remaining_bytes <= cbSrcStream - offset_src)
228 memcpy(pbDstStream + IMediaSample_GetActualDataLength(This->pCurrentSample), pbSrcStream + offset_src, chunk_remaining_bytes);
229 hr = IMediaSample_SetActualDataLength(This->pCurrentSample, chunk_remaining_bytes + IMediaSample_GetActualDataLength(This->pCurrentSample));
235 REFERENCE_TIME tAviStart, tAviStop;
238 if (pOutputPin->dwSamplesProcessed == 0)
239 IMediaSample_SetDiscontinuity(This->pCurrentSample, TRUE);
241 IMediaSample_SetSyncPoint(This->pCurrentSample, bSyncPoint);
243 pOutputPin->dwSamplesProcessed++;
245 if (pOutputPin->dwSampleSize)
246 tAviStart = (LONGLONG)ceil(10000000.0 * (float)(pOutputPin->dwSamplesProcessed - 1) * (float)IMediaSample_GetActualDataLength(This->pCurrentSample) / ((float)pOutputPin->dwSampleSize * pOutputPin->fSamplesPerSec));
248 tAviStart = (LONGLONG)ceil(10000000.0 * (float)(pOutputPin->dwSamplesProcessed - 1) / (float)pOutputPin->fSamplesPerSec);
249 if (pOutputPin->dwSampleSize)
250 tAviStop = (LONGLONG)ceil(10000000.0 * (float)pOutputPin->dwSamplesProcessed * (float)IMediaSample_GetActualDataLength(This->pCurrentSample) / ((float)pOutputPin->dwSampleSize * pOutputPin->fSamplesPerSec));
252 tAviStop = (LONGLONG)ceil(10000000.0 * (float)pOutputPin->dwSamplesProcessed / (float)pOutputPin->fSamplesPerSec);
254 IMediaSample_SetTime(This->pCurrentSample, &tAviStart, &tAviStop);
256 hr = OutputPin_SendSample(&pOutputPin->pin, This->pCurrentSample);
257 if (hr != S_OK && hr != VFW_E_NOT_CONNECTED)
258 ERR("Error sending sample (%lx)\n", hr);
261 if (This->pCurrentSample)
262 IMediaSample_Release(This->pCurrentSample);
264 This->pCurrentSample = NULL;
266 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream))
273 memcpy(pbDstStream + IMediaSample_GetActualDataLength(This->pCurrentSample), pbSrcStream + offset_src, cbSrcStream - offset_src);
274 IMediaSample_SetActualDataLength(This->pCurrentSample, cbSrcStream - offset_src + IMediaSample_GetActualDataLength(This->pCurrentSample));
282 static HRESULT AVISplitter_QueryAccept(LPVOID iface, const AM_MEDIA_TYPE * pmt)
284 if (IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream) && IsEqualIID(&pmt->subtype, &MEDIASUBTYPE_Avi))
289 static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE * pData, DWORD cb)
292 const RIFFCHUNK * pChunk;
295 float fSamplesPerSec = 0.0f;
296 DWORD dwSampleSize = 0;
298 ALLOCATOR_PROPERTIES props;
299 static const WCHAR wszStreamTemplate[] = {'S','t','r','e','a','m',' ','%','0','2','d',0};
303 props.cbBuffer = 0x20000;
306 ZeroMemory(&amt, sizeof(amt));
307 piOutput.dir = PINDIR_OUTPUT;
308 piOutput.pFilter = (IBaseFilter *)This;
309 wsprintfW(piOutput.achName, wszStreamTemplate, This->Parser.cStreams);
311 for (pChunk = (const RIFFCHUNK *)pData;
312 ((const BYTE *)pChunk >= pData) && ((const BYTE *)pChunk + sizeof(RIFFCHUNK) < pData + cb) && (pChunk->cb > 0);
313 pChunk = (const RIFFCHUNK *)((const BYTE*)pChunk + sizeof(RIFFCHUNK) + pChunk->cb)
318 case ckidSTREAMHEADER:
320 const AVISTREAMHEADER * pStrHdr = (const AVISTREAMHEADER *)pChunk;
321 TRACE("processing stream header\n");
323 fSamplesPerSec = (float)pStrHdr->dwRate / (float)pStrHdr->dwScale;
325 switch (pStrHdr->fccType)
327 case streamtypeVIDEO:
328 memcpy(&amt.formattype, &FORMAT_VideoInfo, sizeof(GUID));
332 case streamtypeAUDIO:
333 memcpy(&amt.formattype, &FORMAT_WaveFormatEx, sizeof(GUID));
336 memcpy(&amt.formattype, &FORMAT_None, sizeof(GUID));
338 memcpy(&amt.majortype, &MEDIATYPE_Video, sizeof(GUID));
339 amt.majortype.Data1 = pStrHdr->fccType;
340 memcpy(&amt.subtype, &MEDIATYPE_Video, sizeof(GUID));
341 amt.subtype.Data1 = pStrHdr->fccHandler;
342 TRACE("Subtype FCC: %.04s\n", (LPCSTR)&pStrHdr->fccHandler);
343 amt.lSampleSize = pStrHdr->dwSampleSize;
344 amt.bFixedSizeSamples = (amt.lSampleSize != 0);
346 /* FIXME: Is this right? */
347 if (!amt.lSampleSize)
353 amt.bTemporalCompression = IsEqualGUID(&amt.majortype, &MEDIATYPE_Video); /* FIXME? */
354 dwSampleSize = pStrHdr->dwSampleSize;
355 dwLength = pStrHdr->dwLength;
357 dwLength = This->AviHeader.dwTotalFrames;
359 if (pStrHdr->dwSuggestedBufferSize)
360 props.cbBuffer = pStrHdr->dwSuggestedBufferSize;
364 case ckidSTREAMFORMAT:
365 TRACE("processing stream format data\n");
366 if (IsEqualIID(&amt.formattype, &FORMAT_VideoInfo))
368 VIDEOINFOHEADER * pvi;
369 /* biCompression member appears to override the value in the stream header.
370 * i.e. the stream header can say something completely contradictory to what
371 * is in the BITMAPINFOHEADER! */
372 if (pChunk->cb < sizeof(BITMAPINFOHEADER))
374 ERR("Not enough bytes for BITMAPINFOHEADER\n");
377 amt.cbFormat = sizeof(VIDEOINFOHEADER) - sizeof(BITMAPINFOHEADER) + pChunk->cb;
378 amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
379 ZeroMemory(amt.pbFormat, amt.cbFormat);
380 pvi = (VIDEOINFOHEADER *)amt.pbFormat;
381 pvi->AvgTimePerFrame = (LONGLONG)(10000000.0 / fSamplesPerSec);
382 CopyMemory(&pvi->bmiHeader, (const BYTE *)(pChunk + 1), pChunk->cb);
383 if (pvi->bmiHeader.biCompression)
384 amt.subtype.Data1 = pvi->bmiHeader.biCompression;
388 amt.cbFormat = pChunk->cb;
389 amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
390 CopyMemory(amt.pbFormat, (const BYTE *)(pChunk + 1), amt.cbFormat);
394 TRACE("processing stream name\n");
395 /* FIXME: this doesn't exactly match native version (we omit the "##)" prefix), but hey... */
396 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)(pChunk + 1), pChunk->cb, piOutput.achName, sizeof(piOutput.achName) / sizeof(piOutput.achName[0]));
398 case ckidSTREAMHANDLERDATA:
399 FIXME("process stream handler data\n");
402 TRACE("JUNK chunk ignored\n");
405 FIXME("unknown chunk type \"%.04s\" ignored\n", (LPCSTR)&pChunk->fcc);
409 if (IsEqualGUID(&amt.formattype, &FORMAT_WaveFormatEx))
411 memcpy(&amt.subtype, &MEDIATYPE_Video, sizeof(GUID));
412 amt.subtype.Data1 = ((WAVEFORMATEX *)amt.pbFormat)->wFormatTag;
415 dump_AM_MEDIA_TYPE(&amt);
416 TRACE("fSamplesPerSec = %f\n", (double)fSamplesPerSec);
417 TRACE("dwSampleSize = %lx\n", dwSampleSize);
418 TRACE("dwLength = %lx\n", dwLength);
420 hr = Parser_AddPin(&(This->Parser), &piOutput, &props, &amt, fSamplesPerSec, dwSampleSize, dwLength);
425 /* FIXME: fix leaks on failure here */
426 static HRESULT AVISplitter_InputPin_PreConnect(IPin * iface, IPin * pConnectPin)
428 PullPin *This = (PullPin *)iface;
431 LONGLONG pos = 0; /* in bytes */
433 RIFFCHUNK * pCurrentChunk;
434 AVISplitterImpl * pAviSplit = (AVISplitterImpl *)This->pin.pinInfo.pFilter;
436 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
439 if (list.fcc != ckidRIFF)
441 ERR("Input stream not a RIFF file\n");
444 if (list.cb > 1 * 1024 * 1024 * 1024) /* cannot be more than 1Gb in size */
446 ERR("Input stream violates RIFF spec\n");
449 if (list.fccListType != ckidAVI)
451 ERR("Input stream not an AVI RIFF file\n");
455 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
456 if (list.fcc != ckidLIST)
458 ERR("Expected LIST chunk, but got %.04s\n", (LPSTR)&list.fcc);
461 if (list.fccListType != ckidHEADERLIST)
463 ERR("Header list expected. Got: %.04s\n", (LPSTR)&list.fccListType);
467 pBuffer = HeapAlloc(GetProcessHeap(), 0, list.cb - sizeof(RIFFLIST) + sizeof(RIFFCHUNK));
468 hr = IAsyncReader_SyncRead(This->pReader, pos + sizeof(list), list.cb - sizeof(RIFFLIST) + sizeof(RIFFCHUNK), pBuffer);
470 pAviSplit->AviHeader.cb = 0;
472 for (pCurrentChunk = (RIFFCHUNK *)pBuffer; (BYTE *)pCurrentChunk + sizeof(*pCurrentChunk) < pBuffer + list.cb; pCurrentChunk = (RIFFCHUNK *)(((BYTE *)pCurrentChunk) + sizeof(*pCurrentChunk) + pCurrentChunk->cb))
476 switch (pCurrentChunk->fcc)
478 case ckidMAINAVIHEADER:
479 /* AVIMAINHEADER includes the structure that is pCurrentChunk at the moment */
480 memcpy(&pAviSplit->AviHeader, pCurrentChunk, sizeof(pAviSplit->AviHeader));
483 pList = (RIFFLIST *)pCurrentChunk;
484 switch (pList->fccListType)
487 hr = AVISplitter_ProcessStreamList(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST));
490 FIXME("process ODML header\n");
498 FIXME("unrecognised header list type: %.04s\n", (LPSTR)&pCurrentChunk->fcc);
501 HeapFree(GetProcessHeap(), 0, pBuffer);
503 if (pAviSplit->AviHeader.cb != sizeof(pAviSplit->AviHeader) - sizeof(RIFFCHUNK))
505 ERR("Avi Header wrong size!\n");
509 pos += sizeof(RIFFCHUNK) + list.cb;
510 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
512 if (list.fcc == ckidJUNK)
514 pos += sizeof(RIFFCHUNK) + list.cb;
515 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
518 if (list.fcc != ckidLIST)
520 ERR("Expected LIST, but got %.04s\n", (LPSTR)&list.fcc);
523 if (list.fccListType != ckidAVIMOVIE)
525 ERR("Expected AVI movie list, but got %.04s\n", (LPSTR)&list.fccListType);
531 pAviSplit->CurrentChunkOffset = MEDIATIME_FROM_BYTES(pos + sizeof(RIFFLIST));
532 pAviSplit->EndOfFile = MEDIATIME_FROM_BYTES(pos + list.cb + sizeof(RIFFLIST));
533 hr = IAsyncReader_SyncRead(This->pReader, BYTES_FROM_MEDIATIME(pAviSplit->CurrentChunkOffset), sizeof(pAviSplit->CurrentChunk), (BYTE *)&pAviSplit->CurrentChunk);
539 TRACE("AVI File ok\n");
544 HRESULT AVISplitter_create(IUnknown * pUnkOuter, LPVOID * ppv)
547 AVISplitterImpl * This;
549 TRACE("(%p, %p)\n", pUnkOuter, ppv);
554 return CLASS_E_NOAGGREGATION;
556 /* Note: This memory is managed by the transform filter once created */
557 This = CoTaskMemAlloc(sizeof(AVISplitterImpl));
559 This->pCurrentSample = NULL;
561 hr = Parser_Create(&(This->Parser), &CLSID_AviSplitter, AVISplitter_Sample, AVISplitter_QueryAccept, AVISplitter_InputPin_PreConnect);