From d24260be8983296bc2ecdf94ce4da095f9b58148 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Wed, 30 Apr 2008 15:13:41 -0700 Subject: [PATCH] quartz: Update avisplitter to use seperate threads to stream data, with tests --- dlls/quartz/avisplit.c | 788 ++++++++++++++++++++++++++------------- dlls/quartz/tests/Makefile.in | 1 + dlls/quartz/tests/avisplitter.c | 462 +++++++++++++++++++++++ 3 files changed, 992 insertions(+), 259 deletions(-) create mode 100644 dlls/quartz/tests/avisplitter.c diff --git a/dlls/quartz/avisplit.c b/dlls/quartz/avisplit.c index e09adb7..0aaa935 100644 --- a/dlls/quartz/avisplit.c +++ b/dlls/quartz/avisplit.c @@ -20,9 +20,20 @@ * 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 + * - Seeking doesn't entirely work: Seeking should be done to the last + * keyframe BEFORE the requested position, all samples should probably have + * a special marker, so that the codec (xvid?) knows that it is special + * Otherwise a huge audio/video desynchronization may occurs + * - Files without an index are not handled correctly yet. + * - OpenDML indexes not be handled correctly, see the #if 0'd lines + * This was required to read the only OpenDML index file I had, I need to + * test this with more different files. + * - 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 +72,396 @@ 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; } 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; - hr = IMediaSample_GetPointer(pSample, &pbSrcStream); + TRACE("(%p, %u)->()\n", This, streamnumber); - hr = IMediaSample_GetTime(pSample, &tStart, &tStop); + hr = IMemAllocator_GetBuffer(pin->pAlloc, &sample, NULL, NULL, 0); + if (hr != S_OK) + ERR("... %08x?\n", hr); - cbSrcStream = IMediaSample_GetActualDataLength(pSample); + if (SUCCEEDED(hr)) + { + LONGLONG rtSampleStart; + /* Add 4 for the next header, which should hopefully work */ + /* FIXME: Handle end of stream (current index == amount of indexes) */ + LONGLONG rtSampleStop; - /* trace removed for performance reasons */ - /* TRACE("(%p)\n", pSample); */ + stream->pos = stream->pos_next; + stream->index = stream->index_next; - assert(BYTES_FROM_MEDIATIME(tStop - tStart) == cbSrcStream); + IMediaSample_SetDiscontinuity(sample, stream->seek); + stream->seek = FALSE; + IMediaSample_SetPreroll(sample, FALSE); + IMediaSample_SetSyncPoint(sample, TRUE); - 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) + if (stream->stdindex) { - FIXME("large offset\n"); - hr = S_OK; - goto skip; - } + AVISTDINDEX *index = stream->stdindex[stream->index]; + AVISTDINDEX_ENTRY *entry = &index->aIndex[stream->pos]; + BOOL keyframe; - memcpy(&This->CurrentChunk, pbSrcStream + offset, sizeof(RIFFCHUNK)); - } + rtSampleStart = index->qwBaseOffset; + keyframe = !(entry->dwSize >> 31); + rtSampleStart += entry->dwOffset; + rtSampleStart = MEDIATIME_FROM_BYTES(rtSampleStart); - assert(This->CurrentChunkOffset + MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)) < tStop); + ++stream->pos_next; + if (index->nEntriesInUse == stream->pos_next) + { + stream->pos_next = 0; + ++stream->index_next; + } - 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; + rtSampleStop = rtSampleStart + MEDIATIME_FROM_BYTES(entry->dwSize & ~(1 << 31)); - switch (This->CurrentChunk.fcc) + ERR("offset(%u) size(%u)\n", (DWORD)BYTES_FROM_MEDIATIME(rtSampleStart), (DWORD)BYTES_FROM_MEDIATIME(rtSampleStop - rtSampleStart)); + } + else if (This->oldindex) { - 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)) + 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) { - 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; + FIXME("Only stand alone frames are currently handled correctly!\n"); } -#endif - } + 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 + { + rtSampleStart += MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)); + rtSampleStop += MEDIATIME_FROM_BYTES(sizeof(RIFFCHUNK)); + } + + /* 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 (fcc[0] == 'i' && fcc[1] == 'x') + /* 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); + return S_FALSE; + } + } + else /* TODO: Generate an index automagically */ { - if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE)) - bMoreData = FALSE; - continue; + ERR("CAN'T PLAY WITHOUT AN INDEX! SOS! SOS! SOS!\n"); + assert(0); } - streamId = StreamFromFOURCC(This->CurrentChunk.fcc); + hr = IMediaSample_SetTime(sample, &rtSampleStart, &rtSampleStop); + + hr = IAsyncReader_Request(pin->pReader, sample, streamnumber); - if (streamId > This->Parser.cStreams) + if (FAILED(hr)) + assert(IMediaSample_Release(sample) == 0); + } + else + { + if (sample) { - 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; + ERR("There should be no sample!\n"); + assert(IMediaSample_Release(sample) == 0); } + } + TRACE("--> %08x\n", hr); - pOutputPin = (Parser_OutputPin *)This->Parser.ppPins[streamId + 1]; + return hr; +} - if (!This->pCurrentSample) - { - /* 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); +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]; - if (SUCCEEDED(hr)) - { - hr = IMediaSample_SetActualDataLength(This->pCurrentSample, 0); - assert(hr == S_OK); - } - 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; - } - } + start = pin->dwSamplesProcessed; + start *= stream->streamheader.dwScale; + start *= 10000000; + start /= stream->streamheader.dwRate; - hr = IMediaSample_GetPointer(This->pCurrentSample, &pbDstStream); + if (stream->streamheader.dwSampleSize) + { + ULONG len = IMediaSample_GetActualDataLength(sample); + ULONG size = stream->streamheader.dwSampleSize; - if (SUCCEEDED(hr)) - { - cbDstStream = IMediaSample_GetSize(This->pCurrentSample); + pin->dwSamplesProcessed += len / size; + } + else + ++pin->dwSamplesProcessed; - 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)); + stop = pin->dwSamplesProcessed; + stop *= stream->streamheader.dwScale; + stop *= 10000000; + stop /= stream->streamheader.dwRate; - /* 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); */ - } + IMediaSample_SetTime(sample, &start, &stop); - if (chunk_remaining_bytes <= cbSrcStream - offset_src) - { - 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); - } + hr = OutputPin_SendSample(&pin->pin, sample); - if (SUCCEEDED(hr)) - { - REFERENCE_TIME tAviStart, tAviStop; - StreamData *stream = This->streams + streamId; +/* 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; +} - /* FIXME: hack */ - if (pOutputPin->dwSamplesProcessed == 0) - IMediaSample_SetDiscontinuity(This->pCurrentSample, TRUE); +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; - IMediaSample_SetSyncPoint(This->pCurrentSample, bSyncPoint); + do + { + HRESULT nexthr = S_FALSE; + IMediaSample *sample; - pOutputPin->dwSamplesProcessed++; + WaitForSingleObject(This->streams[streamnumber].packet_queued, INFINITE); + sample = This->streams[streamnumber].sample; + This->streams[streamnumber].sample = NULL; + if (!sample) + break; - 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); + nexthr = AVISplitter_next_request(This, streamnumber); - IMediaSample_SetTime(This->pCurrentSample, &tAviStart, &tAviStop); + hr = AVISplitter_Receive(This, sample, streamnumber); + if (hr != S_OK) + FIXME("Receiving error: %08x\n", hr); - hr = OutputPin_SendSample(&pOutputPin->pin, This->pCurrentSample); - if (hr != S_OK && hr != VFW_E_NOT_CONNECTED) - ERR("Error sending sample (%x)\n", hr); - } + IMediaSample_Release(sample); - if (This->pCurrentSample) - IMediaSample_Release(This->pCurrentSample); - - This->pCurrentSample = NULL; + hr = nexthr; + } while (!This->Parser.pInputPin->stop_playback && hr == S_OK); - if (S_FALSE == AVISplitter_NextChunk(&This->CurrentChunkOffset, &This->CurrentChunk, &tStart, &tStop, pbSrcStream, FALSE)) - bMoreData = FALSE; - } - else - { - if (SUCCEEDED(hr)) - { - memcpy(pbDstStream + IMediaSample_GetActualDataLength(This->pCurrentSample), pbSrcStream + offset_src, cbSrcStream - offset_src); - IMediaSample_SetActualDataLength(This->pCurrentSample, cbSrcStream - offset_src + IMediaSample_GetActualDataLength(This->pCurrentSample)); - } - bMoreData = FALSE; - } - } + 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; +} -skip: - if (tStop >= This->EndOfFile) +/* 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) { - int i; + StreamData *stream = This->streams + x; - TRACE("End of file reached\n"); + /* Nothing should be running at this point */ + assert(!stream->thread); - for (i = 0; i < This->Parser.cStreams; i++) - { - IPin* ppin; - HRESULT hr; + assert(!sample); + /* It could be we asked the thread to terminate, and the thread + * already terminated before receiving the deathwish */ + ResetEvent(stream->packet_queued); - TRACE("Send End Of Stream to output pin %d\n", i); + stream->pos_next = stream->pos; + stream->index_next = stream->index; - hr = IPin_ConnectedTo(This->Parser.ppPins[i+1], &ppin); - if (SUCCEEDED(hr)) - { - hr = IPin_EndOfStream(ppin); - IPin_Release(ppin); - } - if (FAILED(hr)) + /* 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) + { + 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) { - ERR("%x\n", hr); - break; + ERR("dwUser: %lu, x-1: %u\n", dwUser, x-1); + assert(dwUser == x - 1); } + AVISplitter_Sample(iface, sample, dwUser); + IMediaSample_Release(sample); } - /* Force the pullpin thread to stop */ - hr = S_FALSE; + hr = AVISplitter_next_request(This, x); + TRACE("-->%08x\n", hr); + assert(SUCCEEDED(hr)); + /* Could be an EOF instead */ + have_sample = (hr == S_OK); + } + + /* FIXME: Don't do this for each pin that sent an EOF */ + for (x = 0; x < This->Parser.cStreams; ++x) + { + struct thread_args *args = CoTaskMemAlloc(sizeof(*args)); + DWORD tid; + 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); + WaitForSingleObject(stream->thread, INFINITE); + CloseHandle(stream->thread); + stream->thread = NULL; + } + 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)) @@ -404,12 +511,25 @@ static HRESULT AVISplitter_ProcessIndex(AVISplitterImpl *This, AVISTDINDEX **ind return E_INVALIDARG; } +#if 0 + /* For my stupid test movie it seems that the values for offset and size are swapped + * + * Perhaps test if this is more generic? + */ for (x = 0; x < pIndex->nEntriesInUse; ++x) { - BOOL keyframe = !(pIndex->aIndex[x].dwOffset >> 31); - DWORDLONG offset = pIndex->qwBaseOffset + (pIndex->aIndex[x].dwOffset & ~(1<<31)); + DWORD temp = pIndex->aIndex[x].dwSize; + pIndex->aIndex[x].dwSize = pIndex->aIndex[x].dwOffset; + pIndex->aIndex[x].dwOffset = temp; + } +#endif + + for (x = 0; x < pIndex->nEntriesInUse; ++x) + { + 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 +552,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 +588,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 +608,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 +617,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 +680,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 +703,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 +738,7 @@ static HRESULT AVISplitter_ProcessStreamList(AVISplitterImpl * This, const BYTE break; } - if (nstdindex > 0) + if (nstdindex++ > 0) { ERR("Stream %d got more then 1 superindex?\n", This->Parser.cStreams); break; @@ -649,15 +765,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 +796,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 +853,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 +868,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,20 +892,24 @@ 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; @@ -802,7 +926,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 +981,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 +999,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 +1061,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 +1134,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); - if (This->pCurrentSample) - IMediaSample_Release(This->pCurrentSample); - This->pCurrentSample = NULL; + 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; + + ResetEvent(stream->sample); + assert(!stream->thread); + } return S_OK; } @@ -1035,18 +1175,149 @@ 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; + + 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; + ULONG size = stream->streamheader.dwSampleSize; + + pin->dwSamplesProcessed += len / size; + if (len % size) + ++pin->dwSamplesProcessed; + } + else ++pin->dwSamplesProcessed; + if (pin->dwSamplesProcessed >= wanted_frames) + break; + } + if (pin->dwSamplesProcessed >= wanted_frames) + break; + } + stream->index = y; + stream->pos = z; + } + 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 (pin->dwSamplesProcessed >= wanted_frames) + break; + } + assert(n < nMax); + stream->pos = n; + } + + 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 +1347,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; diff --git a/dlls/quartz/tests/Makefile.in b/dlls/quartz/tests/Makefile.in index 10f1046..a3e2bc4 100644 --- a/dlls/quartz/tests/Makefile.in +++ b/dlls/quartz/tests/Makefile.in @@ -6,6 +6,7 @@ TESTDLL = quartz.dll IMPORTS = strmiids uuid ole32 kernel32 CTESTS = \ + avisplitter.c \ filtergraph.c \ memallocator.c \ misc.c \ diff --git a/dlls/quartz/tests/avisplitter.c b/dlls/quartz/tests/avisplitter.c new file mode 100644 index 0000000..1c2a922 --- /dev/null +++ b/dlls/quartz/tests/avisplitter.c @@ -0,0 +1,462 @@ +/* + * Unit tests for the avi splitter functions + * + * Copyright (C) 2007 Google (Lei Zhang) + * Copyright (C) 2008 Google (Maarten Lankhorst) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define COBJMACROS + +#include "wine/test.h" +#include "initguid.h" +#include "dshow.h" +#include "tlhelp32.h" + +static IUnknown *pAviSplitter = NULL; + +static int count_threads(void) +{ + THREADENTRY32 te; + int threads; + HANDLE h; + + h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + te.dwSize = sizeof(te); + + if (h == INVALID_HANDLE_VALUE) + return -1; + + Thread32First(h, &te); + if (te.th32OwnerProcessID == GetCurrentProcessId()) + threads = 1; + else + threads = 0; + + while (Thread32Next(h, &te)) + if (te.th32OwnerProcessID == GetCurrentProcessId()) + ++threads; + + CloseHandle(h); + return threads; +} + +static int create_avisplitter(void) +{ + HRESULT hr; + + hr = CoCreateInstance(&CLSID_AviSplitter, NULL, CLSCTX_INPROC_SERVER, + &IID_IUnknown, (LPVOID*)&pAviSplitter); + return (hr == S_OK && pAviSplitter != NULL); +} + +static void release_avisplitter(void) +{ + HRESULT hr; + + hr = IUnknown_Release(pAviSplitter); + + /* Looks like wine has a reference leak somewhere on test_threads tests, + * it passes in windows + */ + todo_wine ok(hr == 0, "IUnknown_Release failed with %d\n", (INT)hr); + while (hr > 0) + hr = IUnknown_Release(pAviSplitter); + pAviSplitter = NULL; +} + +static void test_query_interface(void) +{ + HRESULT hr; + ULONG ref; + IUnknown *iface= NULL; + + hr = IUnknown_QueryInterface(pAviSplitter, &IID_IBaseFilter, + (void**)&iface); + + ok(hr == S_OK, + "IID_IBaseFilter should exist, got %08x!\n", GetLastError()); + if (hr == S_OK) + { + ref = IUnknown_Release(iface); + iface = NULL; + ok(ref == 1, "Reference is %u, expected 1\n", ref); + } + + hr = IUnknown_QueryInterface(pAviSplitter, &IID_IMediaSeeking, + (void**)&iface); + if (hr == S_OK) + ref = IUnknown_Release(iface); + iface = NULL; + todo_wine ok(hr == E_NOINTERFACE, + "Query for IMediaSeeking returned: %08x\n", hr); + +/* These interfaces should not be present: + IID_IKsPropertySet, IID_IMediaPosition, IID_IQualityControl, IID_IQualProp +*/ +} + +static void test_pin(IPin *pin) +{ + IMemInputPin *mpin = NULL; + + IPin_QueryInterface(pin, &IID_IMemInputPin, (void **)&mpin); + + ok(mpin == NULL, "IMemInputPin found!\n"); + if (mpin) + IMemInputPin_Release(mpin); + /* TODO */ +} + +static void test_basefilter(void) +{ + IEnumPins *pin_enum = NULL; + IBaseFilter *base = NULL; + IPin *pins[2]; + ULONG ref; + HRESULT hr; + + IUnknown_QueryInterface(pAviSplitter, &IID_IBaseFilter, (void *)&base); + if (base == NULL) + { + /* test_query_interface handles this case */ + skip("No IBaseFilter\n"); + return; + } + + hr = IBaseFilter_EnumPins(base, NULL); + ok(hr == E_POINTER, "hr = %08x and not E_POINTER\n", hr); + + hr= IBaseFilter_EnumPins(base, &pin_enum); + ok(hr == S_OK, "hr = %08x and not S_OK\n", hr); + + hr = IEnumPins_Next(pin_enum, 1, NULL, NULL); + ok(hr == E_POINTER, "hr = %08x and not E_POINTER\n", hr); + + hr = IEnumPins_Next(pin_enum, 2, pins, NULL); + ok(hr == E_INVALIDARG, "hr = %08x and not E_INVALIDARG\n", hr); + + pins[0] = (void *)0xdead; + pins[1] = (void *)0xdeed; + + hr = IEnumPins_Next(pin_enum, 2, pins, &ref); + ok(hr == S_FALSE, "hr = %08x instead of S_FALSE\n", hr); + ok(pins[0] != (void *)0xdead && pins[0] != NULL, + "pins[0] = %p\n", pins[0]); + if (pins[0] != (void *)0xdead && pins[0] != NULL) + { + test_pin(pins[0]); + IPin_Release(pins[0]); + } + + ok(pins[1] == (void *)0xdeed, "pins[1] = %p\n", pins[1]); + + ref = IEnumPins_Release(pin_enum); + ok(ref == 0, "ref is %u and not 0!\n", ref); + + IBaseFilter_Release(base); +} + +static const WCHAR wfile[] = {'t','e','s','t','.','a','v','i',0}; +static const char afile[] = "test.avi"; + +/* This test doesn't use the quartz filtergraph because it makes it impossible + * to be certain that a thread is really one owned by the avi splitter + * A lot of the decoder filters will also have their own thread, and windows' + * filtergraph has a seperate thread for start/stop/seeking requests. + * By avoiding the filtergraph all together and connecting streams directly to + * the null renderer I am sure that this is not the case here. + */ +static void test_threads() +{ + IFileSourceFilter *pfile = NULL; + IBaseFilter *preader = NULL, *pavi = NULL; + IEnumPins *enumpins = NULL; + IPin *filepin = NULL, *avipin = NULL; + HRESULT hr; + int baselevel, curlevel, expected; + HANDLE file = NULL; + PIN_DIRECTION dir = PINDIR_OUTPUT; + char buffer[13]; + DWORD readbytes; + FILTER_STATE state; + + /* Before doing anything */ + baselevel = count_threads(); + expected = 1; + ok(baselevel == expected, + "Basic amount of threads should be %d, not %d!\n", expected, baselevel); + + file = CreateFileW(wfile, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + if (!file) + { + skip("Could not read test file \"%s\", skipping test\n", afile); + return; + } + + memset(buffer, 0, 13); + readbytes = 12; + ReadFile(file, buffer, readbytes, &readbytes, NULL); + CloseHandle(file); + if (strncmp(buffer, "RIFF", 4) || strcmp(buffer + 8, "AVI ")) + { + skip("%s is not an avi riff file, not doing the avi splitter test\n", + afile); + return; + } + + hr = IUnknown_QueryInterface(pAviSplitter, &IID_IFileSourceFilter, + (void **)&pfile); + ok(hr == E_NOINTERFACE, + "Avi splitter returns unexpected error: %08x\n", hr); + if (pfile) + IUnknown_Release(pfile); + pfile = NULL; + + hr = CoCreateInstance(&CLSID_AsyncReader, NULL, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (LPVOID*)&preader); + ok(hr == S_OK, "Could not create asynchronous reader: %08x\n", hr); + if (hr != S_OK) + goto fail; + + hr = IUnknown_QueryInterface(preader, &IID_IFileSourceFilter, + (void**)&pfile); + ok(hr == S_OK, "Could not get IFileSourceFilter: %08x\n", hr); + if (hr != S_OK) + goto fail; + + hr = IUnknown_QueryInterface(pAviSplitter, &IID_IBaseFilter, + (void**)&pavi); + ok(hr == S_OK, "Could not get base filter: %08x\n", hr); + if (hr != S_OK) + goto fail; + + hr = IFileSourceFilter_Load(pfile, wfile, NULL); + if (hr != S_OK) + { + trace("Could not load file\n"); + goto fail; + } + + hr = IBaseFilter_EnumPins(preader, &enumpins); + ok(hr == S_OK, "No enumpins: %08x\n", hr); + if (hr != S_OK) + goto fail; + + hr = IEnumPins_Next(enumpins, 1, &filepin, NULL); + ok(hr == S_OK, "No pin: %08x\n", hr); + if (hr != S_OK) + goto fail; + + IUnknown_Release(enumpins); + enumpins = NULL; + + hr = IBaseFilter_EnumPins(pavi, &enumpins); + ok(hr == S_OK, "No enumpins: %08x\n", hr); + if (hr != S_OK) + goto fail; + + hr = IEnumPins_Next(enumpins, 1, &avipin, NULL); + ok(hr == S_OK, "No pin: %08x\n", hr); + if (hr != S_OK) + goto fail; + + curlevel = count_threads(); + ok(curlevel == baselevel, + "Amount of threads should be %d not %d\n", baselevel, curlevel); + + hr = IPin_Connect(filepin, avipin, NULL); + ok(hr == S_OK, "Could not connect: %08x\n", hr); + if (hr != S_OK) + goto fail; + + expected = 1 + baselevel; + curlevel = count_threads(); + todo_wine ok(curlevel == expected, + "Amount of threads should be %d not %d\n", expected, curlevel); + + IUnknown_Release(avipin); + avipin = NULL; + + IEnumPins_Reset(enumpins); + + /* Windows puts the pins in the order: Outputpins - Inputpin, + * wine does the reverse, just don't test it for now + * Hate to admit it, but windows way makes more sense + */ + while (IEnumPins_Next(enumpins, 1, &avipin, NULL) == S_OK) + { + ok(hr == S_OK, "hr: %08x\n", hr); + IPin_QueryDirection(avipin, &dir); + if (dir == PINDIR_OUTPUT) + { + /* Well, connect it to a null renderer! */ + IBaseFilter *pnull = NULL; + IEnumPins *nullenum = NULL; + IPin *nullpin = NULL; + + hr = CoCreateInstance(&CLSID_NullRenderer, NULL, + CLSCTX_INPROC_SERVER, &IID_IBaseFilter, (LPVOID*)&pnull); + ok(hr == S_OK, "Could not create null renderer: %08x\n", hr); + if (hr != S_OK) + break; + + IBaseFilter_EnumPins(pnull, &nullenum); + IEnumPins_Next(nullenum, 1, &nullpin, NULL); + IEnumPins_Release(nullenum); + IPin_QueryDirection(nullpin, &dir); + + hr = IPin_Connect(avipin, nullpin, NULL); + ok(hr == S_OK, "Failed to connect output pin: %08x\n", hr); + IPin_Release(nullpin); + if (hr != S_OK) + { + IBaseFilter_Release(pnull); + break; + } + IBaseFilter_Run(pnull, 0); + ++expected; + } + + IUnknown_Release(avipin); + avipin = NULL; + } + + if (avipin) + IUnknown_Release(avipin); + avipin = NULL; + + if (hr != S_OK) + goto fail2; + /* At this point there is a minimalistic connected avi splitter that can + * Be used for all sorts of source filter tests, however that still needs + * to be written at a later time. + * + * Interesting tests: + * - Can you disconnect an output pin while running? + * Expecting: Yes + * - Can you disconnect the pullpin while running? + * Expecting: No + * - Is the reference count incremented during playback or when connected? + * Does this happen once for every output pin? Or is there something else + * going on. + * Expecting: You tell me + */ + + IBaseFilter_Run(preader, 0); + IBaseFilter_Run(pavi, 0); + IBaseFilter_GetState(pavi, INFINITE, &state); + + curlevel = count_threads(); + /* On a 2 stream filter, there are 4 or 5 threads (seems to be 5) + * One is the thread we are in. That leaves 3 or 4 for other dark purposes + * Wine is 1 thread short! + */ + ok(curlevel == expected || curlevel == expected + 1, + "Amount of threads should be %d not %d\n", expected, curlevel); + + IBaseFilter_Pause(pavi); + IBaseFilter_Pause(preader); + IBaseFilter_Stop(pavi); + IBaseFilter_Stop(preader); + IBaseFilter_GetState(pavi, INFINITE, &state); + IBaseFilter_GetState(preader, INFINITE, &state); + +fail2: + IEnumPins_Reset(enumpins); + while (IEnumPins_Next(enumpins, 1, &avipin, NULL) == S_OK) + { + IPin *to = NULL; + + IPin_QueryDirection(avipin, &dir); + IPin_ConnectedTo(avipin, &to); + if (to) + { + IPin_Release(to); + + if (dir == PINDIR_OUTPUT) + { + PIN_INFO info; + IPin_QueryPinInfo(to, &info); + + /* Release twice: Once normal, second from the + * previous while loop + */ + IBaseFilter_Stop(info.pFilter); + IPin_Disconnect(to); + IPin_Disconnect(avipin); + IBaseFilter_Release(info.pFilter); + IBaseFilter_Release(info.pFilter); + } + else + { + IPin_Disconnect(to); + IPin_Disconnect(avipin); + } + } + IPin_Release(avipin); + avipin = NULL; + } + +fail: + if (hr != S_OK) + skip("Prerequisites not matched, skipping remainder of test\n"); + if (enumpins) + IUnknown_Release(enumpins); + + if (avipin) + IUnknown_Release(avipin); + if (filepin) + { + IPin *to = NULL; + + IPin_ConnectedTo(filepin, &to); + if (to) + { + IPin_Disconnect(filepin); + IPin_Disconnect(to); + } + IUnknown_Release(filepin); + } + + if (preader) + IUnknown_Release(preader); + if (pavi) + IUnknown_Release(pavi); + if (pfile) + IUnknown_Release(pfile); + + ok(baselevel == 1, + "Basic amount of threads should be %d, not %d!\n", 1, baselevel); +} + +START_TEST(avisplitter) +{ + CoInitialize(NULL); + + if (!create_avisplitter()) + { + skip("Could not create avisplitter\n"); + return; + } + + test_query_interface(); + test_basefilter(); + test_threads(); + + release_avisplitter(); +} -- 1.5.4.1