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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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"
37 #include "wine/unicode.h"
38 #include "wine/debug.h"
45 WINE_DEFAULT_DEBUG_CHANNEL(quartz);
47 typedef struct StreamData
54 typedef struct AVISplitterImpl
57 IMediaSample * pCurrentSample;
58 RIFFCHUNK CurrentChunk;
59 LONGLONG CurrentChunkOffset; /* in media time */
61 AVIMAINHEADER AviHeader;
65 static HRESULT AVISplitter_NextChunk(LONGLONG * pllCurrentChunkOffset, RIFFCHUNK * pCurrentChunk, const REFERENCE_TIME * tStart, const REFERENCE_TIME * tStop, const BYTE * pbSrcStream, int inner)
68 *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFLIST));
70 *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK) + RIFFROUND(pCurrentChunk->cb));
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)
76 memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), (DWORD)BYTES_FROM_MEDIATIME(*tStop - *pllCurrentChunkOffset));
77 return S_FALSE; /* no more data */
80 memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), sizeof(RIFFCHUNK));
85 static HRESULT AVISplitter_Sample(LPVOID iface, IMediaSample * pSample)
87 AVISplitterImpl *This = (AVISplitterImpl *)iface;
88 LPBYTE pbSrcStream = NULL;
90 REFERENCE_TIME tStart, tStop;
92 BOOL bMoreData = TRUE;
94 hr = IMediaSample_GetPointer(pSample, &pbSrcStream);
96 hr = IMediaSample_GetTime(pSample, &tStart, &tStop);
98 cbSrcStream = IMediaSample_GetActualDataLength(pSample);
100 /* trace removed for performance reasons */
101 /* TRACE("(%p)\n", pSample); */
103 assert(BYTES_FROM_MEDIATIME(tStop - tStart) == cbSrcStream);
105 if (This->CurrentChunkOffset <= tStart && This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) > tStart)
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);
111 else if (This->CurrentChunkOffset > tStart)
113 DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart);
114 if (offset >= (DWORD)cbSrcStream)
116 FIXME("large offset\n");
121 memcpy(&This->CurrentChunk, pbSrcStream + offset, sizeof(RIFFCHUNK));
124 assert(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) < tStop);
130 long chunk_remaining_bytes = 0;
133 Parser_OutputPin * pOutputPin;
134 BOOL bSyncPoint = TRUE;
136 if (This->CurrentChunkOffset >= tStart)
137 offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
141 switch (This->CurrentChunk.fcc)
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))
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',' '))
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))
160 This->CurrentChunk = *(RIFFCHUNK*) (pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart));
161 offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK);
164 else if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
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))
172 case cktypeDIBcompressed:
176 /* FIXME: check that pin is of type video */
178 case cktypeWAVEbytes:
179 /* FIXME: check that pin is of type audio */
181 case cktypePALchange:
182 FIXME("handle palette change\n");
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))
193 streamId = StreamFromFOURCC(This->CurrentChunk.fcc);
195 if (streamId > This->Parser.cStreams)
197 ERR("Corrupted AVI file (contains stream id %d, but supposed to only have %d streams)\n", streamId, This->Parser.cStreams);
202 pOutputPin = (Parser_OutputPin *)This->Parser.ppPins[streamId + 1];
204 if (!This->pCurrentSample)
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);
212 hr = IMediaSample_SetActualDataLength(This->pCurrentSample, 0);
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))
225 hr = IMediaSample_GetPointer(This->pCurrentSample, &pbDstStream);
229 cbDstStream = IMediaSample_GetSize(This->pCurrentSample);
231 chunk_remaining_bytes = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(This->CurrentChunk.cb + sizeof(RIFFCHUNK)) - tStart) - offset_src;
233 assert(chunk_remaining_bytes >= 0);
234 assert(chunk_remaining_bytes <= cbDstStream - IMediaSample_GetActualDataLength(This->pCurrentSample));
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); */
240 if (chunk_remaining_bytes <= cbSrcStream - offset_src)
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));
251 REFERENCE_TIME tAviStart, tAviStop;
252 StreamData *stream = This->streams + streamId;
255 if (pOutputPin->dwSamplesProcessed == 0)
256 IMediaSample_SetDiscontinuity(This->pCurrentSample, TRUE);
258 IMediaSample_SetSyncPoint(This->pCurrentSample, bSyncPoint);
260 pOutputPin->dwSamplesProcessed++;
262 if (stream->dwSampleSize)
263 tAviStart = (LONGLONG)ceil(10000000.0 * (float)(pOutputPin->dwSamplesProcessed - 1) * (float)IMediaSample_GetActualDataLength(This->pCurrentSample) / ((float)stream->dwSampleSize * stream->fSamplesPerSec));
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));
269 tAviStop = (LONGLONG)ceil(10000000.0 * (float)pOutputPin->dwSamplesProcessed / (float)stream->fSamplesPerSec);
271 IMediaSample_SetTime(This->pCurrentSample, &tAviStart, &tAviStop);
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);
278 if (This->pCurrentSample)
279 IMediaSample_Release(This->pCurrentSample);
281 This->pCurrentSample = NULL;
283 if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE))
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));
298 if (tStop >= This->EndOfFile)
302 TRACE("End of file reached\n");
304 for (i = 0; i < This->Parser.cStreams; i++)
309 TRACE("Send End Of Stream to output pin %d\n", i);
311 hr = IPin_ConnectedTo(This->Parser.ppPins[i+1], &ppin);
314 hr = IPin_EndOfStream(ppin);
324 /* Force the pullpin thread to stop */
331 static HRESULT AVISplitter_QueryAccept(LPVOID iface, const AM_MEDIA_TYPE * pmt)
333 if (IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream) && IsEqualIID(&pmt->subtype, &MEDIASUBTYPE_Avi))
338 static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE * pData, DWORD cb)
341 const RIFFCHUNK * pChunk;
344 float fSamplesPerSec = 0.0f;
345 DWORD dwSampleSize = 0;
347 ALLOCATOR_PROPERTIES props;
348 static const WCHAR wszStreamTemplate[] = {'S','t','r','e','a','m',' ','%','0','2','d',0};
353 props.cbBuffer = 0x20000;
356 ZeroMemory(&amt, sizeof(amt));
357 piOutput.dir = PINDIR_OUTPUT;
358 piOutput.pFilter = (IBaseFilter *)This;
359 wsprintfW(piOutput.achName, wszStreamTemplate, This->Parser.cStreams);
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)
368 case ckidSTREAMHEADER:
370 const AVISTREAMHEADER * pStrHdr = (const AVISTREAMHEADER *)pChunk;
371 TRACE("processing stream header\n");
373 fSamplesPerSec = (float)pStrHdr->dwRate / (float)pStrHdr->dwScale;
375 switch (pStrHdr->fccType)
377 case streamtypeVIDEO:
378 amt.formattype = FORMAT_VideoInfo;
382 case streamtypeAUDIO:
383 amt.formattype = FORMAT_WaveFormatEx;
386 amt.formattype = FORMAT_None;
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);
396 /* FIXME: Is this right? */
397 if (!amt.lSampleSize)
403 amt.bTemporalCompression = IsEqualGUID(&amt.majortype, &MEDIATYPE_Video); /* FIXME? */
404 dwSampleSize = pStrHdr->dwSampleSize;
405 dwLength = pStrHdr->dwLength;
407 dwLength = This->AviHeader.dwTotalFrames;
409 if (pStrHdr->dwSuggestedBufferSize)
410 props.cbBuffer = pStrHdr->dwSuggestedBufferSize;
414 case ckidSTREAMFORMAT:
415 TRACE("processing stream format data\n");
416 if (IsEqualIID(&amt.formattype, &FORMAT_VideoInfo))
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))
424 ERR("Not enough bytes for BITMAPINFOHEADER\n");
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;
438 amt.cbFormat = pChunk->cb;
439 amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
440 CopyMemory(amt.pbFormat, (const BYTE *)(pChunk + 1), amt.cbFormat);
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]));
448 case ckidSTREAMHANDLERDATA:
449 FIXME("process stream handler data\n");
452 TRACE("JUNK chunk ignored\n");
455 FIXME("unknown chunk type \"%.04s\" ignored\n", (LPCSTR)&pChunk->fcc);
459 if (IsEqualGUID(&amt.formattype, &FORMAT_WaveFormatEx))
461 amt.subtype = MEDIATYPE_Video;
462 amt.subtype.Data1 = ((WAVEFORMATEX *)amt.pbFormat)->wFormatTag;
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 */
475 hr = Parser_AddPin(&(This->Parser), &piOutput, &props, &amt);
480 /* FIXME: fix leaks on failure here */
481 static HRESULT AVISplitter_InputPin_PreConnect(IPin * iface, IPin * pConnectPin)
483 PullPin *This = (PullPin *)iface;
486 LONGLONG pos = 0; /* in bytes */
488 RIFFCHUNK * pCurrentChunk;
489 AVISplitterImpl * pAviSplit = (AVISplitterImpl *)This->pin.pinInfo.pFilter;
491 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
494 if (list.fcc != ckidRIFF)
496 ERR("Input stream not a RIFF file\n");
499 if (list.fccListType != ckidAVI)
501 ERR("Input stream not an AVI RIFF file\n");
505 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
506 if (list.fcc != ckidLIST)
508 ERR("Expected LIST chunk, but got %.04s\n", (LPSTR)&list.fcc);
511 if (list.fccListType != ckidHEADERLIST)
513 ERR("Header list expected. Got: %.04s\n", (LPSTR)&list.fccListType);
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);
520 pAviSplit->AviHeader.cb = 0;
522 for (pCurrentChunk = (RIFFCHUNK *)pBuffer; (BYTE *)pCurrentChunk + sizeof(*pCurrentChunk) < pBuffer + list.cb; pCurrentChunk = (RIFFCHUNK *)(((BYTE *)pCurrentChunk) + sizeof(*pCurrentChunk) + pCurrentChunk->cb))
526 switch (pCurrentChunk->fcc)
528 case ckidMAINAVIHEADER:
529 /* AVIMAINHEADER includes the structure that is pCurrentChunk at the moment */
530 memcpy(&pAviSplit->AviHeader, pCurrentChunk, sizeof(pAviSplit->AviHeader));
533 pList = (RIFFLIST *)pCurrentChunk;
534 switch (pList->fccListType)
537 hr = AVISplitter_ProcessStreamList(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST));
540 FIXME("process ODML header\n");
548 FIXME("unrecognised header list type: %.04s\n", (LPSTR)&pCurrentChunk->fcc);
551 HeapFree(GetProcessHeap(), 0, pBuffer);
553 if (pAviSplit->AviHeader.cb != sizeof(pAviSplit->AviHeader) - sizeof(RIFFCHUNK))
555 ERR("Avi Header wrong size!\n");
559 pos += sizeof(RIFFCHUNK) + list.cb;
560 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
562 while (list.fcc == ckidJUNK || (list.fcc == ckidLIST && list.fccListType == ckidINFO))
564 pos += sizeof(RIFFCHUNK) + list.cb;
565 hr = IAsyncReader_SyncRead(This->pReader, pos, sizeof(list), (BYTE *)&list);
568 if (list.fcc != ckidLIST)
570 ERR("Expected LIST, but got %.04s\n", (LPSTR)&list.fcc);
573 if (list.fccListType != ckidAVIMOVIE)
575 ERR("Expected AVI movie list, but got %.04s\n", (LPSTR)&list.fccListType);
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);
589 TRACE("AVI File ok\n");
594 static HRESULT AVISplitter_Cleanup(LPVOID iface)
596 AVISplitterImpl *This = (AVISplitterImpl*)iface;
598 TRACE("(%p)->()\n", This);
600 if (This->pCurrentSample)
601 IMediaSample_Release(This->pCurrentSample);
602 This->pCurrentSample = NULL;
607 HRESULT AVISplitter_create(IUnknown * pUnkOuter, LPVOID * ppv)
610 AVISplitterImpl * This;
612 TRACE("(%p, %p)\n", pUnkOuter, ppv);
617 return CLASS_E_NOAGGREGATION;
619 /* Note: This memory is managed by the transform filter once created */
620 This = CoTaskMemAlloc(sizeof(AVISplitterImpl));
622 This->pCurrentSample = NULL;
623 This->streams = NULL;
625 hr = Parser_Create(&(This->Parser), &CLSID_AviSplitter, AVISplitter_Sample, AVISplitter_QueryAccept, AVISplitter_InputPin_PreConnect, AVISplitter_Cleanup);