| /* |
| * 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 <assert.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; |
| |
| 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; |
| } QTSplitter; |
| |
| static const IPinVtbl QT_OutputPin_Vtbl; |
| static const IPinVtbl QT_InputPin_Vtbl; |
| static const IBaseFilterVtbl QT_Vtbl; |
| |
| static HRESULT QT_AddPin(QTSplitter *This, const PIN_INFO *piOutput, const AM_MEDIA_TYPE *amt, BOOL video); |
| static HRESULT QT_RemoveOutputPins(QTSplitter *This); |
| |
| /* |
| * Base Filter |
| */ |
| |
| static IPin* WINAPI QT_GetPin(BaseFilter *iface, int pos) |
| { |
| QTSplitter *This = (QTSplitter *)iface; |
| TRACE("Asking for pos %x\n", pos); |
| |
| if (pos > 2 || pos < 0) |
| return NULL; |
| switch (pos) |
| { |
| case 0: |
| IPin_AddRef((IPin*)&This->pInputPin); |
| return (IPin*)&This->pInputPin; |
| case 1: |
| if (This->pVideo_Pin) |
| IPin_AddRef((IPin*)This->pVideo_Pin); |
| return (IPin*)This->pVideo_Pin; |
| case 2: |
| if (This->pAudio_Pin) |
| IPin_AddRef((IPin*)This->pAudio_Pin); |
| return (IPin*)This->pAudio_Pin; |
| default: |
| return NULL; |
| } |
| } |
| |
| static LONG WINAPI QT_GetPinCount(BaseFilter *iface) |
| { |
| QTSplitter *This = (QTSplitter *)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}; |
| |
| EnterMovies(); |
| |
| RegisterWineDataHandler(); |
| |
| This = CoTaskMemAlloc(sizeof(*This)); |
| obj = (IUnknown*)This; |
| if (!This) |
| { |
| *phr = E_OUTOFMEMORY; |
| return NULL; |
| } |
| |
| BaseFilter_Init(&This->filter, &QT_Vtbl, &CLSID_QTSplitter, (DWORD_PTR)(__FILE__ ": QTSplitter.csFilter"), &BaseFuncTable); |
| |
| 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 = (IBaseFilter *)This; |
| lstrcpynW(piInput->achName, wcsInputPinName, sizeof(piInput->achName) / sizeof(piInput->achName[0])); |
| This->pInputPin.pin.lpVtbl = &QT_InputPin_Vtbl; |
| This->pInputPin.pin.refCount = 1; |
| This->pInputPin.pin.pConnectedTo = NULL; |
| This->pInputPin.pin.pCritSec = &This->filter.csFilter; |
| ZeroMemory(&This->pInputPin.pin.mtCurrent, sizeof(AM_MEDIA_TYPE)); |
| *phr = S_OK; |
| return obj; |
| } |
| |
| static void QT_Destroy(QTSplitter *This) |
| { |
| IPin *connected = NULL; |
| ULONG pinref; |
| |
| TRACE("Destroying\n"); |
| |
| /* Don't need to clean up output pins, disconnecting input pin will do that */ |
| IPin_ConnectedTo((IPin *)&This->pInputPin, &connected); |
| if (connected) |
| { |
| IPin_Disconnect(connected); |
| IPin_Release(connected); |
| } |
| pinref = IPin_Release((IPin *)&This->pInputPin); |
| if (pinref) |
| { |
| ERR("pinref should be null, is %u, destroying anyway\n", pinref); |
| assert((LONG)pinref > 0); |
| |
| while (pinref) |
| pinref = IPin_Release((IPin *)&This->pInputPin); |
| } |
| |
| if (This->pQTMovie) |
| DisposeMovie(This->pQTMovie); |
| if (This->vContext) |
| QTVisualContextRelease(This->vContext); |
| if (This->aSession) |
| MovieAudioExtractionEnd(This->aSession); |
| CloseHandle(This->runEvent); |
| |
| ExitMovies(); |
| CoTaskMemFree(This); |
| } |
| |
| static HRESULT WINAPI QT_QueryInterface(IBaseFilter *iface, REFIID riid, LPVOID *ppv) |
| { |
| QTSplitter *This = (QTSplitter *)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; |
| |
| if (*ppv) |
| { |
| IUnknown_AddRef((IUnknown *)(*ppv)); |
| return S_OK; |
| } |
| |
| if (!IsEqualIID(riid, &IID_IPin) && !IsEqualIID(riid, &IID_IVideoWindow)) |
| FIXME("No interface for %s!\n", debugstr_guid(riid)); |
| |
| return E_NOINTERFACE; |
| } |
| |
| static ULONG WINAPI QT_Release(IBaseFilter *iface) |
| { |
| QTSplitter *This = (QTSplitter *)iface; |
| ULONG refCount = BaseFilterImpl_Release(iface); |
| |
| 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 = (QTSplitter *)iface; |
| |
| TRACE("()\n"); |
| |
| IAsyncReader_BeginFlush(This->pInputPin.pReader); |
| IAsyncReader_EndFlush(This->pInputPin.pReader); |
| |
| 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_thread(LPVOID data) |
| { |
| QTSplitter *This = (QTSplitter *)data; |
| HRESULT hr = S_OK; |
| TimeValue movie_time=0, next_time; |
| CVPixelBufferRef pixelBuffer = NULL; |
| OSStatus err; |
| TimeRecord tr; |
| |
| if (This->pAudio_Pin) |
| { |
| /* according to QA1469 a movie has to be fully loaded before we |
| can reliably start the Extraction session */ |
| |
| while(GetMovieLoadState(This->pQTMovie) < kMovieLoadStateComplete) |
| MoviesTask(This->pQTMovie,1000); |
| |
| QT_Create_Extract_Session(This); |
| } |
| |
| WaitForSingleObject(This->runEvent, -1); |
| |
| This->state = State_Running; |
| /* Prime the pump: Needed for MPEG streams */ |
| GetMovieNextInterestingTime(This->pQTMovie, nextTimeEdgeOK | nextTimeStep, 0, NULL, movie_time, 1, &next_time, NULL); |
| |
| GetMovieTime(This->pQTMovie, &tr); |
| do |
| { |
| LONGLONG tStart=0, tStop=0; |
| float time; |
| |
| GetMovieNextInterestingTime(This->pQTMovie, nextTimeStep, 0, NULL, movie_time, 1, &next_time, NULL); |
| |
| if (next_time == -1) |
| { |
| TRACE("No next time\n"); |
| break; |
| } |
| |
| tr.value = SInt64ToWide(next_time); |
| SetMovieTime(This->pQTMovie, &tr); |
| MoviesTask(This->pQTMovie,0); |
| QTVisualContextTask(This->vContext); |
| |
| TRACE("In loop at time %ld\n",movie_time); |
| TRACE("In Next time %ld\n",next_time); |
| |
| time = (float)movie_time / tr.scale; |
| tStart = time * 10000000; |
| time = (float)next_time / tr.scale; |
| tStop = time * 10000000; |
| |
| /* Deliver Audio */ |
| if (This->pAudio_Pin && ((BaseOutputPin*)This->pAudio_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((BaseOutputPin*)This->pAudio_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 / tr.scale; |
| time = (float)movie_time / tr.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); |
| TRACE("Got %i frames\n",(int)frames); |
| |
| IMediaSample_SetActualDataLength(sample, frames * pvi->nBlockAlign); |
| |
| IMediaSample_SetMediaTime(sample, &tStart, &tStop); |
| if (tStart) |
| IMediaSample_SetTime(sample, &tStart, &tStop); |
| else |
| IMediaSample_SetTime(sample, NULL, NULL); |
| |
| 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((BaseOutputPin*)This->pVideo_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, &tStart, &tStop); |
| if (tStart) |
| IMediaSample_SetTime(sample, &tStart, &tStop); |
| else |
| IMediaSample_SetTime(sample, NULL, NULL); |
| |
| 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"); |
| |
| movie_time = next_time; |
| } 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 = (QTSplitter *)iface; |
| HRESULT hr_any = VFW_E_NOT_CONNECTED; |
| |
| TRACE("(%s)\n", wine_dbgstr_longlong(tStart)); |
| |
| EnterCriticalSection(&This->filter.csFilter); |
| This->filter.rtStreamStart = tStart; |
| |
| if (This->pVideo_Pin) |
| hr = BaseOutputPinImpl_Active((BaseOutputPin *)This->pVideo_Pin); |
| if (SUCCEEDED(hr)) |
| hr_any = hr; |
| if (This->pAudio_Pin) |
| hr = BaseOutputPinImpl_Active((BaseOutputPin *)This->pAudio_Pin); |
| if (SUCCEEDED(hr)) |
| hr_any = hr; |
| |
| hr = hr_any; |
| LeaveCriticalSection(&This->filter.csFilter); |
| |
| SetEvent(This->runEvent); |
| |
| return hr; |
| } |
| |
| static HRESULT WINAPI QT_GetState(IBaseFilter *iface, DWORD dwMilliSecsTimeout, FILTER_STATE *pState) |
| { |
| QTSplitter *This = (QTSplitter *)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) |
| { |
| hr = BaseOutputPinImpl_BreakConnect(&This->pVideo_Pin->pin); |
| TRACE("Disconnect: %08x\n", hr); |
| IPin_Release((IPin*)This->pVideo_Pin); |
| This->pVideo_Pin = NULL; |
| } |
| if (This->pAudio_Pin) |
| { |
| hr = BaseOutputPinImpl_BreakConnect(&This->pAudio_Pin->pin); |
| TRACE("Disconnect: %08x\n", hr); |
| IPin_Release((IPin*)This->pAudio_Pin); |
| This->pAudio_Pin = NULL; |
| } |
| |
| BaseFilterImpl_IncrementPinVersion((BaseFilter*)This); |
| return S_OK; |
| } |
| |
| static ULONG WINAPI QTInPin_Release(IPin *iface) |
| { |
| QTInPin *This = (QTInPin*)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; |
| This->pin.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; |
| |
| 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; |
| |
| filter->outputSize = outputWidth * outputHeight * outputDepth; |
| 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; |
| } |
| |
| piOutput.dir = PINDIR_OUTPUT; |
| piOutput.pFilter = (IBaseFilter *)filter; |
| 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 = (IBaseFilter *)filter; |
| 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; |
| |
| 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 a audio track\n",trk); |
| if (trk) |
| hr = QT_Process_Audio_Track(filter, trk); |
| |
| CreateThread(NULL, 0, QTSplitter_thread, filter, 0, &tid); |
| TRACE("Created thread 0x%08x\n",tid); |
| |
| 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 = (QTInPin*)iface; |
| |
| 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(&((QTSplitter *)This->pin.pinInfo.pFilter)->filter.csFilter); |
| hr = QT_Process_Movie((QTSplitter *)This->pin.pinInfo.pFilter); |
| if (FAILED(hr)) |
| { |
| LeaveCriticalSection(&((QTSplitter *)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 = ((QTSplitter *)This->pin.pinInfo.pFilter)->outputSize + props.cbAlign; |
| props.cbPrefix = 0; |
| |
| hr = IAsyncReader_RequestAllocator(This->pReader, NULL, &props, &This->pAlloc); |
| if (SUCCEEDED(hr)) |
| { |
| CopyMediaType(&This->pin.mtCurrent, pmt); |
| This->pin.pConnectedTo = pReceivePin; |
| IPin_AddRef(pReceivePin); |
| hr = IMemAllocator_Commit(This->pAlloc); |
| } |
| else |
| { |
| QT_RemoveOutputPins((QTSplitter *)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(&((QTSplitter *)This->pin.pinInfo.pFilter)->filter.csFilter); |
| |
| return hr; |
| } |
| |
| static HRESULT WINAPI QTInPin_Disconnect(IPin *iface) |
| { |
| HRESULT hr; |
| QTInPin *This = (QTInPin*)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 = (QTSplitter *)This->pin.pinInfo.pFilter; |
| |
| if (SUCCEEDED(hr) && state == State_Stopped) |
| { |
| IMemAllocator_Decommit(This->pAlloc); |
| IPin_Disconnect(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 = (QTInPin*)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 = (QTInPin*)iface; |
| QTSplitter *This = (QTSplitter*)pin->pin.pinInfo.pFilter; |
| |
| FIXME("Propagate message on %p\n", This); |
| return S_OK; |
| } |
| |
| static HRESULT WINAPI QTInPin_BeginFlush(IPin *iface) |
| { |
| QTInPin *pin = (QTInPin*)iface; |
| QTSplitter *This = (QTSplitter*)pin->pin.pinInfo.pFilter; |
| |
| FIXME("Propagate message on %p\n", This); |
| return S_OK; |
| } |
| |
| static HRESULT WINAPI QTInPin_EndFlush(IPin *iface) |
| { |
| QTInPin *pin = (QTInPin*)iface; |
| QTSplitter *This = (QTSplitter*)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 = (QTInPin*)iface; |
| QTSplitter *This = (QTSplitter*)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 = (QTInPin*)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) |
| { |
| BasePin *This = (BasePin *)iface; |
| |
| TRACE("(%p/%p)->(%p)\n", This, iface, ppEnum); |
| |
| return EnumMediaTypes_Construct(This, 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 HRESULT WINAPI QTOutPin_QueryInterface(IPin *iface, REFIID riid, void **ppv) |
| { |
| QTOutPin *This = (QTOutPin *)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); |
| |
| 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 = (QTOutPin *)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); |
| OutputQueue_Destroy(This->queue); |
| CoTaskMemFree(This); |
| return 0; |
| } |
| return refCount; |
| } |
| |
| static HRESULT WINAPI QTOutPin_GetMediaType(BasePin *iface, int iPosition, AM_MEDIA_TYPE *pmt) |
| { |
| QTOutPin *This = (QTOutPin *)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 = (QTOutPin *)iface; |
| QTSplitter *QTfilter = (QTSplitter*)This->pin.pin.pinInfo.pFilter; |
| |
| *pAlloc = NULL; |
| if (QTfilter->pInputPin.pAlloc) |
| hr = IMemInputPin_NotifyAllocator(pPin, QTfilter->pInputPin.pAlloc, FALSE); |
| 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((IPin *)This); |
| } |
| 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 const BasePinFuncTable output_BaseFuncTable = { |
| NULL, |
| BaseOutputPinImpl_AttemptConnection, |
| BasePinImpl_GetMediaTypeVersion, |
| QTOutPin_GetMediaType |
| }; |
| |
| static const BaseOutputPinFuncTable output_BaseOutputFuncTable = { |
| 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_BaseFuncTable, &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; |
| |
| BaseFilterImpl_IncrementPinVersion((BaseFilter*)This); |
| |
| hr = OutputQueue_Construct((BaseOutputPin*)pin, TRUE, TRUE, 5, FALSE, THREAD_PRIORITY_NORMAL, &output_OutputQueueFuncTable, &pin->queue); |
| } |
| else |
| ERR("Failed with error %x\n", hr); |
| return hr; |
| } |