| /* | |
| * QuickTime splitter + decoder | |
| * | |
| * Copyright 2011 Aric Stewart for CodeWeavers | |
| * | |
| * 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 | |
| */ | |
| #include "config.h" | |
| #define ULONG CoreFoundation_ULONG | |
| #define HRESULT CoreFoundation_HRESULT | |
| #define LoadResource __carbon_LoadResource | |
| #define CompareString __carbon_CompareString | |
| #define GetCurrentThread __carbon_GetCurrentThread | |
| #define GetCurrentProcess __carbon_GetCurrentProcess | |
| #define AnimatePalette __carbon_AnimatePalette | |
| #define EqualRgn __carbon_EqualRgn | |
| #define FillRgn __carbon_FillRgn | |
| #define FrameRgn __carbon_FrameRgn | |
| #define GetPixel __carbon_GetPixel | |
| #define InvertRgn __carbon_InvertRgn | |
| #define LineTo __carbon_LineTo | |
| #define OffsetRgn __carbon_OffsetRgn | |
| #define PaintRgn __carbon_PaintRgn | |
| #define Polygon __carbon_Polygon | |
| #define ResizePalette __carbon_ResizePalette | |
| #define SetRectRgn __carbon_SetRectRgn | |
| #define CheckMenuItem __carbon_CheckMenuItem | |
| #define DeleteMenu __carbon_DeleteMenu | |
| #define DrawMenuBar __carbon_DrawMenuBar | |
| #define EnableMenuItem __carbon_EnableMenuItem | |
| #define EqualRect __carbon_EqualRect | |
| #define FillRect __carbon_FillRect | |
| #define FrameRect __carbon_FrameRect | |
| #define GetCursor __carbon_GetCursor | |
| #define GetMenu __carbon_GetMenu | |
| #define InvertRect __carbon_InvertRect | |
| #define IsWindowVisible __carbon_IsWindowVisible | |
| #define MoveWindow __carbon_MoveWindow | |
| #define OffsetRect __carbon_OffsetRect | |
| #define PtInRect __carbon_PtInRect | |
| #define SetCursor __carbon_SetCursor | |
| #define SetRect __carbon_SetRect | |
| #define ShowCursor __carbon_ShowCursor | |
| #define ShowWindow __carbon_ShowWindow | |
| #define UnionRect __carbon_UnionRect | |
| #include <QuickTime/Movies.h> | |
| #include <QuickTime/QuickTimeComponents.h> | |
| #undef LoadResource | |
| #undef CompareString | |
| #undef GetCurrentThread | |
| #undef _CDECL | |
| #undef DPRINTF | |
| #undef GetCurrentProcess | |
| #undef AnimatePalette | |
| #undef EqualRgn | |
| #undef FillRgn | |
| #undef FrameRgn | |
| #undef GetPixel | |
| #undef InvertRgn | |
| #undef LineTo | |
| #undef OffsetRgn | |
| #undef PaintRgn | |
| #undef Polygon | |
| #undef ResizePalette | |
| #undef SetRectRgn | |
| #undef CheckMenuItem | |
| #undef DeleteMenu | |
| #undef DrawMenuBar | |
| #undef EnableMenuItem | |
| #undef EqualRect | |
| #undef FillRect | |
| #undef FrameRect | |
| #undef GetCursor | |
| #undef GetMenu | |
| #undef InvertRect | |
| #undef IsWindowVisible | |
| #undef MoveWindow | |
| #undef OffsetRect | |
| #undef PtInRect | |
| #undef SetCursor | |
| #undef SetRect | |
| #undef ShowCursor | |
| #undef ShowWindow | |
| #undef UnionRect | |
| #undef ULONG | |
| #undef HRESULT | |
| #undef DPRINTF | |
| #undef STDMETHODCALLTYPE | |
| #include <assert.h> | |
| #include <stdio.h> | |
| #include <stdarg.h> | |
| #define NONAMELESSSTRUCT | |
| #define NONAMELESSUNION | |
| #define COBJMACROS | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "wtypes.h" | |
| #include "winuser.h" | |
| #include "dshow.h" | |
| #include "wine/unicode.h" | |
| #include "wine/debug.h" | |
| #include "wine/strmbase.h" | |
| #include "qtprivate.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(qtsplitter); | |
| extern CLSID CLSID_QTSplitter; | |
| typedef struct QTOutPin { | |
| BaseOutputPin pin; | |
| IQualityControl IQualityControl_iface; | |
| AM_MEDIA_TYPE * pmt; | |
| OutputQueue * queue; | |
| } QTOutPin; | |
| typedef struct QTInPin { | |
| BasePin pin; | |
| GUID subType; | |
| IAsyncReader *pReader; | |
| IMemAllocator *pAlloc; | |
| } QTInPin; | |
| typedef struct QTSplitter { | |
| BaseFilter filter; | |
| QTInPin pInputPin; | |
| QTOutPin *pVideo_Pin; | |
| QTOutPin *pAudio_Pin; | |
| ALLOCATOR_PROPERTIES props; | |
| Movie pQTMovie; | |
| QTVisualContextRef vContext; | |
| MovieAudioExtractionRef aSession; | |
| HANDLE runEvent; | |
| DWORD outputSize; | |
| FILTER_STATE state; | |
| CRITICAL_SECTION csReceive; | |
| SourceSeeking sourceSeeking; | |
| TimeValue movie_time; | |
| TimeValue movie_start; | |
| TimeScale movie_scale; | |
| HANDLE loaderThread; | |
| HANDLE splitterThread; | |
| } QTSplitter; | |
| static const IPinVtbl QT_OutputPin_Vtbl; | |
| static const IPinVtbl QT_InputPin_Vtbl; | |
| static const IBaseFilterVtbl QT_Vtbl; | |
| static const IMediaSeekingVtbl QT_Seeking_Vtbl; | |
| static HRESULT QT_AddPin(QTSplitter *This, const PIN_INFO *piOutput, const AM_MEDIA_TYPE *amt, BOOL video); | |
| static HRESULT QT_RemoveOutputPins(QTSplitter *This); | |
| static HRESULT WINAPI QTSplitter_ChangeStart(IMediaSeeking *iface); | |
| static HRESULT WINAPI QTSplitter_ChangeStop(IMediaSeeking *iface); | |
| static HRESULT WINAPI QTSplitter_ChangeRate(IMediaSeeking *iface); | |
| static inline QTSplitter *impl_from_IMediaSeeking( IMediaSeeking *iface ) | |
| { | |
| return CONTAINING_RECORD(iface, QTSplitter, sourceSeeking.IMediaSeeking_iface); | |
| } | |
| static inline QTSplitter *impl_from_BaseFilter( BaseFilter *iface ) | |
| { | |
| return CONTAINING_RECORD(iface, QTSplitter, filter); | |
| } | |
| static inline QTSplitter *impl_from_IBaseFilter( IBaseFilter *iface ) | |
| { | |
| return CONTAINING_RECORD(iface, QTSplitter, filter.IBaseFilter_iface); | |
| } | |
| /* | |
| * Base Filter | |
| */ | |
| static IPin* WINAPI QT_GetPin(BaseFilter *iface, int pos) | |
| { | |
| QTSplitter *This = impl_from_BaseFilter(iface); | |
| TRACE("Asking for pos %x\n", pos); | |
| if (pos > 2 || pos < 0) | |
| return NULL; | |
| switch (pos) | |
| { | |
| case 0: | |
| IPin_AddRef(&This->pInputPin.pin.IPin_iface); | |
| return &This->pInputPin.pin.IPin_iface; | |
| case 1: | |
| if (This->pVideo_Pin) | |
| IPin_AddRef(&This->pVideo_Pin->pin.pin.IPin_iface); | |
| return &This->pVideo_Pin->pin.pin.IPin_iface; | |
| case 2: | |
| if (This->pAudio_Pin) | |
| IPin_AddRef(&This->pAudio_Pin->pin.pin.IPin_iface); | |
| return &This->pAudio_Pin->pin.pin.IPin_iface; | |
| default: | |
| return NULL; | |
| } | |
| } | |
| static LONG WINAPI QT_GetPinCount(BaseFilter *iface) | |
| { | |
| QTSplitter *This = impl_from_BaseFilter(iface); | |
| int c = 1; | |
| if (This->pAudio_Pin) c++; | |
| if (This->pVideo_Pin) c++; | |
| return c; | |
| } | |
| static const BaseFilterFuncTable BaseFuncTable = { | |
| QT_GetPin, | |
| QT_GetPinCount | |
| }; | |
| IUnknown * CALLBACK QTSplitter_create(IUnknown *punkout, HRESULT *phr) | |
| { | |
| IUnknown *obj = NULL; | |
| PIN_INFO *piInput; | |
| QTSplitter *This; | |
| static const WCHAR wcsInputPinName[] = {'I','n','p','u','t',' ','P','i','n',0}; | |
| EnterMoviesOnThread(0); | |
| RegisterWineDataHandler(); | |
| This = CoTaskMemAlloc(sizeof(*This)); | |
| obj = (IUnknown*)This; | |
| if (!This) | |
| { | |
| *phr = E_OUTOFMEMORY; | |
| return NULL; | |
| } | |
| ZeroMemory(This,sizeof(*This)); | |
| BaseFilter_Init(&This->filter, &QT_Vtbl, &CLSID_QTSplitter, (DWORD_PTR)(__FILE__ ": QTSplitter.csFilter"), &BaseFuncTable); | |
| InitializeCriticalSection(&This->csReceive); | |
| This->csReceive.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__": QTSplitter.csReceive"); | |
| This->pVideo_Pin = NULL; | |
| This->pAudio_Pin = NULL; | |
| This->state = State_Stopped; | |
| This->aSession = NULL; | |
| This->runEvent = CreateEventW(NULL, 0, 0, NULL); | |
| piInput = &This->pInputPin.pin.pinInfo; | |
| piInput->dir = PINDIR_INPUT; | |
| piInput->pFilter = &This->filter.IBaseFilter_iface; | |
| lstrcpynW(piInput->achName, wcsInputPinName, sizeof(piInput->achName) / sizeof(piInput->achName[0])); | |
| This->pInputPin.pin.IPin_iface.lpVtbl = &QT_InputPin_Vtbl; | |
| This->pInputPin.pin.refCount = 1; | |
| This->pInputPin.pin.pConnectedTo = NULL; | |
| This->pInputPin.pin.pCritSec = &This->filter.csFilter; | |
| SourceSeeking_Init(&This->sourceSeeking, &QT_Seeking_Vtbl, QTSplitter_ChangeStop, QTSplitter_ChangeStart, QTSplitter_ChangeRate, &This->filter.csFilter); | |
| *phr = S_OK; | |
| return obj; | |
| } | |
| static void QT_Destroy(QTSplitter *This) | |
| { | |
| IPin *connected = NULL; | |
| ULONG pinref; | |
| TRACE("Destroying\n"); | |
| EnterCriticalSection(&This->csReceive); | |
| /* Don't need to clean up output pins, disconnecting input pin will do that */ | |
| IPin_ConnectedTo(&This->pInputPin.pin.IPin_iface, &connected); | |
| if (connected) | |
| { | |
| IPin_Disconnect(connected); | |
| IPin_Release(connected); | |
| } | |
| pinref = IPin_Release(&This->pInputPin.pin.IPin_iface); | |
| if (pinref) | |
| { | |
| ERR("pinref should be null, is %u, destroying anyway\n", pinref); | |
| assert((LONG)pinref > 0); | |
| while (pinref) | |
| pinref = IPin_Release(&This->pInputPin.pin.IPin_iface); | |
| } | |
| if (This->pQTMovie) | |
| { | |
| DisposeMovie(This->pQTMovie); | |
| This->pQTMovie = NULL; | |
| } | |
| if (This->vContext) | |
| QTVisualContextRelease(This->vContext); | |
| if (This->aSession) | |
| MovieAudioExtractionEnd(This->aSession); | |
| ExitMoviesOnThread(); | |
| LeaveCriticalSection(&This->csReceive); | |
| if (This->loaderThread) | |
| { | |
| WaitForSingleObject(This->loaderThread, INFINITE); | |
| CloseHandle(This->loaderThread); | |
| } | |
| if (This->splitterThread) | |
| { | |
| SetEvent(This->runEvent); | |
| WaitForSingleObject(This->splitterThread, INFINITE); | |
| CloseHandle(This->splitterThread); | |
| } | |
| CloseHandle(This->runEvent); | |
| This->csReceive.DebugInfo->Spare[0] = 0; | |
| DeleteCriticalSection(&This->csReceive); | |
| BaseFilter_Destroy(&This->filter); | |
| CoTaskMemFree(This); | |
| } | |
| static HRESULT WINAPI QT_QueryInterface(IBaseFilter *iface, REFIID riid, LPVOID *ppv) | |
| { | |
| QTSplitter *This = impl_from_IBaseFilter(iface); | |
| TRACE("(%s, %p)\n", debugstr_guid(riid), ppv); | |
| *ppv = NULL; | |
| if (IsEqualIID(riid, &IID_IUnknown)) | |
| *ppv = This; | |
| else if (IsEqualIID(riid, &IID_IPersist)) | |
| *ppv = This; | |
| else if (IsEqualIID(riid, &IID_IMediaFilter)) | |
| *ppv = This; | |
| else if (IsEqualIID(riid, &IID_IBaseFilter)) | |
| *ppv = This; | |
| else if (IsEqualIID(riid, &IID_IMediaSeeking)) | |
| *ppv = &This->sourceSeeking; | |
| if (*ppv) | |
| { | |
| IUnknown_AddRef((IUnknown *)(*ppv)); | |
| return S_OK; | |
| } | |
| if (!IsEqualIID(riid, &IID_IPin) && !IsEqualIID(riid, &IID_IVideoWindow) && | |
| !IsEqualIID(riid, &IID_IAMFilterMiscFlags)) | |
| FIXME("No interface for %s!\n", debugstr_guid(riid)); | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI QT_Release(IBaseFilter *iface) | |
| { | |
| QTSplitter *This = impl_from_IBaseFilter(iface); | |
| ULONG refCount = InterlockedDecrement(&This->filter.refCount); | |
| TRACE("(%p)->() Release from %d\n", This, refCount + 1); | |
| if (!refCount) | |
| QT_Destroy(This); | |
| return refCount; | |
| } | |
| static HRESULT WINAPI QT_Stop(IBaseFilter *iface) | |
| { | |
| QTSplitter *This = impl_from_IBaseFilter(iface); | |
| TRACE("()\n"); | |
| EnterCriticalSection(&This->csReceive); | |
| IAsyncReader_BeginFlush(This->pInputPin.pReader); | |
| IAsyncReader_EndFlush(This->pInputPin.pReader); | |
| LeaveCriticalSection(&This->csReceive); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI QT_Pause(IBaseFilter *iface) | |
| { | |
| HRESULT hr = S_OK; | |
| TRACE("()\n"); | |
| return hr; | |
| } | |
| static OSErr QT_Create_Extract_Session(QTSplitter *filter) | |
| { | |
| AudioStreamBasicDescription aDesc; | |
| OSErr err; | |
| WAVEFORMATEX* pvi; | |
| pvi = (WAVEFORMATEX*)filter->pAudio_Pin->pmt->pbFormat; | |
| err = MovieAudioExtractionBegin(filter->pQTMovie, 0, &filter->aSession); | |
| if (err != noErr) | |
| { | |
| ERR("Failed to begin Extraction session %i\n",err); | |
| return err; | |
| } | |
| err = MovieAudioExtractionGetProperty(filter->aSession, | |
| kQTPropertyClass_MovieAudioExtraction_Audio, | |
| kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, | |
| sizeof(AudioStreamBasicDescription), &aDesc, NULL); | |
| if (err != noErr) | |
| { | |
| MovieAudioExtractionEnd(filter->aSession); | |
| filter->aSession = NULL; | |
| ERR("Failed to get session description %i\n",err); | |
| return err; | |
| } | |
| aDesc.mFormatID = kAudioFormatLinearPCM; | |
| aDesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger + | |
| kAudioFormatFlagIsPacked; | |
| aDesc.mFramesPerPacket = 1; | |
| aDesc.mChannelsPerFrame = pvi->nChannels; | |
| aDesc.mBitsPerChannel = pvi->wBitsPerSample; | |
| aDesc.mSampleRate = pvi->nSamplesPerSec; | |
| aDesc.mBytesPerFrame = (aDesc.mBitsPerChannel * aDesc.mChannelsPerFrame) / 8; | |
| aDesc.mBytesPerPacket = aDesc.mBytesPerFrame * aDesc.mFramesPerPacket; | |
| err = MovieAudioExtractionSetProperty(filter->aSession, | |
| kQTPropertyClass_MovieAudioExtraction_Audio, | |
| kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, | |
| sizeof(AudioStreamBasicDescription), &aDesc); | |
| if (aDesc.mFormatID != kAudioFormatLinearPCM) | |
| { | |
| ERR("Not PCM Wave\n"); | |
| err = -1; | |
| } | |
| if (aDesc.mFormatFlags != kLinearPCMFormatFlagIsSignedInteger + | |
| kAudioFormatFlagIsPacked) | |
| { | |
| ERR("Unhandled Flags\n"); | |
| err = -1; | |
| } | |
| if (aDesc.mFramesPerPacket != 1) | |
| { | |
| ERR("Unhandled Frames per packet %li\n",aDesc.mFramesPerPacket); | |
| err = -1; | |
| } | |
| if (aDesc.mChannelsPerFrame != pvi->nChannels) | |
| { | |
| ERR("Unhandled channel count %li\n",aDesc.mChannelsPerFrame); | |
| err = -1; | |
| } | |
| if (aDesc.mBitsPerChannel != pvi->wBitsPerSample) | |
| { | |
| ERR("Unhandled bits per channel %li\n",aDesc.mBitsPerChannel); | |
| err = -1; | |
| } | |
| if (aDesc.mSampleRate != pvi->nSamplesPerSec) | |
| { | |
| ERR("Unhandled sample rate %f\n",aDesc.mSampleRate); | |
| err = -1; | |
| } | |
| if (err != noErr) | |
| { | |
| ERR("Failed to create Extraction Session\n"); | |
| MovieAudioExtractionEnd(filter->aSession); | |
| filter->aSession = NULL; | |
| } | |
| return err; | |
| } | |
| static DWORD WINAPI QTSplitter_loading_thread(LPVOID data) | |
| { | |
| QTSplitter *This = (QTSplitter *)data; | |
| if (This->pAudio_Pin) | |
| { | |
| /* according to QA1469 a movie has to be fully loaded before we | |
| can reliably start the Extraction session. | |
| If loaded earlier, then we only get an extraction session for | |
| the part of the movie that is loaded at that time. | |
| We are trying to load as much of the movie as we can before we | |
| start extracting. However we can recreate the extraction session | |
| again when we run out of loaded extraction frames. But we want | |
| to try to minimize that. | |
| */ | |
| EnterCriticalSection(&This->csReceive); | |
| while(This->pQTMovie && GetMovieLoadState(This->pQTMovie) < kMovieLoadStateComplete) | |
| { | |
| MoviesTask(This->pQTMovie, 100); | |
| LeaveCriticalSection(&This->csReceive); | |
| Sleep(0); | |
| EnterCriticalSection(&This->csReceive); | |
| } | |
| LeaveCriticalSection(&This->csReceive); | |
| } | |
| return 0; | |
| } | |
| static DWORD WINAPI QTSplitter_thread(LPVOID data) | |
| { | |
| QTSplitter *This = (QTSplitter *)data; | |
| HRESULT hr = S_OK; | |
| TimeValue next_time; | |
| CVPixelBufferRef pixelBuffer = NULL; | |
| OSStatus err; | |
| TimeRecord tr; | |
| WaitForSingleObject(This->runEvent, -1); | |
| EnterCriticalSection(&This->csReceive); | |
| if (!This->pQTMovie) | |
| { | |
| LeaveCriticalSection(&This->csReceive); | |
| return 0; | |
| } | |
| This->state = State_Running; | |
| /* Prime the pump: Needed for MPEG streams */ | |
| GetMovieNextInterestingTime(This->pQTMovie, nextTimeEdgeOK | nextTimeStep, 0, NULL, This->movie_time, 1, &next_time, NULL); | |
| GetMovieTime(This->pQTMovie, &tr); | |
| if (This->pAudio_Pin) | |
| QT_Create_Extract_Session(This); | |
| LeaveCriticalSection(&This->csReceive); | |
| do | |
| { | |
| LONGLONG tStart=0, tStop=0; | |
| LONGLONG mStart=0, mStop=0; | |
| float time; | |
| EnterCriticalSection(&This->csReceive); | |
| if (!This->pQTMovie) | |
| { | |
| LeaveCriticalSection(&This->csReceive); | |
| return 0; | |
| } | |
| GetMovieNextInterestingTime(This->pQTMovie, nextTimeStep, 0, NULL, This->movie_time, 1, &next_time, NULL); | |
| if (next_time == -1) | |
| { | |
| TRACE("No next time\n"); | |
| LeaveCriticalSection(&This->csReceive); | |
| break; | |
| } | |
| tr.value = SInt64ToWide(next_time); | |
| SetMovieTime(This->pQTMovie, &tr); | |
| MoviesTask(This->pQTMovie,0); | |
| QTVisualContextTask(This->vContext); | |
| TRACE("In loop at time %ld\n",This->movie_time); | |
| TRACE("In Next time %ld\n",next_time); | |
| mStart = This->movie_time; | |
| mStop = next_time; | |
| time = (float)(This->movie_time - This->movie_start) / This->movie_scale; | |
| tStart = time * 10000000; | |
| time = (float)(next_time - This->movie_start) / This->movie_scale; | |
| tStop = time * 10000000; | |
| /* Deliver Audio */ | |
| if (This->pAudio_Pin && This->pAudio_Pin->pin.pin.pConnectedTo && This->aSession) | |
| { | |
| int data_size=0; | |
| BYTE* ptr; | |
| IMediaSample *sample = NULL; | |
| AudioBufferList aData; | |
| UInt32 flags; | |
| UInt32 frames; | |
| WAVEFORMATEX* pvi; | |
| float duration; | |
| pvi = (WAVEFORMATEX*)This->pAudio_Pin->pmt->pbFormat; | |
| hr = BaseOutputPinImpl_GetDeliveryBuffer(&This->pAudio_Pin->pin, &sample, NULL, NULL, 0); | |
| if (FAILED(hr)) | |
| { | |
| ERR("Audio: Unable to get delivery buffer (%x)\n", hr); | |
| goto audio_error; | |
| } | |
| hr = IMediaSample_GetPointer(sample, &ptr); | |
| if (FAILED(hr)) | |
| { | |
| ERR("Audio: Unable to get pointer to buffer (%x)\n", hr); | |
| goto audio_error; | |
| } | |
| duration = (float)next_time / This->movie_scale; | |
| time = (float)This->movie_time / This->movie_scale; | |
| duration -= time; | |
| frames = pvi->nSamplesPerSec * duration; | |
| TRACE("Need audio for %f seconds (%li frames)\n",duration,frames); | |
| data_size = IMediaSample_GetSize(sample); | |
| if (data_size < frames * pvi->nBlockAlign) | |
| FIXME("Audio buffer is too small\n"); | |
| aData.mNumberBuffers = 1; | |
| aData.mBuffers[0].mNumberChannels = pvi->nChannels; | |
| aData.mBuffers[0].mDataByteSize = data_size; | |
| aData.mBuffers[0].mData = ptr; | |
| err = MovieAudioExtractionFillBuffer(This->aSession, &frames, &aData, &flags); | |
| if (frames == 0) | |
| { | |
| TimeRecord etr; | |
| /* Ran out of frames, Restart the extraction session */ | |
| TRACE("Restarting extraction session\n"); | |
| MovieAudioExtractionEnd(This->aSession); | |
| This->aSession = NULL; | |
| QT_Create_Extract_Session(This); | |
| etr = tr; | |
| etr.value = SInt64ToWide(This->movie_time); | |
| MovieAudioExtractionSetProperty(This->aSession, | |
| kQTPropertyClass_MovieAudioExtraction_Movie, | |
| kQTMovieAudioExtractionMoviePropertyID_CurrentTime, | |
| sizeof(TimeRecord), &etr ); | |
| frames = pvi->nSamplesPerSec * duration; | |
| aData.mNumberBuffers = 1; | |
| aData.mBuffers[0].mNumberChannels = pvi->nChannels; | |
| aData.mBuffers[0].mDataByteSize = data_size; | |
| aData.mBuffers[0].mData = ptr; | |
| MovieAudioExtractionFillBuffer(This->aSession, &frames, &aData, &flags); | |
| } | |
| TRACE("Got %i frames\n",(int)frames); | |
| IMediaSample_SetActualDataLength(sample, frames * pvi->nBlockAlign); | |
| IMediaSample_SetMediaTime(sample, &mStart, &mStop); | |
| IMediaSample_SetTime(sample, &tStart, &tStop); | |
| hr = OutputQueue_Receive(This->pAudio_Pin->queue, sample); | |
| TRACE("Audio Delivered (%x)\n",hr); | |
| audio_error: | |
| if (sample) | |
| IMediaSample_Release(sample); | |
| } | |
| else | |
| TRACE("Audio Pin not connected or no Audio\n"); | |
| /* Deliver Video */ | |
| if (This->pVideo_Pin && QTVisualContextIsNewImageAvailable(This->vContext,0)) | |
| { | |
| err = QTVisualContextCopyImageForTime(This->vContext, NULL, NULL, &pixelBuffer); | |
| if (err == noErr) | |
| { | |
| int data_size=0; | |
| BYTE* ptr; | |
| IMediaSample *sample = NULL; | |
| hr = BaseOutputPinImpl_GetDeliveryBuffer(&This->pVideo_Pin->pin, &sample, NULL, NULL, 0); | |
| if (FAILED(hr)) | |
| { | |
| ERR("Video: Unable to get delivery buffer (%x)\n", hr); | |
| goto video_error; | |
| } | |
| data_size = IMediaSample_GetSize(sample); | |
| if (data_size < This->outputSize) | |
| { | |
| ERR("Sample size is too small %d < %d\n", data_size, This->outputSize) | |
| ; | |
| hr = E_FAIL; | |
| goto video_error; | |
| } | |
| hr = IMediaSample_GetPointer(sample, &ptr); | |
| if (FAILED(hr)) | |
| { | |
| ERR("Video: Unable to get pointer to buffer (%x)\n", hr); | |
| goto video_error; | |
| } | |
| hr = AccessPixelBufferPixels( pixelBuffer, ptr); | |
| if (FAILED(hr)) | |
| { | |
| ERR("Failed to access Pixels\n"); | |
| goto video_error; | |
| } | |
| IMediaSample_SetActualDataLength(sample, This->outputSize); | |
| IMediaSample_SetMediaTime(sample, &mStart, &mStop); | |
| IMediaSample_SetTime(sample, &tStart, &tStop); | |
| hr = OutputQueue_Receive(This->pVideo_Pin->queue, sample); | |
| TRACE("Video Delivered (%x)\n",hr); | |
| video_error: | |
| if (sample) | |
| IMediaSample_Release(sample); | |
| if (pixelBuffer) | |
| CVPixelBufferRelease(pixelBuffer); | |
| } | |
| } | |
| else | |
| TRACE("No video to deliver\n"); | |
| This->movie_time = next_time; | |
| LeaveCriticalSection(&This->csReceive); | |
| } while (hr == S_OK); | |
| This->state = State_Stopped; | |
| if (This->pAudio_Pin) | |
| OutputQueue_EOS(This->pAudio_Pin->queue); | |
| if (This->pVideo_Pin) | |
| OutputQueue_EOS(This->pVideo_Pin->queue); | |
| return hr; | |
| } | |
| static HRESULT WINAPI QT_Run(IBaseFilter *iface, REFERENCE_TIME tStart) | |
| { | |
| HRESULT hr = S_OK; | |
| QTSplitter *This = impl_from_IBaseFilter(iface); | |
| HRESULT hr_any = VFW_E_NOT_CONNECTED; | |
| TRACE("(%s)\n", wine_dbgstr_longlong(tStart)); | |
| EnterCriticalSection(&This->csReceive); | |
| This->filter.rtStreamStart = tStart; | |
| if (This->pVideo_Pin) | |
| hr = BaseOutputPinImpl_Active(&This->pVideo_Pin->pin); | |
| if (SUCCEEDED(hr)) | |
| hr_any = hr; | |
| if (This->pAudio_Pin) | |
| hr = BaseOutputPinImpl_Active(&This->pAudio_Pin->pin); | |
| if (SUCCEEDED(hr)) | |
| hr_any = hr; | |
| hr = hr_any; | |
| SetEvent(This->runEvent); | |
| LeaveCriticalSection(&This->csReceive); | |
| return hr; | |
| } | |
| static HRESULT WINAPI QT_GetState(IBaseFilter *iface, DWORD dwMilliSecsTimeout, FILTER_STATE *pState) | |
| { | |
| QTSplitter *This = impl_from_IBaseFilter(iface); | |
| TRACE("(%d, %p)\n", dwMilliSecsTimeout, pState); | |
| *pState = This->state; | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI QT_FindPin(IBaseFilter *iface, LPCWSTR Id, IPin **ppPin) | |
| { | |
| FIXME("(%p)->(%s,%p) stub\n", iface, debugstr_w(Id), ppPin); | |
| return E_NOTIMPL; | |
| } | |
| static const IBaseFilterVtbl QT_Vtbl = { | |
| QT_QueryInterface, | |
| BaseFilterImpl_AddRef, | |
| QT_Release, | |
| BaseFilterImpl_GetClassID, | |
| QT_Stop, | |
| QT_Pause, | |
| QT_Run, | |
| QT_GetState, | |
| BaseFilterImpl_SetSyncSource, | |
| BaseFilterImpl_GetSyncSource, | |
| BaseFilterImpl_EnumPins, | |
| QT_FindPin, | |
| BaseFilterImpl_QueryFilterInfo, | |
| BaseFilterImpl_JoinFilterGraph, | |
| BaseFilterImpl_QueryVendorInfo | |
| }; | |
| /* | |
| * Input Pin | |
| */ | |
| static HRESULT QT_RemoveOutputPins(QTSplitter *This) | |
| { | |
| HRESULT hr; | |
| TRACE("(%p)\n", This); | |
| if (This->pVideo_Pin) | |
| { | |
| OutputQueue_Destroy(This->pVideo_Pin->queue); | |
| hr = BaseOutputPinImpl_BreakConnect(&This->pVideo_Pin->pin); | |
| TRACE("Disconnect: %08x\n", hr); | |
| IPin_Release(&This->pVideo_Pin->pin.pin.IPin_iface); | |
| This->pVideo_Pin = NULL; | |
| } | |
| if (This->pAudio_Pin) | |
| { | |
| OutputQueue_Destroy(This->pAudio_Pin->queue); | |
| hr = BaseOutputPinImpl_BreakConnect(&This->pAudio_Pin->pin); | |
| TRACE("Disconnect: %08x\n", hr); | |
| IPin_Release(&This->pAudio_Pin->pin.pin.IPin_iface); | |
| This->pAudio_Pin = NULL; | |
| } | |
| BaseFilterImpl_IncrementPinVersion(&This->filter); | |
| return S_OK; | |
| } | |
| static inline QTInPin *impl_from_IPin( IPin *iface ) | |
| { | |
| return CONTAINING_RECORD(iface, QTInPin, pin.IPin_iface); | |
| } | |
| static ULONG WINAPI QTInPin_Release(IPin *iface) | |
| { | |
| QTInPin *This = impl_from_IPin(iface); | |
| ULONG refCount = InterlockedDecrement(&This->pin.refCount); | |
| TRACE("(%p)->() Release from %d\n", iface, refCount + 1); | |
| if (!refCount) | |
| { | |
| FreeMediaType(&This->pin.mtCurrent); | |
| if (This->pAlloc) | |
| IMemAllocator_Release(This->pAlloc); | |
| This->pAlloc = NULL; | |
| if (This->pReader) | |
| IAsyncReader_Release(This->pReader); | |
| This->pReader = NULL; | |
| This->pin.IPin_iface.lpVtbl = NULL; | |
| return 0; | |
| } | |
| else | |
| return refCount; | |
| } | |
| static HRESULT QT_Process_Video_Track(QTSplitter* filter, Track trk) | |
| { | |
| AM_MEDIA_TYPE amt; | |
| VIDEOINFOHEADER * pvi; | |
| PIN_INFO piOutput; | |
| HRESULT hr = S_OK; | |
| OSErr err; | |
| static const WCHAR szwVideoOut[] = {'V','i','d','e','o',0}; | |
| CFMutableDictionaryRef pixelBufferOptions = NULL; | |
| CFMutableDictionaryRef visualContextOptions = NULL; | |
| CFNumberRef n = NULL; | |
| int t; | |
| DWORD outputWidth, outputHeight, outputDepth; | |
| Fixed trackWidth, trackHeight; | |
| Media videoMedia; | |
| long sampleCount; | |
| TimeValue64 duration; | |
| TimeScale timeScale; | |
| ZeroMemory(&amt, sizeof(amt)); | |
| amt.formattype = FORMAT_VideoInfo; | |
| amt.majortype = MEDIATYPE_Video; | |
| amt.subtype = MEDIASUBTYPE_RGB24; | |
| GetTrackDimensions(trk, &trackWidth, &trackHeight); | |
| outputDepth = 3; | |
| outputWidth = Fix2Long(trackWidth); | |
| outputHeight = Fix2Long(trackHeight); | |
| TRACE("Width %i Height %i\n",outputWidth, outputHeight); | |
| amt.cbFormat = sizeof(VIDEOINFOHEADER); | |
| amt.pbFormat = CoTaskMemAlloc(amt.cbFormat); | |
| ZeroMemory(amt.pbFormat, amt.cbFormat); | |
| pvi = (VIDEOINFOHEADER *)amt.pbFormat; | |
| pvi->bmiHeader.biSize = sizeof (BITMAPINFOHEADER); | |
| pvi->bmiHeader.biWidth = outputWidth; | |
| pvi->bmiHeader.biHeight = outputHeight; | |
| pvi->bmiHeader.biPlanes = 1; | |
| pvi->bmiHeader.biBitCount = 24; | |
| pvi->bmiHeader.biCompression = BI_RGB; | |
| pvi->bmiHeader.biSizeImage = outputWidth * outputHeight * outputDepth; | |
| filter->outputSize = pvi->bmiHeader.biSizeImage; | |
| amt.lSampleSize = 0; | |
| pixelBufferOptions = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
| t = k32ARGBPixelFormat; | |
| n = CFNumberCreate(NULL, kCFNumberIntType, &t); | |
| CFDictionaryAddValue(pixelBufferOptions, kCVPixelBufferPixelFormatTypeKey, n); | |
| CFRelease(n); | |
| n = CFNumberCreate(NULL, kCFNumberIntType, &outputWidth); | |
| CFDictionaryAddValue(pixelBufferOptions, kCVPixelBufferWidthKey, n); | |
| CFRelease(n); | |
| n = CFNumberCreate(NULL, kCFNumberIntType, &outputHeight); | |
| CFDictionaryAddValue(pixelBufferOptions, kCVPixelBufferHeightKey, n); | |
| CFRelease(n); | |
| t = 16; | |
| n = CFNumberCreate(NULL, kCFNumberIntType, &t); | |
| CFDictionaryAddValue(pixelBufferOptions, kCVPixelBufferBytesPerRowAlignmentKey, n); | |
| CFRelease(n); | |
| visualContextOptions = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
| CFDictionarySetValue(visualContextOptions, kQTVisualContextPixelBufferAttributesKey, pixelBufferOptions); | |
| err = QTPixelBufferContextCreate(NULL, visualContextOptions,&filter->vContext); | |
| CFRelease(pixelBufferOptions); | |
| CFRelease(visualContextOptions); | |
| if (err != noErr) | |
| { | |
| ERR("Failed to create Visual Context\n"); | |
| return E_FAIL; | |
| } | |
| err = SetMovieVisualContext(filter->pQTMovie, filter->vContext); | |
| if (err != noErr) | |
| { | |
| ERR("Failed to set Visual Context\n"); | |
| return E_FAIL; | |
| } | |
| videoMedia = GetTrackMedia(trk); | |
| sampleCount = GetMediaSampleCount(videoMedia); | |
| timeScale = GetMediaTimeScale(videoMedia); | |
| duration = GetMediaDisplayDuration(videoMedia); | |
| pvi->AvgTimePerFrame = (100000.0 * sampleCount * timeScale) / duration; | |
| piOutput.dir = PINDIR_OUTPUT; | |
| piOutput.pFilter = &filter->filter.IBaseFilter_iface; | |
| lstrcpyW(piOutput.achName,szwVideoOut); | |
| hr = QT_AddPin(filter, &piOutput, &amt, TRUE); | |
| if (FAILED(hr)) | |
| ERR("Failed to add Video Track\n"); | |
| else | |
| TRACE("Video Pin %p\n",filter->pVideo_Pin); | |
| return hr; | |
| } | |
| static HRESULT QT_Process_Audio_Track(QTSplitter* filter, Track trk) | |
| { | |
| AM_MEDIA_TYPE amt; | |
| WAVEFORMATEX* pvi; | |
| PIN_INFO piOutput; | |
| HRESULT hr = S_OK; | |
| static const WCHAR szwAudioOut[] = {'A','u','d','i','o',0}; | |
| Media audioMedia; | |
| SoundDescriptionHandle aDesc = (SoundDescriptionHandle) NewHandle(sizeof(SoundDescription)); | |
| audioMedia = GetTrackMedia(trk); | |
| GetMediaSampleDescription(audioMedia, 1, (SampleDescriptionHandle)aDesc); | |
| ZeroMemory(&amt, sizeof(amt)); | |
| amt.formattype = FORMAT_WaveFormatEx; | |
| amt.majortype = MEDIATYPE_Audio; | |
| amt.subtype = MEDIASUBTYPE_PCM; | |
| amt.bTemporalCompression = 0; | |
| amt.cbFormat = sizeof(WAVEFORMATEX); | |
| amt.pbFormat = CoTaskMemAlloc(amt.cbFormat); | |
| ZeroMemory(amt.pbFormat, amt.cbFormat); | |
| pvi = (WAVEFORMATEX*)amt.pbFormat; | |
| pvi->cbSize = sizeof(WAVEFORMATEX); | |
| pvi->wFormatTag = WAVE_FORMAT_PCM; | |
| pvi->nChannels = ((SoundDescription)**aDesc).numChannels; | |
| if (pvi->nChannels < 1 || pvi->nChannels > 2) | |
| pvi->nChannels = 2; | |
| pvi->nSamplesPerSec = (((SoundDescription)**aDesc).sampleRate/65536); | |
| if (pvi->nSamplesPerSec < 8000 || pvi->nChannels > 48000) | |
| pvi->nSamplesPerSec = 44100; | |
| pvi->wBitsPerSample = ((SoundDescription)**aDesc).sampleSize; | |
| if (pvi->wBitsPerSample < 8 || pvi->wBitsPerSample > 32) | |
| pvi->wBitsPerSample = 16; | |
| pvi->nBlockAlign = (pvi->nChannels * pvi->wBitsPerSample) / 8; | |
| pvi->nAvgBytesPerSec = pvi->nSamplesPerSec * pvi->nBlockAlign; | |
| DisposeHandle((Handle)aDesc); | |
| piOutput.dir = PINDIR_OUTPUT; | |
| piOutput.pFilter = &filter->filter.IBaseFilter_iface; | |
| lstrcpyW(piOutput.achName,szwAudioOut); | |
| hr = QT_AddPin(filter, &piOutput, &amt, FALSE); | |
| if (FAILED(hr)) | |
| ERR("Failed to add Audio Track\n"); | |
| else | |
| TRACE("Audio Pin %p\n",filter->pAudio_Pin); | |
| return hr; | |
| } | |
| static HRESULT QT_Process_Movie(QTSplitter* filter) | |
| { | |
| HRESULT hr = S_OK; | |
| OSErr err; | |
| WineDataRefRecord ptrDataRefRec; | |
| Handle dataRef = NULL; | |
| Track trk; | |
| short id = 0; | |
| DWORD tid; | |
| LONGLONG time; | |
| TRACE("Trying movie connect\n"); | |
| ptrDataRefRec.pReader = filter->pInputPin.pReader; | |
| ptrDataRefRec.streamSubtype = filter->pInputPin.subType; | |
| PtrToHand( &ptrDataRefRec, &dataRef, sizeof(WineDataRefRecord)); | |
| err = NewMovieFromDataRef(&filter->pQTMovie, newMovieActive|newMovieDontInteractWithUser|newMovieDontAutoUpdateClock|newMovieDontAskUnresolvedDataRefs|newMovieAsyncOK, &id, dataRef, 'WINE'); | |
| DisposeHandle(dataRef); | |
| if (err != noErr) | |
| { | |
| FIXME("QuickTime cannot handle media type(%i)\n",err); | |
| return VFW_E_TYPE_NOT_ACCEPTED; | |
| } | |
| PrePrerollMovie(filter->pQTMovie, 0, fixed1, NULL, NULL); | |
| PrerollMovie(filter->pQTMovie, 0, fixed1); | |
| GoToBeginningOfMovie(filter->pQTMovie); | |
| SetMovieActive(filter->pQTMovie,TRUE); | |
| if (GetMovieLoadState(filter->pQTMovie) < kMovieLoadStateLoaded) | |
| MoviesTask(filter->pQTMovie,100); | |
| trk = GetMovieIndTrackType(filter->pQTMovie, 1, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly); | |
| TRACE("%p is a video track\n",trk); | |
| if (trk) | |
| hr = QT_Process_Video_Track(filter, trk); | |
| if (FAILED(hr)) | |
| return hr; | |
| trk = GetMovieIndTrackType(filter->pQTMovie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly); | |
| TRACE("%p is an audio track\n",trk); | |
| if (trk) | |
| hr = QT_Process_Audio_Track(filter, trk); | |
| time = GetMovieDuration(filter->pQTMovie); | |
| filter->movie_scale = GetMovieTimeScale(filter->pQTMovie); | |
| filter->sourceSeeking.llDuration = ((double)time / filter->movie_scale) * 10000000; | |
| filter->sourceSeeking.llStop = filter->sourceSeeking.llDuration; | |
| TRACE("Movie duration is %s\n",wine_dbgstr_longlong(filter->sourceSeeking.llDuration)); | |
| filter->loaderThread = CreateThread(NULL, 0, QTSplitter_loading_thread, filter, 0, &tid); | |
| if (filter->loaderThread) | |
| TRACE("Created loading thread 0x%08x\n", tid); | |
| filter->splitterThread = CreateThread(NULL, 0, QTSplitter_thread, filter, 0, &tid); | |
| if (filter->splitterThread) | |
| TRACE("Created processing thread 0x%08x\n", tid); | |
| else | |
| hr = HRESULT_FROM_WIN32(GetLastError()); | |
| return hr; | |
| } | |
| static HRESULT WINAPI QTInPin_ReceiveConnection(IPin *iface, IPin *pReceivePin, const AM_MEDIA_TYPE *pmt) | |
| { | |
| HRESULT hr = S_OK; | |
| ALLOCATOR_PROPERTIES props; | |
| QTInPin *This = impl_from_IPin(iface); | |
| IMemAllocator *pAlloc; | |
| TRACE("(%p/%p)->(%p, %p)\n", This, iface, pReceivePin, pmt); | |
| EnterCriticalSection(This->pin.pCritSec); | |
| This->pReader = NULL; | |
| if (This->pin.pConnectedTo) | |
| hr = VFW_E_ALREADY_CONNECTED; | |
| else if (IPin_QueryAccept(iface, pmt) != S_OK) | |
| hr = VFW_E_TYPE_NOT_ACCEPTED; | |
| else | |
| { | |
| PIN_DIRECTION pindirReceive; | |
| IPin_QueryDirection(pReceivePin, &pindirReceive); | |
| if (pindirReceive != PINDIR_OUTPUT) | |
| hr = VFW_E_INVALID_DIRECTION; | |
| } | |
| if (FAILED(hr)) | |
| { | |
| LeaveCriticalSection(This->pin.pCritSec); | |
| return hr; | |
| } | |
| hr = IPin_QueryInterface(pReceivePin, &IID_IAsyncReader, (LPVOID *)&This->pReader); | |
| if (FAILED(hr)) | |
| { | |
| LeaveCriticalSection(This->pin.pCritSec); | |
| TRACE("Input source is not an AsyncReader\n"); | |
| return hr; | |
| } | |
| LeaveCriticalSection(This->pin.pCritSec); | |
| EnterCriticalSection(&impl_from_IBaseFilter(This->pin.pinInfo.pFilter)->filter.csFilter); | |
| hr = QT_Process_Movie(impl_from_IBaseFilter(This->pin.pinInfo.pFilter)); | |
| if (FAILED(hr)) | |
| { | |
| IAsyncReader_Release(This->pReader); | |
| This->pReader = NULL; | |
| LeaveCriticalSection(&impl_from_IBaseFilter(This->pin.pinInfo.pFilter)->filter.csFilter); | |
| TRACE("Unable to process movie\n"); | |
| return hr; | |
| } | |
| This->pAlloc = NULL; | |
| props.cBuffers = 8; | |
| props.cbAlign = 1; | |
| props.cbBuffer = impl_from_IBaseFilter(This->pin.pinInfo.pFilter)->outputSize + props.cbAlign; | |
| props.cbPrefix = 0; | |
| hr = CoCreateInstance(&CLSID_MemoryAllocator, NULL, CLSCTX_INPROC, | |
| &IID_IMemAllocator, (LPVOID *)&pAlloc); | |
| if (SUCCEEDED(hr)) | |
| { | |
| /* A certain IAsyncReader::RequestAllocator expects to be passed | |
| non-NULL preferred allocator */ | |
| hr = IAsyncReader_RequestAllocator(This->pReader, pAlloc, &props, &This->pAlloc); | |
| if (FAILED(hr)) | |
| WARN("Can't get an allocator, got %08x\n", hr); | |
| IMemAllocator_Release(pAlloc); | |
| } | |
| if (SUCCEEDED(hr)) | |
| { | |
| CopyMediaType(&This->pin.mtCurrent, pmt); | |
| This->pin.pConnectedTo = pReceivePin; | |
| IPin_AddRef(pReceivePin); | |
| hr = IMemAllocator_Commit(This->pAlloc); | |
| } | |
| else | |
| { | |
| QT_RemoveOutputPins(impl_from_IBaseFilter(This->pin.pinInfo.pFilter)); | |
| if (This->pReader) | |
| IAsyncReader_Release(This->pReader); | |
| This->pReader = NULL; | |
| if (This->pAlloc) | |
| IMemAllocator_Release(This->pAlloc); | |
| This->pAlloc = NULL; | |
| } | |
| TRACE("Size: %i\n", props.cbBuffer); | |
| LeaveCriticalSection(&impl_from_IBaseFilter(This->pin.pinInfo.pFilter)->filter.csFilter); | |
| return hr; | |
| } | |
| static HRESULT WINAPI QTInPin_Disconnect(IPin *iface) | |
| { | |
| HRESULT hr; | |
| QTInPin *This = impl_from_IPin(iface); | |
| FILTER_STATE state; | |
| TRACE("()\n"); | |
| hr = IBaseFilter_GetState(This->pin.pinInfo.pFilter, INFINITE, &state); | |
| EnterCriticalSection(This->pin.pCritSec); | |
| if (This->pin.pConnectedTo) | |
| { | |
| QTSplitter *Parser = impl_from_IBaseFilter(This->pin.pinInfo.pFilter); | |
| if (SUCCEEDED(hr) && state == State_Stopped) | |
| { | |
| IMemAllocator_Decommit(This->pAlloc); | |
| IPin_Disconnect(This->pin.pConnectedTo); | |
| IPin_Release(This->pin.pConnectedTo); | |
| This->pin.pConnectedTo = NULL; | |
| hr = QT_RemoveOutputPins(Parser); | |
| } | |
| else | |
| hr = VFW_E_NOT_STOPPED; | |
| } | |
| else | |
| hr = S_FALSE; | |
| LeaveCriticalSection(This->pin.pCritSec); | |
| return hr; | |
| } | |
| static HRESULT WINAPI QTInPin_QueryAccept(IPin *iface, const AM_MEDIA_TYPE *pmt) | |
| { | |
| QTInPin *This = impl_from_IPin(iface); | |
| TRACE("(%p)->(%p)\n", This, pmt); | |
| if (IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream)) | |
| { | |
| This->subType = pmt->subtype; | |
| return S_OK; | |
| } | |
| return S_FALSE; | |
| } | |
| static HRESULT WINAPI QTInPin_EndOfStream(IPin *iface) | |
| { | |
| QTInPin *pin = impl_from_IPin(iface); | |
| QTSplitter *This = impl_from_IBaseFilter(pin->pin.pinInfo.pFilter); | |
| FIXME("Propagate message on %p\n", This); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI QTInPin_BeginFlush(IPin *iface) | |
| { | |
| QTInPin *pin = impl_from_IPin(iface); | |
| QTSplitter *This = impl_from_IBaseFilter(pin->pin.pinInfo.pFilter); | |
| FIXME("Propagate message on %p\n", This); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI QTInPin_EndFlush(IPin *iface) | |
| { | |
| QTInPin *pin = impl_from_IPin(iface); | |
| QTSplitter *This = impl_from_IBaseFilter(pin->pin.pinInfo.pFilter); | |
| FIXME("Propagate message on %p\n", This); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI QTInPin_NewSegment(IPin *iface, REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) | |
| { | |
| QTInPin *pin = impl_from_IPin(iface); | |
| QTSplitter *This = impl_from_IBaseFilter(pin->pin.pinInfo.pFilter); | |
| BasePinImpl_NewSegment(iface, tStart, tStop, dRate); | |
| FIXME("Propagate message on %p\n", This); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI QTInPin_QueryInterface(IPin * iface, REFIID riid, LPVOID * ppv) | |
| { | |
| QTInPin *This = impl_from_IPin(iface); | |
| TRACE("(%p/%p)->(%s, %p)\n", This, iface, debugstr_guid(riid), ppv); | |
| *ppv = NULL; | |
| if (IsEqualIID(riid, &IID_IUnknown)) | |
| *ppv = iface; | |
| else if (IsEqualIID(riid, &IID_IPin)) | |
| *ppv = iface; | |
| else if (IsEqualIID(riid, &IID_IMediaSeeking)) | |
| return IBaseFilter_QueryInterface(This->pin.pinInfo.pFilter, &IID_IMediaSeeking, ppv); | |
| if (*ppv) | |
| { | |
| IUnknown_AddRef((IUnknown *)(*ppv)); | |
| return S_OK; | |
| } | |
| FIXME("No interface for %s!\n", debugstr_guid(riid)); | |
| return E_NOINTERFACE; | |
| } | |
| static HRESULT WINAPI QTInPin_EnumMediaTypes(IPin *iface, IEnumMediaTypes **ppEnum) | |
| { | |
| QTInPin *This = impl_from_IPin(iface); | |
| TRACE("(%p/%p)->(%p)\n", This, iface, ppEnum); | |
| return EnumMediaTypes_Construct(&This->pin, BasePinImpl_GetMediaType, BasePinImpl_GetMediaTypeVersion, ppEnum); | |
| } | |
| static const IPinVtbl QT_InputPin_Vtbl = { | |
| QTInPin_QueryInterface, | |
| BasePinImpl_AddRef, | |
| QTInPin_Release, | |
| BaseInputPinImpl_Connect, | |
| QTInPin_ReceiveConnection, | |
| QTInPin_Disconnect, | |
| BasePinImpl_ConnectedTo, | |
| BasePinImpl_ConnectionMediaType, | |
| BasePinImpl_QueryPinInfo, | |
| BasePinImpl_QueryDirection, | |
| BasePinImpl_QueryId, | |
| QTInPin_QueryAccept, | |
| QTInPin_EnumMediaTypes, | |
| BasePinImpl_QueryInternalConnections, | |
| QTInPin_EndOfStream, | |
| QTInPin_BeginFlush, | |
| QTInPin_EndFlush, | |
| QTInPin_NewSegment | |
| }; | |
| /* | |
| * Output Pin | |
| */ | |
| static inline QTOutPin *impl_QTOutPin_from_IPin( IPin *iface ) | |
| { | |
| return CONTAINING_RECORD(iface, QTOutPin, pin.pin.IPin_iface); | |
| } | |
| static inline QTOutPin *impl_QTOutPin_from_BasePin( BasePin *iface ) | |
| { | |
| return CONTAINING_RECORD(iface, QTOutPin, pin.pin); | |
| } | |
| static inline QTOutPin *impl_QTOutPin_from_BaseOutputPin( BaseOutputPin *iface ) | |
| { | |
| return CONTAINING_RECORD(iface, QTOutPin, pin); | |
| } | |
| static HRESULT WINAPI QTOutPin_QueryInterface(IPin *iface, REFIID riid, void **ppv) | |
| { | |
| QTOutPin *This = impl_QTOutPin_from_IPin(iface); | |
| TRACE("(%s, %p)\n", debugstr_guid(riid), ppv); | |
| *ppv = NULL; | |
| if (IsEqualIID(riid, &IID_IUnknown)) | |
| *ppv = iface; | |
| else if (IsEqualIID(riid, &IID_IPin)) | |
| *ppv = iface; | |
| else if (IsEqualIID(riid, &IID_IMediaSeeking)) | |
| return IBaseFilter_QueryInterface(This->pin.pin.pinInfo.pFilter, &IID_IMediaSeeking, ppv); | |
| else if (IsEqualIID(riid, &IID_IQualityControl)) | |
| *ppv = &This->IQualityControl_iface; | |
| if (*ppv) | |
| { | |
| IUnknown_AddRef((IUnknown *)(*ppv)); | |
| return S_OK; | |
| } | |
| FIXME("No interface for %s!\n", debugstr_guid(riid)); | |
| return E_NOINTERFACE; | |
| } | |
| static ULONG WINAPI QTOutPin_Release(IPin *iface) | |
| { | |
| QTOutPin *This = impl_QTOutPin_from_IPin(iface); | |
| ULONG refCount = InterlockedDecrement(&This->pin.pin.refCount); | |
| TRACE("(%p)->() Release from %d\n", iface, refCount + 1); | |
| if (!refCount) | |
| { | |
| DeleteMediaType(This->pmt); | |
| FreeMediaType(&This->pin.pin.mtCurrent); | |
| if (This->pin.pAllocator) | |
| IMemAllocator_Release(This->pin.pAllocator); | |
| CoTaskMemFree(This); | |
| return 0; | |
| } | |
| return refCount; | |
| } | |
| static HRESULT WINAPI QTOutPin_GetMediaType(BasePin *iface, int iPosition, AM_MEDIA_TYPE *pmt) | |
| { | |
| QTOutPin *This = impl_QTOutPin_from_BasePin(iface); | |
| if (iPosition < 0) | |
| return E_INVALIDARG; | |
| if (iPosition > 0) | |
| return VFW_S_NO_MORE_ITEMS; | |
| CopyMediaType(pmt, This->pmt); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI QTOutPin_DecideBufferSize(BaseOutputPin *iface, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest) | |
| { | |
| /* Unused */ | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI QTOutPin_DecideAllocator(BaseOutputPin *iface, IMemInputPin *pPin, IMemAllocator **pAlloc) | |
| { | |
| HRESULT hr; | |
| QTOutPin *This = impl_QTOutPin_from_BaseOutputPin(iface); | |
| QTSplitter *QTfilter = impl_from_IBaseFilter(This->pin.pin.pinInfo.pFilter); | |
| *pAlloc = NULL; | |
| if (QTfilter->pInputPin.pAlloc) | |
| { | |
| hr = IMemInputPin_NotifyAllocator(pPin, QTfilter->pInputPin.pAlloc, FALSE); | |
| if (SUCCEEDED(hr)) | |
| { | |
| *pAlloc = QTfilter->pInputPin.pAlloc; | |
| IMemAllocator_AddRef(*pAlloc); | |
| } | |
| } | |
| else | |
| hr = VFW_E_NO_ALLOCATOR; | |
| return hr; | |
| } | |
| static HRESULT WINAPI QTOutPin_BreakConnect(BaseOutputPin *This) | |
| { | |
| HRESULT hr; | |
| TRACE("(%p)->()\n", This); | |
| EnterCriticalSection(This->pin.pCritSec); | |
| if (!This->pin.pConnectedTo || !This->pMemInputPin) | |
| hr = VFW_E_NOT_CONNECTED; | |
| else | |
| { | |
| hr = IPin_Disconnect(This->pin.pConnectedTo); | |
| IPin_Disconnect(&This->pin.IPin_iface); | |
| } | |
| LeaveCriticalSection(This->pin.pCritSec); | |
| return hr; | |
| } | |
| static const IPinVtbl QT_OutputPin_Vtbl = { | |
| QTOutPin_QueryInterface, | |
| BasePinImpl_AddRef, | |
| QTOutPin_Release, | |
| BaseOutputPinImpl_Connect, | |
| BaseOutputPinImpl_ReceiveConnection, | |
| BaseOutputPinImpl_Disconnect, | |
| BasePinImpl_ConnectedTo, | |
| BasePinImpl_ConnectionMediaType, | |
| BasePinImpl_QueryPinInfo, | |
| BasePinImpl_QueryDirection, | |
| BasePinImpl_QueryId, | |
| BasePinImpl_QueryAccept, | |
| BasePinImpl_EnumMediaTypes, | |
| BasePinImpl_QueryInternalConnections, | |
| BaseOutputPinImpl_EndOfStream, | |
| BaseOutputPinImpl_BeginFlush, | |
| BaseOutputPinImpl_EndFlush, | |
| BasePinImpl_NewSegment | |
| }; | |
| static inline QTOutPin *impl_from_IQualityControl( IQualityControl *iface ) | |
| { | |
| return CONTAINING_RECORD(iface, QTOutPin, IQualityControl_iface); | |
| } | |
| HRESULT WINAPI QT_QualityControl_QueryInterface(IQualityControl *iface, REFIID riid, void **ppv) | |
| { | |
| QTOutPin *This = impl_from_IQualityControl(iface); | |
| return IPin_QueryInterface(&This->pin.pin.IPin_iface, riid, ppv); | |
| } | |
| ULONG WINAPI QT_QualityControl_AddRef(IQualityControl *iface) | |
| { | |
| QTOutPin *This = impl_from_IQualityControl(iface); | |
| return IPin_AddRef(&This->pin.pin.IPin_iface); | |
| } | |
| ULONG WINAPI QT_QualityControl_Release(IQualityControl *iface) | |
| { | |
| QTOutPin *This = impl_from_IQualityControl(iface); | |
| return IPin_Release(&This->pin.pin.IPin_iface); | |
| } | |
| static HRESULT WINAPI QT_QualityControl_Notify(IQualityControl *iface, IBaseFilter *sender, Quality qm) | |
| { | |
| REFERENCE_TIME late = qm.Late; | |
| if (qm.Late < 0 && -qm.Late > qm.TimeStamp) | |
| late = -qm.TimeStamp; | |
| /* TODO: Do Something */ | |
| return S_OK; | |
| } | |
| HRESULT WINAPI QT_QualityControl_SetSink(IQualityControl *iface, IQualityControl *tonotify) | |
| { | |
| /* Do nothing */ | |
| return S_OK; | |
| } | |
| static const IQualityControlVtbl QTOutPin_QualityControl_Vtbl = { | |
| QT_QualityControl_QueryInterface, | |
| QT_QualityControl_AddRef, | |
| QT_QualityControl_Release, | |
| QT_QualityControl_Notify, | |
| QT_QualityControl_SetSink | |
| }; | |
| static const BaseOutputPinFuncTable output_BaseOutputFuncTable = { | |
| { | |
| NULL, | |
| BaseOutputPinImpl_AttemptConnection, | |
| BasePinImpl_GetMediaTypeVersion, | |
| QTOutPin_GetMediaType | |
| }, | |
| QTOutPin_DecideBufferSize, | |
| QTOutPin_DecideAllocator, | |
| QTOutPin_BreakConnect | |
| }; | |
| static const OutputQueueFuncTable output_OutputQueueFuncTable = { | |
| OutputQueueImpl_ThreadProc | |
| }; | |
| static HRESULT QT_AddPin(QTSplitter *This, const PIN_INFO *piOutput, const AM_MEDIA_TYPE *amt, BOOL video) | |
| { | |
| HRESULT hr; | |
| IPin **target; | |
| if (video) | |
| target = (IPin**)&This->pVideo_Pin; | |
| else | |
| target = (IPin**)&This->pAudio_Pin; | |
| if (*target != NULL) | |
| { | |
| FIXME("We already have a %s pin\n",(video)?"video":"audio"); | |
| return E_FAIL; | |
| } | |
| hr = BaseOutputPin_Construct(&QT_OutputPin_Vtbl, sizeof(QTOutPin), piOutput, &output_BaseOutputFuncTable, &This->filter.csFilter, (IPin**)target); | |
| if (SUCCEEDED(hr)) | |
| { | |
| QTOutPin *pin = (QTOutPin*)*target; | |
| pin->pmt = CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)); | |
| CopyMediaType(pin->pmt, amt); | |
| pin->pin.pin.pinInfo.pFilter = (LPVOID)This; | |
| pin->IQualityControl_iface.lpVtbl = &QTOutPin_QualityControl_Vtbl; | |
| BaseFilterImpl_IncrementPinVersion(&This->filter); | |
| hr = OutputQueue_Construct(&pin->pin, TRUE, TRUE, 5, FALSE, THREAD_PRIORITY_NORMAL, &output_OutputQueueFuncTable, &pin->queue); | |
| } | |
| else | |
| ERR("Failed with error %x\n", hr); | |
| return hr; | |
| } | |
| static HRESULT WINAPI QTSplitter_ChangeStart(IMediaSeeking *iface) | |
| { | |
| QTSplitter *This = impl_from_IMediaSeeking(iface); | |
| TRACE("(%p)\n", iface); | |
| EnterCriticalSection(&This->csReceive); | |
| This->movie_time = (This->sourceSeeking.llCurrent * This->movie_scale)/10000000; | |
| This->movie_start = This->movie_time; | |
| LeaveCriticalSection(&This->csReceive); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI QTSplitter_ChangeStop(IMediaSeeking *iface) | |
| { | |
| FIXME("(%p) filter hasn't implemented stop position change!\n", iface); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI QTSplitter_ChangeRate(IMediaSeeking *iface) | |
| { | |
| FIXME("(%p) filter hasn't implemented rate change!\n", iface); | |
| return S_OK; | |
| } | |
| static HRESULT WINAPI QT_Seeking_QueryInterface(IMediaSeeking * iface, REFIID riid, LPVOID * ppv) | |
| { | |
| QTSplitter *This = impl_from_IMediaSeeking(iface); | |
| return IBaseFilter_QueryInterface(&This->filter.IBaseFilter_iface, riid, ppv); | |
| } | |
| static ULONG WINAPI QT_Seeking_AddRef(IMediaSeeking * iface) | |
| { | |
| QTSplitter *This = impl_from_IMediaSeeking(iface); | |
| return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); | |
| } | |
| static ULONG WINAPI QT_Seeking_Release(IMediaSeeking * iface) | |
| { | |
| QTSplitter *This = impl_from_IMediaSeeking(iface); | |
| return IBaseFilter_Release(&This->filter.IBaseFilter_iface); | |
| } | |
| static const IMediaSeekingVtbl QT_Seeking_Vtbl = | |
| { | |
| QT_Seeking_QueryInterface, | |
| QT_Seeking_AddRef, | |
| QT_Seeking_Release, | |
| SourceSeekingImpl_GetCapabilities, | |
| SourceSeekingImpl_CheckCapabilities, | |
| SourceSeekingImpl_IsFormatSupported, | |
| SourceSeekingImpl_QueryPreferredFormat, | |
| SourceSeekingImpl_GetTimeFormat, | |
| SourceSeekingImpl_IsUsingTimeFormat, | |
| SourceSeekingImpl_SetTimeFormat, | |
| SourceSeekingImpl_GetDuration, | |
| SourceSeekingImpl_GetStopPosition, | |
| SourceSeekingImpl_GetCurrentPosition, | |
| SourceSeekingImpl_ConvertTimeFormat, | |
| SourceSeekingImpl_SetPositions, | |
| SourceSeekingImpl_GetPositions, | |
| SourceSeekingImpl_GetAvailable, | |
| SourceSeekingImpl_SetRate, | |
| SourceSeekingImpl_GetRate, | |
| SourceSeekingImpl_GetPreroll | |
| }; |