From f6a39998315eb07d3d90c2f1ac76fd7eeaf10611 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Fri, 4 Jul 2008 18:44:14 -0700 Subject: [PATCH] quartz: Add rewrite of avi splitter --- dlls/quartz/avisplit.c | 839 +++++++++++++++++++++++++++++++++--------------- 1 files changed, 579 insertions(+), 260 deletions(-) diff --git a/dlls/quartz/avisplit.c b/dlls/quartz/avisplit.c index 0067384..f2c0636 100644 --- a/dlls/quartz/avisplit.c +++ b/dlls/quartz/avisplit.c @@ -20,9 +20,13 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ /* FIXME: - * - we don't do anything with indices yet (we could use them when seeking) - * - we don't support multiple RIFF sections (i.e. large AVI files > 2Gb) - * - Memory leaks, and lots of them + * - Reference leaks, if they are still existant + * - Files without an index are not handled correctly yet. + * - When stopping/starting, a sample is lost. This should be compensated by + * keeping track of previous index/position. + * - Debugging channels are noisy at the moment, especially with thread + * related messages, however this is the only correct thing to do right now, + * since wine doesn't correctly handle all messages yet. */ #include "quartz_private.h" @@ -61,300 +65,432 @@ typedef struct StreamData DWORD entries; AVISTDINDEX **stdindex; DWORD frames; + DWORD seek; + + /* Position, in index units */ + DWORD pos, pos_next, index, index_next; + + /* Packet handling: a thread is created and waits on the packet event handle + * On an event acquire the sample lock, addref the sample and set it to NULL, + * then queue a new packet. + */ + HANDLE thread, packet_queued; + IMediaSample *sample; + + /* Amount of preroll samples for this stream */ + DWORD preroll; } StreamData; typedef struct AVISplitterImpl { ParserImpl Parser; - IMediaSample * pCurrentSample; RIFFCHUNK CurrentChunk; LONGLONG CurrentChunkOffset; /* in media time */ LONGLONG EndOfFile; AVIMAINHEADER AviHeader; AVIEXTHEADER ExtHeader; - /* TODO: Handle old style index, probably by creating an opendml style new index from it for within StreamData */ AVIOLDINDEX *oldindex; DWORD offset; StreamData *streams; } AVISplitterImpl; -static HRESULT AVISplitter_NextChunk(LONGLONG * pllCurrentChunkOffset, RIFFCHUNK * pCurrentChunk, const REFERENCE_TIME * tStart, const REFERENCE_TIME * tStop, const BYTE * pbSrcStream, int inner) +struct thread_args { + AVISplitterImpl *This; + DWORD stream; +}; + +/* The threading stuff cries for an explanation + * + * PullPin starts processing and calls AVISplitter_first_request + * AVISplitter_first_request creates a thread for each stream + * A stream can be audio, video, subtitles or something undefined. + * + * AVISplitter_first_request loads a single packet to each but one stream, + * and queues it for that last stream. This is to prevent WaitForNext to time + * out badly. + * + * The processing loop is entered. It calls IAsyncReader_WaitForNext in the + * PullPin. Every time it receives a packet, it will call AVISplitter_Sample + * AVISplitter_Sample will signal the relevant thread that a new sample is + * arrived, when that thread is ready it will read the packet and transmits + * it downstream with AVISplitter_Receive + * + * Threads terminate upon receiving NULL as packet or when ANY error code + * != S_OK occurs. This means that any error is fatal to processing. + */ + +static HRESULT AVISplitter_SendEndOfFile(AVISplitterImpl *This, DWORD streamnumber) { - if (inner) - *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFLIST)); - else - *pllCurrentChunkOffset += MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK) + RIFFROUND(pCurrentChunk->cb)); - - if (*pllCurrentChunkOffset >= *tStop) - return S_FALSE; /* no more data - we couldn't even get the next chunk header! */ - else if (*pllCurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) >= *tStop) + IPin* ppin = NULL; + HRESULT hr; + + TRACE("End of file reached\n"); + + hr = IPin_ConnectedTo(This->Parser.ppPins[streamnumber+1], &ppin); + if (SUCCEEDED(hr)) { - memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), (DWORD)BYTES_FROM_MEDIATIME(*tStop - *pllCurrentChunkOffset)); - return S_FALSE; /* no more data */ + hr = IPin_EndOfStream(ppin); + IPin_Release(ppin); } - else - memcpy(pCurrentChunk, pbSrcStream + (DWORD)BYTES_FROM_MEDIATIME(*pllCurrentChunkOffset - *tStart), sizeof(RIFFCHUNK)); + TRACE("--> %x\n", hr); - return S_OK; + /* Force the pullpin thread to stop */ + return S_FALSE; } -static HRESULT AVISplitter_Sample(LPVOID iface, IMediaSample * pSample, DWORD_PTR cookie) +/* Thread worker horse */ +static HRESULT AVISplitter_next_request(AVISplitterImpl *This, DWORD streamnumber) { - AVISplitterImpl *This = (AVISplitterImpl *)iface; - LPBYTE pbSrcStream = NULL; - long cbSrcStream = 0; - REFERENCE_TIME tStart, tStop; + StreamData *stream = This->streams + streamnumber; + PullPin *pin = This->Parser.pInputPin; + IMediaSample *sample = NULL; HRESULT hr; - BOOL bMoreData = TRUE; + BOOL endofstream = FALSE; - hr = IMediaSample_GetPointer(pSample, &pbSrcStream); + TRACE("(%p, %u)->()\n", This, streamnumber); - hr = IMediaSample_GetTime(pSample, &tStart, &tStop); - - cbSrcStream = IMediaSample_GetActualDataLength(pSample); + hr = IMemAllocator_GetBuffer(pin->pAlloc, &sample, NULL, NULL, 0); + if (hr != S_OK) + ERR("... %08x?\n", hr); - /* trace removed for performance reasons */ - /* TRACE("(%p)\n", pSample); */ + if (SUCCEEDED(hr)) + { + LONGLONG rtSampleStart; + /* Add 4 for the next header, which should hopefully work */ + LONGLONG rtSampleStop; - assert(BYTES_FROM_MEDIATIME(tStop - tStart) == cbSrcStream); + stream->pos = stream->pos_next; + stream->index = stream->index_next; - if (This->CurrentChunkOffset <= tStart && This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) > tStart) - { - DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(tStart - This->CurrentChunkOffset); - assert(offset <= sizeof(RIFFCHUNK)); - memcpy((BYTE *)&This->CurrentChunk + offset, pbSrcStream, sizeof(RIFFCHUNK) - offset); - } - else if (This->CurrentChunkOffset > tStart) - { - DWORD offset = (DWORD)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart); - if (offset >= (DWORD)cbSrcStream) + IMediaSample_SetDiscontinuity(sample, stream->seek); + stream->seek = FALSE; + if (stream->preroll) { - FIXME("large offset\n"); - hr = S_OK; - goto skip; + --stream->preroll; + IMediaSample_SetPreroll(sample, TRUE); } - - memcpy(&This->CurrentChunk, pbSrcStream + offset, sizeof(RIFFCHUNK)); - } - - assert(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) < tStop); - - while (bMoreData) - { - BYTE * pbDstStream; - long cbDstStream; - long chunk_remaining_bytes = 0; - long offset_src; - WORD streamId; - Parser_OutputPin * pOutputPin; - BOOL bSyncPoint = TRUE; - BYTE *fcc = (BYTE *)&This->CurrentChunk.fcc; - - if (This->CurrentChunkOffset >= tStart) - offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK); else - offset_src = 0; + IMediaSample_SetPreroll(sample, FALSE); + IMediaSample_SetSyncPoint(sample, TRUE); - switch (This->CurrentChunk.fcc) + if (stream->stdindex) { - case ckidAVIOLDINDEX: /* Should not be popping up here! */ - ERR("There should be no index in the stream data!\n"); - case ckidAVIPADDING: - /* silently ignore */ - if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE)) - bMoreData = FALSE; - continue; - case FOURCC_LIST: - /* We only handle the 'rec ' list which contains the stream data */ - if ((*(DWORD*)(pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart) + sizeof(RIFFCHUNK))) == ckidREC) - { - /* FIXME: We only advanced to the first chunk inside the list without keeping track that we are in it. - * This is not clean and the parser should be improved for that but it is enough for most AVI files. */ - if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, TRUE)) - { - bMoreData = FALSE; - continue; - } - This->CurrentChunk = *(RIFFCHUNK*) (pbSrcStream + BYTES_FROM_MEDIATIME(This->CurrentChunkOffset-tStart)); - offset_src = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset - tStart) + sizeof(RIFFCHUNK); - break; - } - else if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE)) - bMoreData = FALSE; - continue; - default: - break; -#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 */ - switch (TWOCCFromFOURCC(This->CurrentChunk.fcc)) + AVISTDINDEX *index = stream->stdindex[stream->index]; + AVISTDINDEX_ENTRY *entry = &index->aIndex[stream->pos]; + BOOL keyframe; + + rtSampleStart = index->qwBaseOffset; + keyframe = !(entry->dwSize >> 31); + rtSampleStart += entry->dwOffset; + rtSampleStart = MEDIATIME_FROM_BYTES(rtSampleStart); + + ++stream->pos_next; + if (index->nEntriesInUse == stream->pos_next) { - case cktypeDIBcompressed: - bSyncPoint = FALSE; - /* fall-through */ - case cktypeDIBbits: - /* FIXME: check that pin is of type video */ - break; - case cktypeWAVEbytes: - /* FIXME: check that pin is of type audio */ - break; - case cktypePALchange: - FIXME("handle palette change\n"); - break; - default: - 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)); - if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE)) - bMoreData = FALSE; - continue; + stream->pos_next = 0; + ++stream->index_next; } -#endif - } - if (fcc[0] == 'i' && fcc[1] == 'x') - { - if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE)) - bMoreData = FALSE; - continue; - } + rtSampleStop = rtSampleStart + MEDIATIME_FROM_BYTES(entry->dwSize & ~(1 << 31)); - streamId = StreamFromFOURCC(This->CurrentChunk.fcc); + TRACE("offset(%u) size(%u)\n", (DWORD)BYTES_FROM_MEDIATIME(rtSampleStart), (DWORD)BYTES_FROM_MEDIATIME(rtSampleStop - rtSampleStart)); - if (streamId > This->Parser.cStreams) - { - 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); - hr = E_FAIL; - break; + /* End of file */ + if (stream->index_next >= stream->entries) + { + ERR("END OF STREAM ON %u\n", streamnumber); + hr = AVISplitter_SendEndOfFile(This, streamnumber); + return S_FALSE; + } } - - pOutputPin = (Parser_OutputPin *)This->Parser.ppPins[streamId + 1]; - - if (!This->pCurrentSample) + else if (This->oldindex) { - /* cache media sample until it is ready to be despatched - * (i.e. we reach the end of the chunk) */ - hr = OutputPin_GetDeliveryBuffer(&pOutputPin->pin, &This->pCurrentSample, NULL, NULL, 0); - - if (SUCCEEDED(hr)) + DWORD flags = This->oldindex->aIndex[stream->pos].dwFlags; + DWORD size = This->oldindex->aIndex[stream->pos].dwSize; + BOOL keyframe; + keyframe = !!(flags & AVIIF_KEYFRAME); + + rtSampleStart = MEDIATIME_FROM_BYTES(This->offset); + rtSampleStart += MEDIATIME_FROM_BYTES(This->oldindex->aIndex[stream->pos].dwOffset); + rtSampleStop = rtSampleStart + MEDIATIME_FROM_BYTES(size); + if (flags & AVIIF_MIDPART) { - hr = IMediaSample_SetActualDataLength(This->pCurrentSample, 0); - assert(hr == S_OK); + FIXME("Only stand alone frames are currently handled correctly!\n"); + } + if (flags & AVIIF_LIST) + { + FIXME("Not sure if this is handled correctly\n"); + rtSampleStart += MEDIATIME_FROM_BYTES(sizeof(RIFFLIST)); + rtSampleStop += MEDIATIME_FROM_BYTES(sizeof(RIFFLIST)); } else { - TRACE("Skipping sending sample for stream %02d due to error (%x)\n", streamId, hr); - This->pCurrentSample = NULL; - if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE)) - bMoreData = FALSE; - continue; + rtSampleStart += MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)); + rtSampleStop += MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)); } - } - hr = IMediaSample_GetPointer(This->pCurrentSample, &pbDstStream); + /* Slow way of finding next index */ + do { + stream->pos_next++; + } while (stream->pos_next * sizeof(This->oldindex->aIndex[0]) < This->oldindex->cb + && StreamFromFOURCC(This->oldindex->aIndex[stream->pos_next].dwChunkId) != streamnumber); - if (SUCCEEDED(hr)) + /* End of file */ + if (stream->pos_next * sizeof(This->oldindex->aIndex[0]) >= This->oldindex->cb) + { + stream->pos_next = 0; + ++stream->index_next; + ERR("END OF STREAM ON %u\n", streamnumber); + hr = AVISplitter_SendEndOfFile(This, streamnumber); + endofstream = TRUE; + } + } + else /* TODO: Generate an index automagically */ { - cbDstStream = IMediaSample_GetSize(This->pCurrentSample); + ERR("CAN'T PLAY WITHOUT AN INDEX! SOS! SOS! SOS!\n"); + assert(0); + } - chunk_remaining_bytes = (long)BYTES_FROM_MEDIATIME(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(This->CurrentChunk.cb + sizeof(RIFFCHUNK)) - tStart) - offset_src; - - assert(chunk_remaining_bytes >= 0); - assert(chunk_remaining_bytes <= cbDstStream - IMediaSample_GetActualDataLength(This->pCurrentSample)); + hr = IMediaSample_SetTime(sample, &rtSampleStart, &rtSampleStop); - /* trace removed for performance reasons */ -/* TRACE("chunk_remaining_bytes: 0x%x, cbSrcStream: 0x%x, offset_src: 0x%x\n", chunk_remaining_bytes, cbSrcStream, offset_src); */ - } + hr = IAsyncReader_Request(pin->pReader, sample, streamnumber); - if (chunk_remaining_bytes <= cbSrcStream - offset_src) + if (FAILED(hr)) + assert(IMediaSample_Release(sample) == 0); + } + else + { + if (sample) { - if (SUCCEEDED(hr)) - { - memcpy(pbDstStream + IMediaSample_GetActualDataLength(This->pCurrentSample), pbSrcStream + offset_src, chunk_remaining_bytes); - hr = IMediaSample_SetActualDataLength(This->pCurrentSample, chunk_remaining_bytes + IMediaSample_GetActualDataLength(This->pCurrentSample)); - assert(hr == S_OK); - } + ERR("There should be no sample!\n"); + assert(IMediaSample_Release(sample) == 0); + } + } + TRACE("--> %08x\n", hr); - if (SUCCEEDED(hr)) - { - REFERENCE_TIME tAviStart, tAviStop; - StreamData *stream = This->streams + streamId; + if (endofstream && hr == S_OK) + return S_FALSE; - /* FIXME: hack */ - if (pOutputPin->dwSamplesProcessed == 0) - IMediaSample_SetDiscontinuity(This->pCurrentSample, TRUE); + return hr; +} - IMediaSample_SetSyncPoint(This->pCurrentSample, bSyncPoint); +static HRESULT AVISplitter_Receive(AVISplitterImpl *This, IMediaSample *sample, DWORD streamnumber) +{ + Parser_OutputPin *pin = (Parser_OutputPin *)This->Parser.ppPins[1+streamnumber]; + HRESULT hr; + LONGLONG start, stop; + StreamData *stream = &This->streams[streamnumber]; - pOutputPin->dwSamplesProcessed++; + start = pin->dwSamplesProcessed; + start *= stream->streamheader.dwScale; + start *= 10000000; + start /= stream->streamheader.dwRate; - if (stream->dwSampleSize) - tAviStart = (LONGLONG)ceil(10000000.0 * (float)(pOutputPin->dwSamplesProcessed - 1) * (float)IMediaSample_GetActualDataLength(This->pCurrentSample) / ((float)stream->dwSampleSize * stream->fSamplesPerSec)); - else - tAviStart = (LONGLONG)ceil(10000000.0 * (float)(pOutputPin->dwSamplesProcessed - 1) / (float)stream->fSamplesPerSec); - if (stream->dwSampleSize) - tAviStop = (LONGLONG)ceil(10000000.0 * (float)pOutputPin->dwSamplesProcessed * (float)IMediaSample_GetActualDataLength(This->pCurrentSample) / ((float)stream->dwSampleSize * stream->fSamplesPerSec)); - else - tAviStop = (LONGLONG)ceil(10000000.0 * (float)pOutputPin->dwSamplesProcessed / (float)stream->fSamplesPerSec); + if (stream->streamheader.dwSampleSize) + { + ULONG len = IMediaSample_GetActualDataLength(sample); + ULONG size = stream->streamheader.dwSampleSize; - IMediaSample_SetTime(This->pCurrentSample, &tAviStart, &tAviStop); + pin->dwSamplesProcessed += len / size; + } + else + ++pin->dwSamplesProcessed; - hr = OutputPin_SendSample(&pOutputPin->pin, This->pCurrentSample); - if (hr != S_OK && hr != VFW_E_NOT_CONNECTED) - ERR("Error sending sample (%x)\n", hr); - } + stop = pin->dwSamplesProcessed; + stop *= stream->streamheader.dwScale; + stop *= 10000000; + stop /= stream->streamheader.dwRate; - if (This->pCurrentSample) - IMediaSample_Release(This->pCurrentSample); - - This->pCurrentSample = NULL; + IMediaSample_SetTime(sample, &start, &stop); - if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE)) - bMoreData = FALSE; - } - else + hr = OutputPin_SendSample(&pin->pin, sample); + +/* Uncomment this if you want to debug the time differences between the + * different streams, it is useful for that + * + FIXME("stream %u, hr: %08x, Start: %u.%03u, Stop: %u.%03u\n", streamnumber, hr, + (DWORD)(start / 10000000), (DWORD)((start / 10000)%1000), + (DWORD)(stop / 10000000), (DWORD)((stop / 10000)%1000)); +*/ + return hr; +} + +static DWORD WINAPI AVISplitter_thread_reader(LPVOID data) +{ + struct thread_args *args = data; + AVISplitterImpl *This = args->This; + DWORD streamnumber = args->stream; + HRESULT hr = S_OK; + + do + { + HRESULT nexthr = S_FALSE; + IMediaSample *sample; + + WaitForSingleObject(This->streams[streamnumber].packet_queued, INFINITE); + sample = This->streams[streamnumber].sample; + This->streams[streamnumber].sample = NULL; + if (!sample) + break; + + nexthr = AVISplitter_next_request(This, streamnumber); + + hr = AVISplitter_Receive(This, sample, streamnumber); + if (hr != S_OK) + FIXME("Receiving error: %08x\n", hr); + + IMediaSample_Release(sample); + if (hr == S_OK) + hr = nexthr; + } while (hr == S_OK); + + FIXME("Thread %u terminated with hr %08x!\n", streamnumber, hr); + + return hr; +} + +static HRESULT AVISplitter_Sample(LPVOID iface, IMediaSample * pSample, DWORD_PTR cookie) +{ + AVISplitterImpl *This = iface; + StreamData *stream = This->streams + cookie; + HRESULT hr = S_OK; + + if (!IMediaSample_GetActualDataLength(pSample)) + return S_OK; + + /* Send the sample to whatever thread is appropiate + * That thread should also not have a sample queued at the moment + */ + /* Debugging */ + TRACE("(%p)->(%p size: %u, %lu)\n", This, pSample, IMediaSample_GetActualDataLength(pSample), cookie); + assert(cookie < This->Parser.cStreams); + assert(!stream->sample); + assert(WaitForSingleObject(stream->packet_queued, 0) == WAIT_TIMEOUT); + + IMediaSample_AddRef(pSample); + + stream->sample = pSample; + SetEvent(stream->packet_queued); + + return hr; +} + +/* On the first request we have to be sure that (cStreams-1) samples have + * already been processed, because otherwise some pins might not ever finish + * a Pause state change + */ +static HRESULT AVISplitter_first_request(LPVOID iface) +{ + AVISplitterImpl *This = (AVISplitterImpl *)iface; + HRESULT hr = S_OK; + int x; + IMediaSample *sample = NULL; + BOOL have_sample = FALSE; + + TRACE("(%p)->()\n", This); + + for (x = 0; x < This->Parser.cStreams; ++x) + { + StreamData *stream = This->streams + x; + + /* Nothing should be running at this point */ + assert(!stream->thread); + + assert(!sample); + /* It could be we asked the thread to terminate, and the thread + * already terminated before receiving the deathwish */ + ResetEvent(stream->packet_queued); + + stream->pos_next = stream->pos; + stream->index_next = stream->index; + + /* There should be a a packet queued from AVISplitter_next_request last time + * It needs to be done now because this is the only way to ensure that every + * stream will have at least 1 packet processed + * If this is done after the threads start it could go all awkward and we + * would have no guarantees that it's successful at all + */ + + if (have_sample) { - if (SUCCEEDED(hr)) + DWORD_PTR dwUser = ~0; + hr = IAsyncReader_WaitForNext(This->Parser.pInputPin->pReader, 10000, &sample, &dwUser); + assert(hr == S_OK); + assert(sample); + + /* This should not happen! (Maybe the threads didn't terminate?) */ + if (dwUser != x - 1) { - memcpy(pbDstStream + IMediaSample_GetActualDataLength(This->pCurrentSample), pbSrcStream + offset_src, cbSrcStream - offset_src); - IMediaSample_SetActualDataLength(This->pCurrentSample, cbSrcStream - offset_src + IMediaSample_GetActualDataLength(This->pCurrentSample)); + ERR("dwUser: %lu, x-1: %u\n", dwUser, x-1); + assert(dwUser == x - 1); } - bMoreData = FALSE; + AVISplitter_Sample(iface, sample, dwUser); + IMediaSample_Release(sample); } + + hr = AVISplitter_next_request(This, x); + TRACE("-->%08x\n", hr); + /* assert(SUCCEEDED(hr)); * With quick transitions this might not be the case (eg stop->running->stop) */ + + /* Could be an EOF instead */ + have_sample = (hr == S_OK); } -skip: - if (tStop >= This->EndOfFile) + /* FIXME: Don't do this for each pin that sent an EOF */ + for (x = 0; x < This->Parser.cStreams; ++x) { - int i; - - TRACE("End of file reached\n"); + struct thread_args *args; + DWORD tid; - for (i = 0; i < This->Parser.cStreams; i++) + if ((This->streams[x].stdindex && This->streams[x].index_next >= This->streams[x].entries) || + (!This->streams[x].stdindex && This->streams[x].index_next)) { - IPin* ppin; - HRESULT hr; - - TRACE("Send End Of Stream to output pin %d\n", i); - - hr = IPin_ConnectedTo(This->Parser.ppPins[i+1], &ppin); - if (SUCCEEDED(hr)) - { - hr = IPin_EndOfStream(ppin); - IPin_Release(ppin); - } - if (FAILED(hr)) - { - ERR("%x\n", hr); - break; - } + This->streams[x].thread = NULL; + continue; } - /* Force the pullpin thread to stop */ - hr = S_FALSE; + args = CoTaskMemAlloc(sizeof(*args)); + args->This = This; + args->stream = x; + This->streams[x].thread = CreateThread(NULL, 0, AVISplitter_thread_reader, args, 0, &tid); + FIXME("Created stream %u thread 0x%08x\n", x, tid); } + if (FAILED(hr)) + ERR("Horsemen of the apocalypse came to bring error 0x%08x\n", hr); + return hr; } +static HRESULT AVISplitter_done_process(LPVOID iface) +{ + AVISplitterImpl *This = iface; + + DWORD x; + + for (x = 0; x < This->Parser.cStreams; ++x) + { + StreamData *stream = This->streams + x; + + FIXME("Waiting for %u to terminate\n", x); + /* Make the thread return first */ + SetEvent(stream->packet_queued); + assert(WaitForSingleObject(stream->thread, 100000) != WAIT_TIMEOUT); + CloseHandle(stream->thread); + stream->thread = NULL; + + if (stream->sample) + assert(IMediaSample_Release(stream->sample) == 0); + stream->sample = NULL; + + ResetEvent(stream->packet_queued); + } + + return S_OK; +} + static HRESULT AVISplitter_QueryAccept(LPVOID iface, const AM_MEDIA_TYPE * pmt) { if (IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream) && IsEqualIID(&pmt->subtype, &MEDIASUBTYPE_Avi)) @@ -387,6 +523,7 @@ static HRESULT AVISplitter_ProcessIndex(AVISplitterImpl *This, AVISTDINDEX **ind IAsyncReader_SyncRead(((PullPin *)This->Parser.ppPins[0])->pReader, qwOffset, pIndex->cb, (BYTE *)pIndex); rest = pIndex->cb - sizeof(AVISUPERINDEX) + sizeof(RIFFCHUNK) + sizeof(pIndex->aIndex[0]) * ANYSIZE_ARRAY; + TRACE("FOURCC: %s\n", debugstr_an((char *)&pIndex->fcc, 4)); TRACE("wLongsPerEntry: %hd\n", pIndex->wLongsPerEntry); TRACE("bIndexSubType: %hd\n", pIndex->bIndexSubType); TRACE("bIndexType: %hd\n", pIndex->bIndexType); @@ -406,10 +543,10 @@ static HRESULT AVISplitter_ProcessIndex(AVISplitterImpl *This, AVISTDINDEX **ind for (x = 0; x < pIndex->nEntriesInUse; ++x) { - BOOL keyframe = !(pIndex->aIndex[x].dwOffset >> 31); - DWORDLONG offset = pIndex->qwBaseOffset + (pIndex->aIndex[x].dwOffset & ~(1<<31)); + BOOL keyframe = !(pIndex->aIndex[x].dwSize >> 31); + DWORDLONG offset = pIndex->qwBaseOffset + pIndex->aIndex[x].dwOffset; TRACE("dwOffset: %x%08x\n", (DWORD)(offset >> 32), (DWORD)offset); - TRACE("dwSize: %u\n", pIndex->aIndex[x].dwSize); + TRACE("dwSize: %u\n", (pIndex->aIndex[x].dwSize & ~(1<<31))); TRACE("Frame is a keyframe: %s\n", keyframe ? "yes" : "no"); } @@ -432,13 +569,17 @@ static HRESULT AVISplitter_ProcessOldIndex(AVISplitterImpl *This) offset = pAviOldIndex->aIndex[x].dwOffset; chunkid = pAviOldIndex->aIndex[x].dwChunkId; + TRACE("dwChunkId: %.4s\n", (char *)&chunkid); + TRACE("dwFlags: %08x\n", pAviOldIndex->aIndex[x].dwFlags); + TRACE("dwOffset (%s): %08x\n", relative ? "relative" : "absolute", offset); + TRACE("dwSize: %08x\n", pAviOldIndex->aIndex[x].dwSize); + /* Only scan once, or else this will take too long */ if (relative == -1) { IAsyncReader_SyncRead(pin->pReader, offset, sizeof(DWORD), (BYTE *)&temp); relative = (chunkid != temp); - TRACE("dwChunkId: %.4s\n", (char *)&chunkid); if (chunkid == mmioFOURCC('7','F','x','x') && ((char *)&temp)[0] == 'i' && ((char *)&temp)[1] == 'x') relative = FALSE; @@ -464,11 +605,9 @@ static HRESULT AVISplitter_ProcessOldIndex(AVISplitterImpl *This) TRACE("Scanned dwChunkId: %s\n", debugstr_an((char *)&temp2, 4)); } else if (!relative) - TRACE("Scanned dwChunkId: %s\n", debugstr_an((char *)&temp, 4)); - TRACE("dwFlags: %08x\n", pAviOldIndex->aIndex[x].dwFlags); - TRACE("dwOffset (%s): %08x\n", relative ? "relative" : "absolute", offset); - TRACE("dwSize: %08x\n", pAviOldIndex->aIndex[x].dwSize); + TRACE("Scanned dwChunkId: %s\n", debugstr_an((char *)&temp, 4)); } + /* Only dump one packet */ else break; } @@ -486,7 +625,7 @@ static HRESULT AVISplitter_ProcessOldIndex(AVISplitterImpl *This) return S_OK; } -static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE * pData, DWORD cb) +static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE * pData, DWORD cb, ALLOCATOR_PROPERTIES *props) { PIN_INFO piOutput; const RIFFCHUNK * pChunk; @@ -495,24 +634,17 @@ static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE float fSamplesPerSec = 0.0f; DWORD dwSampleSize = 0; DWORD dwLength = 0; - ALLOCATOR_PROPERTIES props; + DWORD nstdindex = 0; static const WCHAR wszStreamTemplate[] = {'S','t','r','e','a','m',' ','%','0','2','d',0}; StreamData *stream; - AVISTDINDEX **stdindex = NULL; - DWORD nstdindex = 0; - - props.cbAlign = 1; - props.cbPrefix = 0; - props.cbBuffer = 0x20000; - props.cBuffers = 2; - ZeroMemory(&amt, sizeof(amt)); piOutput.dir = PINDIR_OUTPUT; piOutput.pFilter = (IBaseFilter *)This; wsprintfW(piOutput.achName, wszStreamTemplate, This->Parser.cStreams); This->streams = CoTaskMemRealloc(This->streams, sizeof(StreamData) * (This->Parser.cStreams+1)); stream = This->streams + This->Parser.cStreams; + ZeroMemory(stream, sizeof(*stream)); for (pChunk = (const RIFFCHUNK *)pData; ((const BYTE *)pChunk >= pData) && ((const BYTE *)pChunk + sizeof(RIFFCHUNK) < pData + cb) && (pChunk->cb > 0); @@ -565,8 +697,8 @@ static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE if (!dwLength) dwLength = This->AviHeader.dwTotalFrames; - if (pStrHdr->dwSuggestedBufferSize) - props.cbBuffer = pStrHdr->dwSuggestedBufferSize; + if (pStrHdr->dwSuggestedBufferSize && pStrHdr->dwSuggestedBufferSize > props->cbBuffer) + props->cbBuffer = pStrHdr->dwSuggestedBufferSize; break; } @@ -588,6 +720,7 @@ static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE ZeroMemory(amt.pbFormat, amt.cbFormat); pvi = (VIDEOINFOHEADER *)amt.pbFormat; pvi->AvgTimePerFrame = (LONGLONG)(10000000.0 / fSamplesPerSec); + CopyMemory(&pvi->bmiHeader, (const BYTE *)(pChunk + 1), pChunk->cb); if (pvi->bmiHeader.biCompression) amt.subtype.Data1 = pvi->bmiHeader.biCompression; @@ -622,7 +755,7 @@ static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE break; } - if (nstdindex > 0) + if (nstdindex++ > 0) { ERR("Stream %d got more than 1 superindex?\n", This->Parser.cStreams); break; @@ -649,15 +782,15 @@ static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE break; } + stream->entries = pIndex->nEntriesInUse; + stream->stdindex = CoTaskMemRealloc(stream->stdindex, sizeof(*stream->stdindex) * stream->entries); for (x = 0; x < pIndex->nEntriesInUse; ++x) { TRACE("qwOffset: %x%08x\n", (DWORD)(pIndex->aIndex[x].qwOffset >> 32), (DWORD)pIndex->aIndex[x].qwOffset); TRACE("dwSize: %u\n", pIndex->aIndex[x].dwSize); TRACE("dwDuration: %u (unreliable)\n", pIndex->aIndex[x].dwDuration); - ++nstdindex; - stdindex = CoTaskMemRealloc(stdindex, sizeof(*stdindex) * nstdindex); - AVISplitter_ProcessIndex(This, &stdindex[nstdindex-1], pIndex->aIndex[x].qwOffset, pIndex->aIndex[x].dwSize); + AVISplitter_ProcessIndex(This, &stream->stdindex[nstdindex-1], pIndex->aIndex[x].qwOffset, pIndex->aIndex[x].dwSize); } break; } @@ -680,12 +813,12 @@ static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE stream->fSamplesPerSec = fSamplesPerSec; stream->dwSampleSize = dwSampleSize; stream->dwLength = dwLength; /* TODO: Use this for mediaseeking */ - stream->entries = nstdindex; - stream->stdindex = stdindex; + stream->packet_queued = CreateEventW(NULL, 0, 0, NULL); - hr = Parser_AddPin(&(This->Parser), &piOutput, &props, &amt); + hr = Parser_AddPin(&(This->Parser), &piOutput, props, &amt); CoTaskMemFree(amt.pbFormat); + return hr; } @@ -737,6 +870,8 @@ static HRESULT AVISplitter_InitializeStreams(AVISplitterImpl *This) for (x = 0; x < This->Parser.cStreams; ++x) { This->streams[x].frames = 0; + This->streams[x].pos = ~0; + This->streams[x].index = 0; } nMax = This->oldindex->cb / sizeof(This->oldindex->aIndex[0]); @@ -750,6 +885,8 @@ static HRESULT AVISplitter_InitializeStreams(AVISplitterImpl *This) FIXME("Stream id %s ignored\n", debugstr_an((char*)&This->oldindex->aIndex[n].dwChunkId, 4)); continue; } + if (This->streams[streamId].pos == ~0) + This->streams[streamId].pos = n; if (This->streams[streamId].streamheader.dwSampleSize) This->streams[streamId].frames += This->oldindex->aIndex[n].dwSize / This->streams[streamId].streamheader.dwSampleSize; @@ -772,23 +909,38 @@ static HRESULT AVISplitter_InitializeStreams(AVISplitterImpl *This) { This->streams[x].frames = This->streams[x].streamheader.dwLength; } + /* MS Avi splitter does seek through the whole file, we should! */ + ERR("We should be manually seeking through the entire file to build an index, because the index is missing!!!\n"); + return E_NOTIMPL; } /* Not much here yet */ for (x = 0; x < This->Parser.cStreams; ++x) { StreamData *stream = This->streams + x; - /* WOEI! */ - double fps; int y; DWORD64 frames = 0; - fps = (double)stream->streamheader.dwRate / (float)stream->streamheader.dwScale; + stream->seek = 1; + if (stream->stdindex) { + stream->index = 0; + stream->pos = 0; for (y = 0; y < stream->entries; ++y) { - frames += stream->stdindex[y]->nEntriesInUse; + if (stream->streamheader.dwSampleSize) + { + int z; + + for (z = 0; z < stream->stdindex[y]->nEntriesInUse; ++z) + { + UINT len = stream->stdindex[y]->aIndex[z].dwSize & ~(1 << 31); + frames += len / stream->streamheader.dwSampleSize + !!(len % stream->streamheader.dwSampleSize); + } + } + else + frames += stream->stdindex[y]->nEntriesInUse; } } else frames = stream->frames; @@ -802,7 +954,6 @@ static HRESULT AVISplitter_InitializeStreams(AVISplitterImpl *This) frames /= stream->streamheader.dwRate; - TRACE("fps: %f\n", fps); TRACE("Duration: %d days, %d hours, %d minutes and %d seconds\n", (DWORD)(frames / 86400), (DWORD)((frames % 86400) / 3600), (DWORD)((frames % 3600) / 60), (DWORD)(frames % 60)); } @@ -858,6 +1009,9 @@ static HRESULT AVISplitter_InputPin_PreConnect(IPin * iface, IPin * pConnectPin, pAviSplit->AviHeader.cb = 0; + /* Stream list will set the buffer size here, so set a default and allow an override */ + props->cbBuffer = 0x20000; + for (pCurrentChunk = (RIFFCHUNK *)pBuffer; (BYTE *)pCurrentChunk + sizeof(*pCurrentChunk) < pBuffer + list.cb; pCurrentChunk = (RIFFCHUNK *)(((BYTE *)pCurrentChunk) + sizeof(*pCurrentChunk) + pCurrentChunk->cb)) { RIFFLIST * pList; @@ -873,7 +1027,7 @@ static HRESULT AVISplitter_InputPin_PreConnect(IPin * iface, IPin * pConnectPin, switch (pList->fccListType) { case ckidSTREAMLIST: - hr = AVISplitter_ProcessStreamList(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST)); + hr = AVISplitter_ProcessStreamList(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST), props); break; case ckidODML: hr = AVISplitter_ProcessODML(pAviSplit, (BYTE *)pCurrentChunk + sizeof(RIFFLIST), pCurrentChunk->cb + sizeof(RIFFCHUNK) - sizeof(RIFFLIST)); @@ -935,6 +1089,11 @@ static HRESULT AVISplitter_InputPin_PreConnect(IPin * iface, IPin * pConnectPin, hr = IAsyncReader_SyncRead(This->pReader, BYTES_FROM_MEDIATIME(pAviSplit->CurrentChunkOffset), sizeof(pAviSplit->CurrentChunk), (BYTE *)&pAviSplit->CurrentChunk); } + props->cbAlign = 1; + props->cbPrefix = 0; + /* Comrades, prevent shortage of buffers, or you will feel the consequences! DA! */ + props->cBuffers = 3 * pAviSplit->Parser.cStreams; + /* Now peek into the idx1 index, if available */ if (hr == S_OK && (total - pos) > sizeof(RIFFCHUNK)) { @@ -1003,15 +1162,24 @@ static HRESULT AVISplitter_InputPin_PreConnect(IPin * iface, IPin * pConnectPin, return hr; } -static HRESULT AVISplitter_Cleanup(LPVOID iface) +static HRESULT AVISplitter_Flush(LPVOID iface) { AVISplitterImpl *This = (AVISplitterImpl*)iface; + DWORD x; - TRACE("(%p)->()\n", This); + ERR("(%p)->()\n", This); + + for (x = 0; x < This->Parser.cStreams; ++x) + { + StreamData *stream = This->streams + x; + + if (stream->sample) + assert(IMediaSample_Release(stream->sample) == 0); + stream->sample = NULL; - if (This->pCurrentSample) - IMediaSample_Release(This->pCurrentSample); - This->pCurrentSample = NULL; + ResetEvent(stream->packet_queued); + assert(!stream->thread); + } return S_OK; } @@ -1035,18 +1203,170 @@ static HRESULT AVISplitter_Disconnect(LPVOID iface) CoTaskMemFree(stream->stdindex[i]); CoTaskMemFree(stream->stdindex); + CloseHandle(stream->packet_queued); } CoTaskMemFree(This->streams); This->streams = NULL; + return S_OK; +} + +static ULONG WINAPI AVISplitter_Release(IBaseFilter *iface) +{ + AVISplitterImpl *This = (AVISplitterImpl *)iface; + ULONG ref; + + ref = InterlockedDecrement(&This->Parser.refCount); + + TRACE("(%p)->() Release from %d\n", This, ref + 1); + + if (!ref) + { + AVISplitter_Flush(This); + Parser_Destroy(&This->Parser); + } + + return ref; +} + +static HRESULT AVISplitter_seek(IBaseFilter *iface) +{ + AVISplitterImpl *This = (AVISplitterImpl *)iface; + PullPin *pPin = This->Parser.pInputPin; + LONGLONG newpos, endpos; + DWORD x; + + newpos = This->Parser.mediaSeeking.llCurrent; + endpos = This->Parser.mediaSeeking.llDuration; + + if (newpos > endpos) + { + WARN("Requesting position %x%08x beyond end of stream %x%08x\n", (DWORD)(newpos>>32), (DWORD)newpos, (DWORD)(endpos>>32), (DWORD)endpos); + return E_INVALIDARG; + } + + FIXME("Moving position to %u.%03u s!\n", (DWORD)(newpos / 10000000), (DWORD)((newpos / 10000)%1000)); + + EnterCriticalSection(&pPin->thread_lock); + /* Send a flush to all output pins */ + IPin_BeginFlush((IPin *)pPin); + + /* Make sure this is done while stopped, BeginFlush takes care of this */ + EnterCriticalSection(&This->Parser.csFilter); + for (x = 0; x < This->Parser.cStreams; ++x) + { + Parser_OutputPin *pin = (Parser_OutputPin *)This->Parser.ppPins[1+x]; + StreamData *stream = This->streams + x; + IPin *victim = NULL; + LONGLONG wanted_frames; + DWORD last_keyframe = 0, last_keyframeidx = 0, preroll = 0; + + wanted_frames = newpos; + wanted_frames *= stream->streamheader.dwRate; + wanted_frames /= 10000000; + wanted_frames /= stream->streamheader.dwScale; + + IPin_ConnectedTo((IPin *)pin, &victim); + if (victim) + { + IPin_NewSegment(victim, newpos, endpos, pPin->dRate); + IPin_Release(victim); + } + + pin->dwSamplesProcessed = 0; + stream->index = 0; + stream->pos = 0; + stream->seek = 1; + if (stream->stdindex) + { + DWORD y, z = 0; + + for (y = 0; y < stream->entries; ++y) + { + for (z = 0; z < stream->stdindex[y]->nEntriesInUse; ++z) + { + if (stream->streamheader.dwSampleSize) + { + ULONG len = stream->stdindex[y]->aIndex[z].dwSize & ~(1 << 31); + ULONG size = stream->streamheader.dwSampleSize; + + pin->dwSamplesProcessed += len / size; + if (len % size) + ++pin->dwSamplesProcessed; + } + else ++pin->dwSamplesProcessed; + + if (!(stream->stdindex[y]->aIndex[z].dwSize >> 31)) + { + last_keyframe = z; + last_keyframeidx = y; + preroll = 0; + } + else + ++preroll; + + if (pin->dwSamplesProcessed >= wanted_frames) + break; + } + if (pin->dwSamplesProcessed >= wanted_frames) + break; + } + stream->index = last_keyframeidx; + stream->pos = last_keyframe; + } + else + { + DWORD nMax, n; + nMax = This->oldindex->cb / sizeof(This->oldindex->aIndex[0]); + + for (n = 0; n < nMax; ++n) + { + DWORD streamId = StreamFromFOURCC(This->oldindex->aIndex[n].dwChunkId); + if (streamId != x) + continue; + + if (stream->streamheader.dwSampleSize) + { + ULONG len = This->oldindex->aIndex[n].dwSize; + ULONG size = stream->streamheader.dwSampleSize; + + pin->dwSamplesProcessed += len / size; + if (len % size) + ++pin->dwSamplesProcessed; + } + else ++pin->dwSamplesProcessed; + + if (This->oldindex->aIndex[n].dwFlags & AVIIF_KEYFRAME) + { + last_keyframe = n; + preroll = 0; + } + else + ++preroll; + + if (pin->dwSamplesProcessed >= wanted_frames) + break; + } + assert(n < nMax); + stream->pos = last_keyframe; + stream->index = 0; + } + stream->preroll = preroll; + stream->seek = 1; + } + LeaveCriticalSection(&This->Parser.csFilter); + + TRACE("Done flushing\n"); + IPin_EndFlush((IPin *)pPin); + LeaveCriticalSection(&pPin->thread_lock); return S_OK; } -static const IBaseFilterVtbl AVISplitter_Vtbl = +static const IBaseFilterVtbl AVISplitterImpl_Vtbl = { Parser_QueryInterface, Parser_AddRef, - Parser_Release, + AVISplitter_Release, Parser_GetClassID, Parser_Stop, Parser_Pause, @@ -1076,11 +1396,10 @@ HRESULT AVISplitter_create(IUnknown * pUnkOuter, LPVOID * ppv) /* Note: This memory is managed by the transform filter once created */ This = CoTaskMemAlloc(sizeof(AVISplitterImpl)); - This->pCurrentSample = NULL; This->streams = NULL; This->oldindex = NULL; - 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); + hr = Parser_Create(&(This->Parser), &AVISplitterImpl_Vtbl, &CLSID_AviSplitter, AVISplitter_Sample, AVISplitter_QueryAccept, AVISplitter_InputPin_PreConnect, AVISplitter_Flush, AVISplitter_Disconnect, AVISplitter_first_request, AVISplitter_done_process, NULL, AVISplitter_seek, NULL); if (FAILED(hr)) return hr; -- 1.5.4.1