From 56aa5137e7d582388c842d798a3c55dc10eab0ed Mon Sep 17 00:00:00 2001 From: Oleg Dubinskiy Date: Sat, 7 Mar 2020 21:42:35 +0200 Subject: [PATCH] [QCAP] Import qcap.dll from Wine Staging 3.3. CORE-16350 (#2421) Fix also MSVC build. [DOC] Add qcap entry in README.WINE Co-Authored-By: Thomas Faber <18138474+ThFabba@users.noreply.github.com> --- dll/directx/wine/CMakeLists.txt | 1 + dll/directx/wine/qcap/CMakeLists.txt | 28 + dll/directx/wine/qcap/audiorecord.c | 302 +++ dll/directx/wine/qcap/avico.c | 738 ++++++++ dll/directx/wine/qcap/avimux.c | 2421 ++++++++++++++++++++++++ dll/directx/wine/qcap/capture.h | 37 + dll/directx/wine/qcap/capturegraph.c | 881 +++++++++ dll/directx/wine/qcap/enummedia.c | 44 + dll/directx/wine/qcap/qcap.spec | 4 + dll/directx/wine/qcap/qcap_main.c | 198 ++ dll/directx/wine/qcap/qcap_main.h | 62 + dll/directx/wine/qcap/smartteefilter.c | 697 +++++++ dll/directx/wine/qcap/v4l.c | 978 ++++++++++ dll/directx/wine/qcap/version.rc | 26 + dll/directx/wine/qcap/vfwcapture.c | 834 ++++++++ dll/directx/wine/qcap/yuv.c | 200 ++ media/doc/README.WINE | 1 + 17 files changed, 7452 insertions(+) create mode 100644 dll/directx/wine/qcap/CMakeLists.txt create mode 100644 dll/directx/wine/qcap/audiorecord.c create mode 100644 dll/directx/wine/qcap/avico.c create mode 100644 dll/directx/wine/qcap/avimux.c create mode 100644 dll/directx/wine/qcap/capture.h create mode 100644 dll/directx/wine/qcap/capturegraph.c create mode 100644 dll/directx/wine/qcap/enummedia.c create mode 100644 dll/directx/wine/qcap/qcap.spec create mode 100644 dll/directx/wine/qcap/qcap_main.c create mode 100644 dll/directx/wine/qcap/qcap_main.h create mode 100644 dll/directx/wine/qcap/smartteefilter.c create mode 100644 dll/directx/wine/qcap/v4l.c create mode 100644 dll/directx/wine/qcap/version.rc create mode 100644 dll/directx/wine/qcap/vfwcapture.c create mode 100644 dll/directx/wine/qcap/yuv.c diff --git a/dll/directx/wine/CMakeLists.txt b/dll/directx/wine/CMakeLists.txt index 9cccbb14662..ee7f69896ef 100644 --- a/dll/directx/wine/CMakeLists.txt +++ b/dll/directx/wine/CMakeLists.txt @@ -36,6 +36,7 @@ add_subdirectory(dpnhpast) add_subdirectory(dsound) add_subdirectory(dxdiagn) add_subdirectory(msdmo) +add_subdirectory(qcap) add_subdirectory(qedit) add_subdirectory(quartz) add_subdirectory(wined3d) diff --git a/dll/directx/wine/qcap/CMakeLists.txt b/dll/directx/wine/qcap/CMakeLists.txt new file mode 100644 index 00000000000..cbd24d39bcc --- /dev/null +++ b/dll/directx/wine/qcap/CMakeLists.txt @@ -0,0 +1,28 @@ + +add_definitions(-D__WINESRC__) + +include_directories(${REACTOS_SOURCE_DIR}/sdk/include/reactos/wine) +spec2def(qcap.dll qcap.spec) + +list(APPEND SOURCE + audiorecord.c + avico.c + avimux.c + capturegraph.c + enummedia.c + qcap_main.c + smartteefilter.c + v4l.c + vfwcapture.c + yuv.c) + +add_library(qcap MODULE ${SOURCE} + ${CMAKE_CURRENT_BINARY_DIR}/qcap_stubs.c + ${CMAKE_CURRENT_BINARY_DIR}/qcap.def + version.rc) + +set_module_type(qcap win32dll) +target_link_libraries(qcap strmbase strmiids uuid wine) +add_importlibs(qcap ole32 oleaut32 gdi32 advapi32 advapi32_vista msvcrt kernel32 ntdll) +add_delay_importlibs(qcap msvfw32) +add_cd_file(TARGET qcap DESTINATION reactos/system32 FOR all) diff --git a/dll/directx/wine/qcap/audiorecord.c b/dll/directx/wine/qcap/audiorecord.c new file mode 100644 index 00000000000..1187d4afab2 --- /dev/null +++ b/dll/directx/wine/qcap/audiorecord.c @@ -0,0 +1,302 @@ +/* Implementation of the Audio Capture Filter (CLSID_AudioRecord) + * + * Copyright 2015 Damjan Jovanovic + * + * 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 + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "wtypes.h" +#include "wingdi.h" +#include "winuser.h" +#include "dshow.h" + +#include "qcap_main.h" + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(qcap); + +typedef struct { + IUnknown IUnknown_iface; + IUnknown *outerUnknown; + BaseFilter filter; + IPersistPropertyBag IPersistPropertyBag_iface; + BaseOutputPin *output; +} AudioRecord; + +static inline AudioRecord *impl_from_IUnknown(IUnknown *iface) +{ + return CONTAINING_RECORD(iface, AudioRecord, IUnknown_iface); +} + +static inline AudioRecord *impl_from_BaseFilter(BaseFilter *filter) +{ + return CONTAINING_RECORD(filter, AudioRecord, filter); +} + +static inline AudioRecord *impl_from_IBaseFilter(IBaseFilter *iface) +{ + BaseFilter *filter = CONTAINING_RECORD(iface, BaseFilter, IBaseFilter_iface); + return impl_from_BaseFilter(filter); +} + +static inline AudioRecord *impl_from_IPersistPropertyBag(IPersistPropertyBag *iface) +{ + return CONTAINING_RECORD(iface, AudioRecord, IPersistPropertyBag_iface); +} + +static HRESULT WINAPI Unknown_QueryInterface(IUnknown *iface, REFIID riid, LPVOID *ppv) +{ + AudioRecord *This = impl_from_IUnknown(iface); + if (IsEqualIID(riid, &IID_IUnknown)) { + TRACE("(%p)->(IID_IUnknown, %p)\n", This, ppv); + *ppv = &This->filter.IBaseFilter_iface; + } else if (IsEqualIID(riid, &IID_IPersist)) { + TRACE("(%p)->(IID_IPersist, %p)\n", This, ppv); + *ppv = &This->filter.IBaseFilter_iface; + } else if (IsEqualIID(riid, &IID_IMediaFilter)) { + TRACE("(%p)->(IID_IMediaFilter, %p)\n", This, ppv); + *ppv = &This->filter.IBaseFilter_iface; + } else if (IsEqualIID(riid, &IID_IBaseFilter)) { + TRACE("(%p)->(IID_IBaseFilter, %p)\n", This, ppv); + *ppv = &This->filter.IBaseFilter_iface; + } else if (IsEqualIID(riid, &IID_IPersistPropertyBag)) { + TRACE("(%p)->(IID_IPersistPropertyBag, %p)\n", This, ppv); + *ppv = &This->IPersistPropertyBag_iface; + } else { + FIXME("(%p): no interface for %s\n", This, debugstr_guid(riid)); + *ppv = NULL; + return E_NOINTERFACE; + } + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI Unknown_AddRef(IUnknown *iface) +{ + AudioRecord *This = impl_from_IUnknown(iface); + return BaseFilterImpl_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI Unknown_Release(IUnknown *iface) +{ + AudioRecord *This = impl_from_IUnknown(iface); + ULONG ref = BaseFilterImpl_Release(&This->filter.IBaseFilter_iface); + TRACE("(%p/%p)->() ref=%d\n", iface, This, ref); + if (!ref) { + CoTaskMemFree(This); + } + return ref; +} + +static const IUnknownVtbl UnknownVtbl = { + Unknown_QueryInterface, + Unknown_AddRef, + Unknown_Release +}; + +static HRESULT WINAPI AudioRecord_QueryInterface(IBaseFilter *iface, REFIID riid, void **ppv) +{ + AudioRecord *This = impl_from_IBaseFilter(iface); + return IUnknown_QueryInterface(This->outerUnknown, riid, ppv); +} + +static ULONG WINAPI AudioRecord_AddRef(IBaseFilter *iface) +{ + AudioRecord *This = impl_from_IBaseFilter(iface); + return IUnknown_AddRef(This->outerUnknown); +} + +static ULONG WINAPI AudioRecord_Release(IBaseFilter *iface) +{ + AudioRecord *This = impl_from_IBaseFilter(iface); + return IUnknown_Release(This->outerUnknown); +} + +static HRESULT WINAPI AudioRecord_Stop(IBaseFilter *iface) +{ + AudioRecord *This = impl_from_IBaseFilter(iface); + FIXME("(%p): stub\n", This); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioRecord_Pause(IBaseFilter *iface) +{ + AudioRecord *This = impl_from_IBaseFilter(iface); + FIXME("(%p): stub\n", This); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioRecord_Run(IBaseFilter *iface, REFERENCE_TIME tStart) +{ + AudioRecord *This = impl_from_IBaseFilter(iface); + FIXME("(%p, %s): stub\n", This, wine_dbgstr_longlong(tStart)); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioRecord_FindPin(IBaseFilter *iface, LPCWSTR Id, IPin **ppPin) +{ + AudioRecord *This = impl_from_IBaseFilter(iface); + FIXME("(%p)->(%s, %p): stub\n", This, debugstr_w(Id), ppPin); + return E_NOTIMPL; +} + +static const IBaseFilterVtbl AudioRecordVtbl = { + AudioRecord_QueryInterface, + AudioRecord_AddRef, + AudioRecord_Release, + BaseFilterImpl_GetClassID, + AudioRecord_Stop, + AudioRecord_Pause, + AudioRecord_Run, + BaseFilterImpl_GetState, + BaseFilterImpl_SetSyncSource, + BaseFilterImpl_GetSyncSource, + BaseFilterImpl_EnumPins, + AudioRecord_FindPin, + BaseFilterImpl_QueryFilterInfo, + BaseFilterImpl_JoinFilterGraph, + BaseFilterImpl_QueryVendorInfo +}; + +static IPin* WINAPI AudioRecord_GetPin(BaseFilter *iface, int pos) +{ + AudioRecord *This = impl_from_BaseFilter(iface); + FIXME("(%p, %d): stub\n", This, pos); + return NULL; +} + +static LONG WINAPI AudioRecord_GetPinCount(BaseFilter *iface) +{ + AudioRecord *This = impl_from_BaseFilter(iface); + FIXME("(%p): stub\n", This); + return 0; +} + +static const BaseFilterFuncTable AudioRecordFuncs = { + AudioRecord_GetPin, + AudioRecord_GetPinCount +}; + +static HRESULT WINAPI PPB_QueryInterface(IPersistPropertyBag *iface, REFIID riid, LPVOID *ppv) +{ + AudioRecord *This = impl_from_IPersistPropertyBag(iface); + return IUnknown_QueryInterface(This->outerUnknown, riid, ppv); +} + +static ULONG WINAPI PPB_AddRef(IPersistPropertyBag *iface) +{ + AudioRecord *This = impl_from_IPersistPropertyBag(iface); + return IUnknown_AddRef(This->outerUnknown); +} + +static ULONG WINAPI PPB_Release(IPersistPropertyBag *iface) +{ + AudioRecord *This = impl_from_IPersistPropertyBag(iface); + return IUnknown_Release(This->outerUnknown); +} + +static HRESULT WINAPI PPB_GetClassID(IPersistPropertyBag *iface, CLSID *pClassID) +{ + AudioRecord *This = impl_from_IPersistPropertyBag(iface); + TRACE("(%p/%p)->(%p)\n", iface, This, pClassID); + return IBaseFilter_GetClassID(&This->filter.IBaseFilter_iface, pClassID); +} + +static HRESULT WINAPI PPB_InitNew(IPersistPropertyBag *iface) +{ + AudioRecord *This = impl_from_IPersistPropertyBag(iface); + FIXME("(%p/%p)->(): stub\n", iface, This); + return E_NOTIMPL; +} + +static HRESULT WINAPI PPB_Load(IPersistPropertyBag *iface, IPropertyBag *pPropBag, + IErrorLog *pErrorLog) +{ + AudioRecord *This = impl_from_IPersistPropertyBag(iface); + HRESULT hr; + VARIANT var; + static const WCHAR WaveInIDW[] = {'W','a','v','e','I','n','I','D',0}; + + TRACE("(%p/%p)->(%p, %p)\n", iface, This, pPropBag, pErrorLog); + + V_VT(&var) = VT_I4; + hr = IPropertyBag_Read(pPropBag, WaveInIDW, &var, pErrorLog); + if (SUCCEEDED(hr)) + { + FIXME("FIXME: implement opening waveIn device %d\n", V_I4(&var)); + } + + return hr; +} + +static HRESULT WINAPI PPB_Save(IPersistPropertyBag *iface, IPropertyBag *pPropBag, + BOOL fClearDirty, BOOL fSaveAllProperties) +{ + AudioRecord *This = impl_from_IPersistPropertyBag(iface); + FIXME("(%p/%p)->(%p, %u, %u): stub\n", iface, This, pPropBag, fClearDirty, fSaveAllProperties); + return E_NOTIMPL; +} + +static const IPersistPropertyBagVtbl PersistPropertyBagVtbl = +{ + PPB_QueryInterface, + PPB_AddRef, + PPB_Release, + PPB_GetClassID, + PPB_InitNew, + PPB_Load, + PPB_Save +}; + +IUnknown* WINAPI QCAP_createAudioCaptureFilter(IUnknown *outer, HRESULT *phr) +{ + HRESULT hr; + AudioRecord *This = NULL; + + FIXME("(%p, %p): the entire CLSID_AudioRecord implementation is just stubs\n", outer, phr); + + This = CoTaskMemAlloc(sizeof(*This)); + if (This == NULL) { + hr = E_OUTOFMEMORY; + goto end; + } + memset(This, 0, sizeof(*This)); + This->IUnknown_iface.lpVtbl = &UnknownVtbl; + This->IPersistPropertyBag_iface.lpVtbl = &PersistPropertyBagVtbl; + if (outer) + This->outerUnknown = outer; + else + This->outerUnknown = &This->IUnknown_iface; + + hr = BaseFilter_Init(&This->filter, &AudioRecordVtbl, &CLSID_AudioRecord, + (DWORD_PTR)(__FILE__ ": AudioRecord.csFilter"), &AudioRecordFuncs); + +end: + *phr = hr; + if (SUCCEEDED(hr)) { + return (IUnknown*)&This->filter.IBaseFilter_iface; + } else { + if (This) + IBaseFilter_Release(&This->filter.IBaseFilter_iface); + return NULL; + } +} diff --git a/dll/directx/wine/qcap/avico.c b/dll/directx/wine/qcap/avico.c new file mode 100644 index 00000000000..32f3b56e7a5 --- /dev/null +++ b/dll/directx/wine/qcap/avico.c @@ -0,0 +1,738 @@ +/* + * Copyright 2013 Jacek Caban 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 + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "dshow.h" +#include "vfw.h" +#include "aviriff.h" + +#include "qcap_main.h" + +#include "wine/debug.h" +#include "wine/heap.h" + +WINE_DEFAULT_DEBUG_CHANNEL(qcap); + +typedef struct { + BaseFilter filter; + IPersistPropertyBag IPersistPropertyBag_iface; + + BaseInputPin *in; + BaseOutputPin *out; + + DWORD fcc_handler; + HIC hic; + + VIDEOINFOHEADER *videoinfo; + size_t videoinfo_size; + DWORD driver_flags; + DWORD max_frame_size; + + DWORD frame_cnt; +} AVICompressor; + +static inline AVICompressor *impl_from_BaseFilter(BaseFilter *filter) +{ + return CONTAINING_RECORD(filter, AVICompressor, filter); +} + +static inline AVICompressor *impl_from_IBaseFilter(IBaseFilter *iface) +{ + BaseFilter *filter = CONTAINING_RECORD(iface, BaseFilter, IBaseFilter_iface); + return impl_from_BaseFilter(filter); +} + +static inline AVICompressor *impl_from_BasePin(BasePin *pin) +{ + return impl_from_IBaseFilter(pin->pinInfo.pFilter); +} + +static HRESULT ensure_driver(AVICompressor *This) +{ + if(This->hic) + return S_OK; + + This->hic = ICOpen(FCC('v','i','d','c'), This->fcc_handler, ICMODE_COMPRESS); + if(!This->hic) { + FIXME("ICOpen failed\n"); + return E_FAIL; + } + + return S_OK; +} + +static HRESULT fill_format_info(AVICompressor *This, VIDEOINFOHEADER *src_videoinfo) +{ + DWORD size; + ICINFO icinfo; + HRESULT hres; + + hres = ensure_driver(This); + if(hres != S_OK) + return hres; + + size = ICGetInfo(This->hic, &icinfo, sizeof(icinfo)); + if(size != sizeof(icinfo)) + return E_FAIL; + + size = ICCompressGetFormatSize(This->hic, &src_videoinfo->bmiHeader); + if(!size) { + FIXME("ICCompressGetFormatSize failed\n"); + return E_FAIL; + } + + size += FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader); + This->videoinfo = heap_alloc(size); + if(!This->videoinfo) + return E_OUTOFMEMORY; + + This->videoinfo_size = size; + This->driver_flags = icinfo.dwFlags; + memset(This->videoinfo, 0, sizeof(*This->videoinfo)); + ICCompressGetFormat(This->hic, &src_videoinfo->bmiHeader, &This->videoinfo->bmiHeader); + + This->videoinfo->dwBitRate = 10000000/src_videoinfo->AvgTimePerFrame * This->videoinfo->bmiHeader.biSizeImage * 8; + This->videoinfo->AvgTimePerFrame = src_videoinfo->AvgTimePerFrame; + This->max_frame_size = This->videoinfo->bmiHeader.biSizeImage; + return S_OK; +} + +static HRESULT WINAPI AVICompressor_QueryInterface(IBaseFilter *iface, REFIID riid, void **ppv) +{ + AVICompressor *This = impl_from_IBaseFilter(iface); + + if(IsEqualIID(riid, &IID_IUnknown)) { + TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv); + *ppv = &This->filter.IBaseFilter_iface; + }else if(IsEqualIID(riid, &IID_IPersist)) { + TRACE("(%p)->(IID_IPersist %p)\n", This, ppv); + *ppv = &This->filter.IBaseFilter_iface; + }else if(IsEqualIID(riid, &IID_IMediaFilter)) { + TRACE("(%p)->(IID_IMediaFilter %p)\n", This, ppv); + *ppv = &This->filter.IBaseFilter_iface; + }else if(IsEqualIID(riid, &IID_IBaseFilter)) { + TRACE("(%p)->(IID_IBaseFilter %p)\n", This, ppv); + *ppv = &This->filter.IBaseFilter_iface; + }else if(IsEqualIID(riid, &IID_IPersistPropertyBag)) { + TRACE("(%p)->(IID_IPersistPropertyBag %p)\n", This, ppv); + *ppv = &This->IPersistPropertyBag_iface; + }else { + FIXME("no interface for %s\n", debugstr_guid(riid)); + *ppv = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + +} + +static ULONG WINAPI AVICompressor_Release(IBaseFilter *iface) +{ + AVICompressor *This = impl_from_IBaseFilter(iface); + ULONG ref = BaseFilterImpl_Release(&This->filter.IBaseFilter_iface); + + TRACE("(%p) ref=%d\n", This, ref); + + if(!ref) { + if(This->hic) + ICClose(This->hic); + heap_free(This->videoinfo); + if(This->in) + BaseInputPinImpl_Release(&This->in->pin.IPin_iface); + if(This->out) + BaseOutputPinImpl_Release(&This->out->pin.IPin_iface); + heap_free(This); + } + + return ref; +} + +static HRESULT WINAPI AVICompressor_Stop(IBaseFilter *iface) +{ + AVICompressor *This = impl_from_IBaseFilter(iface); + + TRACE("(%p)\n", This); + + if(This->filter.state == State_Stopped) + return S_OK; + + ICCompressEnd(This->hic); + This->filter.state = State_Stopped; + return S_OK; +} + +static HRESULT WINAPI AVICompressor_Pause(IBaseFilter *iface) +{ + AVICompressor *This = impl_from_IBaseFilter(iface); + FIXME("(%p)\n", This); + return E_NOTIMPL; +} + +static HRESULT WINAPI AVICompressor_Run(IBaseFilter *iface, REFERENCE_TIME tStart) +{ + AVICompressor *This = impl_from_IBaseFilter(iface); + HRESULT hres; + + TRACE("(%p)->(%s)\n", This, wine_dbgstr_longlong(tStart)); + + if(This->filter.state == State_Running) + return S_OK; + + hres = IMemAllocator_Commit(This->out->pAllocator); + if(FAILED(hres)) { + FIXME("Commit failed: %08x\n", hres); + return hres; + } + + This->frame_cnt = 0; + + This->filter.state = State_Running; + return S_OK; +} + +static HRESULT WINAPI AVICompressor_FindPin(IBaseFilter *iface, LPCWSTR Id, IPin **ppPin) +{ + AVICompressor *This = impl_from_IBaseFilter(iface); + FIXME("(%p)->(%s %p)\n", This, debugstr_w(Id), ppPin); + return VFW_E_NOT_FOUND; +} + +static HRESULT WINAPI AVICompressor_QueryFilterInfo(IBaseFilter *iface, FILTER_INFO *pInfo) +{ + AVICompressor *This = impl_from_IBaseFilter(iface); + FIXME("(%p)->(%p)\n", This, pInfo); + return E_NOTIMPL; +} + +static HRESULT WINAPI AVICompressor_QueryVendorInfo(IBaseFilter *iface, LPWSTR *pVendorInfo) +{ + AVICompressor *This = impl_from_IBaseFilter(iface); + FIXME("(%p)->(%p)\n", This, pVendorInfo); + return E_NOTIMPL; +} + +static const IBaseFilterVtbl AVICompressorVtbl = { + AVICompressor_QueryInterface, + BaseFilterImpl_AddRef, + AVICompressor_Release, + BaseFilterImpl_GetClassID, + AVICompressor_Stop, + AVICompressor_Pause, + AVICompressor_Run, + BaseFilterImpl_GetState, + BaseFilterImpl_SetSyncSource, + BaseFilterImpl_GetSyncSource, + BaseFilterImpl_EnumPins, + AVICompressor_FindPin, + AVICompressor_QueryFilterInfo, + BaseFilterImpl_JoinFilterGraph, + AVICompressor_QueryVendorInfo +}; + +static IPin* WINAPI AVICompressor_GetPin(BaseFilter *iface, int pos) +{ + AVICompressor *This = impl_from_BaseFilter(iface); + IPin *ret; + + TRACE("(%p)->(%d)\n", This, pos); + + switch(pos) { + case 0: + ret = &This->in->pin.IPin_iface; + break; + case 1: + ret = &This->out->pin.IPin_iface; + break; + default: + TRACE("No pin %d\n", pos); + return NULL; + }; + + IPin_AddRef(ret); + return ret; +} + +static LONG WINAPI AVICompressor_GetPinCount(BaseFilter *iface) +{ + return 2; +} + +static const BaseFilterFuncTable filter_func_table = { + AVICompressor_GetPin, + AVICompressor_GetPinCount +}; + +static AVICompressor *impl_from_IPersistPropertyBag(IPersistPropertyBag *iface) +{ + return CONTAINING_RECORD(iface, AVICompressor, IPersistPropertyBag_iface); +} + +static HRESULT WINAPI AVICompressorPropertyBag_QueryInterface(IPersistPropertyBag *iface, REFIID riid, void **ppv) +{ + AVICompressor *This = impl_from_IPersistPropertyBag(iface); + return IBaseFilter_QueryInterface(&This->filter.IBaseFilter_iface, riid, ppv); +} + +static ULONG WINAPI AVICompressorPropertyBag_AddRef(IPersistPropertyBag *iface) +{ + AVICompressor *This = impl_from_IPersistPropertyBag(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI AVICompressorPropertyBag_Release(IPersistPropertyBag *iface) +{ + AVICompressor *This = impl_from_IPersistPropertyBag(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI AVICompressorPropertyBag_GetClassID(IPersistPropertyBag *iface, CLSID *pClassID) +{ + AVICompressor *This = impl_from_IPersistPropertyBag(iface); + return IBaseFilter_GetClassID(&This->filter.IBaseFilter_iface, pClassID); +} + +static HRESULT WINAPI AVICompressorPropertyBag_InitNew(IPersistPropertyBag *iface) +{ + AVICompressor *This = impl_from_IPersistPropertyBag(iface); + FIXME("(%p)->()\n", This); + return E_NOTIMPL; +} + +static HRESULT WINAPI AVICompressorPropertyBag_Load(IPersistPropertyBag *iface, IPropertyBag *pPropBag, IErrorLog *pErrorLog) +{ + AVICompressor *This = impl_from_IPersistPropertyBag(iface); + BSTR str; + VARIANT v; + HRESULT hres; + + static const WCHAR fcc_handlerW[] = {'F','c','c','H','a','n','d','l','e','r',0}; + + TRACE("(%p)->(%p %p)\n", This, pPropBag, pErrorLog); + + V_VT(&v) = VT_EMPTY; + hres = IPropertyBag_Read(pPropBag, fcc_handlerW, &v, NULL); + if(FAILED(hres)) { + WARN("Could not read FccHandler: %08x\n", hres); + return hres; + } + + if(V_VT(&v) != VT_BSTR) { + FIXME("Got vt %d\n", V_VT(&v)); + VariantClear(&v); + return E_FAIL; + } + + str = V_BSTR(&v); + TRACE("FccHandler = %s\n", debugstr_w(str)); + if(SysStringLen(str) != 4) { + FIXME("Invalid FccHandler len\n"); + SysFreeString(str); + return E_FAIL; + } + + This->fcc_handler = FCC(str[0], str[1], str[2], str[3]); + SysFreeString(str); + return S_OK; +} + +static HRESULT WINAPI AVICompressorPropertyBag_Save(IPersistPropertyBag *iface, IPropertyBag *pPropBag, + BOOL fClearDirty, BOOL fSaveAllProperties) +{ + AVICompressor *This = impl_from_IPersistPropertyBag(iface); + FIXME("(%p)->(%p %x %x)\n", This, pPropBag, fClearDirty, fSaveAllProperties); + return E_NOTIMPL; +} + +static const IPersistPropertyBagVtbl PersistPropertyBagVtbl = { + AVICompressorPropertyBag_QueryInterface, + AVICompressorPropertyBag_AddRef, + AVICompressorPropertyBag_Release, + AVICompressorPropertyBag_GetClassID, + AVICompressorPropertyBag_InitNew, + AVICompressorPropertyBag_Load, + AVICompressorPropertyBag_Save +}; + +static inline AVICompressor *impl_from_IPin(IPin *iface) +{ + BasePin *bp = CONTAINING_RECORD(iface, BasePin, IPin_iface); + return impl_from_IBaseFilter(bp->pinInfo.pFilter); +} + +static HRESULT WINAPI AVICompressorIn_QueryInterface(IPin *iface, REFIID riid, void **ppv) +{ + return BaseInputPinImpl_QueryInterface(iface, riid, ppv); +} + +static ULONG WINAPI AVICompressorIn_AddRef(IPin *iface) +{ + AVICompressor *This = impl_from_IPin(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI AVICompressorIn_Release(IPin *iface) +{ + AVICompressor *This = impl_from_IPin(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI AVICompressorIn_ReceiveConnection(IPin *iface, + IPin *pConnector, const AM_MEDIA_TYPE *pmt) +{ + AVICompressor *This = impl_from_IPin(iface); + HRESULT hres; + + TRACE("(%p)->(%p AM_MEDIA_TYPE(%p))\n", This, pConnector, pmt); + dump_AM_MEDIA_TYPE(pmt); + + hres = BaseInputPinImpl_ReceiveConnection(iface, pConnector, pmt); + if(FAILED(hres)) + return hres; + + hres = fill_format_info(This, (VIDEOINFOHEADER*)pmt->pbFormat); + if(FAILED(hres)) + BasePinImpl_Disconnect(iface); + return hres; +} + +static HRESULT WINAPI AVICompressorIn_Disconnect(IPin *iface) +{ + AVICompressor *This = impl_from_IPin(iface); + HRESULT hres; + + TRACE("(%p)\n", This); + + hres = BasePinImpl_Disconnect(iface); + if(FAILED(hres)) + return hres; + + heap_free(This->videoinfo); + This->videoinfo = NULL; + return S_OK; +} + +static const IPinVtbl AVICompressorInputPinVtbl = { + AVICompressorIn_QueryInterface, + AVICompressorIn_AddRef, + AVICompressorIn_Release, + BaseInputPinImpl_Connect, + AVICompressorIn_ReceiveConnection, + AVICompressorIn_Disconnect, + BasePinImpl_ConnectedTo, + BasePinImpl_ConnectionMediaType, + BasePinImpl_QueryPinInfo, + BasePinImpl_QueryDirection, + BasePinImpl_QueryId, + BasePinImpl_QueryAccept, + BasePinImpl_EnumMediaTypes, + BasePinImpl_QueryInternalConnections, + BaseInputPinImpl_EndOfStream, + BaseInputPinImpl_BeginFlush, + BaseInputPinImpl_EndFlush, + BaseInputPinImpl_NewSegment +}; + +static HRESULT WINAPI AVICompressorIn_CheckMediaType(BasePin *base, const AM_MEDIA_TYPE *pmt) +{ + AVICompressor *This = impl_from_BasePin(base); + VIDEOINFOHEADER *videoinfo; + HRESULT hres; + DWORD res; + + TRACE("(%p)->(AM_MEDIA_TYPE(%p))\n", base, pmt); + dump_AM_MEDIA_TYPE(pmt); + + if(!IsEqualIID(&pmt->majortype, &MEDIATYPE_Video)) + return S_FALSE; + + if(!IsEqualIID(&pmt->formattype, &FORMAT_VideoInfo)) { + FIXME("formattype %s unsupported\n", debugstr_guid(&pmt->formattype)); + return S_FALSE; + } + + hres = ensure_driver(This); + if(hres != S_OK) + return hres; + + videoinfo = (VIDEOINFOHEADER*)pmt->pbFormat; + res = ICCompressQuery(This->hic, &videoinfo->bmiHeader, NULL); + return res == ICERR_OK ? S_OK : S_FALSE; +} + +static LONG WINAPI AVICompressorIn_GetMediaTypeVersion(BasePin *base) +{ + return 0; +} + +static HRESULT WINAPI AVICompressorIn_GetMediaType(BasePin *base, int iPosition, AM_MEDIA_TYPE *amt) +{ + TRACE("(%p)->(%d %p)\n", base, iPosition, amt); + return S_FALSE; +} + +static HRESULT WINAPI AVICompressorIn_Receive(BaseInputPin *base, IMediaSample *pSample) +{ + AVICompressor *This = impl_from_BasePin(&base->pin); + VIDEOINFOHEADER *src_videoinfo; + REFERENCE_TIME start, stop; + IMediaSample *out_sample; + AM_MEDIA_TYPE *mt; + IMediaSample2 *sample2; + DWORD comp_flags = 0; + BOOL is_preroll; + BOOL sync_point; + BYTE *ptr, *buf; + DWORD res; + HRESULT hres; + + TRACE("(%p)->(%p)\n", base, pSample); + + if(!This->hic) { + FIXME("Driver not loaded\n"); + return E_UNEXPECTED; + } + + hres = IMediaSample_QueryInterface(pSample, &IID_IMediaSample2, (void**)&sample2); + if(SUCCEEDED(hres)) { + FIXME("Use IMediaSample2\n"); + IMediaSample2_Release(sample2); + } + + is_preroll = IMediaSample_IsPreroll(pSample) == S_OK; + sync_point = IMediaSample_IsSyncPoint(pSample) == S_OK; + + hres = IMediaSample_GetTime(pSample, &start, &stop); + if(FAILED(hres)) { + WARN("GetTime failed: %08x\n", hres); + return hres; + } + + hres = IMediaSample_GetMediaType(pSample, &mt); + if(FAILED(hres)) + return hres; + + hres = IMediaSample_GetPointer(pSample, &ptr); + if(FAILED(hres)) { + WARN("GetPointer failed: %08x\n", hres); + return hres; + } + + hres = BaseOutputPinImpl_GetDeliveryBuffer(This->out, &out_sample, &start, &stop, 0); + if(FAILED(hres)) + return hres; + + hres = IMediaSample_GetPointer(out_sample, &buf); + if(FAILED(hres)) + return hres; + + if((This->driver_flags & VIDCF_TEMPORAL) && !(This->driver_flags & VIDCF_FASTTEMPORALC)) + FIXME("Unsupported temporal compression\n"); + + src_videoinfo = (VIDEOINFOHEADER*)This->in->pin.mtCurrent.pbFormat; + This->videoinfo->bmiHeader.biSizeImage = This->max_frame_size; + res = ICCompress(This->hic, sync_point ? ICCOMPRESS_KEYFRAME : 0, &This->videoinfo->bmiHeader, buf, + &src_videoinfo->bmiHeader, ptr, 0, &comp_flags, This->frame_cnt, 0, 0, NULL, NULL); + if(res != ICERR_OK) { + WARN("ICCompress failed: %d\n", res); + IMediaSample_Release(out_sample); + return E_FAIL; + } + + IMediaSample_SetActualDataLength(out_sample, This->videoinfo->bmiHeader.biSizeImage); + IMediaSample_SetPreroll(out_sample, is_preroll); + IMediaSample_SetSyncPoint(out_sample, (comp_flags&AVIIF_KEYFRAME) != 0); + IMediaSample_SetDiscontinuity(out_sample, (IMediaSample_IsDiscontinuity(pSample) == S_OK)); + + if (IMediaSample_GetMediaTime(pSample, &start, &stop) == S_OK) + IMediaSample_SetMediaTime(out_sample, &start, &stop); + else + IMediaSample_SetMediaTime(out_sample, NULL, NULL); + + hres = BaseOutputPinImpl_Deliver(This->out, out_sample); + if(FAILED(hres)) + WARN("Deliver failed: %08x\n", hres); + + IMediaSample_Release(out_sample); + This->frame_cnt++; + return hres; +} + +static const BaseInputPinFuncTable AVICompressorBaseInputPinVtbl = { + { + AVICompressorIn_CheckMediaType, + NULL, + AVICompressorIn_GetMediaTypeVersion, + AVICompressorIn_GetMediaType + }, + AVICompressorIn_Receive +}; + +static HRESULT WINAPI AVICompressorOut_QueryInterface(IPin *iface, REFIID riid, void **ppv) +{ + return BaseInputPinImpl_QueryInterface(iface, riid, ppv); +} + +static ULONG WINAPI AVICompressorOut_AddRef(IPin *iface) +{ + AVICompressor *This = impl_from_IPin(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI AVICompressorOut_Release(IPin *iface) +{ + AVICompressor *This = impl_from_IPin(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static const IPinVtbl AVICompressorOutputPinVtbl = { + AVICompressorOut_QueryInterface, + AVICompressorOut_AddRef, + AVICompressorOut_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 LONG WINAPI AVICompressorOut_GetMediaTypeVersion(BasePin *base) +{ + FIXME("(%p)\n", base); + return 0; +} + +static HRESULT WINAPI AVICompressorOut_GetMediaType(BasePin *base, int iPosition, AM_MEDIA_TYPE *amt) +{ + AVICompressor *This = impl_from_IBaseFilter(base->pinInfo.pFilter); + + TRACE("(%p)->(%d %p)\n", base, iPosition, amt); + + if(iPosition || !This->videoinfo) + return S_FALSE; + + amt->majortype = MEDIATYPE_Video; + amt->subtype = MEDIASUBTYPE_PCM; + amt->bFixedSizeSamples = FALSE; + amt->bTemporalCompression = (This->driver_flags & VIDCF_TEMPORAL) != 0; + amt->lSampleSize = This->in->pin.mtCurrent.lSampleSize; + amt->formattype = FORMAT_VideoInfo; + amt->pUnk = NULL; + amt->cbFormat = This->videoinfo_size; + amt->pbFormat = (BYTE*)This->videoinfo; + return S_OK; +} + +static HRESULT WINAPI AVICompressorOut_DecideBufferSize(BaseOutputPin *base, IMemAllocator *alloc, ALLOCATOR_PROPERTIES *ppropInputRequest) +{ + AVICompressor *This = impl_from_BasePin(&base->pin); + ALLOCATOR_PROPERTIES actual; + + TRACE("(%p)\n", This); + + if (!ppropInputRequest->cBuffers) + ppropInputRequest->cBuffers = 1; + if (ppropInputRequest->cbBuffer < This->max_frame_size) + ppropInputRequest->cbBuffer = This->max_frame_size; + if (!ppropInputRequest->cbAlign) + ppropInputRequest->cbAlign = 1; + + return IMemAllocator_SetProperties(alloc, ppropInputRequest, &actual); +} + +static HRESULT WINAPI AVICompressorOut_DecideAllocator(BaseOutputPin *base, + IMemInputPin *pPin, IMemAllocator **pAlloc) +{ + TRACE("(%p)->(%p %p)\n", base, pPin, pAlloc); + return BaseOutputPinImpl_DecideAllocator(base, pPin, pAlloc); +} + +static HRESULT WINAPI AVICompressorOut_BreakConnect(BaseOutputPin *base) +{ + FIXME("(%p)\n", base); + return E_NOTIMPL; +} + +static const BaseOutputPinFuncTable AVICompressorBaseOutputPinVtbl = { + { + NULL, + BaseOutputPinImpl_AttemptConnection, + AVICompressorOut_GetMediaTypeVersion, + AVICompressorOut_GetMediaType + }, + AVICompressorOut_DecideBufferSize, + AVICompressorOut_DecideAllocator, + AVICompressorOut_BreakConnect +}; + +IUnknown* WINAPI QCAP_createAVICompressor(IUnknown *outer, HRESULT *phr) +{ + PIN_INFO in_pin_info = {NULL, PINDIR_INPUT, {'I','n','p','u','t',0}}; + PIN_INFO out_pin_info = {NULL, PINDIR_OUTPUT, {'O','u','t','p','u','t',0}}; + AVICompressor *compressor; + HRESULT hres; + + TRACE("\n"); + + compressor = heap_alloc_zero(sizeof(*compressor)); + if(!compressor) { + *phr = E_NOINTERFACE; + return NULL; + } + + BaseFilter_Init(&compressor->filter, &AVICompressorVtbl, &CLSID_AVICo, + (DWORD_PTR)(__FILE__ ": AVICompressor.csFilter"), &filter_func_table); + + compressor->IPersistPropertyBag_iface.lpVtbl = &PersistPropertyBagVtbl; + + in_pin_info.pFilter = &compressor->filter.IBaseFilter_iface; + hres = BaseInputPin_Construct(&AVICompressorInputPinVtbl, sizeof(BaseInputPin), &in_pin_info, + &AVICompressorBaseInputPinVtbl, &compressor->filter.csFilter, NULL, (IPin**)&compressor->in); + if(FAILED(hres)) { + IBaseFilter_Release(&compressor->filter.IBaseFilter_iface); + *phr = hres; + return NULL; + } + + out_pin_info.pFilter = &compressor->filter.IBaseFilter_iface; + hres = BaseOutputPin_Construct(&AVICompressorOutputPinVtbl, sizeof(BaseOutputPin), &out_pin_info, + &AVICompressorBaseOutputPinVtbl, &compressor->filter.csFilter, (IPin**)&compressor->out); + if(FAILED(hres)) { + IBaseFilter_Release(&compressor->filter.IBaseFilter_iface); + *phr = hres; + return NULL; + } + + *phr = S_OK; + return (IUnknown*)&compressor->filter.IBaseFilter_iface; +} diff --git a/dll/directx/wine/qcap/avimux.c b/dll/directx/wine/qcap/avimux.c new file mode 100644 index 00000000000..1b655dea228 --- /dev/null +++ b/dll/directx/wine/qcap/avimux.c @@ -0,0 +1,2421 @@ +/* + * Copyright (C) 2013 Piotr Caban 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 +#include + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "wtypes.h" +#include "dshow.h" +#include "vfw.h" +#include "aviriff.h" + +#include "qcap_main.h" + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(qcap); + +#define MAX_PIN_NO 128 +#define AVISUPERINDEX_ENTRIES 2000 +#define AVISTDINDEX_ENTRIES 4000 +#define ALIGN(x) ((x+1)/2*2) + +typedef struct { + BaseOutputPin pin; + IQualityControl IQualityControl_iface; + + int cur_stream; + LONGLONG cur_time; + + int buf_pos; + BYTE buf[65536]; + + int movi_off; + int out_pos; + int size; + IStream *stream; +} AviMuxOut; + +typedef struct { + BaseInputPin pin; + IAMStreamControl IAMStreamControl_iface; + IPropertyBag IPropertyBag_iface; + IQualityControl IQualityControl_iface; + + REFERENCE_TIME avg_time_per_frame; + REFERENCE_TIME stop; + int stream_id; + LONGLONG stream_time; + + /* strl chunk */ + AVISTREAMHEADER strh; + struct { + FOURCC fcc; + DWORD cb; + BYTE data[1]; + } *strf; + AVISUPERINDEX *indx; +#ifdef __REACTOS__ + BYTE indx_data[FIELD_OFFSET(AVISUPERINDEX, aIndex) + AVISUPERINDEX_ENTRIES * sizeof(struct _avisuperindex_entry)]; +#else + BYTE indx_data[FIELD_OFFSET(AVISUPERINDEX, aIndex[AVISUPERINDEX_ENTRIES])]; +#endif + + /* movi chunk */ + int ix_off; + AVISTDINDEX *ix; +#ifdef __REACTOS__ + BYTE ix_data[FIELD_OFFSET(AVISTDINDEX, aIndex) + AVISTDINDEX_ENTRIES * sizeof(struct _avisuperindex_entry)]; +#else + BYTE ix_data[FIELD_OFFSET(AVISTDINDEX, aIndex[AVISTDINDEX_ENTRIES])]; +#endif + + IMediaSample *samples_head; + IMemAllocator *samples_allocator; +} AviMuxIn; + +typedef struct { + BaseFilter filter; + IConfigAviMux IConfigAviMux_iface; + IConfigInterleaving IConfigInterleaving_iface; + IMediaSeeking IMediaSeeking_iface; + IPersistMediaPropertyBag IPersistMediaPropertyBag_iface; + ISpecifyPropertyPages ISpecifyPropertyPages_iface; + + InterleavingMode mode; + REFERENCE_TIME interleave; + REFERENCE_TIME preroll; + + AviMuxOut *out; + int input_pin_no; + AviMuxIn *in[MAX_PIN_NO-1]; + + REFERENCE_TIME start, stop; + AVIMAINHEADER avih; + + int idx1_entries; + int idx1_size; + AVIINDEXENTRY *idx1; +} AviMux; + +static HRESULT create_input_pin(AviMux*); + +static inline AviMux* impl_from_BaseFilter(BaseFilter *filter) +{ + return CONTAINING_RECORD(filter, AviMux, filter); +} + +static IPin* WINAPI AviMux_GetPin(BaseFilter *iface, int pos) +{ + AviMux *This = impl_from_BaseFilter(iface); + + TRACE("(%p)->(%d)\n", This, pos); + + if(pos == 0) { + IPin_AddRef(&This->out->pin.pin.IPin_iface); + return &This->out->pin.pin.IPin_iface; + }else if(pos>0 && pos<=This->input_pin_no) { + IPin_AddRef(&This->in[pos-1]->pin.pin.IPin_iface); + return &This->in[pos-1]->pin.pin.IPin_iface; + } + + return NULL; +} + +static LONG WINAPI AviMux_GetPinCount(BaseFilter *iface) +{ + AviMux *This = impl_from_BaseFilter(iface); + TRACE("(%p)\n", This); + return This->input_pin_no+1; +} + +static const BaseFilterFuncTable filter_func_table = { + AviMux_GetPin, + AviMux_GetPinCount +}; + +static inline AviMux* impl_from_IBaseFilter(IBaseFilter *iface) +{ + BaseFilter *filter = CONTAINING_RECORD(iface, BaseFilter, IBaseFilter_iface); + return impl_from_BaseFilter(filter); +} + +static HRESULT WINAPI AviMux_QueryInterface(IBaseFilter *iface, REFIID riid, void **ppv) +{ + AviMux *This = impl_from_IBaseFilter(iface); + + TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv); + + if(IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IPersist) || + IsEqualIID(riid, &IID_IMediaFilter) || IsEqualIID(riid, &IID_IBaseFilter)) + *ppv = &This->filter.IBaseFilter_iface; + else if(IsEqualIID(riid, &IID_IConfigAviMux)) + *ppv = &This->IConfigAviMux_iface; + else if(IsEqualIID(riid, &IID_IConfigInterleaving)) + *ppv = &This->IConfigInterleaving_iface; + else if(IsEqualIID(riid, &IID_IMediaSeeking)) + *ppv = &This->IMediaSeeking_iface; + else if(IsEqualIID(riid, &IID_IPersistMediaPropertyBag)) + *ppv = &This->IPersistMediaPropertyBag_iface; + else if(IsEqualIID(riid, &IID_ISpecifyPropertyPages)) + *ppv = &This->ISpecifyPropertyPages_iface; + else { + FIXME("no interface for %s\n", debugstr_guid(riid)); + *ppv = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI AviMux_Release(IBaseFilter *iface) +{ + AviMux *This = impl_from_IBaseFilter(iface); + ULONG ref = BaseFilterImpl_Release(iface); + + TRACE("(%p) new refcount: %u\n", This, ref); + + if(!ref) { + int i; + + BaseOutputPinImpl_Release(&This->out->pin.pin.IPin_iface); + + for(i=0; iinput_pin_no; i++) { + IPin_Disconnect(&This->in[i]->pin.pin.IPin_iface); + IMemAllocator_Release(This->in[i]->samples_allocator); + This->in[i]->samples_allocator = NULL; + BaseInputPinImpl_Release(&This->in[i]->pin.pin.IPin_iface); + } + + HeapFree(GetProcessHeap(), 0, This->idx1); + HeapFree(GetProcessHeap(), 0, This); + ObjectRefCount(FALSE); + } + return ref; +} + +static HRESULT out_flush(AviMux *This) +{ + ULONG written; + HRESULT hr; + + if(!This->out->buf_pos) + return S_OK; + + hr = IStream_Write(This->out->stream, This->out->buf, This->out->buf_pos, &written); + if(FAILED(hr)) + return hr; + if(written != This->out->buf_pos) + return E_FAIL; + + This->out->buf_pos = 0; + return S_OK; +} + +static HRESULT out_seek(AviMux *This, int pos) +{ + LARGE_INTEGER li; + HRESULT hr; + + hr = out_flush(This); + if(FAILED(hr)) + return hr; + + li.QuadPart = pos; + hr = IStream_Seek(This->out->stream, li, STREAM_SEEK_SET, NULL); + if(FAILED(hr)) + return hr; + + This->out->out_pos = pos; + if(This->out->out_pos > This->out->size) + This->out->size = This->out->out_pos; + return hr; +} + +static HRESULT out_write(AviMux *This, const void *data, int size) +{ + int chunk_size; + HRESULT hr; + + while(1) { + if(size > sizeof(This->out->buf)-This->out->buf_pos) + chunk_size = sizeof(This->out->buf)-This->out->buf_pos; + else + chunk_size = size; + + memcpy(This->out->buf + This->out->buf_pos, data, chunk_size); + size -= chunk_size; + data = (const BYTE*)data + chunk_size; + This->out->buf_pos += chunk_size; + This->out->out_pos += chunk_size; + if(This->out->out_pos > This->out->size) + This->out->size = This->out->out_pos; + + if(!size) + break; + hr = out_flush(This); + if(FAILED(hr)) + return hr; + } + + return S_OK; +} + +static inline HRESULT idx1_add_entry(AviMux *avimux, DWORD ckid, DWORD flags, DWORD off, DWORD len) +{ + if(avimux->idx1_entries == avimux->idx1_size) { + AVIINDEXENTRY *new_idx = HeapReAlloc(GetProcessHeap(), 0, avimux->idx1, + sizeof(*avimux->idx1)*2*avimux->idx1_size); + if(!new_idx) + return E_OUTOFMEMORY; + + avimux->idx1_size *= 2; + avimux->idx1 = new_idx; + } + + avimux->idx1[avimux->idx1_entries].ckid = ckid; + avimux->idx1[avimux->idx1_entries].dwFlags = flags; + avimux->idx1[avimux->idx1_entries].dwChunkOffset = off; + avimux->idx1[avimux->idx1_entries].dwChunkLength = len; + avimux->idx1_entries++; + return S_OK; +} + +static HRESULT flush_queue(AviMux *avimux, AviMuxIn *avimuxin, BOOL closing) +{ + IMediaSample *sample, **prev, **head_prev; + BYTE *data; + RIFFCHUNK rf; + DWORD size; + DWORD flags; + HRESULT hr; + + if(avimux->out->cur_stream != avimuxin->stream_id) + return S_OK; + + while(avimuxin->samples_head) { + hr = IMediaSample_GetPointer(avimuxin->samples_head, (BYTE**)&head_prev); + if(FAILED(hr)) + return hr; + head_prev--; + + hr = IMediaSample_GetPointer(*head_prev, (BYTE**)&prev); + if(FAILED(hr)) + return hr; + prev--; + + sample = *head_prev; + size = IMediaSample_GetActualDataLength(sample); + hr = IMediaSample_GetPointer(sample, &data); + if(FAILED(hr)) + return hr; + flags = IMediaSample_IsDiscontinuity(sample)==S_OK ? AM_SAMPLE_TIMEDISCONTINUITY : 0; + if(IMediaSample_IsSyncPoint(sample) == S_OK) + flags |= AM_SAMPLE_SPLICEPOINT; + + if(avimuxin->stream_time + (closing ? 0 : avimuxin->strh.dwScale) > avimux->out->cur_time && + !(flags & AM_SAMPLE_TIMEDISCONTINUITY)) { + if(closing) + break; + + avimux->out->cur_stream++; + if(avimux->out->cur_stream >= avimux->input_pin_no-1) { + avimux->out->cur_time += avimux->interleave; + avimux->out->cur_stream = 0; + } + avimuxin = avimux->in[avimux->out->cur_stream]; + continue; + } + + if(avimuxin->ix->nEntriesInUse == AVISTDINDEX_ENTRIES) { + /* TODO: use output pins Deliver/Receive method */ + hr = out_seek(avimux, avimuxin->ix_off); + if(FAILED(hr)) + return hr; + hr = out_write(avimux, avimuxin->ix, sizeof(avimuxin->ix_data)); + if(FAILED(hr)) + return hr; + + avimuxin->indx->aIndex[avimuxin->indx->nEntriesInUse].qwOffset = avimuxin->ix_off; + avimuxin->indx->aIndex[avimuxin->indx->nEntriesInUse].dwSize = sizeof(avimuxin->ix_data); + avimuxin->indx->aIndex[avimuxin->indx->nEntriesInUse].dwDuration = AVISTDINDEX_ENTRIES; + avimuxin->indx->nEntriesInUse++; + + memset(avimuxin->ix->aIndex, 0, sizeof(avimuxin->ix->aIndex)*avimuxin->ix->nEntriesInUse); + avimuxin->ix->nEntriesInUse = 0; + avimuxin->ix->qwBaseOffset = 0; + + avimuxin->ix_off = avimux->out->size; + avimux->out->size += sizeof(avimuxin->ix_data); + } + + if(*head_prev == avimuxin->samples_head) + avimuxin->samples_head = NULL; + else + *head_prev = *prev; + + avimuxin->stream_time += avimuxin->strh.dwScale; + avimuxin->strh.dwLength++; + if(!(flags & AM_SAMPLE_TIMEDISCONTINUITY)) { + if(!avimuxin->ix->qwBaseOffset) + avimuxin->ix->qwBaseOffset = avimux->out->size; + avimuxin->ix->aIndex[avimuxin->ix->nEntriesInUse].dwOffset = avimux->out->size + + sizeof(RIFFCHUNK) - avimuxin->ix->qwBaseOffset; + + hr = out_seek(avimux, avimux->out->size); + if(FAILED(hr)) { + IMediaSample_Release(sample); + return hr; + } + } + avimuxin->ix->aIndex[avimuxin->ix->nEntriesInUse].dwSize = size | + (flags & AM_SAMPLE_SPLICEPOINT ? 0 : AVISTDINDEX_DELTAFRAME); + avimuxin->ix->nEntriesInUse++; + + rf.fcc = FCC('0'+avimuxin->stream_id/10, '0'+avimuxin->stream_id%10, + 'd', flags & AM_SAMPLE_SPLICEPOINT ? 'b' : 'c'); + rf.cb = size; + hr = idx1_add_entry(avimux, rf.fcc, flags & AM_SAMPLE_SPLICEPOINT ? AVIIF_KEYFRAME : 0, + flags & AM_SAMPLE_TIMEDISCONTINUITY ? + avimux->idx1[avimux->idx1_entries-1].dwChunkOffset : avimux->out->size, size); + if(FAILED(hr)) { + IMediaSample_Release(sample); + return hr; + } + + if(!(flags & AM_SAMPLE_TIMEDISCONTINUITY)) { + hr = out_write(avimux, &rf, sizeof(rf)); + if(FAILED(hr)) { + IMediaSample_Release(sample); + return hr; + } + hr = out_write(avimux, data, size); + if(FAILED(hr)) { + IMediaSample_Release(sample); + return hr; + } + flags = 0; + hr = out_write(avimux, &flags, ALIGN(rf.cb)-rf.cb); + if(FAILED(hr)) { + IMediaSample_Release(sample); + return hr; + } + } + IMediaSample_Release(sample); + } + return S_OK; +} + +static HRESULT queue_sample(AviMux *avimux, AviMuxIn *avimuxin, IMediaSample *sample) +{ + IMediaSample **prev, **head_prev; + HRESULT hr; + + hr = IMediaSample_GetPointer(sample, (BYTE**)&prev); + if(FAILED(hr)) + return hr; + prev--; + + if(avimuxin->samples_head) { + hr = IMediaSample_GetPointer(avimuxin->samples_head, (BYTE**)&head_prev); + if(FAILED(hr)) + return hr; + head_prev--; + + *prev = *head_prev; + *head_prev = sample; + }else { + *prev = sample; + } + avimuxin->samples_head = sample; + IMediaSample_AddRef(sample); + + return flush_queue(avimux, avimuxin, FALSE); +} + +static HRESULT WINAPI AviMux_Stop(IBaseFilter *iface) +{ + AviMux *This = impl_from_IBaseFilter(iface); + HRESULT hr; + int i; + + TRACE("(%p)\n", This); + + if(This->filter.state == State_Stopped) + return S_OK; + + if(This->out->stream) { + AVIEXTHEADER dmlh; + RIFFCHUNK rc; + RIFFLIST rl; + int idx1_off, empty_stream; + + empty_stream = This->out->cur_stream; + for(i=empty_stream+1; ; i++) { + if(i >= This->input_pin_no-1) + i = 0; + if(i == empty_stream) + break; + + This->out->cur_stream = i; + hr = flush_queue(This, This->in[This->out->cur_stream], TRUE); + if(FAILED(hr)) + return hr; + } + + idx1_off = This->out->size; + rc.fcc = ckidAVIOLDINDEX; + rc.cb = This->idx1_entries * sizeof(*This->idx1); + hr = out_write(This, &rc, sizeof(rc)); + if(FAILED(hr)) + return hr; + hr = out_write(This, This->idx1, This->idx1_entries * sizeof(*This->idx1)); + if(FAILED(hr)) + return hr; + /* native writes 8 '\0' characters after the end of RIFF data */ + i = 0; + hr = out_write(This, &i, sizeof(i)); + if(FAILED(hr)) + return hr; + hr = out_write(This, &i, sizeof(i)); + if(FAILED(hr)) + return hr; + + for(i=0; iinput_pin_no; i++) { + if(!This->in[i]->pin.pin.pConnectedTo) + continue; + + hr = out_seek(This, This->in[i]->ix_off); + if(FAILED(hr)) + return hr; + + This->in[i]->indx->aIndex[This->in[i]->indx->nEntriesInUse].qwOffset = This->in[i]->ix_off; + This->in[i]->indx->aIndex[This->in[i]->indx->nEntriesInUse].dwSize = sizeof(This->in[i]->ix_data); + This->in[i]->indx->aIndex[This->in[i]->indx->nEntriesInUse].dwDuration = This->in[i]->strh.dwLength; + if(This->in[i]->indx->nEntriesInUse) { + This->in[i]->indx->aIndex[This->in[i]->indx->nEntriesInUse].dwDuration -= + This->in[i]->indx->aIndex[This->in[i]->indx->nEntriesInUse-1].dwDuration; + } + This->in[i]->indx->nEntriesInUse++; + hr = out_write(This, This->in[i]->ix, sizeof(This->in[i]->ix_data)); + if(FAILED(hr)) + return hr; + } + + hr = out_seek(This, 0); + if(FAILED(hr)) + return hr; + + rl.fcc = FCC('R','I','F','F'); + rl.cb = This->out->size-sizeof(RIFFCHUNK)-2*sizeof(int); + rl.fccListType = FCC('A','V','I',' '); + hr = out_write(This, &rl, sizeof(rl)); + if(FAILED(hr)) + return hr; + + rl.fcc = FCC('L','I','S','T'); + rl.cb = This->out->movi_off - sizeof(RIFFLIST) - sizeof(RIFFCHUNK); + rl.fccListType = FCC('h','d','r','l'); + hr = out_write(This, &rl, sizeof(rl)); + if(FAILED(hr)) + return hr; + + /* FIXME: set This->avih.dwMaxBytesPerSec value */ + This->avih.dwTotalFrames = (This->stop-This->start) / 10 / This->avih.dwMicroSecPerFrame; + hr = out_write(This, &This->avih, sizeof(This->avih)); + if(FAILED(hr)) + return hr; + + for(i=0; iinput_pin_no; i++) { + if(!This->in[i]->pin.pin.pConnectedTo) + continue; + + rl.cb = sizeof(FOURCC) + sizeof(AVISTREAMHEADER) + sizeof(RIFFCHUNK) + + This->in[i]->strf->cb + sizeof(This->in[i]->indx_data); + rl.fccListType = ckidSTREAMLIST; + hr = out_write(This, &rl, sizeof(rl)); + if(FAILED(hr)) + return hr; + + hr = out_write(This, &This->in[i]->strh, sizeof(AVISTREAMHEADER)); + if(FAILED(hr)) + return hr; + + hr = out_write(This, This->in[i]->strf, sizeof(RIFFCHUNK) + This->in[i]->strf->cb); + if(FAILED(hr)) + return hr; + + hr = out_write(This, This->in[i]->indx, sizeof(This->in[i]->indx_data)); + if(FAILED(hr)) + return hr; + } + + rl.cb = sizeof(dmlh) + sizeof(FOURCC); + rl.fccListType = ckidODML; + hr = out_write(This, &rl, sizeof(rl)); + if(FAILED(hr)) + return hr; + + memset(&dmlh, 0, sizeof(dmlh)); + dmlh.fcc = ckidAVIEXTHEADER; + dmlh.cb = sizeof(dmlh) - sizeof(RIFFCHUNK); + dmlh.dwGrandFrames = This->in[0]->strh.dwLength; + hr = out_write(This, &dmlh, sizeof(dmlh)); + + rl.cb = idx1_off - This->out->movi_off - sizeof(RIFFCHUNK); + rl.fccListType = FCC('m','o','v','i'); + out_write(This, &rl, sizeof(rl)); + out_flush(This); + + IStream_Release(This->out->stream); + This->out->stream = NULL; + } + + This->filter.state = State_Stopped; + return S_OK; +} + +static HRESULT WINAPI AviMux_Pause(IBaseFilter *iface) +{ + AviMux *This = impl_from_IBaseFilter(iface); + FIXME("(%p)\n", This); + return E_NOTIMPL; +} + +static HRESULT WINAPI AviMux_Run(IBaseFilter *iface, REFERENCE_TIME tStart) +{ + AviMux *This = impl_from_IBaseFilter(iface); + HRESULT hr; + int i, stream_id; + + TRACE("(%p)->(%s)\n", This, wine_dbgstr_longlong(tStart)); + + if(This->filter.state == State_Running) + return S_OK; + + if(This->mode != INTERLEAVE_FULL) { + FIXME("mode not supported (%d)\n", This->mode); + return E_NOTIMPL; + } + + if(tStart) + FIXME("tStart parameter ignored\n"); + + for(i=0; iinput_pin_no; i++) { + IMediaSeeking *ms; + LONGLONG cur, stop; + + if(!This->in[i]->pin.pin.pConnectedTo) + continue; + + hr = IPin_QueryInterface(This->in[i]->pin.pin.pConnectedTo, + &IID_IMediaSeeking, (void**)&ms); + if(FAILED(hr)) + continue; + + hr = IMediaSeeking_GetPositions(ms, &cur, &stop); + if(FAILED(hr)) { + IMediaSeeking_Release(ms); + continue; + } + + FIXME("Use IMediaSeeking to fill stream header\n"); + IMediaSeeking_Release(ms); + } + + if(This->out->pin.pMemInputPin) { + hr = IMemInputPin_QueryInterface(This->out->pin.pMemInputPin, + &IID_IStream, (void**)&This->out->stream); + if(FAILED(hr)) + return hr; + } + + This->idx1_entries = 0; + if(!This->idx1_size) { + This->idx1_size = 1024; + This->idx1 = HeapAlloc(GetProcessHeap(), 0, sizeof(*This->idx1)*This->idx1_size); + if(!This->idx1) + return E_OUTOFMEMORY; + } + + This->out->size = 3*sizeof(RIFFLIST) + sizeof(AVIMAINHEADER) + sizeof(AVIEXTHEADER); + This->start = -1; + This->stop = -1; + memset(&This->avih, 0, sizeof(This->avih)); + for(i=0; iinput_pin_no; i++) { + if(!This->in[i]->pin.pin.pConnectedTo) + continue; + + This->avih.dwStreams++; + This->out->size += sizeof(RIFFLIST) + sizeof(AVISTREAMHEADER) + sizeof(RIFFCHUNK) + + This->in[i]->strf->cb + sizeof(This->in[i]->indx_data); + + This->in[i]->strh.dwScale = MulDiv(This->in[i]->avg_time_per_frame, This->interleave, 10000000); + This->in[i]->strh.dwRate = This->interleave; + + hr = IMemAllocator_Commit(This->in[i]->pin.pAllocator); + if(FAILED(hr)) { + if(This->out->stream) { + IStream_Release(This->out->stream); + This->out->stream = NULL; + } + return hr; + } + } + + This->out->movi_off = This->out->size; + This->out->size += sizeof(RIFFLIST); + + idx1_add_entry(This, FCC('7','F','x','x'), 0, This->out->movi_off+sizeof(RIFFLIST), 0); + + stream_id = 0; + for(i=0; iinput_pin_no; i++) { + if(!This->in[i]->pin.pin.pConnectedTo) + continue; + + This->in[i]->ix_off = This->out->size; + This->out->size += sizeof(This->in[i]->ix_data); + This->in[i]->ix->fcc = FCC('i','x','0'+stream_id/10,'0'+stream_id%10); + This->in[i]->ix->cb = sizeof(This->in[i]->ix_data) - sizeof(RIFFCHUNK); + This->in[i]->ix->wLongsPerEntry = 2; + This->in[i]->ix->bIndexSubType = 0; + This->in[i]->ix->bIndexType = AVI_INDEX_OF_CHUNKS; + This->in[i]->ix->dwChunkId = FCC('0'+stream_id/10,'0'+stream_id%10,'d','b'); + This->in[i]->ix->qwBaseOffset = 0; + + This->in[i]->indx->fcc = ckidAVISUPERINDEX; + This->in[i]->indx->cb = sizeof(This->in[i]->indx_data) - sizeof(RIFFCHUNK); + This->in[i]->indx->wLongsPerEntry = 4; + This->in[i]->indx->bIndexSubType = 0; + This->in[i]->indx->bIndexType = AVI_INDEX_OF_INDEXES; + This->in[i]->indx->dwChunkId = This->in[i]->ix->dwChunkId; + This->in[i]->stream_id = stream_id++; + } + + This->out->buf_pos = 0; + This->out->out_pos = 0; + + This->avih.fcc = ckidMAINAVIHEADER; + This->avih.cb = sizeof(AVIMAINHEADER) - sizeof(RIFFCHUNK); + /* TODO: Use first video stream */ + This->avih.dwMicroSecPerFrame = This->in[0]->avg_time_per_frame/10; + This->avih.dwPaddingGranularity = 1; + This->avih.dwFlags = AVIF_TRUSTCKTYPE | AVIF_HASINDEX; + This->avih.dwWidth = ((BITMAPINFOHEADER*)This->in[0]->strf->data)->biWidth; + This->avih.dwHeight = ((BITMAPINFOHEADER*)This->in[0]->strf->data)->biHeight; + + This->filter.state = State_Running; + return S_OK; +} + +static HRESULT WINAPI AviMux_EnumPins(IBaseFilter *iface, IEnumPins **ppEnum) +{ + AviMux *This = impl_from_IBaseFilter(iface); + TRACE("(%p)->(%p)\n", This, ppEnum); + return BaseFilterImpl_EnumPins(iface, ppEnum); +} + +static HRESULT WINAPI AviMux_FindPin(IBaseFilter *iface, LPCWSTR Id, IPin **ppPin) +{ + AviMux *This = impl_from_IBaseFilter(iface); + int i; + + TRACE("(%p)->(%s %p)\n", This, debugstr_w(Id), ppPin); + + if(!Id || !ppPin) + return E_POINTER; + + if(!lstrcmpiW(Id, This->out->pin.pin.pinInfo.achName)) { + IPin_AddRef(&This->out->pin.pin.IPin_iface); + *ppPin = &This->out->pin.pin.IPin_iface; + return S_OK; + } + + for(i=0; iinput_pin_no; i++) { + if(lstrcmpiW(Id, This->in[i]->pin.pin.pinInfo.achName)) + continue; + + IPin_AddRef(&This->in[i]->pin.pin.IPin_iface); + *ppPin = &This->in[i]->pin.pin.IPin_iface; + return S_OK; + } + + return VFW_E_NOT_FOUND; +} + +static HRESULT WINAPI AviMux_QueryFilterInfo(IBaseFilter *iface, FILTER_INFO *pInfo) +{ + AviMux *This = impl_from_IBaseFilter(iface); + FIXME("(%p)->(%p)\n", This, pInfo); + return E_NOTIMPL; +} + +static HRESULT WINAPI AviMux_QueryVendorInfo(IBaseFilter *iface, LPWSTR *pVendorInfo) +{ + AviMux *This = impl_from_IBaseFilter(iface); + FIXME("(%p)->(%p)\n", This, pVendorInfo); + return E_NOTIMPL; +} + +static const IBaseFilterVtbl AviMuxVtbl = { + AviMux_QueryInterface, + BaseFilterImpl_AddRef, + AviMux_Release, + BaseFilterImpl_GetClassID, + AviMux_Stop, + AviMux_Pause, + AviMux_Run, + BaseFilterImpl_GetState, + BaseFilterImpl_SetSyncSource, + BaseFilterImpl_GetSyncSource, + AviMux_EnumPins, + AviMux_FindPin, + AviMux_QueryFilterInfo, + BaseFilterImpl_JoinFilterGraph, + AviMux_QueryVendorInfo +}; + +static inline AviMux* impl_from_IConfigAviMux(IConfigAviMux *iface) +{ + return CONTAINING_RECORD(iface, AviMux, IConfigAviMux_iface); +} + +static HRESULT WINAPI ConfigAviMux_QueryInterface( + IConfigAviMux *iface, REFIID riid, void **ppv) +{ + AviMux *This = impl_from_IConfigAviMux(iface); + return IBaseFilter_QueryInterface(&This->filter.IBaseFilter_iface, riid, ppv); +} + +static ULONG WINAPI ConfigAviMux_AddRef(IConfigAviMux *iface) +{ + AviMux *This = impl_from_IConfigAviMux(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI ConfigAviMux_Release(IConfigAviMux *iface) +{ + AviMux *This = impl_from_IConfigAviMux(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI ConfigAviMux_SetMasterStream(IConfigAviMux *iface, LONG iStream) +{ + AviMux *This = impl_from_IConfigAviMux(iface); + FIXME("(%p)->(%d)\n", This, iStream); + return E_NOTIMPL; +} + +static HRESULT WINAPI ConfigAviMux_GetMasterStream(IConfigAviMux *iface, LONG *pStream) +{ + AviMux *This = impl_from_IConfigAviMux(iface); + FIXME("(%p)->(%p)\n", This, pStream); + return E_NOTIMPL; +} + +static HRESULT WINAPI ConfigAviMux_SetOutputCompatibilityIndex( + IConfigAviMux *iface, BOOL fOldIndex) +{ + AviMux *This = impl_from_IConfigAviMux(iface); + FIXME("(%p)->(%x)\n", This, fOldIndex); + return E_NOTIMPL; +} + +static HRESULT WINAPI ConfigAviMux_GetOutputCompatibilityIndex( + IConfigAviMux *iface, BOOL *pfOldIndex) +{ + AviMux *This = impl_from_IConfigAviMux(iface); + FIXME("(%p)->(%p)\n", This, pfOldIndex); + return E_NOTIMPL; +} + +static const IConfigAviMuxVtbl ConfigAviMuxVtbl = { + ConfigAviMux_QueryInterface, + ConfigAviMux_AddRef, + ConfigAviMux_Release, + ConfigAviMux_SetMasterStream, + ConfigAviMux_GetMasterStream, + ConfigAviMux_SetOutputCompatibilityIndex, + ConfigAviMux_GetOutputCompatibilityIndex +}; + +static inline AviMux* impl_from_IConfigInterleaving(IConfigInterleaving *iface) +{ + return CONTAINING_RECORD(iface, AviMux, IConfigInterleaving_iface); +} + +static HRESULT WINAPI ConfigInterleaving_QueryInterface( + IConfigInterleaving *iface, REFIID riid, void **ppv) +{ + AviMux *This = impl_from_IConfigInterleaving(iface); + return IBaseFilter_QueryInterface(&This->filter.IBaseFilter_iface, riid, ppv); +} + +static ULONG WINAPI ConfigInterleaving_AddRef(IConfigInterleaving *iface) +{ + AviMux *This = impl_from_IConfigInterleaving(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI ConfigInterleaving_Release(IConfigInterleaving *iface) +{ + AviMux *This = impl_from_IConfigInterleaving(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI ConfigInterleaving_put_Mode( + IConfigInterleaving *iface, InterleavingMode mode) +{ + AviMux *This = impl_from_IConfigInterleaving(iface); + + TRACE("(%p)->(%d)\n", This, mode); + + if(mode>INTERLEAVE_NONE_BUFFERED) + return E_INVALIDARG; + + if(This->mode != mode) { + if(This->out->pin.pin.pConnectedTo) { + HRESULT hr = IFilterGraph_Reconnect(This->filter.filterInfo.pGraph, + &This->out->pin.pin.IPin_iface); + if(FAILED(hr)) + return hr; + } + + This->mode = mode; + } + + return S_OK; +} + +static HRESULT WINAPI ConfigInterleaving_get_Mode( + IConfigInterleaving *iface, InterleavingMode *pMode) +{ + AviMux *This = impl_from_IConfigInterleaving(iface); + FIXME("(%p)->(%p)\n", This, pMode); + return E_NOTIMPL; +} + +static HRESULT WINAPI ConfigInterleaving_put_Interleaving(IConfigInterleaving *iface, + const REFERENCE_TIME *prtInterleave, const REFERENCE_TIME *prtPreroll) +{ + AviMux *This = impl_from_IConfigInterleaving(iface); + + TRACE("(%p)->(%p %p)\n", This, prtInterleave, prtPreroll); + + if(prtInterleave) + This->interleave = *prtInterleave; + if(prtPreroll) + This->preroll = *prtPreroll; + return S_OK; +} + +static HRESULT WINAPI ConfigInterleaving_get_Interleaving(IConfigInterleaving *iface, + REFERENCE_TIME *prtInterleave, REFERENCE_TIME *prtPreroll) +{ + AviMux *This = impl_from_IConfigInterleaving(iface); + FIXME("(%p)->(%p %p)\n", This, prtInterleave, prtPreroll); + return E_NOTIMPL; +} + +static const IConfigInterleavingVtbl ConfigInterleavingVtbl = { + ConfigInterleaving_QueryInterface, + ConfigInterleaving_AddRef, + ConfigInterleaving_Release, + ConfigInterleaving_put_Mode, + ConfigInterleaving_get_Mode, + ConfigInterleaving_put_Interleaving, + ConfigInterleaving_get_Interleaving +}; + +static inline AviMux* impl_from_IMediaSeeking(IMediaSeeking *iface) +{ + return CONTAINING_RECORD(iface, AviMux, IMediaSeeking_iface); +} + +static HRESULT WINAPI MediaSeeking_QueryInterface( + IMediaSeeking *iface, REFIID riid, void **ppv) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + return IBaseFilter_QueryInterface(&This->filter.IBaseFilter_iface, riid, ppv); +} + +static ULONG WINAPI MediaSeeking_AddRef(IMediaSeeking *iface) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI MediaSeeking_Release(IMediaSeeking *iface) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI MediaSeeking_GetCapabilities( + IMediaSeeking *iface, DWORD *pCapabilities) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p)\n", This, pCapabilities); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_CheckCapabilities( + IMediaSeeking *iface, DWORD *pCapabilities) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p)\n", This, pCapabilities); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_IsFormatSupported( + IMediaSeeking *iface, const GUID *pFormat) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%s)\n", This, debugstr_guid(pFormat)); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_QueryPreferredFormat( + IMediaSeeking *iface, GUID *pFormat) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p)\n", This, pFormat); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_GetTimeFormat( + IMediaSeeking *iface, GUID *pFormat) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p)\n", This, pFormat); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_IsUsingTimeFormat( + IMediaSeeking *iface, const GUID *pFormat) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%s)\n", This, debugstr_guid(pFormat)); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_SetTimeFormat( + IMediaSeeking *iface, const GUID *pFormat) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%s)\n", This, debugstr_guid(pFormat)); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_GetDuration( + IMediaSeeking *iface, LONGLONG *pDuration) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p)\n", This, pDuration); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_GetStopPosition( + IMediaSeeking *iface, LONGLONG *pStop) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p)\n", This, pStop); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_GetCurrentPosition( + IMediaSeeking *iface, LONGLONG *pCurrent) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p)\n", This, pCurrent); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_ConvertTimeFormat(IMediaSeeking *iface, LONGLONG *pTarget, + const GUID *pTargetFormat, LONGLONG Source, const GUID *pSourceFormat) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p %s %s %s)\n", This, pTarget, debugstr_guid(pTargetFormat), + wine_dbgstr_longlong(Source), debugstr_guid(pSourceFormat)); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_SetPositions(IMediaSeeking *iface, LONGLONG *pCurrent, + DWORD dwCurrentFlags, LONGLONG *pStop, DWORD dwStopFlags) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p %x %p %x)\n", This, pCurrent, dwCurrentFlags, pStop, dwStopFlags); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_GetPositions(IMediaSeeking *iface, + LONGLONG *pCurrent, LONGLONG *pStop) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p %p)\n", This, pCurrent, pStop); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_GetAvailable(IMediaSeeking *iface, + LONGLONG *pEarliest, LONGLONG *pLatest) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p %p)\n", This, pEarliest, pLatest); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_SetRate(IMediaSeeking *iface, double dRate) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%lf)\n", This, dRate); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_GetRate(IMediaSeeking *iface, double *pdRate) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p)\n", This, pdRate); + return E_NOTIMPL; +} + +static HRESULT WINAPI MediaSeeking_GetPreroll(IMediaSeeking *iface, LONGLONG *pllPreroll) +{ + AviMux *This = impl_from_IMediaSeeking(iface); + FIXME("(%p)->(%p)\n", This, pllPreroll); + return E_NOTIMPL; +} + +static const IMediaSeekingVtbl MediaSeekingVtbl = { + MediaSeeking_QueryInterface, + MediaSeeking_AddRef, + MediaSeeking_Release, + MediaSeeking_GetCapabilities, + MediaSeeking_CheckCapabilities, + MediaSeeking_IsFormatSupported, + MediaSeeking_QueryPreferredFormat, + MediaSeeking_GetTimeFormat, + MediaSeeking_IsUsingTimeFormat, + MediaSeeking_SetTimeFormat, + MediaSeeking_GetDuration, + MediaSeeking_GetStopPosition, + MediaSeeking_GetCurrentPosition, + MediaSeeking_ConvertTimeFormat, + MediaSeeking_SetPositions, + MediaSeeking_GetPositions, + MediaSeeking_GetAvailable, + MediaSeeking_SetRate, + MediaSeeking_GetRate, + MediaSeeking_GetPreroll +}; + +static inline AviMux* impl_from_IPersistMediaPropertyBag(IPersistMediaPropertyBag *iface) +{ + return CONTAINING_RECORD(iface, AviMux, IPersistMediaPropertyBag_iface); +} + +static HRESULT WINAPI PersistMediaPropertyBag_QueryInterface( + IPersistMediaPropertyBag *iface, REFIID riid, void **ppv) +{ + AviMux *This = impl_from_IPersistMediaPropertyBag(iface); + return IBaseFilter_QueryInterface(&This->filter.IBaseFilter_iface, riid, ppv); +} + +static ULONG WINAPI PersistMediaPropertyBag_AddRef(IPersistMediaPropertyBag *iface) +{ + AviMux *This = impl_from_IPersistMediaPropertyBag(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI PersistMediaPropertyBag_Release(IPersistMediaPropertyBag *iface) +{ + AviMux *This = impl_from_IPersistMediaPropertyBag(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI PersistMediaPropertyBag_GetClassID( + IPersistMediaPropertyBag *iface, CLSID *pClassID) +{ + AviMux *This = impl_from_IPersistMediaPropertyBag(iface); + return IBaseFilter_GetClassID(&This->filter.IBaseFilter_iface, pClassID); +} + +static HRESULT WINAPI PersistMediaPropertyBag_InitNew(IPersistMediaPropertyBag *iface) +{ + AviMux *This = impl_from_IPersistMediaPropertyBag(iface); + FIXME("(%p)->()\n", This); + return E_NOTIMPL; +} + +static HRESULT WINAPI PersistMediaPropertyBag_Load(IPersistMediaPropertyBag *iface, + IMediaPropertyBag *pPropBag, IErrorLog *pErrorLog) +{ + AviMux *This = impl_from_IPersistMediaPropertyBag(iface); + FIXME("(%p)->()\n", This); + return E_NOTIMPL; +} + +static HRESULT WINAPI PersistMediaPropertyBag_Save(IPersistMediaPropertyBag *iface, + IMediaPropertyBag *pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties) +{ + AviMux *This = impl_from_IPersistMediaPropertyBag(iface); + FIXME("(%p)->()\n", This); + return E_NOTIMPL; +} + +static const IPersistMediaPropertyBagVtbl PersistMediaPropertyBagVtbl = { + PersistMediaPropertyBag_QueryInterface, + PersistMediaPropertyBag_AddRef, + PersistMediaPropertyBag_Release, + PersistMediaPropertyBag_GetClassID, + PersistMediaPropertyBag_InitNew, + PersistMediaPropertyBag_Load, + PersistMediaPropertyBag_Save +}; + +static inline AviMux* impl_from_ISpecifyPropertyPages(ISpecifyPropertyPages *iface) +{ + return CONTAINING_RECORD(iface, AviMux, ISpecifyPropertyPages_iface); +} + +static HRESULT WINAPI SpecifyPropertyPages_QueryInterface( + ISpecifyPropertyPages *iface, REFIID riid, void **ppv) +{ + AviMux *This = impl_from_ISpecifyPropertyPages(iface); + return IBaseFilter_QueryInterface(&This->filter.IBaseFilter_iface, riid, ppv); +} + +static ULONG WINAPI SpecifyPropertyPages_AddRef(ISpecifyPropertyPages *iface) +{ + AviMux *This = impl_from_ISpecifyPropertyPages(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI SpecifyPropertyPages_Release(ISpecifyPropertyPages *iface) +{ + AviMux *This = impl_from_ISpecifyPropertyPages(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI SpecifyPropertyPages_GetPages( + ISpecifyPropertyPages *iface, CAUUID *pPages) +{ + AviMux *This = impl_from_ISpecifyPropertyPages(iface); + FIXME("(%p)->(%p)\n", This, pPages); + return E_NOTIMPL; +} + +static const ISpecifyPropertyPagesVtbl SpecifyPropertyPagesVtbl = { + SpecifyPropertyPages_QueryInterface, + SpecifyPropertyPages_AddRef, + SpecifyPropertyPages_Release, + SpecifyPropertyPages_GetPages +}; + +static HRESULT WINAPI AviMuxOut_AttemptConnection(BasePin *base, + IPin *pReceivePin, const AM_MEDIA_TYPE *pmt) +{ + PIN_DIRECTION dir; + HRESULT hr; + + TRACE("(%p)->(%p AM_MEDIA_TYPE(%p))\n", base, pReceivePin, pmt); + dump_AM_MEDIA_TYPE(pmt); + + hr = IPin_QueryDirection(pReceivePin, &dir); + if(hr==S_OK && dir!=PINDIR_INPUT) + return VFW_E_INVALID_DIRECTION; + + return BaseOutputPinImpl_AttemptConnection(base, pReceivePin, pmt); +} + +static LONG WINAPI AviMuxOut_GetMediaTypeVersion(BasePin *base) +{ + return 0; +} + +static HRESULT WINAPI AviMuxOut_GetMediaType(BasePin *base, int iPosition, AM_MEDIA_TYPE *amt) +{ + TRACE("(%p)->(%d %p)\n", base, iPosition, amt); + + if(iPosition < 0) + return E_INVALIDARG; + if(iPosition > 0) + return VFW_S_NO_MORE_ITEMS; + + amt->majortype = MEDIATYPE_Stream; + amt->subtype = MEDIASUBTYPE_Avi; + amt->bFixedSizeSamples = TRUE; + amt->bTemporalCompression = FALSE; + amt->lSampleSize = 1; + amt->formattype = GUID_NULL; + amt->pUnk = NULL; + amt->cbFormat = 0; + amt->pbFormat = NULL; + return S_OK; +} + +static HRESULT WINAPI AviMuxOut_DecideAllocator(BaseOutputPin *base, + IMemInputPin *pPin, IMemAllocator **pAlloc) +{ + ALLOCATOR_PROPERTIES req, actual; + HRESULT hr; + + TRACE("(%p)->(%p %p)\n", base, pPin, pAlloc); + + hr = BaseOutputPinImpl_InitAllocator(base, pAlloc); + if(FAILED(hr)) + return hr; + + hr = IMemInputPin_GetAllocatorRequirements(pPin, &req); + if(FAILED(hr)) + req.cbAlign = 1; + req.cBuffers = 32; + req.cbBuffer = 0; + req.cbPrefix = 0; + + hr = IMemAllocator_SetProperties(*pAlloc, &req, &actual); + if(FAILED(hr)) + return hr; + + return IMemInputPin_NotifyAllocator(pPin, *pAlloc, TRUE); +} + +static HRESULT WINAPI AviMuxOut_BreakConnect(BaseOutputPin *base) +{ + FIXME("(%p)\n", base); + return E_NOTIMPL; +} + +static const BaseOutputPinFuncTable AviMuxOut_BaseOutputFuncTable = { + { + NULL, + AviMuxOut_AttemptConnection, + AviMuxOut_GetMediaTypeVersion, + AviMuxOut_GetMediaType + }, + NULL, + AviMuxOut_DecideAllocator, + AviMuxOut_BreakConnect +}; + +static inline AviMux* impl_from_out_IPin(IPin *iface) +{ + BasePin *bp = CONTAINING_RECORD(iface, BasePin, IPin_iface); + IBaseFilter *bf = bp->pinInfo.pFilter; + + return impl_from_IBaseFilter(bf); +} + +static HRESULT WINAPI AviMuxOut_QueryInterface(IPin *iface, REFIID riid, void **ppv) +{ + AviMux *This = impl_from_out_IPin(iface); + + TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv); + + if(IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IPin)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IQualityControl)) + *ppv = &This->out->IQualityControl_iface; + else { + FIXME("no interface for %s\n", debugstr_guid(riid)); + *ppv = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI AviMuxOut_AddRef(IPin *iface) +{ + AviMux *This = impl_from_out_IPin(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI AviMuxOut_Release(IPin *iface) +{ + AviMux *This = impl_from_out_IPin(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI AviMuxOut_Connect(IPin *iface, + IPin *pReceivePin, const AM_MEDIA_TYPE *pmt) +{ + AviMux *This = impl_from_out_IPin(iface); + HRESULT hr; + int i; + + TRACE("(%p)->(%p AM_MEDIA_TYPE(%p))\n", This, pReceivePin, pmt); + dump_AM_MEDIA_TYPE(pmt); + + hr = BaseOutputPinImpl_Connect(iface, pReceivePin, pmt); + if(FAILED(hr)) + return hr; + + for(i=0; iinput_pin_no; i++) { + if(!This->in[i]->pin.pin.pConnectedTo) + continue; + + hr = IFilterGraph_Reconnect(This->filter.filterInfo.pGraph, &This->in[i]->pin.pin.IPin_iface); + if(FAILED(hr)) { + BaseOutputPinImpl_Disconnect(iface); + break; + } + } + + if(hr == S_OK) + IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); + return hr; +} + +static HRESULT WINAPI AviMuxOut_ReceiveConnection(IPin *iface, + IPin *pConnector, const AM_MEDIA_TYPE *pmt) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)->(%p AM_MEDIA_TYPE(%p)\n", This, pConnector, pmt); + dump_AM_MEDIA_TYPE(pmt); + return BaseOutputPinImpl_ReceiveConnection(iface, pConnector, pmt); +} + +static HRESULT WINAPI AviMuxOut_Disconnect(IPin *iface) +{ + AviMux *This = impl_from_out_IPin(iface); + HRESULT hr; + + TRACE("(%p)\n", This); + + hr = BaseOutputPinImpl_Disconnect(iface); + if(hr == S_OK) + IBaseFilter_Release(&This->filter.IBaseFilter_iface); + return hr; +} + +static HRESULT WINAPI AviMuxOut_ConnectedTo(IPin *iface, IPin **pPin) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)->(%p)\n", This, pPin); + return BasePinImpl_ConnectedTo(iface, pPin); +} + +static HRESULT WINAPI AviMuxOut_ConnectionMediaType(IPin *iface, AM_MEDIA_TYPE *pmt) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)->(%p)\n", This, pmt); + return BasePinImpl_ConnectionMediaType(iface, pmt); +} + +static HRESULT WINAPI AviMuxOut_QueryPinInfo(IPin *iface, PIN_INFO *pInfo) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)->(%p)\n", This, pInfo); + return BasePinImpl_QueryPinInfo(iface, pInfo); +} + +static HRESULT WINAPI AviMuxOut_QueryDirection(IPin *iface, PIN_DIRECTION *pPinDir) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)->(%p)\n", This, pPinDir); + return BasePinImpl_QueryDirection(iface, pPinDir); +} + +static HRESULT WINAPI AviMuxOut_QueryId(IPin *iface, LPWSTR *Id) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)->(%p)\n", This, Id); + return BasePinImpl_QueryId(iface, Id); +} + +static HRESULT WINAPI AviMuxOut_QueryAccept(IPin *iface, const AM_MEDIA_TYPE *pmt) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)->(AM_MEDIA_TYPE(%p))\n", This, pmt); + dump_AM_MEDIA_TYPE(pmt); + return BasePinImpl_QueryAccept(iface, pmt); +} + +static HRESULT WINAPI AviMuxOut_EnumMediaTypes(IPin *iface, IEnumMediaTypes **ppEnum) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)->(%p)\n", This, ppEnum); + return BasePinImpl_EnumMediaTypes(iface, ppEnum); +} + +static HRESULT WINAPI AviMuxOut_QueryInternalConnections( + IPin *iface, IPin **apPin, ULONG *nPin) +{ + AviMux *This = impl_from_out_IPin(iface); + FIXME("(%p)->(%p %p)\n", This, apPin, nPin); + return E_NOTIMPL; +} + +static HRESULT WINAPI AviMuxOut_EndOfStream(IPin *iface) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)\n", This); + return BaseOutputPinImpl_EndOfStream(iface); +} + +static HRESULT WINAPI AviMuxOut_BeginFlush(IPin *iface) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)\n", This); + return BaseOutputPinImpl_BeginFlush(iface); +} + +static HRESULT WINAPI AviMuxOut_EndFlush(IPin *iface) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)\n", This); + return BaseOutputPinImpl_EndFlush(iface); +} + +static HRESULT WINAPI AviMuxOut_NewSegment(IPin *iface, REFERENCE_TIME tStart, + REFERENCE_TIME tStop, double dRate) +{ + AviMux *This = impl_from_out_IPin(iface); + TRACE("(%p)->(%s %s %f)\n", This, wine_dbgstr_longlong(tStart), wine_dbgstr_longlong(tStop), dRate); + return BasePinImpl_NewSegment(iface, tStart, tStop, dRate); +} + +static const IPinVtbl AviMuxOut_PinVtbl = { + AviMuxOut_QueryInterface, + AviMuxOut_AddRef, + AviMuxOut_Release, + AviMuxOut_Connect, + AviMuxOut_ReceiveConnection, + AviMuxOut_Disconnect, + AviMuxOut_ConnectedTo, + AviMuxOut_ConnectionMediaType, + AviMuxOut_QueryPinInfo, + AviMuxOut_QueryDirection, + AviMuxOut_QueryId, + AviMuxOut_QueryAccept, + AviMuxOut_EnumMediaTypes, + AviMuxOut_QueryInternalConnections, + AviMuxOut_EndOfStream, + AviMuxOut_BeginFlush, + AviMuxOut_EndFlush, + AviMuxOut_NewSegment +}; + +static inline AviMux* impl_from_out_IQualityControl(IQualityControl *iface) +{ + AviMuxOut *amo = CONTAINING_RECORD(iface, AviMuxOut, IQualityControl_iface); + return impl_from_IBaseFilter(amo->pin.pin.pinInfo.pFilter); +} + +static HRESULT WINAPI AviMuxOut_QualityControl_QueryInterface( + IQualityControl *iface, REFIID riid, void **ppv) +{ + AviMux *This = impl_from_out_IQualityControl(iface); + return IPin_QueryInterface(&This->out->pin.pin.IPin_iface, riid, ppv); +} + +static ULONG WINAPI AviMuxOut_QualityControl_AddRef(IQualityControl *iface) +{ + AviMux *This = impl_from_out_IQualityControl(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI AviMuxOut_QualityControl_Release(IQualityControl *iface) +{ + AviMux *This = impl_from_out_IQualityControl(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI AviMuxOut_QualityControl_Notify(IQualityControl *iface, + IBaseFilter *pSelf, Quality q) +{ + AviMux *This = impl_from_out_IQualityControl(iface); + FIXME("(%p)->(%p { 0x%x %u %s %s })\n", This, pSelf, + q.Type, q.Proportion, + wine_dbgstr_longlong(q.Late), + wine_dbgstr_longlong(q.TimeStamp)); + return E_NOTIMPL; +} + +static HRESULT WINAPI AviMuxOut_QualityControl_SetSink( + IQualityControl *iface, IQualityControl *piqc) +{ + AviMux *This = impl_from_out_IQualityControl(iface); + FIXME("(%p)->(%p)\n", This, piqc); + return E_NOTIMPL; +} + +static const IQualityControlVtbl AviMuxOut_QualityControlVtbl = { + AviMuxOut_QualityControl_QueryInterface, + AviMuxOut_QualityControl_AddRef, + AviMuxOut_QualityControl_Release, + AviMuxOut_QualityControl_Notify, + AviMuxOut_QualityControl_SetSink +}; + +static HRESULT WINAPI AviMuxIn_CheckMediaType(BasePin *base, const AM_MEDIA_TYPE *pmt) +{ + TRACE("(%p:%s)->(AM_MEDIA_TYPE(%p))\n", base, debugstr_w(base->pinInfo.achName), pmt); + dump_AM_MEDIA_TYPE(pmt); + + if(IsEqualIID(&pmt->majortype, &MEDIATYPE_Audio) && + IsEqualIID(&pmt->formattype, &FORMAT_WaveFormatEx)) + return S_OK; + if(IsEqualIID(&pmt->majortype, &MEDIATYPE_Interleaved) && + IsEqualIID(&pmt->formattype, &FORMAT_DvInfo)) + return S_OK; + if(IsEqualIID(&pmt->majortype, &MEDIATYPE_Video) && + (IsEqualIID(&pmt->formattype, &FORMAT_VideoInfo) || + IsEqualIID(&pmt->formattype, &FORMAT_DvInfo))) + return S_OK; + return S_FALSE; +} + +static LONG WINAPI AviMuxIn_GetMediaTypeVersion(BasePin *base) +{ + return 0; +} + +static HRESULT WINAPI AviMuxIn_GetMediaType(BasePin *base, int iPosition, AM_MEDIA_TYPE *amt) +{ + return S_FALSE; +} + +static HRESULT WINAPI AviMuxIn_Receive(BaseInputPin *base, IMediaSample *pSample) +{ + AviMuxIn *avimuxin = CONTAINING_RECORD(base, AviMuxIn, pin); + AviMux *avimux = impl_from_IBaseFilter(base->pin.pinInfo.pFilter); + REFERENCE_TIME start, stop; + IMediaSample *sample; + int frames_no; + IMediaSample2 *ms2; + BYTE *frame, *buf; + DWORD max_size, size; + DWORD flags; + HRESULT hr; + + TRACE("(%p:%s)->(%p)\n", base, debugstr_w(base->pin.pinInfo.achName), pSample); + + hr = IMediaSample_QueryInterface(pSample, &IID_IMediaSample2, (void**)&ms2); + if(SUCCEEDED(hr)) { + AM_SAMPLE2_PROPERTIES props; + + memset(&props, 0, sizeof(props)); + hr = IMediaSample2_GetProperties(ms2, sizeof(props), (BYTE*)&props); + IMediaSample2_Release(ms2); + if(FAILED(hr)) + return hr; + + flags = props.dwSampleFlags; + frame = props.pbBuffer; + size = props.lActual; + }else { + flags = IMediaSample_IsSyncPoint(pSample) == S_OK ? AM_SAMPLE_SPLICEPOINT : 0; + hr = IMediaSample_GetPointer(pSample, &frame); + if(FAILED(hr)) + return hr; + size = IMediaSample_GetActualDataLength(pSample); + } + + if(!avimuxin->pin.pin.mtCurrent.bTemporalCompression) + flags |= AM_SAMPLE_SPLICEPOINT; + + hr = IMediaSample_GetTime(pSample, &start, &stop); + if(FAILED(hr)) + return hr; + + if(avimuxin->stop>stop) + return VFW_E_START_TIME_AFTER_END; + + if(avimux->start == -1) + avimux->start = start; + if(avimux->stop < stop) + avimux->stop = stop; + + if(avimux->avih.dwSuggestedBufferSize < ALIGN(size)+sizeof(RIFFCHUNK)) + avimux->avih.dwSuggestedBufferSize = ALIGN(size) + sizeof(RIFFCHUNK); + if(avimuxin->strh.dwSuggestedBufferSize < ALIGN(size)+sizeof(RIFFCHUNK)) + avimuxin->strh.dwSuggestedBufferSize = ALIGN(size) + sizeof(RIFFCHUNK); + + frames_no = 1; + if(avimuxin->stop!=-1 && start > avimuxin->stop) { + frames_no += (double)(start - avimuxin->stop) / 10000000 + * avimuxin->strh.dwRate / avimuxin->strh.dwScale + 0.5; + } + avimuxin->stop = stop; + + while(--frames_no) { + /* TODO: store all control frames in one buffer */ + hr = IMemAllocator_GetBuffer(avimuxin->samples_allocator, &sample, NULL, NULL, 0); + if(FAILED(hr)) + return hr; + hr = IMediaSample_SetActualDataLength(sample, 0); + if(SUCCEEDED(hr)) + hr = IMediaSample_SetDiscontinuity(sample, TRUE); + if(SUCCEEDED(hr)) + hr = IMediaSample_SetSyncPoint(sample, FALSE); + if(SUCCEEDED(hr)) + hr = queue_sample(avimux, avimuxin, sample); + IMediaSample_Release(sample); + if(FAILED(hr)) + return hr; + } + + hr = IMemAllocator_GetBuffer(avimuxin->samples_allocator, &sample, NULL, NULL, 0); + if(FAILED(hr)) + return hr; + max_size = IMediaSample_GetSize(sample); + if(size > max_size) + size = max_size; + hr = IMediaSample_SetActualDataLength(sample, size); + if(SUCCEEDED(hr)) + hr = IMediaSample_SetDiscontinuity(sample, FALSE); + if(SUCCEEDED(hr)) + hr = IMediaSample_SetSyncPoint(sample, flags & AM_SAMPLE_SPLICEPOINT); + /* TODO: avoid unnecessary copying */ + if(SUCCEEDED(hr)) + hr = IMediaSample_GetPointer(sample, &buf); + if(SUCCEEDED(hr)) { + memcpy(buf, frame, size); + hr = queue_sample(avimux, avimuxin, sample); + } + IMediaSample_Release(sample); + + return hr; +} + +static const BaseInputPinFuncTable AviMuxIn_BaseInputFuncTable = { + { + AviMuxIn_CheckMediaType, + NULL, + AviMuxIn_GetMediaTypeVersion, + AviMuxIn_GetMediaType + }, + AviMuxIn_Receive +}; + +static inline AviMux* impl_from_in_IPin(IPin *iface) +{ + BasePin *bp = CONTAINING_RECORD(iface, BasePin, IPin_iface); + IBaseFilter *bf = bp->pinInfo.pFilter; + + return impl_from_IBaseFilter(bf); +} + +static inline AviMuxIn* AviMuxIn_from_IPin(IPin *iface) +{ + BasePin *bp = CONTAINING_RECORD(iface, BasePin, IPin_iface); + BaseInputPin *bip = CONTAINING_RECORD(bp, BaseInputPin, pin); + return CONTAINING_RECORD(bip, AviMuxIn, pin); +} + +static HRESULT WINAPI AviMuxIn_QueryInterface(IPin *iface, REFIID riid, void **ppv) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + + TRACE("(%p:%s)->(%s %p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), + debugstr_guid(riid), ppv); + + if(IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IPin)) + *ppv = &avimuxin->pin.pin.IPin_iface; + else if(IsEqualIID(riid, &IID_IAMStreamControl)) + *ppv = &avimuxin->IAMStreamControl_iface; + else if(IsEqualIID(riid, &IID_IMemInputPin)) + *ppv = &avimuxin->pin.IMemInputPin_iface; + else if(IsEqualIID(riid, &IID_IPropertyBag)) + *ppv = &avimuxin->IPropertyBag_iface; + else if(IsEqualIID(riid, &IID_IQualityControl)) + *ppv = &avimuxin->IQualityControl_iface; + else { + FIXME("no interface for %s\n", debugstr_guid(riid)); + *ppv = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI AviMuxIn_AddRef(IPin *iface) +{ + AviMux *This = impl_from_in_IPin(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI AviMuxIn_Release(IPin *iface) +{ + AviMux *This = impl_from_in_IPin(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI AviMuxIn_Connect(IPin *iface, + IPin *pReceivePin, const AM_MEDIA_TYPE *pmt) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)->(%p AM_MEDIA_TYPE(%p))\n", This, + debugstr_w(avimuxin->pin.pin.pinInfo.achName), pReceivePin, pmt); + dump_AM_MEDIA_TYPE(pmt); + return BaseInputPinImpl_Connect(iface, pReceivePin, pmt); +} + +static HRESULT WINAPI AviMuxIn_ReceiveConnection(IPin *iface, + IPin *pConnector, const AM_MEDIA_TYPE *pmt) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + HRESULT hr; + + TRACE("(%p:%s)->(%p AM_MEDIA_TYPE(%p))\n", This, + debugstr_w(avimuxin->pin.pin.pinInfo.achName), pConnector, pmt); + dump_AM_MEDIA_TYPE(pmt); + + if(!pmt) + return E_POINTER; + + hr = BaseInputPinImpl_ReceiveConnection(iface, pConnector, pmt); + if(FAILED(hr)) + return hr; + + if(IsEqualIID(&pmt->majortype, &MEDIATYPE_Video) && + IsEqualIID(&pmt->formattype, &FORMAT_VideoInfo)) { + ALLOCATOR_PROPERTIES req, act; + VIDEOINFOHEADER *vih; + int size; + + vih = (VIDEOINFOHEADER*)pmt->pbFormat; + avimuxin->strh.fcc = ckidSTREAMHEADER; + avimuxin->strh.cb = sizeof(AVISTREAMHEADER) - FIELD_OFFSET(AVISTREAMHEADER, fccType); + avimuxin->strh.fccType = streamtypeVIDEO; + /* FIXME: fccHandler should be set differently */ + avimuxin->strh.fccHandler = vih->bmiHeader.biCompression ? + vih->bmiHeader.biCompression : FCC('D','I','B',' '); + avimuxin->avg_time_per_frame = vih->AvgTimePerFrame; + avimuxin->stop = -1; + + req.cBuffers = 32; + req.cbBuffer = vih->bmiHeader.biSizeImage; + req.cbAlign = 1; + req.cbPrefix = sizeof(void*); + hr = IMemAllocator_SetProperties(avimuxin->samples_allocator, &req, &act); + if(SUCCEEDED(hr)) + hr = IMemAllocator_Commit(avimuxin->samples_allocator); + if(FAILED(hr)) { + BasePinImpl_Disconnect(iface); + return hr; + } + + size = pmt->cbFormat - FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader); + avimuxin->strf = CoTaskMemAlloc(sizeof(RIFFCHUNK) + ALIGN(FIELD_OFFSET(BITMAPINFO, bmiColors[vih->bmiHeader.biClrUsed]))); + avimuxin->strf->fcc = ckidSTREAMFORMAT; + avimuxin->strf->cb = FIELD_OFFSET(BITMAPINFO, bmiColors[vih->bmiHeader.biClrUsed]); + if(size > avimuxin->strf->cb) + size = avimuxin->strf->cb; + memcpy(avimuxin->strf->data, &vih->bmiHeader, size); + }else { + FIXME("format not supported: %s %s\n", debugstr_guid(&pmt->majortype), + debugstr_guid(&pmt->formattype)); + return E_NOTIMPL; + } + + return create_input_pin(This); +} + +static HRESULT WINAPI AviMuxIn_Disconnect(IPin *iface) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + IMediaSample **prev, *cur; + HRESULT hr; + + TRACE("(%p:%s)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName)); + + hr = BasePinImpl_Disconnect(iface); + if(FAILED(hr)) + return hr; + + IMemAllocator_Decommit(avimuxin->samples_allocator); + while(avimuxin->samples_head) { + cur = avimuxin->samples_head; + hr = IMediaSample_GetPointer(cur, (BYTE**)&prev); + if(FAILED(hr)) + break; + prev--; + + cur = avimuxin->samples_head; + avimuxin->samples_head = *prev; + IMediaSample_Release(cur); + + if(cur == avimuxin->samples_head) + avimuxin->samples_head = NULL; + } + CoTaskMemFree(avimuxin->strf); + avimuxin->strf = NULL; + return hr; +} + +static HRESULT WINAPI AviMuxIn_ConnectedTo(IPin *iface, IPin **pPin) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)->(%p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), pPin); + return BasePinImpl_ConnectedTo(iface, pPin); +} + +static HRESULT WINAPI AviMuxIn_ConnectionMediaType(IPin *iface, AM_MEDIA_TYPE *pmt) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)->(%p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), pmt); + return BasePinImpl_ConnectionMediaType(iface, pmt); +} + +static HRESULT WINAPI AviMuxIn_QueryPinInfo(IPin *iface, PIN_INFO *pInfo) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)->(%p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), pInfo); + return BasePinImpl_QueryPinInfo(iface, pInfo); +} + +static HRESULT WINAPI AviMuxIn_QueryDirection(IPin *iface, PIN_DIRECTION *pPinDir) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)->(%p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), pPinDir); + return BasePinImpl_QueryDirection(iface, pPinDir); +} + +static HRESULT WINAPI AviMuxIn_QueryId(IPin *iface, LPWSTR *Id) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)->(%p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), Id); + return BasePinImpl_QueryId(iface, Id); +} + +static HRESULT WINAPI AviMuxIn_QueryAccept(IPin *iface, const AM_MEDIA_TYPE *pmt) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)->(AM_MEDIA_TYPE(%p))\n", This, + debugstr_w(avimuxin->pin.pin.pinInfo.achName), pmt); + dump_AM_MEDIA_TYPE(pmt); + return BasePinImpl_QueryAccept(iface, pmt); +} + +static HRESULT WINAPI AviMuxIn_EnumMediaTypes(IPin *iface, IEnumMediaTypes **ppEnum) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)->(%p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), ppEnum); + return BasePinImpl_EnumMediaTypes(iface, ppEnum); +} + +static HRESULT WINAPI AviMuxIn_QueryInternalConnections( + IPin *iface, IPin **apPin, ULONG *nPin) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)->(%p %p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), apPin, nPin); + return BasePinImpl_QueryInternalConnections(iface, apPin, nPin); +} + +static HRESULT WINAPI AviMuxIn_EndOfStream(IPin *iface) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName)); + return BaseInputPinImpl_EndOfStream(iface); +} + +static HRESULT WINAPI AviMuxIn_BeginFlush(IPin *iface) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName)); + return BaseInputPinImpl_BeginFlush(iface); +} + +static HRESULT WINAPI AviMuxIn_EndFlush(IPin *iface) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName)); + return BaseInputPinImpl_EndFlush(iface); +} + +static HRESULT WINAPI AviMuxIn_NewSegment(IPin *iface, REFERENCE_TIME tStart, + REFERENCE_TIME tStop, double dRate) +{ + AviMux *This = impl_from_in_IPin(iface); + AviMuxIn *avimuxin = AviMuxIn_from_IPin(iface); + TRACE("(%p:%s)->(%s %s %f)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), + wine_dbgstr_longlong(tStart), wine_dbgstr_longlong(tStop), dRate); + return BasePinImpl_NewSegment(iface, tStart, tStop, dRate); +} + +static const IPinVtbl AviMuxIn_PinVtbl = { + AviMuxIn_QueryInterface, + AviMuxIn_AddRef, + AviMuxIn_Release, + AviMuxIn_Connect, + AviMuxIn_ReceiveConnection, + AviMuxIn_Disconnect, + AviMuxIn_ConnectedTo, + AviMuxIn_ConnectionMediaType, + AviMuxIn_QueryPinInfo, + AviMuxIn_QueryDirection, + AviMuxIn_QueryId, + AviMuxIn_QueryAccept, + AviMuxIn_EnumMediaTypes, + AviMuxIn_QueryInternalConnections, + AviMuxIn_EndOfStream, + AviMuxIn_BeginFlush, + AviMuxIn_EndFlush, + AviMuxIn_NewSegment +}; + +static inline AviMuxIn* AviMuxIn_from_IAMStreamControl(IAMStreamControl *iface) +{ + return CONTAINING_RECORD(iface, AviMuxIn, IAMStreamControl_iface); +} + +static HRESULT WINAPI AviMuxIn_AMStreamControl_QueryInterface( + IAMStreamControl *iface, REFIID riid, void **ppv) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IAMStreamControl(iface); + return IPin_QueryInterface(&avimuxin->pin.pin.IPin_iface, riid, ppv); +} + +static ULONG WINAPI AviMuxIn_AMStreamControl_AddRef(IAMStreamControl *iface) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IAMStreamControl(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI AviMuxIn_AMStreamControl_Release(IAMStreamControl *iface) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IAMStreamControl(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI AviMuxIn_AMStreamControl_StartAt(IAMStreamControl *iface, + const REFERENCE_TIME *ptStart, DWORD dwCookie) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IAMStreamControl(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + FIXME("(%p:%s)->(%p %x)\n", This, + debugstr_w(avimuxin->pin.pin.pinInfo.achName), ptStart, dwCookie); + return E_NOTIMPL; +} + +static HRESULT WINAPI AviMuxIn_AMStreamControl_StopAt(IAMStreamControl *iface, + const REFERENCE_TIME *ptStop, BOOL bSendExtra, DWORD dwCookie) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IAMStreamControl(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + FIXME("(%p:%s)->(%p %x %x)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), + ptStop, bSendExtra, dwCookie); + return E_NOTIMPL; +} + +static HRESULT WINAPI AviMuxIn_AMStreamControl_GetInfo( + IAMStreamControl *iface, AM_STREAM_INFO *pInfo) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IAMStreamControl(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + FIXME("(%p:%s)->(%p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), pInfo); + return E_NOTIMPL; +} + +static const IAMStreamControlVtbl AviMuxIn_AMStreamControlVtbl = { + AviMuxIn_AMStreamControl_QueryInterface, + AviMuxIn_AMStreamControl_AddRef, + AviMuxIn_AMStreamControl_Release, + AviMuxIn_AMStreamControl_StartAt, + AviMuxIn_AMStreamControl_StopAt, + AviMuxIn_AMStreamControl_GetInfo +}; + +static inline AviMuxIn* AviMuxIn_from_IMemInputPin(IMemInputPin *iface) +{ + BaseInputPin *bip = CONTAINING_RECORD(iface, BaseInputPin, IMemInputPin_iface); + return CONTAINING_RECORD(bip, AviMuxIn, pin); +} + +static HRESULT WINAPI AviMuxIn_MemInputPin_QueryInterface( + IMemInputPin *iface, REFIID riid, void **ppv) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IMemInputPin(iface); + return IPin_QueryInterface(&avimuxin->pin.pin.IPin_iface, riid, ppv); +} + +static ULONG WINAPI AviMuxIn_MemInputPin_AddRef(IMemInputPin *iface) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IMemInputPin(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI AviMuxIn_MemInputPin_Release(IMemInputPin *iface) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IMemInputPin(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI AviMuxIn_MemInputPin_GetAllocator( + IMemInputPin *iface, IMemAllocator **ppAllocator) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IMemInputPin(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + + TRACE("(%p:%s)->(%p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), ppAllocator); + + if(!ppAllocator) + return E_POINTER; + + IMemAllocator_AddRef(avimuxin->pin.pAllocator); + *ppAllocator = avimuxin->pin.pAllocator; + return S_OK; +} + +static HRESULT WINAPI AviMuxIn_MemInputPin_NotifyAllocator( + IMemInputPin *iface, IMemAllocator *pAllocator, BOOL bReadOnly) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IMemInputPin(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + ALLOCATOR_PROPERTIES props; + HRESULT hr; + + TRACE("(%p:%s)->(%p %x)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), + pAllocator, bReadOnly); + + if(!pAllocator) + return E_POINTER; + + memset(&props, 0, sizeof(props)); + hr = IMemAllocator_GetProperties(pAllocator, &props); + if(FAILED(hr)) + return hr; + + props.cbAlign = 1; + props.cbPrefix = 8; + return IMemAllocator_SetProperties(avimuxin->pin.pAllocator, &props, &props); +} + +static HRESULT WINAPI AviMuxIn_MemInputPin_GetAllocatorRequirements( + IMemInputPin *iface, ALLOCATOR_PROPERTIES *pProps) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IMemInputPin(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + + TRACE("(%p:%s)->(%p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), pProps); + + if(!pProps) + return E_POINTER; + + pProps->cbAlign = 1; + pProps->cbPrefix = 8; + return S_OK; +} + +static HRESULT WINAPI AviMuxIn_MemInputPin_Receive( + IMemInputPin *iface, IMediaSample *pSample) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IMemInputPin(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + + TRACE("(%p:%s)->(%p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), pSample); + + return avimuxin->pin.pFuncsTable->pfnReceive(&avimuxin->pin, pSample); +} + +static HRESULT WINAPI AviMuxIn_MemInputPin_ReceiveMultiple(IMemInputPin *iface, + IMediaSample **pSamples, LONG nSamples, LONG *nSamplesProcessed) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IMemInputPin(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + HRESULT hr = S_OK; + + TRACE("(%p:%s)->(%p %d %p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), + pSamples, nSamples, nSamplesProcessed); + + for(*nSamplesProcessed=0; *nSamplesProcessedpin.pFuncsTable->pfnReceive(&avimuxin->pin, pSamples[*nSamplesProcessed]); + if(hr != S_OK) + break; + } + + return hr; +} + +static HRESULT WINAPI AviMuxIn_MemInputPin_ReceiveCanBlock(IMemInputPin *iface) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IMemInputPin(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + HRESULT hr; + + TRACE("(%p:%s)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName)); + + if(!This->out->pin.pMemInputPin) + return S_FALSE; + + hr = IMemInputPin_ReceiveCanBlock(This->out->pin.pMemInputPin); + return hr != S_FALSE ? S_OK : S_FALSE; +} + +static const IMemInputPinVtbl AviMuxIn_MemInputPinVtbl = { + AviMuxIn_MemInputPin_QueryInterface, + AviMuxIn_MemInputPin_AddRef, + AviMuxIn_MemInputPin_Release, + AviMuxIn_MemInputPin_GetAllocator, + AviMuxIn_MemInputPin_NotifyAllocator, + AviMuxIn_MemInputPin_GetAllocatorRequirements, + AviMuxIn_MemInputPin_Receive, + AviMuxIn_MemInputPin_ReceiveMultiple, + AviMuxIn_MemInputPin_ReceiveCanBlock +}; + +static inline AviMuxIn* AviMuxIn_from_IPropertyBag(IPropertyBag *iface) +{ + return CONTAINING_RECORD(iface, AviMuxIn, IPropertyBag_iface); +} + +static HRESULT WINAPI AviMuxIn_PropertyBag_QueryInterface( + IPropertyBag *iface, REFIID riid, void **ppv) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IPropertyBag(iface); + return IPin_QueryInterface(&avimuxin->pin.pin.IPin_iface, riid, ppv); +} + +static ULONG WINAPI AviMuxIn_PropertyBag_AddRef(IPropertyBag *iface) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IPropertyBag(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI AviMuxIn_PropertyBag_Release(IPropertyBag *iface) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IPropertyBag(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI AviMuxIn_PropertyBag_Read(IPropertyBag *iface, + LPCOLESTR pszPropName, VARIANT *pVar, IErrorLog *pErrorLog) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IPropertyBag(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + FIXME("(%p:%s)->(%s %p %p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), + debugstr_w(pszPropName), pVar, pErrorLog); + return E_NOTIMPL; +} + +static HRESULT WINAPI AviMuxIn_PropertyBag_Write(IPropertyBag *iface, + LPCOLESTR pszPropName, VARIANT *pVar) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IPropertyBag(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + FIXME("(%p:%s)->(%s %p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), + debugstr_w(pszPropName), pVar); + return E_NOTIMPL; +} + +static const IPropertyBagVtbl AviMuxIn_PropertyBagVtbl = { + AviMuxIn_PropertyBag_QueryInterface, + AviMuxIn_PropertyBag_AddRef, + AviMuxIn_PropertyBag_Release, + AviMuxIn_PropertyBag_Read, + AviMuxIn_PropertyBag_Write +}; + +static inline AviMuxIn* AviMuxIn_from_IQualityControl(IQualityControl *iface) +{ + return CONTAINING_RECORD(iface, AviMuxIn, IQualityControl_iface); +} + +static HRESULT WINAPI AviMuxIn_QualityControl_QueryInterface( + IQualityControl *iface, REFIID riid, void **ppv) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IQualityControl(iface); + return IPin_QueryInterface(&avimuxin->pin.pin.IPin_iface, riid, ppv); +} + +static ULONG WINAPI AviMuxIn_QualityControl_AddRef(IQualityControl *iface) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IQualityControl(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI AviMuxIn_QualityControl_Release(IQualityControl *iface) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IQualityControl(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI AviMuxIn_QualityControl_Notify(IQualityControl *iface, + IBaseFilter *pSelf, Quality q) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IQualityControl(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + FIXME("(%p:%s)->(%p { 0x%x %u %s %s })\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), pSelf, + q.Type, q.Proportion, + wine_dbgstr_longlong(q.Late), + wine_dbgstr_longlong(q.TimeStamp)); + return E_NOTIMPL; +} + +static HRESULT WINAPI AviMuxIn_QualityControl_SetSink( + IQualityControl *iface, IQualityControl *piqc) +{ + AviMuxIn *avimuxin = AviMuxIn_from_IQualityControl(iface); + AviMux *This = impl_from_in_IPin(&avimuxin->pin.pin.IPin_iface); + FIXME("(%p:%s)->(%p)\n", This, debugstr_w(avimuxin->pin.pin.pinInfo.achName), piqc); + return E_NOTIMPL; +} + +static const IQualityControlVtbl AviMuxIn_QualityControlVtbl = { + AviMuxIn_QualityControl_QueryInterface, + AviMuxIn_QualityControl_AddRef, + AviMuxIn_QualityControl_Release, + AviMuxIn_QualityControl_Notify, + AviMuxIn_QualityControl_SetSink +}; + +static HRESULT create_input_pin(AviMux *avimux) +{ + static const WCHAR name[] = {'I','n','p','u','t',' ','0','0',0}; + PIN_INFO info; + HRESULT hr; + + if(avimux->input_pin_no >= MAX_PIN_NO-1) + return E_FAIL; + + info.dir = PINDIR_INPUT; + info.pFilter = &avimux->filter.IBaseFilter_iface; + memcpy(info.achName, name, sizeof(name)); + info.achName[7] = '0' + (avimux->input_pin_no+1) % 10; + info.achName[6] = '0' + (avimux->input_pin_no+1) / 10; + + hr = BaseInputPin_Construct(&AviMuxIn_PinVtbl, sizeof(AviMuxIn), &info, + &AviMuxIn_BaseInputFuncTable, &avimux->filter.csFilter, NULL, (IPin**)&avimux->in[avimux->input_pin_no]); + if(FAILED(hr)) + return hr; + avimux->in[avimux->input_pin_no]->pin.IMemInputPin_iface.lpVtbl = &AviMuxIn_MemInputPinVtbl; + avimux->in[avimux->input_pin_no]->IAMStreamControl_iface.lpVtbl = &AviMuxIn_AMStreamControlVtbl; + avimux->in[avimux->input_pin_no]->IPropertyBag_iface.lpVtbl = &AviMuxIn_PropertyBagVtbl; + avimux->in[avimux->input_pin_no]->IQualityControl_iface.lpVtbl = &AviMuxIn_QualityControlVtbl; + + avimux->in[avimux->input_pin_no]->samples_head = NULL; + hr = CoCreateInstance(&CLSID_MemoryAllocator, NULL, CLSCTX_INPROC_SERVER, + &IID_IMemAllocator, (void**)&avimux->in[avimux->input_pin_no]->samples_allocator); + if(FAILED(hr)) { + BaseInputPinImpl_Release(&avimux->in[avimux->input_pin_no]->pin.pin.IPin_iface); + return hr; + } + + hr = CoCreateInstance(&CLSID_MemoryAllocator, NULL, CLSCTX_INPROC_SERVER, + &IID_IMemAllocator, (void**)&avimux->in[avimux->input_pin_no]->pin.pAllocator); + if(FAILED(hr)) { + IMemAllocator_Release(avimux->in[avimux->input_pin_no]->samples_allocator); + BaseInputPinImpl_Release(&avimux->in[avimux->input_pin_no]->pin.pin.IPin_iface); + return hr; + } + + avimux->in[avimux->input_pin_no]->stream_time = 0; + memset(&avimux->in[avimux->input_pin_no]->strh, 0, sizeof(avimux->in[avimux->input_pin_no]->strh)); + avimux->in[avimux->input_pin_no]->strf = NULL; + memset(&avimux->in[avimux->input_pin_no]->indx_data, 0, sizeof(avimux->in[avimux->input_pin_no]->indx_data)); + memset(&avimux->in[avimux->input_pin_no]->ix_data, 0, sizeof(avimux->in[avimux->input_pin_no]->ix_data)); + avimux->in[avimux->input_pin_no]->indx = (AVISUPERINDEX*)&avimux->in[avimux->input_pin_no]->indx_data; + avimux->in[avimux->input_pin_no]->ix = (AVISTDINDEX*)avimux->in[avimux->input_pin_no]->ix_data; + + avimux->input_pin_no++; + return S_OK; +} + +IUnknown* WINAPI QCAP_createAVIMux(IUnknown *pUnkOuter, HRESULT *phr) +{ + static const WCHAR output_name[] = {'A','V','I',' ','O','u','t',0}; + + AviMux *avimux; + PIN_INFO info; + HRESULT hr; + + TRACE("(%p)\n", pUnkOuter); + + if(pUnkOuter) { + *phr = CLASS_E_NOAGGREGATION; + return NULL; + } + + avimux = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(AviMux)); + if(!avimux) { + *phr = E_OUTOFMEMORY; + return NULL; + } + + BaseFilter_Init(&avimux->filter, &AviMuxVtbl, &CLSID_AviDest, + (DWORD_PTR)(__FILE__ ": AviMux.csFilter"), &filter_func_table); + avimux->IConfigAviMux_iface.lpVtbl = &ConfigAviMuxVtbl; + avimux->IConfigInterleaving_iface.lpVtbl = &ConfigInterleavingVtbl; + avimux->IMediaSeeking_iface.lpVtbl = &MediaSeekingVtbl; + avimux->IPersistMediaPropertyBag_iface.lpVtbl = &PersistMediaPropertyBagVtbl; + avimux->ISpecifyPropertyPages_iface.lpVtbl = &SpecifyPropertyPagesVtbl; + + info.dir = PINDIR_OUTPUT; + info.pFilter = &avimux->filter.IBaseFilter_iface; + lstrcpyW(info.achName, output_name); + hr = BaseOutputPin_Construct(&AviMuxOut_PinVtbl, sizeof(AviMuxOut), &info, + &AviMuxOut_BaseOutputFuncTable, &avimux->filter.csFilter, (IPin**)&avimux->out); + if(FAILED(hr)) { + BaseFilterImpl_Release(&avimux->filter.IBaseFilter_iface); + HeapFree(GetProcessHeap(), 0, avimux); + *phr = hr; + return NULL; + } + avimux->out->IQualityControl_iface.lpVtbl = &AviMuxOut_QualityControlVtbl; + avimux->out->cur_stream = 0; + avimux->out->cur_time = 0; + avimux->out->stream = NULL; + + hr = create_input_pin(avimux); + if(FAILED(hr)) { + BaseOutputPinImpl_Release(&avimux->out->pin.pin.IPin_iface); + BaseFilterImpl_Release(&avimux->filter.IBaseFilter_iface); + HeapFree(GetProcessHeap(), 0, avimux); + *phr = hr; + return NULL; + } + + avimux->interleave = 10000000; + + ObjectRefCount(TRUE); + *phr = S_OK; + return (IUnknown*)&avimux->filter.IBaseFilter_iface; +} diff --git a/dll/directx/wine/qcap/capture.h b/dll/directx/wine/qcap/capture.h new file mode 100644 index 00000000000..65ed2dfc271 --- /dev/null +++ b/dll/directx/wine/qcap/capture.h @@ -0,0 +1,37 @@ +/* DirectShow private capture header (QCAP.DLL) + * + * Copyright 2005 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 + */ + +#ifndef __QCAP_CAPTURE_H__ +#define __QCAP_CAPTURE_H__ + +struct _Capture; +typedef struct _Capture Capture; + +Capture *qcap_driver_init(IPin*,USHORT) DECLSPEC_HIDDEN; +HRESULT qcap_driver_destroy(Capture*) DECLSPEC_HIDDEN; +HRESULT qcap_driver_set_format(Capture*,AM_MEDIA_TYPE*) DECLSPEC_HIDDEN; +HRESULT qcap_driver_get_format(const Capture*,AM_MEDIA_TYPE**) DECLSPEC_HIDDEN; +HRESULT qcap_driver_get_prop_range(Capture*,VideoProcAmpProperty,LONG*,LONG*,LONG*,LONG*,LONG*) DECLSPEC_HIDDEN; +HRESULT qcap_driver_get_prop(Capture*,VideoProcAmpProperty,LONG*,LONG*) DECLSPEC_HIDDEN; +HRESULT qcap_driver_set_prop(Capture*,VideoProcAmpProperty,LONG,LONG) DECLSPEC_HIDDEN; +HRESULT qcap_driver_run(Capture*,FILTER_STATE*) DECLSPEC_HIDDEN; +HRESULT qcap_driver_pause(Capture*,FILTER_STATE*) DECLSPEC_HIDDEN; +HRESULT qcap_driver_stop(Capture*,FILTER_STATE*) DECLSPEC_HIDDEN; + +#endif /* __QCAP_CAPTURE_H__ */ diff --git a/dll/directx/wine/qcap/capturegraph.c b/dll/directx/wine/qcap/capturegraph.c new file mode 100644 index 00000000000..bae75f83725 --- /dev/null +++ b/dll/directx/wine/qcap/capturegraph.c @@ -0,0 +1,881 @@ +/* Capture Graph Builder, Minimal edition + * + * Copyright 2005 Maarten Lankhorst + * Copyright 2005 Rolf Kalbermatter + * + * 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" + +#include +#include + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "wingdi.h" +#include "winerror.h" +#include "objbase.h" + +#include "evcode.h" +#include "strmif.h" +#include "control.h" +#include "vfwmsgs.h" +/* + *#include "amvideo.h" + *#include "mmreg.h" + *#include "dshow.h" + *#include "ddraw.h" + */ +#include "uuids.h" +#include "qcap_main.h" + +#include "wine/unicode.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(qcap); + +/*********************************************************************** +* ICaptureGraphBuilder & ICaptureGraphBuilder2 implementation +*/ +typedef struct CaptureGraphImpl +{ + ICaptureGraphBuilder2 ICaptureGraphBuilder2_iface; + ICaptureGraphBuilder ICaptureGraphBuilder_iface; + LONG ref; + IGraphBuilder *mygraph; + CRITICAL_SECTION csFilter; +} CaptureGraphImpl; + +static const ICaptureGraphBuilderVtbl builder_Vtbl; +static const ICaptureGraphBuilder2Vtbl builder2_Vtbl; + +static inline CaptureGraphImpl *impl_from_ICaptureGraphBuilder(ICaptureGraphBuilder *iface) +{ + return CONTAINING_RECORD(iface, CaptureGraphImpl, ICaptureGraphBuilder_iface); +} + +static inline CaptureGraphImpl *impl_from_ICaptureGraphBuilder2(ICaptureGraphBuilder2 *iface) +{ + return CONTAINING_RECORD(iface, CaptureGraphImpl, ICaptureGraphBuilder2_iface); +} + + +IUnknown * CALLBACK QCAP_createCaptureGraphBuilder2(IUnknown *pUnkOuter, + HRESULT *phr) +{ + CaptureGraphImpl * pCapture = NULL; + + TRACE("(%p, %p)\n", pUnkOuter, phr); + + *phr = CLASS_E_NOAGGREGATION; + if (pUnkOuter) + { + return NULL; + } + *phr = E_OUTOFMEMORY; + + pCapture = CoTaskMemAlloc(sizeof(CaptureGraphImpl)); + if (pCapture) + { + pCapture->ICaptureGraphBuilder2_iface.lpVtbl = &builder2_Vtbl; + pCapture->ICaptureGraphBuilder_iface.lpVtbl = &builder_Vtbl; + pCapture->ref = 1; + pCapture->mygraph = NULL; + InitializeCriticalSection(&pCapture->csFilter); + pCapture->csFilter.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": CaptureGraphImpl.csFilter"); + *phr = S_OK; + ObjectRefCount(TRUE); + } + return (IUnknown *)&pCapture->ICaptureGraphBuilder_iface; +} + +static HRESULT WINAPI +fnCaptureGraphBuilder2_QueryInterface(ICaptureGraphBuilder2 * iface, + REFIID riid, + LPVOID * ppv) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + + TRACE("(%p/%p)->(%s, %p)\n", This, iface, debugstr_guid(riid), ppv); + + *ppv = NULL; + if (IsEqualIID(riid, &IID_IUnknown)) + *ppv = &This->ICaptureGraphBuilder2_iface; + else if (IsEqualIID(riid, &IID_ICaptureGraphBuilder)) + *ppv = &This->ICaptureGraphBuilder_iface; + else if (IsEqualIID(riid, &IID_ICaptureGraphBuilder2)) + *ppv = &This->ICaptureGraphBuilder2_iface; + + if (*ppv) + { + IUnknown_AddRef((IUnknown *)(*ppv)); + TRACE ("-- Interface = %p\n", *ppv); + return S_OK; + } + + TRACE ("-- Interface: E_NOINTERFACE\n"); + return E_NOINTERFACE; +} + +static ULONG WINAPI +fnCaptureGraphBuilder2_AddRef(ICaptureGraphBuilder2 * iface) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + DWORD ref = InterlockedIncrement(&This->ref); + + TRACE("(%p/%p)->() AddRef from %d\n", This, iface, ref - 1); + return ref; +} + +static ULONG WINAPI fnCaptureGraphBuilder2_Release(ICaptureGraphBuilder2 * iface) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + DWORD ref = InterlockedDecrement(&This->ref); + + TRACE("(%p/%p)->() Release from %d\n", This, iface, ref + 1); + + if (!ref) + { + This->csFilter.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&This->csFilter); + if (This->mygraph) + IGraphBuilder_Release(This->mygraph); + CoTaskMemFree(This); + ObjectRefCount(FALSE); + } + return ref; +} + +static HRESULT WINAPI +fnCaptureGraphBuilder2_SetFilterGraph(ICaptureGraphBuilder2 * iface, + IGraphBuilder *pfg) +{ +/* The graph builder will automatically create a filter graph if you don't call + this method. If you call this method after the graph builder has created its + own filter graph, the call will fail. */ + IMediaEvent *pmev; + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + + TRACE("(%p/%p)->(%p)\n", This, iface, pfg); + + if (This->mygraph) + return E_UNEXPECTED; + + if (!pfg) + return E_POINTER; + + This->mygraph = pfg; + IGraphBuilder_AddRef(This->mygraph); + if (SUCCEEDED(IGraphBuilder_QueryInterface(This->mygraph, + &IID_IMediaEvent, (LPVOID *)&pmev))) + { + IMediaEvent_CancelDefaultHandling(pmev, EC_REPAINT); + IMediaEvent_Release(pmev); + } + return S_OK; +} + +static HRESULT WINAPI +fnCaptureGraphBuilder2_GetFilterGraph(ICaptureGraphBuilder2 * iface, + IGraphBuilder **pfg) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + + TRACE("(%p/%p)->(%p)\n", This, iface, pfg); + + if (!pfg) + return E_POINTER; + + *pfg = This->mygraph; + if (!This->mygraph) + { + TRACE("(%p) Getting NULL filtergraph\n", iface); + return E_UNEXPECTED; + } + + IGraphBuilder_AddRef(This->mygraph); + + TRACE("(%p) return filtergraph %p\n", iface, *pfg); + return S_OK; +} + +static HRESULT WINAPI +fnCaptureGraphBuilder2_SetOutputFileName(ICaptureGraphBuilder2 * iface, + const GUID *pType, + LPCOLESTR lpstrFile, + IBaseFilter **ppf, + IFileSinkFilter **ppSink) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + + FIXME("(%p/%p)->(%s, %s, %p, %p) Stub!\n", This, iface, + debugstr_guid(pType), debugstr_w(lpstrFile), ppf, ppSink); + + return E_NOTIMPL; +} + +static HRESULT WINAPI +fnCaptureGraphBuilder2_FindInterface(ICaptureGraphBuilder2 * iface, + const GUID *pCategory, + const GUID *pType, + IBaseFilter *pf, + REFIID riid, + void **ppint) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + + FIXME("(%p/%p)->(%s, %s, %p, %s, %p) - workaround stub!\n", This, iface, + debugstr_guid(pCategory), debugstr_guid(pType), + pf, debugstr_guid(riid), ppint); + + return IBaseFilter_QueryInterface(pf, riid, ppint); + /* Looks for the specified interface on the filter, upstream and + * downstream from the filter, and, optionally, only on the output + * pin of the given category. + */ +} + +static HRESULT match_smart_tee_pin(CaptureGraphImpl *This, + const GUID *pCategory, + const GUID *pType, + IUnknown *pSource, + IPin **source_out) +{ + static const WCHAR inputW[] = {'I','n','p','u','t',0}; + static const WCHAR captureW[] = {'C','a','p','t','u','r','e',0}; + static const WCHAR previewW[] = {'P','r','e','v','i','e','w',0}; + IPin *capture = NULL; + IPin *preview = NULL; + IPin *peer = NULL; + IBaseFilter *smartTee = NULL; + BOOL needSmartTee = FALSE; + HRESULT hr; + + TRACE("(%p, %s, %s, %p, %p)\n", This, debugstr_guid(pCategory), debugstr_guid(pType), pSource, source_out); + hr = ICaptureGraphBuilder2_FindPin(&This->ICaptureGraphBuilder2_iface, pSource, + PINDIR_OUTPUT, &PIN_CATEGORY_CAPTURE, pType, FALSE, 0, &capture); + if (SUCCEEDED(hr)) { + hr = ICaptureGraphBuilder2_FindPin(&This->ICaptureGraphBuilder2_iface, pSource, + PINDIR_OUTPUT, &PIN_CATEGORY_PREVIEW, pType, FALSE, 0, &preview); + if (FAILED(hr)) + needSmartTee = TRUE; + } else { + hr = E_INVALIDARG; + goto end; + } + if (!needSmartTee) { + if (IsEqualIID(pCategory, &PIN_CATEGORY_CAPTURE)) { + hr = IPin_ConnectedTo(capture, &peer); + if (hr == VFW_E_NOT_CONNECTED) { + *source_out = capture; + IPin_AddRef(*source_out); + hr = S_OK; + } else + hr = E_INVALIDARG; + } else { + hr = IPin_ConnectedTo(preview, &peer); + if (hr == VFW_E_NOT_CONNECTED) { + *source_out = preview; + IPin_AddRef(*source_out); + hr = S_OK; + } else + hr = E_INVALIDARG; + } + goto end; + } + hr = IPin_ConnectedTo(capture, &peer); + if (SUCCEEDED(hr)) { + PIN_INFO pinInfo; + GUID classID; + hr = IPin_QueryPinInfo(peer, &pinInfo); + if (SUCCEEDED(hr)) { + hr = IBaseFilter_GetClassID(pinInfo.pFilter, &classID); + if (SUCCEEDED(hr)) { + if (IsEqualIID(&classID, &CLSID_SmartTee)) { + smartTee = pinInfo.pFilter; + IBaseFilter_AddRef(smartTee); + } + } + IBaseFilter_Release(pinInfo.pFilter); + } + if (!smartTee) { + hr = E_INVALIDARG; + goto end; + } + } else if (hr == VFW_E_NOT_CONNECTED) { + hr = CoCreateInstance(&CLSID_SmartTee, NULL, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (LPVOID*)&smartTee); + if (SUCCEEDED(hr)) { + hr = IGraphBuilder_AddFilter(This->mygraph, smartTee, NULL); + if (SUCCEEDED(hr)) { + IPin *smartTeeInput = NULL; + hr = IBaseFilter_FindPin(smartTee, inputW, &smartTeeInput); + if (SUCCEEDED(hr)) { + hr = IGraphBuilder_ConnectDirect(This->mygraph, capture, smartTeeInput, NULL); + IPin_Release(smartTeeInput); + } + } + } + if (FAILED(hr)) { + TRACE("adding SmartTee failed with hr=0x%08x\n", hr); + hr = E_INVALIDARG; + goto end; + } + } else { + hr = E_INVALIDARG; + goto end; + } + if (IsEqualIID(pCategory, &PIN_CATEGORY_CAPTURE)) + hr = IBaseFilter_FindPin(smartTee, captureW, source_out); + else { + hr = IBaseFilter_FindPin(smartTee, previewW, source_out); + if (SUCCEEDED(hr)) + hr = VFW_S_NOPREVIEWPIN; + } + +end: + if (capture) + IPin_Release(capture); + if (preview) + IPin_Release(preview); + if (peer) + IPin_Release(peer); + if (smartTee) + IBaseFilter_Release(smartTee); + TRACE("for %s returning hr=0x%08x, *source_out=%p\n", IsEqualIID(pCategory, &PIN_CATEGORY_CAPTURE) ? "capture" : "preview", hr, source_out ? *source_out : 0); + return hr; +} + +static HRESULT find_unconnected_pin(CaptureGraphImpl *This, + const GUID *pCategory, const GUID *pType, IUnknown *pSource, IPin **out_pin) +{ + int index = 0; + IPin *source_out; + HRESULT hr; + BOOL usedSmartTeePreviewPin = FALSE; + + /* depth-first search the graph for the first unconnected pin that matches + * the given category and type */ + for(;;){ + IPin *nextpin; + + if (pCategory && (IsEqualIID(pCategory, &PIN_CATEGORY_CAPTURE) || IsEqualIID(pCategory, &PIN_CATEGORY_PREVIEW))){ + IBaseFilter *sourceFilter = NULL; + hr = IUnknown_QueryInterface(pSource, &IID_IBaseFilter, (void**)&sourceFilter); + if (SUCCEEDED(hr)) { + hr = match_smart_tee_pin(This, pCategory, pType, pSource, &source_out); + if (hr == VFW_S_NOPREVIEWPIN) + usedSmartTeePreviewPin = TRUE; + IBaseFilter_Release(sourceFilter); + } else { + hr = ICaptureGraphBuilder2_FindPin(&This->ICaptureGraphBuilder2_iface, pSource, PINDIR_OUTPUT, pCategory, pType, FALSE, index, &source_out); + } + if (FAILED(hr)) + return E_INVALIDARG; + } else { + hr = ICaptureGraphBuilder2_FindPin(&This->ICaptureGraphBuilder2_iface, pSource, PINDIR_OUTPUT, pCategory, pType, FALSE, index, &source_out); + if (FAILED(hr)) + return E_INVALIDARG; + } + + hr = IPin_ConnectedTo(source_out, &nextpin); + if(SUCCEEDED(hr)){ + PIN_INFO info; + + IPin_Release(source_out); + + hr = IPin_QueryPinInfo(nextpin, &info); + if(FAILED(hr) || !info.pFilter){ + WARN("QueryPinInfo failed: %08x\n", hr); + return hr; + } + + hr = find_unconnected_pin(This, pCategory, pType, (IUnknown*)info.pFilter, out_pin); + + IBaseFilter_Release(info.pFilter); + + if(SUCCEEDED(hr)) + return hr; + }else{ + *out_pin = source_out; + if(usedSmartTeePreviewPin) + return VFW_S_NOPREVIEWPIN; + return S_OK; + } + + index++; + } +} + +static HRESULT WINAPI +fnCaptureGraphBuilder2_RenderStream(ICaptureGraphBuilder2 * iface, + const GUID *pCategory, + const GUID *pType, + IUnknown *pSource, + IBaseFilter *pfCompressor, + IBaseFilter *pfRenderer) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + IPin *source_out = NULL, *renderer_in; + BOOL rendererNeedsRelease = FALSE; + HRESULT hr, return_hr = S_OK; + + FIXME("(%p/%p)->(%s, %s, %p, %p, %p) semi-stub!\n", This, iface, + debugstr_guid(pCategory), debugstr_guid(pType), + pSource, pfCompressor, pfRenderer); + + if (!This->mygraph) + { + FIXME("Need a capture graph\n"); + return E_UNEXPECTED; + } + + if (pCategory && IsEqualIID(pCategory, &PIN_CATEGORY_VBI)) { + FIXME("Tee/Sink-to-Sink filter not supported\n"); + return E_NOTIMPL; + } + + hr = find_unconnected_pin(This, pCategory, pType, pSource, &source_out); + if (FAILED(hr)) + return hr; + return_hr = hr; + + if (!pfRenderer) + { + IEnumMediaTypes *enumMedia = NULL; + hr = IPin_EnumMediaTypes(source_out, &enumMedia); + if (SUCCEEDED(hr)) { + AM_MEDIA_TYPE *mediaType; + hr = IEnumMediaTypes_Next(enumMedia, 1, &mediaType, NULL); + if (SUCCEEDED(hr)) { + if (IsEqualIID(&mediaType->majortype, &MEDIATYPE_Video)) { + hr = CoCreateInstance(&CLSID_VideoRenderer, NULL, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (void**)&pfRenderer); + } else if (IsEqualIID(&mediaType->majortype, &MEDIATYPE_Audio)) { + hr = CoCreateInstance(&CLSID_DSoundRender, NULL, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (void**)&pfRenderer); + } else { + FIXME("cannot automatically load renderer for majortype %s\n", debugstr_guid(&mediaType->majortype)); + hr = E_FAIL; + } + if (SUCCEEDED(hr)) { + rendererNeedsRelease = TRUE; + hr = IGraphBuilder_AddFilter(This->mygraph, pfRenderer, NULL); + } + DeleteMediaType(mediaType); + } + IEnumMediaTypes_Release(enumMedia); + } + if (FAILED(hr)) { + if (rendererNeedsRelease) + IBaseFilter_Release(pfRenderer); + IPin_Release(source_out); + return hr; + } + } + + hr = ICaptureGraphBuilder2_FindPin(iface, (IUnknown*)pfRenderer, PINDIR_INPUT, NULL, NULL, TRUE, 0, &renderer_in); + if (FAILED(hr)) + { + if (rendererNeedsRelease) + IBaseFilter_Release(pfRenderer); + IPin_Release(source_out); + return hr; + } + + if (!pfCompressor) + hr = IGraphBuilder_Connect(This->mygraph, source_out, renderer_in); + else + { + IPin *compressor_in, *compressor_out; + + hr = ICaptureGraphBuilder2_FindPin(iface, (IUnknown*)pfCompressor, + PINDIR_INPUT, NULL, NULL, TRUE, 0, &compressor_in); + if (SUCCEEDED(hr)) + { + hr = IGraphBuilder_Connect(This->mygraph, source_out, compressor_in); + IPin_Release(compressor_in); + } + + if (SUCCEEDED(hr)) + { + hr = ICaptureGraphBuilder2_FindPin(iface, (IUnknown*)pfCompressor, + PINDIR_OUTPUT, NULL, NULL, TRUE, 0, &compressor_out); + if (SUCCEEDED(hr)) + { + hr = IGraphBuilder_Connect(This->mygraph, compressor_out, renderer_in); + IPin_Release(compressor_out); + } + } + } + + IPin_Release(source_out); + IPin_Release(renderer_in); + if (rendererNeedsRelease) + IBaseFilter_Release(pfRenderer); + if (SUCCEEDED(hr)) + return return_hr; + return hr; +} + +static HRESULT WINAPI +fnCaptureGraphBuilder2_ControlStream(ICaptureGraphBuilder2 * iface, + const GUID *pCategory, + const GUID *pType, + IBaseFilter *pFilter, + REFERENCE_TIME *pstart, + REFERENCE_TIME *pstop, + WORD wStartCookie, + WORD wStopCookie) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + + FIXME("(%p/%p)->(%s, %s, %p, %p, %p, %i, %i) Stub!\n", This, iface, + debugstr_guid(pCategory), debugstr_guid(pType), + pFilter, pstart, pstop, wStartCookie, wStopCookie); + + return E_NOTIMPL; +} + +static HRESULT WINAPI +fnCaptureGraphBuilder2_AllocCapFile(ICaptureGraphBuilder2 * iface, + LPCOLESTR lpwstr, + DWORDLONG dwlSize) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + + FIXME("(%p/%p)->(%s, 0x%s) Stub!\n", This, iface, + debugstr_w(lpwstr), wine_dbgstr_longlong(dwlSize)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI +fnCaptureGraphBuilder2_CopyCaptureFile(ICaptureGraphBuilder2 * iface, + LPOLESTR lpwstrOld, + LPOLESTR lpwstrNew, + int fAllowEscAbort, + IAMCopyCaptureFileProgress *pCallback) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + + FIXME("(%p/%p)->(%s, %s, %i, %p) Stub!\n", This, iface, + debugstr_w(lpwstrOld), debugstr_w(lpwstrNew), + fAllowEscAbort, pCallback); + + return E_NOTIMPL; +} + +static HRESULT pin_matches(IPin *pin, PIN_DIRECTION direction, const GUID *cat, const GUID *type, BOOL unconnected) +{ + IPin *partner; + PIN_DIRECTION pindir; + HRESULT hr; + + hr = IPin_QueryDirection(pin, &pindir); + + if (unconnected && IPin_ConnectedTo(pin, &partner) == S_OK && partner!=NULL) + { + IPin_Release(partner); + TRACE("No match, %p already connected to %p\n", pin, partner); + return FAILED(hr) ? hr : S_FALSE; + } + + if (FAILED(hr)) + return hr; + if (SUCCEEDED(hr) && pindir != direction) + return S_FALSE; + + if (cat) + { + IKsPropertySet *props; + GUID category; + DWORD fetched; + + hr = IPin_QueryInterface(pin, &IID_IKsPropertySet, (void**)&props); + if (FAILED(hr)) + return S_FALSE; + + hr = IKsPropertySet_Get(props, &ROPSETID_Pin, 0, NULL, + 0, &category, sizeof(category), &fetched); + IKsPropertySet_Release(props); + if (FAILED(hr) || !IsEqualIID(&category, cat)) + return S_FALSE; + } + + if (type) + { + IEnumMediaTypes *types; + AM_MEDIA_TYPE *media_type; + ULONG fetched; + + hr = IPin_EnumMediaTypes(pin, &types); + if (FAILED(hr)) + return S_FALSE; + + IEnumMediaTypes_Reset(types); + while (1) { + if (IEnumMediaTypes_Next(types, 1, &media_type, &fetched) != S_OK || fetched != 1) + { + IEnumMediaTypes_Release(types); + return S_FALSE; + } + + if (IsEqualIID(&media_type->majortype, type)) + { + DeleteMediaType(media_type); + break; + } + DeleteMediaType(media_type); + } + IEnumMediaTypes_Release(types); + } + + TRACE("Pin matched\n"); + return S_OK; +} + +static HRESULT WINAPI +fnCaptureGraphBuilder2_FindPin(ICaptureGraphBuilder2 * iface, + IUnknown *pSource, + PIN_DIRECTION pindir, + const GUID *pCategory, + const GUID *pType, + BOOL fUnconnected, + INT num, + IPin **ppPin) +{ + HRESULT hr; + IEnumPins *enumpins = NULL; + IPin *pin; + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface); + + TRACE("(%p/%p)->(%p, %x, %s, %s, %d, %i, %p)\n", This, iface, + pSource, pindir, debugstr_guid(pCategory), debugstr_guid(pType), + fUnconnected, num, ppPin); + + pin = NULL; + + hr = IUnknown_QueryInterface(pSource, &IID_IPin, (void**)&pin); + if (hr == E_NOINTERFACE) + { + IBaseFilter *filter = NULL; + int numcurrent = 0; + + hr = IUnknown_QueryInterface(pSource, &IID_IBaseFilter, (void**)&filter); + if (hr == E_NOINTERFACE) + { + WARN("Input not filter or pin?!\n"); + return E_NOINTERFACE; + } + + hr = IBaseFilter_EnumPins(filter, &enumpins); + if (FAILED(hr)) + { + WARN("Could not enumerate\n"); + IBaseFilter_Release(filter); + return hr; + } + + while (1) + { + ULONG fetched; + + hr = IEnumPins_Next(enumpins, 1, &pin, &fetched); + if (hr == VFW_E_ENUM_OUT_OF_SYNC) + { + numcurrent = 0; + IEnumPins_Reset(enumpins); + pin = NULL; + continue; + } + if (hr != S_OK) + break; + if (fetched != 1) + { + hr = E_FAIL; + break; + } + + TRACE("Testing match\n"); + hr = pin_matches(pin, pindir, pCategory, pType, fUnconnected); + if (hr == S_OK && numcurrent++ == num) + break; + IPin_Release(pin); + pin = NULL; + if (FAILED(hr)) + break; + } + IEnumPins_Release(enumpins); + IBaseFilter_Release(filter); + + if (hr != S_OK) + { + WARN("Could not find %s pin # %d\n", (pindir == PINDIR_OUTPUT ? "output" : "input"), numcurrent); + return E_FAIL; + } + } + else if (pin_matches(pin, pindir, pCategory, pType, fUnconnected) != S_OK) + { + IPin_Release(pin); + return E_FAIL; + } + + *ppPin = pin; + return S_OK; +} + +static const ICaptureGraphBuilder2Vtbl builder2_Vtbl = +{ + fnCaptureGraphBuilder2_QueryInterface, + fnCaptureGraphBuilder2_AddRef, + fnCaptureGraphBuilder2_Release, + fnCaptureGraphBuilder2_SetFilterGraph, + fnCaptureGraphBuilder2_GetFilterGraph, + fnCaptureGraphBuilder2_SetOutputFileName, + fnCaptureGraphBuilder2_FindInterface, + fnCaptureGraphBuilder2_RenderStream, + fnCaptureGraphBuilder2_ControlStream, + fnCaptureGraphBuilder2_AllocCapFile, + fnCaptureGraphBuilder2_CopyCaptureFile, + fnCaptureGraphBuilder2_FindPin +}; + + +static HRESULT WINAPI +fnCaptureGraphBuilder_QueryInterface(ICaptureGraphBuilder * iface, + REFIID riid, LPVOID * ppv) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder(iface); + TRACE("%p --> Forwarding to v2 (%p)\n", iface, This); + return ICaptureGraphBuilder2_QueryInterface(&This->ICaptureGraphBuilder2_iface, riid, ppv); +} + +static ULONG WINAPI +fnCaptureGraphBuilder_AddRef(ICaptureGraphBuilder * iface) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder(iface); + TRACE("%p --> Forwarding to v2 (%p)\n", iface, This); + return ICaptureGraphBuilder2_AddRef(&This->ICaptureGraphBuilder2_iface); +} + +static ULONG WINAPI +fnCaptureGraphBuilder_Release(ICaptureGraphBuilder * iface) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder(iface); + TRACE("%p --> Forwarding to v2 (%p)\n", iface, This); + return ICaptureGraphBuilder2_Release(&This->ICaptureGraphBuilder2_iface); +} + +static HRESULT WINAPI +fnCaptureGraphBuilder_SetFiltergraph(ICaptureGraphBuilder * iface, + IGraphBuilder *pfg) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder(iface); + TRACE("%p --> Forwarding to v2 (%p)\n", iface, This); + return ICaptureGraphBuilder2_SetFiltergraph(&This->ICaptureGraphBuilder2_iface, pfg); +} + +static HRESULT WINAPI +fnCaptureGraphBuilder_GetFiltergraph(ICaptureGraphBuilder * iface, + IGraphBuilder **pfg) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder(iface); + TRACE("%p --> Forwarding to v2 (%p)\n", iface, This); + return ICaptureGraphBuilder2_GetFiltergraph(&This->ICaptureGraphBuilder2_iface, pfg); +} + +static HRESULT WINAPI +fnCaptureGraphBuilder_SetOutputFileName(ICaptureGraphBuilder * iface, + const GUID *pType, LPCOLESTR lpstrFile, + IBaseFilter **ppf, IFileSinkFilter **ppSink) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder(iface); + TRACE("%p --> Forwarding to v2 (%p)\n", iface, This); + return ICaptureGraphBuilder2_SetOutputFileName(&This->ICaptureGraphBuilder2_iface, pType, + lpstrFile, ppf, ppSink); +} + +static HRESULT WINAPI +fnCaptureGraphBuilder_FindInterface(ICaptureGraphBuilder * iface, + const GUID *pCategory, IBaseFilter *pf, + REFIID riid, void **ppint) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder(iface); + TRACE("%p --> Forwarding to v2 (%p)\n", iface, This); + return ICaptureGraphBuilder2_FindInterface(&This->ICaptureGraphBuilder2_iface, pCategory, NULL, + pf, riid, ppint); +} + +static HRESULT WINAPI +fnCaptureGraphBuilder_RenderStream(ICaptureGraphBuilder * iface, + const GUID *pCategory, IUnknown *pSource, + IBaseFilter *pfCompressor, IBaseFilter *pfRenderer) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder(iface); + TRACE("%p --> Forwarding to v2 (%p)\n", iface, This); + return ICaptureGraphBuilder2_RenderStream(&This->ICaptureGraphBuilder2_iface, pCategory, NULL, + pSource, pfCompressor, pfRenderer); +} + +static HRESULT WINAPI +fnCaptureGraphBuilder_ControlStream(ICaptureGraphBuilder * iface, + const GUID *pCategory, IBaseFilter *pFilter, + REFERENCE_TIME *pstart, REFERENCE_TIME *pstop, + WORD wStartCookie, WORD wStopCookie) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder(iface); + TRACE("%p --> Forwarding to v2 (%p)\n", iface, This); + return ICaptureGraphBuilder2_ControlStream(&This->ICaptureGraphBuilder2_iface, pCategory, NULL, + pFilter, pstart, pstop, wStartCookie, wStopCookie); +} + +static HRESULT WINAPI +fnCaptureGraphBuilder_AllocCapFile(ICaptureGraphBuilder * iface, + LPCOLESTR lpstr, DWORDLONG dwlSize) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder(iface); + TRACE("%p --> Forwarding to v2 (%p)\n", iface, This); + return ICaptureGraphBuilder2_AllocCapFile(&This->ICaptureGraphBuilder2_iface, lpstr, dwlSize); +} + +static HRESULT WINAPI +fnCaptureGraphBuilder_CopyCaptureFile(ICaptureGraphBuilder * iface, + LPOLESTR lpwstrOld, LPOLESTR lpwstrNew, + int fAllowEscAbort, + IAMCopyCaptureFileProgress *pCallback) +{ + CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder(iface); + TRACE("%p --> Forwarding to v2 (%p)\n", iface, This); + return ICaptureGraphBuilder2_CopyCaptureFile(&This->ICaptureGraphBuilder2_iface, lpwstrOld, + lpwstrNew, fAllowEscAbort, pCallback); +} + +static const ICaptureGraphBuilderVtbl builder_Vtbl = +{ + fnCaptureGraphBuilder_QueryInterface, + fnCaptureGraphBuilder_AddRef, + fnCaptureGraphBuilder_Release, + fnCaptureGraphBuilder_SetFiltergraph, + fnCaptureGraphBuilder_GetFiltergraph, + fnCaptureGraphBuilder_SetOutputFileName, + fnCaptureGraphBuilder_FindInterface, + fnCaptureGraphBuilder_RenderStream, + fnCaptureGraphBuilder_ControlStream, + fnCaptureGraphBuilder_AllocCapFile, + fnCaptureGraphBuilder_CopyCaptureFile +}; diff --git a/dll/directx/wine/qcap/enummedia.c b/dll/directx/wine/qcap/enummedia.c new file mode 100644 index 00000000000..45552e16a46 --- /dev/null +++ b/dll/directx/wine/qcap/enummedia.c @@ -0,0 +1,44 @@ +/* + * Implementation of IEnumMediaTypes Interface + * + * Copyright 2003 Robert Shearman + * + * 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 + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "wtypes.h" +#include "wingdi.h" +#include "winuser.h" +#include "dshow.h" + +#include "qcap_main.h" + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(qcap); + +void dump_AM_MEDIA_TYPE(const AM_MEDIA_TYPE * pmt) +{ + if (!pmt) + return; + TRACE("\t%s\n\t%s\n\t...\n\t%s\n", debugstr_guid(&pmt->majortype), + debugstr_guid(&pmt->subtype), debugstr_guid(&pmt->formattype)); +} diff --git a/dll/directx/wine/qcap/qcap.spec b/dll/directx/wine/qcap/qcap.spec new file mode 100644 index 00000000000..b16365d0c9f --- /dev/null +++ b/dll/directx/wine/qcap/qcap.spec @@ -0,0 +1,4 @@ +@ stdcall -private DllCanUnloadNow() +@ stdcall -private DllGetClassObject(ptr ptr ptr) +@ stdcall -private DllRegisterServer() +@ stdcall -private DllUnregisterServer() diff --git a/dll/directx/wine/qcap/qcap_main.c b/dll/directx/wine/qcap/qcap_main.c new file mode 100644 index 00000000000..bc0b903d0b1 --- /dev/null +++ b/dll/directx/wine/qcap/qcap_main.c @@ -0,0 +1,198 @@ +/* + * Qcap implementation, dllentry points + * + * Copyright (C) 2003 Dominik Strasser + * Copyright (C) 2005 Rolf Kalbermatter + * + * 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" + +#include +#include +#include + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "wingdi.h" +#include "winerror.h" +#include "objbase.h" +#include "uuids.h" +#include "strmif.h" + +#include "qcap_main.h" + +#include "wine/unicode.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(qcap); + +static LONG objects_ref = 0; + +static const WCHAR wAudioCaptureFilter[] = +{'A','u','d','i','o',' ','C','a','p','t','u','r','e',' ','F','i','l','t','e','r',0}; +static const WCHAR wAVICompressor[] = +{'A','V','I',' ','C','o','m','p','r','e','s','s','o','r',0}; +static const WCHAR wVFWCaptFilter[] = +{'V','F','W',' ','C','a','p','t','u','r','e',' ','F','i','l','t','e','r',0}; +static const WCHAR wVFWCaptFilterProp[] = +{'V','F','W',' ','C','a','p','t','u','r','e',' ','F','i','l','t','e','r',' ', + 'P','r','o','p','e','r','t','y',' ','P','a','g','e',0}; +static const WCHAR wAVIMux[] = +{'A','V','I',' ','m','u','x',0}; +static const WCHAR wAVIMuxPropPage[] = +{'A','V','I',' ','m','u','x',' ','P','r','o','p','e','r','t','y',' ','P','a','g','e',0}; +static const WCHAR wAVIMuxPropPage1[] = +{'A','V','I',' ','m','u','x',' ','P','r','o','p','e','r','t','y',' ','P','a','g','e','1',0}; +static const WCHAR wFileWriter[] = +{'F','i','l','e',' ','W','r','i','t','e','r',0}; +static const WCHAR wCaptGraphBuilder[] = +{'C','a','p','t','u','r','e',' ','G','r','a','p','h',' ','B','u','i','l','d','e','r',0}; +static const WCHAR wCaptGraphBuilder2[] = +{'C','a','p','t','u','r','e',' ','G','r','a','p','h',' ','B','u','i','l','d','e','r','2',0}; +static const WCHAR wInfPinTeeFilter[] = +{'I','n','f','i','n','i','t','e',' ','P','i','n',' ','T','e','e',' ','F','i', + 'l','t','e','r',0}; +static const WCHAR wSmartTeeFilter[] = +{'S','m','a','r','t',' ','T','e','e',' ','F','i','l','t','e','r',0}; +static const WCHAR wAudioInMixerProp[] = +{'A','u','d','i','o','I','n','p','u','t','M','i','x','e','r',' ','P','r','o', + 'p','e','r','t','y',' ','P','a','g','e',0}; + +FactoryTemplate const g_Templates[] = { + { + wAudioCaptureFilter, + &CLSID_AudioRecord, + QCAP_createAudioCaptureFilter, + NULL + },{ + wAVICompressor, + &CLSID_AVICo, + QCAP_createAVICompressor, + NULL + },{ + wVFWCaptFilter, + &CLSID_VfwCapture, + QCAP_createVFWCaptureFilter, + NULL + },{ + wVFWCaptFilterProp, + &CLSID_CaptureProperties, + NULL, /* FIXME: Implement QCAP_createVFWCaptureFilterPropertyPage */ + NULL + },{ + wAVIMux, + &CLSID_AviDest, + QCAP_createAVIMux, + NULL + },{ + wAVIMuxPropPage, + &CLSID_AviMuxProptyPage, + NULL, /* FIXME: Implement QCAP_createAVIMuxPropertyPage */ + NULL + },{ + wAVIMuxPropPage1, + &CLSID_AviMuxProptyPage1, + NULL, /* FIXME: Implement QCAP_createAVIMuxPropertyPage1 */ + NULL + },{ + wFileWriter, + &CLSID_FileWriter, + NULL, /* FIXME: Implement QCAP_createFileWriter */ + NULL + },{ + wCaptGraphBuilder, + &CLSID_CaptureGraphBuilder, + QCAP_createCaptureGraphBuilder2, + NULL + },{ + wCaptGraphBuilder2, + &CLSID_CaptureGraphBuilder2, + QCAP_createCaptureGraphBuilder2, + NULL + },{ + wInfPinTeeFilter, + &CLSID_InfTee, + NULL, /* FIXME: Implement QCAP_createInfinitePinTeeFilter */ + NULL + },{ + wSmartTeeFilter, + &CLSID_SmartTee, + QCAP_createSmartTeeFilter, + NULL + },{ + wAudioInMixerProp, + &CLSID_AudioInputMixerProperties, + NULL, /* FIXME: Implement QCAP_createAudioInputMixerPropertyPage */ + NULL + } +}; + +int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); + +/*********************************************************************** + * Dll EntryPoint (QCAP.@) + */ +BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpv) +{ + return STRMBASE_DllMain(hInstDLL,fdwReason,lpv); +} + +/*********************************************************************** + * DllGetClassObject + */ +HRESULT WINAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) +{ + return STRMBASE_DllGetClassObject( rclsid, riid, ppv ); +} + +/*********************************************************************** + * DllRegisterServer (QCAP.@) + */ +HRESULT WINAPI DllRegisterServer(void) +{ + TRACE("()\n"); + return AMovieDllRegisterServer2(TRUE); +} + +/*********************************************************************** + * DllUnregisterServer (QCAP.@) + */ +HRESULT WINAPI DllUnregisterServer(void) +{ + TRACE("\n"); + return AMovieDllRegisterServer2(FALSE); +} + +/*********************************************************************** + * DllCanUnloadNow (QCAP.@) + */ +HRESULT WINAPI DllCanUnloadNow(void) +{ + TRACE("\n"); + + if (STRMBASE_DllCanUnloadNow() == S_OK && objects_ref == 0) + return S_OK; + return S_FALSE; +} + +DWORD ObjectRefCount(BOOL increment) +{ + if (increment) + return InterlockedIncrement(&objects_ref); + return InterlockedDecrement(&objects_ref); +} diff --git a/dll/directx/wine/qcap/qcap_main.h b/dll/directx/wine/qcap/qcap_main.h new file mode 100644 index 00000000000..69d94bfff69 --- /dev/null +++ b/dll/directx/wine/qcap/qcap_main.h @@ -0,0 +1,62 @@ +/* + * Qcap main header file + * + * Copyright (C) 2005 Rolf Kalbermatter + * + * 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 + */ +#ifndef _QCAP_MAIN_H_DEFINED +#define _QCAP_MAIN_H_DEFINED + +#include "wine/strmbase.h" + +extern DWORD ObjectRefCount(BOOL increment) DECLSPEC_HIDDEN; + +extern IUnknown * WINAPI QCAP_createAudioCaptureFilter(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createAVICompressor(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createVFWCaptureFilter(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createVFWCaptureFilterPropertyPage(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createAVICompressor(IUnknown*,HRESULT*) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createAVIMux(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createAVIMuxPropertyPage(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createAVIMuxPropertyPage1(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createFileWriter(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createCaptureGraphBuilder2(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createInfinitePinTeeFilter(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createSmartTeeFilter(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; +extern IUnknown * WINAPI QCAP_createAudioInputMixerPropertyPage(IUnknown *pUnkOuter, HRESULT *phr) DECLSPEC_HIDDEN; + +void dump_AM_MEDIA_TYPE(const AM_MEDIA_TYPE * pmt) DECLSPEC_HIDDEN; + +enum YUV_Format { + /* Last 2 numbers give the skip info, the smaller they are the better + * Planar: + * HSKIP : VSKIP */ + YUVP_421, /* 2 : 1 */ + YUVP_422, /* 2 : 2 */ + YUVP_441, /* 4 : 1 */ + YUVP_444, /* 4 : 4 */ + ENDPLANAR, /* No format, just last planar item so we can check on it */ + + /* Non-planar */ + YUYV, /* Order: YUYV (Guess why it's named like that) */ + UYVY, /* Order: UYVY (Looks like someone got bored and swapped the Y's) */ + UYYVYY, /* YUV411 linux style, perhaps YUV420 is YYUYYV? */ +}; + +void YUV_Init(void) DECLSPEC_HIDDEN; +void YUV_To_RGB24(enum YUV_Format format, unsigned char *target, const unsigned char *source, int width, int height) DECLSPEC_HIDDEN; + +#endif /* _QCAP_MAIN_H_DEFINED */ diff --git a/dll/directx/wine/qcap/smartteefilter.c b/dll/directx/wine/qcap/smartteefilter.c new file mode 100644 index 00000000000..27aee4705ac --- /dev/null +++ b/dll/directx/wine/qcap/smartteefilter.c @@ -0,0 +1,697 @@ +/* + * Implementation of the SmartTee filter + * + * Copyright 2015 Damjan Jovanovic + * + * 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 + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "wtypes.h" +#include "wingdi.h" +#include "winuser.h" +#include "dshow.h" + +#include "qcap_main.h" + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(qcap); + +typedef struct { + IUnknown IUnknown_iface; + IUnknown *outerUnknown; + BaseFilter filter; + BaseInputPin *input; + BaseOutputPin *capture; + BaseOutputPin *preview; +} SmartTeeFilter; + +static inline SmartTeeFilter *impl_from_IUnknown(IUnknown *iface) +{ + return CONTAINING_RECORD(iface, SmartTeeFilter, IUnknown_iface); +} + +static inline SmartTeeFilter *impl_from_BaseFilter(BaseFilter *filter) +{ + return CONTAINING_RECORD(filter, SmartTeeFilter, filter); +} + +static inline SmartTeeFilter *impl_from_IBaseFilter(IBaseFilter *iface) +{ + BaseFilter *filter = CONTAINING_RECORD(iface, BaseFilter, IBaseFilter_iface); + return impl_from_BaseFilter(filter); +} + +static inline SmartTeeFilter *impl_from_BasePin(BasePin *pin) +{ + return impl_from_IBaseFilter(pin->pinInfo.pFilter); +} + +static inline SmartTeeFilter *impl_from_IPin(IPin *iface) +{ + BasePin *bp = CONTAINING_RECORD(iface, BasePin, IPin_iface); + return impl_from_IBaseFilter(bp->pinInfo.pFilter); +} + +static HRESULT WINAPI Unknown_QueryInterface(IUnknown *iface, REFIID riid, void **ppv) +{ + SmartTeeFilter *This = impl_from_IUnknown(iface); + if (IsEqualIID(riid, &IID_IUnknown)) { + TRACE("(%p)->(IID_IUnknown, %p)\n", This, ppv); + *ppv = &This->IUnknown_iface; + } else if (IsEqualIID(riid, &IID_IPersist)) { + TRACE("(%p)->(IID_IPersist, %p)\n", This, ppv); + *ppv = &This->filter.IBaseFilter_iface; + } else if (IsEqualIID(riid, &IID_IMediaFilter)) { + TRACE("(%p)->(IID_IMediaFilter, %p)\n", This, ppv); + *ppv = &This->filter.IBaseFilter_iface; + } else if (IsEqualIID(riid, &IID_IBaseFilter)) { + TRACE("(%p)->(IID_IBaseFilter, %p)\n", This, ppv); + *ppv = &This->filter.IBaseFilter_iface; + } else { + FIXME("(%p): no interface for %s\n", This, debugstr_guid(riid)); + *ppv = NULL; + return E_NOINTERFACE; + } + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI Unknown_AddRef(IUnknown *iface) +{ + SmartTeeFilter *This = impl_from_IUnknown(iface); + return BaseFilterImpl_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI Unknown_Release(IUnknown *iface) +{ + SmartTeeFilter *This = impl_from_IUnknown(iface); + ULONG ref = BaseFilterImpl_Release(&This->filter.IBaseFilter_iface); + + TRACE("(%p)->() ref=%d\n", This, ref); + + if (!ref) { + if(This->input) + BaseInputPinImpl_Release(&This->input->pin.IPin_iface); + if(This->capture) + BaseOutputPinImpl_Release(&This->capture->pin.IPin_iface); + if(This->preview) + BaseOutputPinImpl_Release(&This->preview->pin.IPin_iface); + CoTaskMemFree(This); + } + return ref; +} + +static const IUnknownVtbl UnknownVtbl = { + Unknown_QueryInterface, + Unknown_AddRef, + Unknown_Release +}; + +static HRESULT WINAPI SmartTeeFilter_QueryInterface(IBaseFilter *iface, REFIID riid, void **ppv) +{ + SmartTeeFilter *This = impl_from_IBaseFilter(iface); + return IUnknown_QueryInterface(This->outerUnknown, riid, ppv); +} + +static ULONG WINAPI SmartTeeFilter_AddRef(IBaseFilter *iface) +{ + SmartTeeFilter *This = impl_from_IBaseFilter(iface); + return IUnknown_AddRef(This->outerUnknown); +} + +static ULONG WINAPI SmartTeeFilter_Release(IBaseFilter *iface) +{ + SmartTeeFilter *This = impl_from_IBaseFilter(iface); + return IUnknown_Release(This->outerUnknown); +} + +static HRESULT WINAPI SmartTeeFilter_Stop(IBaseFilter *iface) +{ + SmartTeeFilter *This = impl_from_IBaseFilter(iface); + TRACE("(%p)\n", This); + EnterCriticalSection(&This->filter.csFilter); + This->filter.state = State_Stopped; + LeaveCriticalSection(&This->filter.csFilter); + return S_OK; +} + +static HRESULT WINAPI SmartTeeFilter_Pause(IBaseFilter *iface) +{ + SmartTeeFilter *This = impl_from_IBaseFilter(iface); + FIXME("(%p): stub\n", This); + return E_NOTIMPL; +} + +static HRESULT WINAPI SmartTeeFilter_Run(IBaseFilter *iface, REFERENCE_TIME tStart) +{ + SmartTeeFilter *This = impl_from_IBaseFilter(iface); + HRESULT hr = S_OK; + TRACE("(%p, %s)\n", This, wine_dbgstr_longlong(tStart)); + EnterCriticalSection(&This->filter.csFilter); + if(This->filter.state != State_Running) { + /* We share an allocator among all pins, an allocator can only get committed + * once, state transitions occur in upstream order, and only output pins + * commit allocators, so let the filter attached to the input pin worry about it. */ + if (This->input->pin.pConnectedTo) + This->filter.state = State_Running; + else + hr = VFW_E_NOT_CONNECTED; + } + LeaveCriticalSection(&This->filter.csFilter); + return hr; +} + +static HRESULT WINAPI SmartTeeFilter_FindPin(IBaseFilter *iface, LPCWSTR Id, IPin **ppPin) +{ + SmartTeeFilter *This = impl_from_IBaseFilter(iface); + TRACE("(%p)->(%s, %p)\n", This, debugstr_w(Id), ppPin); + if (lstrcmpW(Id, This->input->pin.pinInfo.achName) == 0) { + *ppPin = &This->input->pin.IPin_iface; + IPin_AddRef(*ppPin); + return S_OK; + } else if (lstrcmpW(Id, This->capture->pin.pinInfo.achName) == 0) { + *ppPin = &This->capture->pin.IPin_iface; + IPin_AddRef(*ppPin); + return S_OK; + } else if (lstrcmpW(Id, This->preview->pin.pinInfo.achName) == 0) { + *ppPin = &This->preview->pin.IPin_iface; + IPin_AddRef(*ppPin); + return S_OK; + } + return VFW_E_NOT_FOUND; +} + +static const IBaseFilterVtbl SmartTeeFilterVtbl = { + SmartTeeFilter_QueryInterface, + SmartTeeFilter_AddRef, + SmartTeeFilter_Release, + BaseFilterImpl_GetClassID, + SmartTeeFilter_Stop, + SmartTeeFilter_Pause, + SmartTeeFilter_Run, + BaseFilterImpl_GetState, + BaseFilterImpl_SetSyncSource, + BaseFilterImpl_GetSyncSource, + BaseFilterImpl_EnumPins, + SmartTeeFilter_FindPin, + BaseFilterImpl_QueryFilterInfo, + BaseFilterImpl_JoinFilterGraph, + BaseFilterImpl_QueryVendorInfo +}; + +static IPin* WINAPI SmartTeeFilter_GetPin(BaseFilter *iface, int pos) +{ + SmartTeeFilter *This = impl_from_BaseFilter(iface); + IPin *ret; + + TRACE("(%p)->(%d)\n", This, pos); + + switch(pos) { + case 0: + ret = &This->input->pin.IPin_iface; + break; + case 1: + ret = &This->capture->pin.IPin_iface; + break; + case 2: + ret = &This->preview->pin.IPin_iface; + break; + default: + TRACE("No pin %d\n", pos); + return NULL; + } + + IPin_AddRef(ret); + return ret; +} + +static LONG WINAPI SmartTeeFilter_GetPinCount(BaseFilter *iface) +{ + return 3; +} +static const BaseFilterFuncTable SmartTeeFilterFuncs = { + SmartTeeFilter_GetPin, + SmartTeeFilter_GetPinCount +}; + +static ULONG WINAPI SmartTeeFilterInput_AddRef(IPin *iface) +{ + SmartTeeFilter *This = impl_from_IPin(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI SmartTeeFilterInput_Release(IPin *iface) +{ + SmartTeeFilter *This = impl_from_IPin(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + + +static const IPinVtbl SmartTeeFilterInputVtbl = { + BaseInputPinImpl_QueryInterface, + SmartTeeFilterInput_AddRef, + SmartTeeFilterInput_Release, + BaseInputPinImpl_Connect, + BaseInputPinImpl_ReceiveConnection, + BasePinImpl_Disconnect, + BasePinImpl_ConnectedTo, + BasePinImpl_ConnectionMediaType, + BasePinImpl_QueryPinInfo, + BasePinImpl_QueryDirection, + BasePinImpl_QueryId, + BasePinImpl_QueryAccept, + BasePinImpl_EnumMediaTypes, + BasePinImpl_QueryInternalConnections, + BaseInputPinImpl_EndOfStream, + BaseInputPinImpl_BeginFlush, + BaseInputPinImpl_EndFlush, + BaseInputPinImpl_NewSegment +}; + +static HRESULT WINAPI SmartTeeFilterInput_CheckMediaType(BasePin *base, const AM_MEDIA_TYPE *pmt) +{ + SmartTeeFilter *This = impl_from_BasePin(base); + TRACE("(%p, AM_MEDIA_TYPE(%p))\n", This, pmt); + dump_AM_MEDIA_TYPE(pmt); + if (!pmt) + return VFW_E_TYPE_NOT_ACCEPTED; + /* We'll take any media type, but the output pins will later + * struggle to connect downstream. */ + return S_OK; +} + +static LONG WINAPI SmartTeeFilterInput_GetMediaTypeVersion(BasePin *base) +{ + return 0; +} + +static HRESULT WINAPI SmartTeeFilterInput_GetMediaType(BasePin *base, int iPosition, AM_MEDIA_TYPE *amt) +{ + SmartTeeFilter *This = impl_from_BasePin(base); + HRESULT hr; + TRACE("(%p)->(%d, %p)\n", This, iPosition, amt); + if (iPosition) + return S_FALSE; + EnterCriticalSection(&This->filter.csFilter); + if (This->input->pin.pConnectedTo) { + CopyMediaType(amt, &This->input->pin.mtCurrent); + hr = S_OK; + } else + hr = S_FALSE; + LeaveCriticalSection(&This->filter.csFilter); + return hr; +} + +static HRESULT copy_sample(IMediaSample *inputSample, IMemAllocator *allocator, IMediaSample **pOutputSample) +{ + REFERENCE_TIME startTime, endTime; + BOOL haveStartTime = TRUE, haveEndTime = TRUE; + IMediaSample *outputSample = NULL; + BYTE *ptrIn, *ptrOut; + AM_MEDIA_TYPE *mediaType = NULL; + HRESULT hr; + + hr = IMediaSample_GetTime(inputSample, &startTime, &endTime); + if (hr == S_OK) + ; + else if (hr == VFW_S_NO_STOP_TIME) + haveEndTime = FALSE; + else if (hr == VFW_E_SAMPLE_TIME_NOT_SET) + haveStartTime = haveEndTime = FALSE; + else + goto end; + + hr = IMemAllocator_GetBuffer(allocator, &outputSample, + haveStartTime ? &startTime : NULL, haveEndTime ? &endTime : NULL, 0); + if (FAILED(hr)) goto end; + if (IMediaSample_GetSize(outputSample) < IMediaSample_GetActualDataLength(inputSample)) { + ERR("insufficient space in sample\n"); + hr = VFW_E_BUFFER_OVERFLOW; + goto end; + } + + hr = IMediaSample_SetTime(outputSample, haveStartTime ? &startTime : NULL, haveEndTime ? &endTime : NULL); + if (FAILED(hr)) goto end; + + hr = IMediaSample_GetPointer(inputSample, &ptrIn); + if (FAILED(hr)) goto end; + hr = IMediaSample_GetPointer(outputSample, &ptrOut); + if (FAILED(hr)) goto end; + memcpy(ptrOut, ptrIn, IMediaSample_GetActualDataLength(inputSample)); + IMediaSample_SetActualDataLength(outputSample, IMediaSample_GetActualDataLength(inputSample)); + + hr = IMediaSample_SetDiscontinuity(outputSample, IMediaSample_IsDiscontinuity(inputSample) == S_OK); + if (FAILED(hr)) goto end; + + haveStartTime = haveEndTime = TRUE; + hr = IMediaSample_GetMediaTime(inputSample, &startTime, &endTime); + if (hr == S_OK) + ; + else if (hr == VFW_S_NO_STOP_TIME) + haveEndTime = FALSE; + else if (hr == VFW_E_MEDIA_TIME_NOT_SET) + haveStartTime = haveEndTime = FALSE; + else + goto end; + hr = IMediaSample_SetMediaTime(outputSample, haveStartTime ? &startTime : NULL, haveEndTime ? &endTime : NULL); + if (FAILED(hr)) goto end; + + hr = IMediaSample_GetMediaType(inputSample, &mediaType); + if (FAILED(hr)) goto end; + if (hr == S_OK) { + hr = IMediaSample_SetMediaType(outputSample, mediaType); + if (FAILED(hr)) goto end; + } + + hr = IMediaSample_SetPreroll(outputSample, IMediaSample_IsPreroll(inputSample) == S_OK); + if (FAILED(hr)) goto end; + + hr = IMediaSample_SetSyncPoint(outputSample, IMediaSample_IsSyncPoint(inputSample) == S_OK); + if (FAILED(hr)) goto end; + +end: + if (mediaType) + DeleteMediaType(mediaType); + if (FAILED(hr) && outputSample) { + IMediaSample_Release(outputSample); + outputSample = NULL; + } + *pOutputSample = outputSample; + return hr; +} + +static HRESULT WINAPI SmartTeeFilterInput_Receive(BaseInputPin *base, IMediaSample *inputSample) +{ + SmartTeeFilter *This = impl_from_BasePin(&base->pin); + IMediaSample *captureSample = NULL; + IMediaSample *previewSample = NULL; + HRESULT hrCapture = VFW_E_NOT_CONNECTED, hrPreview = VFW_E_NOT_CONNECTED; + + TRACE("(%p)->(%p)\n", This, inputSample); + + /* Modifying the image coming out of one pin doesn't modify the image + * coming out of the other. MSDN claims the filter doesn't copy, + * but unless it somehow uses copy-on-write, I just don't see how + * that's possible. */ + + /* FIXME: we should ideally do each of these in a separate thread */ + EnterCriticalSection(&This->filter.csFilter); + if (This->capture->pin.pConnectedTo) + hrCapture = copy_sample(inputSample, This->capture->pAllocator, &captureSample); + LeaveCriticalSection(&This->filter.csFilter); + if (SUCCEEDED(hrCapture)) + hrCapture = BaseOutputPinImpl_Deliver(This->capture, captureSample); + if (captureSample) + IMediaSample_Release(captureSample); + + EnterCriticalSection(&This->filter.csFilter); + if (This->preview->pin.pConnectedTo) + hrPreview = copy_sample(inputSample, This->preview->pAllocator, &previewSample); + LeaveCriticalSection(&This->filter.csFilter); + /* No timestamps on preview stream: */ + if (SUCCEEDED(hrPreview)) + hrPreview = IMediaSample_SetTime(previewSample, NULL, NULL); + if (SUCCEEDED(hrPreview)) + hrPreview = BaseOutputPinImpl_Deliver(This->preview, previewSample); + if (previewSample) + IMediaSample_Release(previewSample); + + /* FIXME: how to merge the HRESULTs from the 2 pins? */ + if (SUCCEEDED(hrCapture)) + return hrCapture; + else + return hrPreview; +} + +static const BaseInputPinFuncTable SmartTeeFilterInputFuncs = { + { + SmartTeeFilterInput_CheckMediaType, + NULL, + SmartTeeFilterInput_GetMediaTypeVersion, + SmartTeeFilterInput_GetMediaType + }, + SmartTeeFilterInput_Receive +}; + +static ULONG WINAPI SmartTeeFilterCapture_AddRef(IPin *iface) +{ + SmartTeeFilter *This = impl_from_IPin(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI SmartTeeFilterCapture_Release(IPin *iface) +{ + SmartTeeFilter *This = impl_from_IPin(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI SmartTeeFilterCapture_EnumMediaTypes(IPin *iface, IEnumMediaTypes **ppEnum) +{ + SmartTeeFilter *This = impl_from_IPin(iface); + HRESULT hr; + TRACE("(%p)->(%p)\n", This, ppEnum); + EnterCriticalSection(&This->filter.csFilter); + if (This->input->pin.pConnectedTo) { + hr = BasePinImpl_EnumMediaTypes(iface, ppEnum); + } else + hr = VFW_E_NOT_CONNECTED; + LeaveCriticalSection(&This->filter.csFilter); + return hr; +} + +static const IPinVtbl SmartTeeFilterCaptureVtbl = { + BaseOutputPinImpl_QueryInterface, + SmartTeeFilterCapture_AddRef, + SmartTeeFilterCapture_Release, + BaseOutputPinImpl_Connect, + BaseOutputPinImpl_ReceiveConnection, + BaseOutputPinImpl_Disconnect, + BasePinImpl_ConnectedTo, + BasePinImpl_ConnectionMediaType, + BasePinImpl_QueryPinInfo, + BasePinImpl_QueryDirection, + BasePinImpl_QueryId, + BasePinImpl_QueryAccept, + SmartTeeFilterCapture_EnumMediaTypes, + BasePinImpl_QueryInternalConnections, + BaseOutputPinImpl_EndOfStream, + BaseOutputPinImpl_BeginFlush, + BaseOutputPinImpl_EndFlush, + BasePinImpl_NewSegment +}; + +static LONG WINAPI SmartTeeFilterCapture_GetMediaTypeVersion(BasePin *base) +{ + SmartTeeFilter *This = impl_from_BasePin(base); + TRACE("(%p)\n", This); + return 0; +} + +static HRESULT WINAPI SmartTeeFilterCapture_GetMediaType(BasePin *base, int iPosition, AM_MEDIA_TYPE *amt) +{ + SmartTeeFilter *This = impl_from_BasePin(base); + TRACE("(%p, %d, %p)\n", This, iPosition, amt); + if (iPosition == 0) { + CopyMediaType(amt, &This->input->pin.mtCurrent); + return S_OK; + } else + return S_FALSE; +} + +static HRESULT WINAPI SmartTeeFilterCapture_DecideAllocator(BaseOutputPin *base, IMemInputPin *pPin, IMemAllocator **pAlloc) +{ + SmartTeeFilter *This = impl_from_BasePin(&base->pin); + TRACE("(%p, %p, %p)\n", This, pPin, pAlloc); + *pAlloc = This->input->pAllocator; + IMemAllocator_AddRef(This->input->pAllocator); + return IMemInputPin_NotifyAllocator(pPin, This->input->pAllocator, TRUE); +} + +static HRESULT WINAPI SmartTeeFilterCapture_BreakConnect(BaseOutputPin *base) +{ + SmartTeeFilter *This = impl_from_BasePin(&base->pin); + FIXME("(%p): stub\n", This); + return E_NOTIMPL; +} + +static const BaseOutputPinFuncTable SmartTeeFilterCaptureFuncs = { + { + NULL, + BaseOutputPinImpl_AttemptConnection, + SmartTeeFilterCapture_GetMediaTypeVersion, + SmartTeeFilterCapture_GetMediaType + }, + NULL, + SmartTeeFilterCapture_DecideAllocator, + SmartTeeFilterCapture_BreakConnect +}; + +static ULONG WINAPI SmartTeeFilterPreview_AddRef(IPin *iface) +{ + SmartTeeFilter *This = impl_from_IPin(iface); + return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface); +} + +static ULONG WINAPI SmartTeeFilterPreview_Release(IPin *iface) +{ + SmartTeeFilter *This = impl_from_IPin(iface); + return IBaseFilter_Release(&This->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI SmartTeeFilterPreview_EnumMediaTypes(IPin *iface, IEnumMediaTypes **ppEnum) +{ + SmartTeeFilter *This = impl_from_IPin(iface); + HRESULT hr; + TRACE("(%p)->(%p)\n", This, ppEnum); + EnterCriticalSection(&This->filter.csFilter); + if (This->input->pin.pConnectedTo) { + hr = BasePinImpl_EnumMediaTypes(iface, ppEnum); + } else + hr = VFW_E_NOT_CONNECTED; + LeaveCriticalSection(&This->filter.csFilter); + return hr; +} + +static const IPinVtbl SmartTeeFilterPreviewVtbl = { + BaseOutputPinImpl_QueryInterface, + SmartTeeFilterPreview_AddRef, + SmartTeeFilterPreview_Release, + BaseOutputPinImpl_Connect, + BaseOutputPinImpl_ReceiveConnection, + BaseOutputPinImpl_Disconnect, + BasePinImpl_ConnectedTo, + BasePinImpl_ConnectionMediaType, + BasePinImpl_QueryPinInfo, + BasePinImpl_QueryDirection, + BasePinImpl_QueryId, + BasePinImpl_QueryAccept, + SmartTeeFilterPreview_EnumMediaTypes, + BasePinImpl_QueryInternalConnections, + BaseOutputPinImpl_EndOfStream, + BaseOutputPinImpl_BeginFlush, + BaseOutputPinImpl_EndFlush, + BasePinImpl_NewSegment +}; + +static LONG WINAPI SmartTeeFilterPreview_GetMediaTypeVersion(BasePin *base) +{ + SmartTeeFilter *This = impl_from_BasePin(base); + TRACE("(%p)\n", This); + return 0; +} + +static HRESULT WINAPI SmartTeeFilterPreview_GetMediaType(BasePin *base, int iPosition, AM_MEDIA_TYPE *amt) +{ + SmartTeeFilter *This = impl_from_BasePin(base); + TRACE("(%p, %d, %p)\n", This, iPosition, amt); + if (iPosition == 0) { + CopyMediaType(amt, &This->input->pin.mtCurrent); + return S_OK; + } else + return S_FALSE; +} + +static HRESULT WINAPI SmartTeeFilterPreview_DecideAllocator(BaseOutputPin *base, IMemInputPin *pPin, IMemAllocator **pAlloc) +{ + SmartTeeFilter *This = impl_from_BasePin(&base->pin); + TRACE("(%p, %p, %p)\n", This, pPin, pAlloc); + *pAlloc = This->input->pAllocator; + IMemAllocator_AddRef(This->input->pAllocator); + return IMemInputPin_NotifyAllocator(pPin, This->input->pAllocator, TRUE); +} + +static HRESULT WINAPI SmartTeeFilterPreview_BreakConnect(BaseOutputPin *base) +{ + SmartTeeFilter *This = impl_from_BasePin(&base->pin); + FIXME("(%p): stub\n", This); + return E_NOTIMPL; +} + +static const BaseOutputPinFuncTable SmartTeeFilterPreviewFuncs = { + { + NULL, + BaseOutputPinImpl_AttemptConnection, + SmartTeeFilterPreview_GetMediaTypeVersion, + SmartTeeFilterPreview_GetMediaType + }, + NULL, + SmartTeeFilterPreview_DecideAllocator, + SmartTeeFilterPreview_BreakConnect +}; +IUnknown* WINAPI QCAP_createSmartTeeFilter(IUnknown *outer, HRESULT *phr) +{ + PIN_INFO inputPinInfo = {NULL, PINDIR_INPUT, {'I','n','p','u','t',0}}; + PIN_INFO capturePinInfo = {NULL, PINDIR_OUTPUT, {'C','a','p','t','u','r','e',0}}; + PIN_INFO previewPinInfo = {NULL, PINDIR_OUTPUT, {'P','r','e','v','i','e','w',0}}; + HRESULT hr; + SmartTeeFilter *This = NULL; + + TRACE("(%p, %p)\n", outer, phr); + + This = CoTaskMemAlloc(sizeof(*This)); + if (This == NULL) { + hr = E_OUTOFMEMORY; + goto end; + } + memset(This, 0, sizeof(*This)); + This->IUnknown_iface.lpVtbl = &UnknownVtbl; + if (outer) + This->outerUnknown = outer; + else + This->outerUnknown = &This->IUnknown_iface; + + BaseFilter_Init(&This->filter, &SmartTeeFilterVtbl, &CLSID_SmartTee, + (DWORD_PTR)(__FILE__ ": SmartTeeFilter.csFilter"), &SmartTeeFilterFuncs); + + inputPinInfo.pFilter = &This->filter.IBaseFilter_iface; + hr = BaseInputPin_Construct(&SmartTeeFilterInputVtbl, sizeof(BaseInputPin), &inputPinInfo, + &SmartTeeFilterInputFuncs, &This->filter.csFilter, NULL, (IPin**)&This->input); + if (FAILED(hr)) + goto end; + hr = CoCreateInstance(&CLSID_MemoryAllocator, NULL, CLSCTX_INPROC_SERVER, + &IID_IMemAllocator, (void**)&This->input->pAllocator); + if (FAILED(hr)) + goto end; + + capturePinInfo.pFilter = &This->filter.IBaseFilter_iface; + hr = BaseOutputPin_Construct(&SmartTeeFilterCaptureVtbl, sizeof(BaseOutputPin), &capturePinInfo, + &SmartTeeFilterCaptureFuncs, &This->filter.csFilter, (IPin**)&This->capture); + if (FAILED(hr)) + goto end; + + previewPinInfo.pFilter = &This->filter.IBaseFilter_iface; + hr = BaseOutputPin_Construct(&SmartTeeFilterPreviewVtbl, sizeof(BaseOutputPin), &previewPinInfo, + &SmartTeeFilterPreviewFuncs, &This->filter.csFilter, (IPin**)&This->preview); + +end: + *phr = hr; + if (SUCCEEDED(hr)) { + if (outer) + return &This->IUnknown_iface; + else + return (IUnknown*)&This->filter.IBaseFilter_iface; + } else { + if (This) + IBaseFilter_Release(&This->filter.IBaseFilter_iface); + return NULL; + } +} diff --git a/dll/directx/wine/qcap/v4l.c b/dll/directx/wine/qcap/v4l.c new file mode 100644 index 00000000000..b4b7b96c9e2 --- /dev/null +++ b/dll/directx/wine/qcap/v4l.c @@ -0,0 +1,978 @@ +/* + * DirectShow capture services (QCAP.DLL) + * + * Copyright 2005 Maarten Lankhorst + * + * This file contains the part of the vfw capture interface that + * does the actual Video4Linux(1/2) stuff required for capturing + * and setting/getting media format.. + * + * 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" +#include "wine/port.h" + +#define COBJMACROS + +#include +#include +#include +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_SYS_MMAN_H +#include +#endif +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_ASM_TYPES_H +#include +#endif +#ifdef HAVE_LIBV4L1_H +#include +#endif +#ifdef HAVE_LINUX_VIDEODEV_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "windef.h" +#include "winbase.h" +#include "wtypes.h" +#include "wingdi.h" +#include "winuser.h" +#include "dshow.h" +#include "vfwmsgs.h" +#include "amvideo.h" +#include "wine/debug.h" +#include "wine/library.h" + +#include "capture.h" +#include "qcap_main.h" + +WINE_DEFAULT_DEBUG_CHANNEL(qcap_v4l); + +#ifdef VIDIOCMCAPTURE + +static typeof(open) *video_open = open; +static typeof(close) *video_close = close; +static typeof(ioctl) *video_ioctl = ioctl; +static typeof(read) *video_read = read; +static typeof(mmap) *video_mmap = mmap; +static typeof(munmap) *video_munmap = munmap; + +static void video_init(void) +{ +#ifdef SONAME_LIBV4L1 + static void *video_lib; + + if (video_lib) + return; + video_lib = wine_dlopen(SONAME_LIBV4L1, RTLD_NOW, NULL, 0); + if (!video_lib) + return; + video_open = wine_dlsym(video_lib, "v4l1_open", NULL, 0); + video_close = wine_dlsym(video_lib, "v4l1_close", NULL, 0); + video_ioctl = wine_dlsym(video_lib, "v4l1_ioctl", NULL, 0); + video_read = wine_dlsym(video_lib, "v4l1_read", NULL, 0); + video_mmap = wine_dlsym(video_lib, "v4l1_mmap", NULL, 0); + video_munmap = wine_dlsym(video_lib, "v4l1_munmap", NULL, 0); +#endif +} + +typedef void (* Renderer)(const Capture *, LPBYTE bufferin, const BYTE *stream); + +struct _Capture +{ + UINT width, height, bitDepth, fps, outputwidth, outputheight; + BOOL swresize; + + CRITICAL_SECTION CritSect; + + IPin *pOut; + int fd, mmap; + BOOL iscommitted, stopped; + struct video_picture pict; + int dbrightness, dhue, dcolour, dcontrast; + + /* mmap (V4l1) */ + struct video_mmap *grab_buf; + struct video_mbuf gb_buffers; + unsigned char *pmap; + int buffers; + + /* read (V4l1) */ + int imagesize; + char * grab_data; + + int curframe; + + HANDLE thread; + Renderer renderer; +}; + +struct renderlist +{ + int depth; + const char* name; + Renderer renderer; +}; + +static void renderer_RGB(const Capture *capBox, LPBYTE bufferin, const BYTE *stream); +static void renderer_YUV(const Capture *capBox, LPBYTE bufferin, const BYTE *stream); + +static const struct renderlist renderlist_V4l[] = { + { 0, "NULL renderer", NULL }, + { 8, "Gray scales", NULL }, /* 1, Don't support */ + { 0, "High 240 cube (BT848)", NULL }, /* 2, Don't support */ + { 16, "16 bit RGB (565)", NULL }, /* 3, Don't support */ + { 24, "24 bit RGB values", renderer_RGB }, /* 4, Supported, */ + { 32, "32 bit RGB values", renderer_RGB }, /* 5, Supported */ + { 16, "15 bit RGB (555)", NULL }, /* 6, Don't support */ + { 16, "YUV 422 (Not P)", renderer_YUV }, /* 7, Supported */ + { 16, "YUYV (Not P)", renderer_YUV }, /* 8, Supported */ + { 16, "UYVY (Not P)", renderer_YUV }, /* 9, Supported */ + { 16, "YUV 420 (Not P)", NULL }, /* 10, Not supported, if I had to guess it's YYUYYV */ + { 12, "YUV 411 (Not P)", renderer_YUV }, /* 11, Supported */ + { 0, "Raw capturing (BT848)", NULL }, /* 12, Don't support */ + { 16, "YUV 422 (Planar)", renderer_YUV }, /* 13, Supported */ + { 12, "YUV 411 (Planar)", renderer_YUV }, /* 14, Supported */ + { 12, "YUV 420 (Planar)", renderer_YUV }, /* 15, Supported */ + { 10, "YUV 410 (Planar)", renderer_YUV }, /* 16, Supported */ + /* FIXME: add YUV420 support */ + { 0, NULL, NULL }, +}; + +static const int fallback_V4l[] = { 4, 5, 7, 8, 9, 13, 15, 14, 16, 11, -1 }; +/* Fallback: First try raw formats (Should try yuv first perhaps?), then yuv */ + +/* static const Capture defbox; */ + +static int xioctl(int fd, int request, void * arg) +{ + int r; + + do { + r = video_ioctl (fd, request, arg); + } while (-1 == r && EINTR == errno); + + return r; +} + +/* Prepare the capture buffers */ +static HRESULT V4l_Prepare(Capture *capBox) +{ + TRACE("%p: Preparing for %dx%d resolution\n", capBox, capBox->width, capBox->height); + + /* Try mmap */ + capBox->mmap = 0; + if (xioctl(capBox->fd, VIDIOCGMBUF, &capBox->gb_buffers) != -1 && + capBox->gb_buffers.frames) + { + capBox->buffers = capBox->gb_buffers.frames; + if (capBox->gb_buffers.frames > 1) + capBox->buffers = 1; + TRACE("%p: Using %d/%d buffers\n", capBox, + capBox->buffers, capBox->gb_buffers.frames); + + capBox->pmap = video_mmap( 0, capBox->gb_buffers.size, PROT_READ|PROT_WRITE, + MAP_SHARED, capBox->fd, 0 ); + if (capBox->pmap != MAP_FAILED) + { + int i; + + capBox->grab_buf = CoTaskMemAlloc(sizeof(struct video_mmap) * capBox->buffers); + if (!capBox->grab_buf) + { + video_munmap(capBox->pmap, capBox->gb_buffers.size); + return E_OUTOFMEMORY; + } + + /* Setup mmap capture buffers. */ + for (i = 0; i < capBox->buffers; i++) + { + capBox->grab_buf[i].format = capBox->pict.palette; + capBox->grab_buf[i].frame = i; + capBox->grab_buf[i].width = capBox->width; + capBox->grab_buf[i].height = capBox->height; + } + capBox->mmap = 1; + } + } + if (!capBox->mmap) + { + capBox->buffers = 1; + capBox->imagesize = renderlist_V4l[capBox->pict.palette].depth * + capBox->height * capBox->width / 8; + capBox->grab_data = CoTaskMemAlloc(capBox->imagesize); + if (!capBox->grab_data) + return E_OUTOFMEMORY; + } + TRACE("Using mmap: %d\n", capBox->mmap); + return S_OK; +} + +static void V4l_Unprepare(Capture *capBox) +{ + if (capBox->mmap) + { + for (capBox->curframe = 0; capBox->curframe < capBox->buffers; capBox->curframe++) + xioctl(capBox->fd, VIDIOCSYNC, &capBox->grab_buf[capBox->curframe]); + video_munmap(capBox->pmap, capBox->gb_buffers.size); + CoTaskMemFree(capBox->grab_buf); + } + else + CoTaskMemFree(capBox->grab_data); +} + +HRESULT qcap_driver_destroy(Capture *capBox) +{ + TRACE("%p\n", capBox); + + if( capBox->fd != -1 ) + video_close(capBox->fd); + capBox->CritSect.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&capBox->CritSect); + CoTaskMemFree(capBox); + return S_OK; +} + +HRESULT qcap_driver_set_format(Capture *capBox, AM_MEDIA_TYPE * mT) +{ + int newheight, newwidth; + struct video_window window; + VIDEOINFOHEADER *format; + + TRACE("%p\n", capBox); + + format = (VIDEOINFOHEADER *) mT->pbFormat; + if (format->bmiHeader.biBitCount != 24 || + format->bmiHeader.biCompression != BI_RGB) + { + FIXME("unsupported media type %d %d\n", format->bmiHeader.biBitCount, + format->bmiHeader.biCompression ); + return VFW_E_INVALIDMEDIATYPE; + } + + newwidth = format->bmiHeader.biWidth; + newheight = format->bmiHeader.biHeight; + + TRACE("%p -> (%p) - %d %d\n", capBox, mT, newwidth, newheight); + + if (capBox->height == newheight && capBox->width == newwidth) + return S_OK; + + if(-1 == xioctl(capBox->fd, VIDIOCGWIN, &window)) + { + ERR("ioctl(VIDIOCGWIN) failed (%d)\n", errno); + return E_FAIL; + } + window.width = newwidth; + window.height = newheight; + if (xioctl(capBox->fd, VIDIOCSWIN, &window) == -1) + { + TRACE("using software resize: %dx%d -> %dx%d\n", + window.width, window.height, capBox->width, capBox->height); + capBox->swresize = TRUE; + } + else + { + capBox->height = window.height; + capBox->width = window.width; + capBox->swresize = FALSE; + } + capBox->outputwidth = window.width; + capBox->outputheight = window.height; + return S_OK; +} + +HRESULT qcap_driver_get_format(const Capture *capBox, AM_MEDIA_TYPE ** mT) +{ + VIDEOINFOHEADER *vi; + + mT[0] = CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)); + if (!mT[0]) + return E_OUTOFMEMORY; + vi = CoTaskMemAlloc(sizeof(VIDEOINFOHEADER)); + mT[0]->cbFormat = sizeof(VIDEOINFOHEADER); + if (!vi) + { + CoTaskMemFree(mT[0]); + mT[0] = NULL; + return E_OUTOFMEMORY; + } + mT[0]->majortype = MEDIATYPE_Video; + mT[0]->subtype = MEDIASUBTYPE_RGB24; + mT[0]->formattype = FORMAT_VideoInfo; + mT[0]->bFixedSizeSamples = TRUE; + mT[0]->bTemporalCompression = FALSE; + mT[0]->pUnk = NULL; + mT[0]->lSampleSize = capBox->outputwidth * capBox->outputheight * capBox->bitDepth / 8; + TRACE("Output format: %dx%d - %d bits = %u KB\n", capBox->outputwidth, + capBox->outputheight, capBox->bitDepth, mT[0]->lSampleSize/1024); + vi->rcSource.left = 0; vi->rcSource.top = 0; + vi->rcTarget.left = 0; vi->rcTarget.top = 0; + vi->rcSource.right = capBox->width; vi->rcSource.bottom = capBox->height; + vi->rcTarget.right = capBox->outputwidth; vi->rcTarget.bottom = capBox->outputheight; + vi->dwBitRate = capBox->fps * mT[0]->lSampleSize; + vi->dwBitErrorRate = 0; + vi->AvgTimePerFrame = (LONGLONG)10000000.0 / (LONGLONG)capBox->fps; + vi->bmiHeader.biSize = 40; + vi->bmiHeader.biWidth = capBox->outputwidth; + vi->bmiHeader.biHeight = capBox->outputheight; + vi->bmiHeader.biPlanes = 1; + vi->bmiHeader.biBitCount = 24; + vi->bmiHeader.biCompression = BI_RGB; + vi->bmiHeader.biSizeImage = mT[0]->lSampleSize; + vi->bmiHeader.biClrUsed = vi->bmiHeader.biClrImportant = 0; + vi->bmiHeader.biXPelsPerMeter = 100; + vi->bmiHeader.biYPelsPerMeter = 100; + mT[0]->pbFormat = (void *)vi; + dump_AM_MEDIA_TYPE(mT[0]); + return S_OK; +} + +HRESULT qcap_driver_get_prop_range( Capture *capBox, + VideoProcAmpProperty Property, LONG *pMin, LONG *pMax, + LONG *pSteppingDelta, LONG *pDefault, LONG *pCapsFlags ) +{ + TRACE("%p -> %d %p %p %p %p %p\n", capBox, Property, + pMin, pMax, pSteppingDelta, pDefault, pCapsFlags); + + switch (Property) + { + case VideoProcAmp_Brightness: + *pDefault = capBox->dbrightness; + break; + case VideoProcAmp_Contrast: + *pDefault = capBox->dcontrast; + break; + case VideoProcAmp_Hue: + *pDefault = capBox->dhue; + break; + case VideoProcAmp_Saturation: + *pDefault = capBox->dcolour; + break; + default: + FIXME("Not implemented %d\n", Property); + return E_NOTIMPL; + } + *pMin = 0; + *pMax = 65535; + *pSteppingDelta = 65536/256; + *pCapsFlags = VideoProcAmp_Flags_Manual; + return S_OK; +} + +HRESULT qcap_driver_get_prop( Capture *capBox, + VideoProcAmpProperty Property, LONG *lValue, LONG *Flags ) +{ + TRACE("%p -> %d %p %p\n", capBox, Property, lValue, Flags); + + switch (Property) + { + case VideoProcAmp_Brightness: + *lValue = capBox->pict.brightness; + break; + case VideoProcAmp_Contrast: + *lValue = capBox->pict.contrast; + break; + case VideoProcAmp_Hue: + *lValue = capBox->pict.hue; + break; + case VideoProcAmp_Saturation: + *lValue = capBox->pict.colour; + break; + default: + FIXME("Not implemented %d\n", Property); + return E_NOTIMPL; + } + *Flags = VideoProcAmp_Flags_Manual; + return S_OK; +} + +HRESULT qcap_driver_set_prop(Capture *capBox, VideoProcAmpProperty Property, + LONG lValue, LONG Flags) +{ + TRACE("%p -> %d %d %d\n", capBox, Property, lValue, Flags); + + switch (Property) + { + case VideoProcAmp_Brightness: + capBox->pict.brightness = lValue; + break; + case VideoProcAmp_Contrast: + capBox->pict.contrast = lValue; + break; + case VideoProcAmp_Hue: + capBox->pict.hue = lValue; + break; + case VideoProcAmp_Saturation: + capBox->pict.colour = lValue; + break; + default: + FIXME("Not implemented %d\n", Property); + return E_NOTIMPL; + } + + if (xioctl(capBox->fd, VIDIOCSPICT, &capBox->pict) == -1) + { + ERR("ioctl(VIDIOCSPICT) failed (%d)\n",errno); + return E_FAIL; + } + return S_OK; +} + +static void renderer_RGB(const Capture *capBox, LPBYTE bufferin, const BYTE *stream) +{ + int depth = renderlist_V4l[capBox->pict.palette].depth; + int size = capBox->height * capBox->width * depth / 8; + int pointer, offset; + + switch (depth) + { + case 24: + memcpy(bufferin, stream, size); + break; + case 32: + pointer = 0; + offset = 1; + while (pointer + offset <= size) + { + bufferin[pointer] = stream[pointer + offset]; + pointer++; + bufferin[pointer] = stream[pointer + offset]; + pointer++; + bufferin[pointer] = stream[pointer + offset]; + pointer++; + offset++; + } + break; + default: + ERR("Unknown bit depth %d\n", depth); + return; + } +} + +static void renderer_YUV(const Capture *capBox, LPBYTE bufferin, const BYTE *stream) +{ + enum YUV_Format format; + + switch (capBox->pict.palette) + { + case 7: /* YUV422 - same as YUYV */ + case 8: /* YUYV */ + format = YUYV; + break; + case 9: /* UYVY */ + format = UYVY; + break; + case 11: /* YUV411 */ + format = UYYVYY; + break; + case 13: /* YUV422P */ + format = YUVP_421; + break; + case 14: /* YUV411P */ + format = YUVP_441; + break; + case 15: /* YUV420P */ + format = YUVP_422; + break; + case 16: /* YUV410P */ + format = YUVP_444; + break; + default: + ERR("Unknown palette %d\n", capBox->pict.palette); + return; + } + YUV_To_RGB24(format, bufferin, stream, capBox->width, capBox->height); +} + +static void Resize(const Capture * capBox, LPBYTE output, const BYTE *input) +{ + /* the whole image needs to be reversed, + because the dibs are messed up in windows */ + if (!capBox->swresize) + { + int depth = capBox->bitDepth / 8; + int inoffset = 0, outoffset = capBox->height * capBox->width * depth; + int ow = capBox->width * depth; + while (outoffset > 0) + { + int x; + outoffset -= ow; + for (x = 0; x < ow; x++) + output[outoffset + x] = input[inoffset + x]; + inoffset += ow; + } + } + else + { + HDC dc_s, dc_d; + HBITMAP bmp_s, bmp_d; + int depth = capBox->bitDepth / 8; + int inoffset = 0, outoffset = (capBox->outputheight) * capBox->outputwidth * depth; + int ow = capBox->outputwidth * depth; + LPBYTE myarray; + + /* FIXME: Improve software resizing: add error checks and optimize */ + + myarray = CoTaskMemAlloc(capBox->outputwidth * capBox->outputheight * depth); + dc_s = CreateCompatibleDC(NULL); + dc_d = CreateCompatibleDC(NULL); + bmp_s = CreateBitmap(capBox->width, capBox->height, 1, capBox->bitDepth, input); + bmp_d = CreateBitmap(capBox->outputwidth, capBox->outputheight, 1, capBox->bitDepth, NULL); + SelectObject(dc_s, bmp_s); + SelectObject(dc_d, bmp_d); + StretchBlt(dc_d, 0, 0, capBox->outputwidth, capBox->outputheight, + dc_s, 0, 0, capBox->width, capBox->height, SRCCOPY); + GetBitmapBits(bmp_d, capBox->outputwidth * capBox->outputheight * depth, myarray); + while (outoffset > 0) + { + int i; + + outoffset -= ow; + for (i = 0; i < ow; i++) + output[outoffset + i] = myarray[inoffset + i]; + inoffset += ow; + } + CoTaskMemFree(myarray); + DeleteObject(dc_s); + DeleteObject(dc_d); + DeleteObject(bmp_s); + DeleteObject(bmp_d); + } +} + +static void V4l_GetFrame(Capture * capBox, unsigned char ** pInput) +{ + if (capBox->mmap) + { + if (xioctl(capBox->fd, VIDIOCSYNC, &capBox->grab_buf[capBox->curframe]) == -1) + WARN("Syncing ioctl failed: %d\n", errno); + + *pInput = capBox->pmap + capBox->gb_buffers.offsets[capBox->curframe]; + } + else + { + int retval; + while ((retval = video_read(capBox->fd, capBox->grab_data, capBox->imagesize)) == -1) + if (errno != EAGAIN) break; + if (retval == -1) + WARN("Error occurred while reading from device: %s\n", strerror(errno)); + *pInput = (unsigned char*) capBox->grab_data; + } +} + +static void V4l_FreeFrame(Capture * capBox) +{ + TRACE("\n"); + if (capBox->mmap) + { + if (xioctl(capBox->fd, VIDIOCMCAPTURE, &capBox->grab_buf[capBox->curframe]) == -1) + ERR("Freeing frame for capture failed: %s\n", strerror(errno)); + } + if (++capBox->curframe == capBox->buffers) + capBox->curframe = 0; +} + +static DWORD WINAPI ReadThread(LPVOID lParam) +{ + Capture * capBox = lParam; + HRESULT hr; + IMediaSample *pSample = NULL; + ULONG framecount = 0; + unsigned char *pTarget, *pInput, *pOutput; + + hr = V4l_Prepare(capBox); + if (FAILED(hr)) + { + ERR("Stop IFilterGraph: %x\n", hr); + capBox->thread = 0; + capBox->stopped = TRUE; + return 0; + } + + pOutput = CoTaskMemAlloc(capBox->width * capBox->height * capBox->bitDepth / 8); + capBox->curframe = 0; + do { + V4l_FreeFrame(capBox); + } while (capBox->curframe != 0); + + while (1) + { + EnterCriticalSection(&capBox->CritSect); + if (capBox->stopped) + break; + hr = BaseOutputPinImpl_GetDeliveryBuffer((BaseOutputPin *)capBox->pOut, &pSample, NULL, NULL, 0); + if (SUCCEEDED(hr)) + { + int len; + + if (!capBox->swresize) + len = capBox->height * capBox->width * capBox->bitDepth / 8; + else + len = capBox->outputheight * capBox->outputwidth * capBox->bitDepth / 8; + IMediaSample_SetActualDataLength(pSample, len); + + len = IMediaSample_GetActualDataLength(pSample); + TRACE("Data length: %d KB\n", len / 1024); + + IMediaSample_GetPointer(pSample, &pTarget); + /* FIXME: Check return values.. */ + V4l_GetFrame(capBox, &pInput); + capBox->renderer(capBox, pOutput, pInput); + Resize(capBox, pTarget, pOutput); + hr = BaseOutputPinImpl_Deliver((BaseOutputPin *)capBox->pOut, pSample); + TRACE("%p -> Frame %u: %x\n", capBox, ++framecount, hr); + IMediaSample_Release(pSample); + V4l_FreeFrame(capBox); + } + if (FAILED(hr) && hr != VFW_E_NOT_CONNECTED) + { + TRACE("Return %x, stop IFilterGraph\n", hr); + V4l_Unprepare(capBox); + capBox->thread = 0; + capBox->stopped = TRUE; + break; + } + LeaveCriticalSection(&capBox->CritSect); + } + + LeaveCriticalSection(&capBox->CritSect); + CoTaskMemFree(pOutput); + return 0; +} + +HRESULT qcap_driver_run(Capture *capBox, FILTER_STATE *state) +{ + HANDLE thread; + HRESULT hr; + + TRACE("%p -> (%p)\n", capBox, state); + + if (*state == State_Running) return S_OK; + + EnterCriticalSection(&capBox->CritSect); + + capBox->stopped = FALSE; + + if (*state == State_Stopped) + { + *state = State_Running; + if (!capBox->iscommitted) + { + ALLOCATOR_PROPERTIES ap, actual; + BaseOutputPin *out; + + capBox->iscommitted = TRUE; + + ap.cBuffers = 3; + if (!capBox->swresize) + ap.cbBuffer = capBox->width * capBox->height; + else + ap.cbBuffer = capBox->outputwidth * capBox->outputheight; + ap.cbBuffer = (ap.cbBuffer * capBox->bitDepth) / 8; + ap.cbAlign = 1; + ap.cbPrefix = 0; + + out = (BaseOutputPin *)capBox->pOut; + + hr = IMemAllocator_SetProperties(out->pAllocator, &ap, &actual); + + if (SUCCEEDED(hr)) + hr = IMemAllocator_Commit(out->pAllocator); + + TRACE("Committing allocator: %x\n", hr); + } + + thread = CreateThread(NULL, 0, ReadThread, capBox, 0, NULL); + if (thread) + { + capBox->thread = thread; + SetThreadPriority(thread, THREAD_PRIORITY_LOWEST); + LeaveCriticalSection(&capBox->CritSect); + return S_OK; + } + ERR("Creating thread failed.. %u\n", GetLastError()); + LeaveCriticalSection(&capBox->CritSect); + return E_FAIL; + } + + ResumeThread(capBox->thread); + *state = State_Running; + LeaveCriticalSection(&capBox->CritSect); + return S_OK; +} + +HRESULT qcap_driver_pause(Capture *capBox, FILTER_STATE *state) +{ + TRACE("%p -> (%p)\n", capBox, state); + + if (*state == State_Paused) + return S_OK; + if (*state == State_Stopped) + qcap_driver_run(capBox, state); + + EnterCriticalSection(&capBox->CritSect); + *state = State_Paused; + SuspendThread(capBox->thread); + LeaveCriticalSection(&capBox->CritSect); + + return S_OK; +} + +HRESULT qcap_driver_stop(Capture *capBox, FILTER_STATE *state) +{ + TRACE("%p -> (%p)\n", capBox, state); + + if (*state == State_Stopped) + return S_OK; + + EnterCriticalSection(&capBox->CritSect); + + if (capBox->thread) + { + if (*state == State_Paused) + ResumeThread(capBox->thread); + capBox->stopped = TRUE; + capBox->thread = 0; + if (capBox->iscommitted) + { + BaseOutputPin *out; + HRESULT hr; + + capBox->iscommitted = FALSE; + + out = (BaseOutputPin*)capBox->pOut; + + hr = IMemAllocator_Decommit(out->pAllocator); + + if (hr != S_OK && hr != VFW_E_NOT_COMMITTED) + WARN("Decommitting allocator: %x\n", hr); + } + V4l_Unprepare(capBox); + } + + *state = State_Stopped; + LeaveCriticalSection(&capBox->CritSect); + return S_OK; +} + +Capture * qcap_driver_init( IPin *pOut, USHORT card ) +{ + Capture * capBox = NULL; + char device[20]; + struct video_capability capa; + struct video_picture pict; + struct video_window window; + + YUV_Init(); + video_init(); + + capBox = CoTaskMemAlloc(sizeof(Capture)); + if (!capBox) + goto error; + + /* capBox->vtbl = &defboxVtbl; */ + + InitializeCriticalSection( &capBox->CritSect ); + capBox->CritSect.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": Capture.CritSect"); + + sprintf(device, "/dev/video%i", card); + TRACE("opening %s\n", device); +#ifdef O_CLOEXEC + if ((capBox->fd = video_open(device, O_RDWR | O_NONBLOCK | O_CLOEXEC)) == -1 && errno == EINVAL) +#endif + capBox->fd = video_open(device, O_RDWR | O_NONBLOCK); + if (capBox->fd == -1) + { + WARN("open failed (%d)\n", errno); + goto error; + } + fcntl( capBox->fd, F_SETFD, FD_CLOEXEC ); /* in case O_CLOEXEC isn't supported */ + + memset(&capa, 0, sizeof(capa)); + + if (xioctl(capBox->fd, VIDIOCGCAP, &capa) == -1) + { + WARN("ioctl(VIDIOCGCAP) failed (%d)\n", errno); + goto error; + } + + if (!(capa.type & VID_TYPE_CAPTURE)) + { + WARN("not a video capture device\n"); + goto error; + } + + TRACE("%d inputs on %s\n", capa.channels, capa.name ); + + if (xioctl(capBox->fd, VIDIOCGPICT, &pict) == -1) + { + ERR("ioctl(VIDIOCGPICT) failed (%d)\n", errno ); + goto error; + } + + TRACE("depth %d palette %d (%s) hue %d color %d contrast %d\n", + pict.depth, pict.palette, renderlist_V4l[pict.palette].name, + pict.hue, pict.colour, pict.contrast ); + + capBox->dbrightness = pict.brightness; + capBox->dcolour = pict.colour; + capBox->dhue = pict.hue; + capBox->dcontrast = pict.contrast; + + if (!renderlist_V4l[pict.palette].renderer) + { + int palet = pict.palette, i; + + TRACE("No renderer available for %s, falling back to defaults\n", + renderlist_V4l[pict.palette].name); + capBox->renderer = NULL; + for (i = 0; fallback_V4l[i] >=0 ; i++) + { + int n = fallback_V4l[i]; + + if (renderlist_V4l[n].renderer == NULL) + continue; + + pict.depth = renderlist_V4l[n].depth; + pict.palette = n; + if (xioctl(capBox->fd, VIDIOCSPICT, &pict) == -1) + { + TRACE("Could not render with %s (%d)\n", + renderlist_V4l[n].name, n); + continue; + } + TRACE("using renderer %s (%d)\n", + renderlist_V4l[n].name, n); + capBox->renderer = renderlist_V4l[n].renderer; + break; + } + + if (!capBox->renderer) + { + ERR("video format %s isn't available\n", + renderlist_V4l[palet].name); + goto error; + } + } + else + { + TRACE("Using the suggested format\n"); + capBox->renderer = renderlist_V4l[pict.palette].renderer; + } + memcpy(&capBox->pict, &pict, sizeof(struct video_picture)); + + memset(&window, 0, sizeof(window)); + if (xioctl(capBox->fd, VIDIOCGWIN, &window) == -1) + { + WARN("VIDIOCGWIN failed (%d)\n", errno); + goto error; + } + + capBox->height = capBox->outputheight = window.height; + capBox->width = capBox->outputwidth = window.width; + capBox->swresize = FALSE; + capBox->bitDepth = 24; + capBox->pOut = pOut; + capBox->fps = 3; + capBox->stopped = FALSE; + capBox->curframe = 0; + capBox->iscommitted = FALSE; + + TRACE("format: %d bits - %d x %d\n", capBox->bitDepth, capBox->width, capBox->height); + + return capBox; + +error: + if (capBox) + qcap_driver_destroy( capBox ); + + return NULL; +} + +#else + +Capture * qcap_driver_init( IPin *pOut, USHORT card ) +{ + const char msg[] = + "The v4l headers were not available at compile time,\n" + "so video capture support is not available.\n"; + MESSAGE(msg); + return NULL; +} + +#define FAIL_WITH_ERR \ + ERR("v4l absent: shouldn't be called\n"); \ + return E_NOTIMPL + +HRESULT qcap_driver_destroy(Capture *capBox) +{ + FAIL_WITH_ERR; +} + +HRESULT qcap_driver_set_format(Capture *capBox, AM_MEDIA_TYPE * mT) +{ + FAIL_WITH_ERR; +} + +HRESULT qcap_driver_get_format(const Capture *capBox, AM_MEDIA_TYPE ** mT) +{ + FAIL_WITH_ERR; +} + +HRESULT qcap_driver_get_prop_range( Capture *capBox, + VideoProcAmpProperty Property, LONG *pMin, LONG *pMax, + LONG *pSteppingDelta, LONG *pDefault, LONG *pCapsFlags ) +{ + FAIL_WITH_ERR; +} + +HRESULT qcap_driver_get_prop(Capture *capBox, + VideoProcAmpProperty Property, LONG *lValue, LONG *Flags) +{ + FAIL_WITH_ERR; +} + +HRESULT qcap_driver_set_prop(Capture *capBox, VideoProcAmpProperty Property, + LONG lValue, LONG Flags) +{ + FAIL_WITH_ERR; +} + +HRESULT qcap_driver_run(Capture *capBox, FILTER_STATE *state) +{ + FAIL_WITH_ERR; +} + +HRESULT qcap_driver_pause(Capture *capBox, FILTER_STATE *state) +{ + FAIL_WITH_ERR; +} + +HRESULT qcap_driver_stop(Capture *capBox, FILTER_STATE *state) +{ + FAIL_WITH_ERR; +} + +#endif /* defined(VIDIOCMCAPTURE) */ diff --git a/dll/directx/wine/qcap/version.rc b/dll/directx/wine/qcap/version.rc new file mode 100644 index 00000000000..ca7afb13fad --- /dev/null +++ b/dll/directx/wine/qcap/version.rc @@ -0,0 +1,26 @@ +/* + * Copyright 2004 Ivan Leo Puoti + * + * 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 WINE_FILEDESCRIPTION_STR "Wine qcap dll" +#define WINE_FILENAME_STR "qcap.dll" +#define WINE_FILEVERSION 6,5,1,900 +#define WINE_FILEVERSION_STR "6.5.1.900" +#define WINE_PRODUCTVERSION 6,5,1,900 +#define WINE_PRODUCTVERSION_STR "6.5" + +#include "wine/wine_common_ver.rc" diff --git a/dll/directx/wine/qcap/vfwcapture.c b/dll/directx/wine/qcap/vfwcapture.c new file mode 100644 index 00000000000..46ae6c0b7ab --- /dev/null +++ b/dll/directx/wine/qcap/vfwcapture.c @@ -0,0 +1,834 @@ +/* Video For Windows Steering structure + * + * Copyright 2005 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 "config.h" +#include + +#include "windef.h" +#include "winbase.h" +#include "wtypes.h" +#include "wingdi.h" +#include "winuser.h" +#include "dshow.h" + +#include "qcap_main.h" +#include "wine/debug.h" + +#include "capture.h" +#include "uuids.h" +#include "vfwmsgs.h" +#include "amvideo.h" +#include "strmif.h" +#include "ddraw.h" +#include "ocidl.h" +#include "oleauto.h" + +WINE_DEFAULT_DEBUG_CHANNEL(qcap); + +static const IBaseFilterVtbl VfwCapture_Vtbl; +static const IAMStreamConfigVtbl IAMStreamConfig_VTable; +static const IAMVideoProcAmpVtbl IAMVideoProcAmp_VTable; +static const IPersistPropertyBagVtbl IPersistPropertyBag_VTable; +static const IPinVtbl VfwPin_Vtbl; + +static HRESULT VfwPin_Construct( IBaseFilter *, LPCRITICAL_SECTION, IPin ** ); + +typedef struct VfwCapture +{ + IUnknown IUnknown_inner; + BaseFilter filter; + IAMStreamConfig IAMStreamConfig_iface; + IAMVideoProcAmp IAMVideoProcAmp_iface; + IPersistPropertyBag IPersistPropertyBag_iface; + IUnknown *outer_unk; + BOOL init; + Capture *driver_info; + + IPin * pOutputPin; +} VfwCapture; + +static inline VfwCapture *impl_from_IUnknown(IUnknown *iface) +{ + return CONTAINING_RECORD(iface, VfwCapture, IUnknown_inner); +} + +static inline VfwCapture *impl_from_BaseFilter(BaseFilter *iface) +{ + return CONTAINING_RECORD(iface, VfwCapture, filter); +} + +static inline VfwCapture *impl_from_IBaseFilter(IBaseFilter *iface) +{ + return CONTAINING_RECORD(iface, VfwCapture, filter.IBaseFilter_iface); +} + +static inline VfwCapture *impl_from_IAMStreamConfig(IAMStreamConfig *iface) +{ + return CONTAINING_RECORD(iface, VfwCapture, IAMStreamConfig_iface); +} + +static inline VfwCapture *impl_from_IAMVideoProcAmp(IAMVideoProcAmp *iface) +{ + return CONTAINING_RECORD(iface, VfwCapture, IAMVideoProcAmp_iface); +} + +static inline VfwCapture *impl_from_IPersistPropertyBag(IPersistPropertyBag *iface) +{ + return CONTAINING_RECORD(iface, VfwCapture, IPersistPropertyBag_iface); +} + +/* VfwPin implementation */ +typedef struct VfwPinImpl +{ + BaseOutputPin pin; + IKsPropertySet IKsPropertySet_iface; + VfwCapture *parent; +} VfwPinImpl; + + +/* VfwCapture inner IUnknown */ +static HRESULT WINAPI unknown_inner_QueryInterface(IUnknown *iface, REFIID riid, void **ret_iface) +{ + VfwCapture *This = impl_from_IUnknown(iface); + + TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), ret_iface); + + *ret_iface = NULL; + + if (IsEqualIID(riid, &IID_IUnknown)) + *ret_iface = &This->IUnknown_inner; + else if (IsEqualIID(riid, &IID_IPersist) || IsEqualIID(riid, &IID_IMediaFilter) || + IsEqualIID(riid, &IID_IBaseFilter)) + *ret_iface = &This->filter.IBaseFilter_iface; + else if (IsEqualIID(riid, &IID_IPersistPropertyBag)) + *ret_iface = &This->IPersistPropertyBag_iface; + else if (IsEqualIID(riid, &IID_IAMFilterMiscFlags)) + FIXME("IAMFilterMiscFlags not supported yet!\n"); + else if (IsEqualIID(riid, &IID_ISpecifyPropertyPages)) + FIXME("ISpecifyPropertyPages not supported yet!\n"); + else if (IsEqualIID(riid, &IID_IAMVfwCaptureDialogs)) + FIXME("IAMVfwCaptureDialogs not supported yet!\n"); + else if (IsEqualIID(riid, &IID_IAMStreamConfig)) + *ret_iface = &This->IAMStreamConfig_iface; + else if (IsEqualIID(riid, &IID_IAMVideoProcAmp)) + *ret_iface = &This->IAMVideoProcAmp_iface; + else + WARN("(%p, %s, %p): not found\n", This, debugstr_guid(riid), ret_iface); + + if (!*ret_iface) + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown*)*ret_iface); + return S_OK; +} + +static ULONG WINAPI unknown_inner_AddRef(IUnknown *iface) +{ + VfwCapture *This = impl_from_IUnknown(iface); + ULONG ref = BaseFilterImpl_AddRef(&This->filter.IBaseFilter_iface); + + TRACE("(%p) ref=%d\n", This, ref); + + return ref; +} + +static ULONG WINAPI unknown_inner_Release(IUnknown *iface) +{ + VfwCapture *This = impl_from_IUnknown(iface); + ULONG ref = InterlockedDecrement(&This->filter.refCount); + + TRACE("(%p) ref=%d\n", This, ref); + + if (!ref) + { + IPin *conn = NULL; + + TRACE("destroying everything\n"); + if (This->init) + { + if (This->filter.state != State_Stopped) + qcap_driver_stop(This->driver_info, &This->filter.state); + qcap_driver_destroy(This->driver_info); + } + IPin_ConnectedTo(This->pOutputPin, &conn); + if (conn) + { + IPin_Disconnect(conn); + IPin_Disconnect(This->pOutputPin); + IPin_Release(conn); + } + IPin_Release(This->pOutputPin); + BaseFilter_Destroy(&This->filter); + CoTaskMemFree(This); + ObjectRefCount(FALSE); + } + + return ref; +} + +static const IUnknownVtbl unknown_inner_vtbl = +{ + unknown_inner_QueryInterface, + unknown_inner_AddRef, + unknown_inner_Release, +}; + +static IPin* WINAPI VfwCapture_GetPin(BaseFilter *iface, int pos) +{ + VfwCapture *This = impl_from_BaseFilter(iface); + + if (pos >= 1 || pos < 0) + return NULL; + + IPin_AddRef(This->pOutputPin); + return This->pOutputPin; +} + +static LONG WINAPI VfwCapture_GetPinCount(BaseFilter *iface) +{ + return 1; +} + +static const BaseFilterFuncTable BaseFuncTable = { + VfwCapture_GetPin, + VfwCapture_GetPinCount +}; + +IUnknown * WINAPI QCAP_createVFWCaptureFilter(IUnknown *pUnkOuter, HRESULT *phr) +{ + VfwCapture *pVfwCapture; + HRESULT hr; + + TRACE("%p - %p\n", pUnkOuter, phr); + + *phr = E_OUTOFMEMORY; + pVfwCapture = CoTaskMemAlloc( sizeof(VfwCapture) ); + if (!pVfwCapture) + return NULL; + + BaseFilter_Init(&pVfwCapture->filter, &VfwCapture_Vtbl, &CLSID_VfwCapture, (DWORD_PTR)(__FILE__ ": VfwCapture.csFilter"), &BaseFuncTable); + + pVfwCapture->IUnknown_inner.lpVtbl = &unknown_inner_vtbl; + pVfwCapture->IAMStreamConfig_iface.lpVtbl = &IAMStreamConfig_VTable; + pVfwCapture->IAMVideoProcAmp_iface.lpVtbl = &IAMVideoProcAmp_VTable; + pVfwCapture->IPersistPropertyBag_iface.lpVtbl = &IPersistPropertyBag_VTable; + pVfwCapture->init = FALSE; + + if (pUnkOuter) + pVfwCapture->outer_unk = pUnkOuter; + else + pVfwCapture->outer_unk = &pVfwCapture->IUnknown_inner; + + hr = VfwPin_Construct(&pVfwCapture->filter.IBaseFilter_iface, + &pVfwCapture->filter.csFilter, &pVfwCapture->pOutputPin); + if (FAILED(hr)) + { + CoTaskMemFree(pVfwCapture); + return NULL; + } + TRACE("-- created at %p\n", pVfwCapture); + + ObjectRefCount(TRUE); + *phr = S_OK; + return &pVfwCapture->IUnknown_inner; +} + +static HRESULT WINAPI VfwCapture_QueryInterface(IBaseFilter *iface, REFIID riid, void **ret_iface) +{ + VfwCapture *This = impl_from_IBaseFilter(iface); + + return IUnknown_QueryInterface(This->outer_unk, riid, ret_iface); +} + +static ULONG WINAPI VfwCapture_AddRef(IBaseFilter *iface) +{ + VfwCapture *This = impl_from_IBaseFilter(iface); + + return IUnknown_AddRef(This->outer_unk); +} + +static ULONG WINAPI VfwCapture_Release(IBaseFilter * iface) +{ + VfwCapture *This = impl_from_IBaseFilter(iface); + + return IUnknown_Release(This->outer_unk); +} + +/** IMediaFilter methods **/ + +static HRESULT WINAPI VfwCapture_Stop(IBaseFilter * iface) +{ + VfwCapture *This = impl_from_IBaseFilter(iface); + + TRACE("()\n"); + return qcap_driver_stop(This->driver_info, &This->filter.state); +} + +static HRESULT WINAPI VfwCapture_Pause(IBaseFilter * iface) +{ + VfwCapture *This = impl_from_IBaseFilter(iface); + + TRACE("()\n"); + return qcap_driver_pause(This->driver_info, &This->filter.state); +} + +static HRESULT WINAPI VfwCapture_Run(IBaseFilter * iface, REFERENCE_TIME tStart) +{ + VfwCapture *This = impl_from_IBaseFilter(iface); + TRACE("(%s)\n", wine_dbgstr_longlong(tStart)); + return qcap_driver_run(This->driver_info, &This->filter.state); +} + +/** IBaseFilter methods **/ +static HRESULT WINAPI VfwCapture_FindPin(IBaseFilter * iface, LPCWSTR Id, IPin **ppPin) +{ + FIXME("(%s, %p) - stub\n", debugstr_w(Id), ppPin); + return E_NOTIMPL; +} + +static const IBaseFilterVtbl VfwCapture_Vtbl = +{ + VfwCapture_QueryInterface, + VfwCapture_AddRef, + VfwCapture_Release, + BaseFilterImpl_GetClassID, + VfwCapture_Stop, + VfwCapture_Pause, + VfwCapture_Run, + BaseFilterImpl_GetState, + BaseFilterImpl_SetSyncSource, + BaseFilterImpl_GetSyncSource, + BaseFilterImpl_EnumPins, + VfwCapture_FindPin, + BaseFilterImpl_QueryFilterInfo, + BaseFilterImpl_JoinFilterGraph, + BaseFilterImpl_QueryVendorInfo +}; + +/* AMStreamConfig interface, we only need to implement {G,S}etFormat */ +static HRESULT WINAPI AMStreamConfig_QueryInterface(IAMStreamConfig *iface, REFIID riid, + void **ret_iface) +{ + VfwCapture *This = impl_from_IAMStreamConfig(iface); + + return IUnknown_QueryInterface(This->outer_unk, riid, ret_iface); +} + +static ULONG WINAPI AMStreamConfig_AddRef( IAMStreamConfig * iface ) +{ + VfwCapture *This = impl_from_IAMStreamConfig(iface); + + return IUnknown_AddRef(This->outer_unk); +} + +static ULONG WINAPI AMStreamConfig_Release( IAMStreamConfig * iface ) +{ + VfwCapture *This = impl_from_IAMStreamConfig(iface); + + return IUnknown_Release(This->outer_unk); +} + +static HRESULT WINAPI +AMStreamConfig_SetFormat(IAMStreamConfig *iface, AM_MEDIA_TYPE *pmt) +{ + HRESULT hr; + VfwCapture *This = impl_from_IAMStreamConfig(iface); + BasePin *pin; + + TRACE("(%p): %p->%p\n", iface, pmt, pmt ? pmt->pbFormat : NULL); + + if (This->filter.state != State_Stopped) + { + TRACE("Returning not stopped error\n"); + return VFW_E_NOT_STOPPED; + } + + if (!pmt) + { + TRACE("pmt is NULL\n"); + return E_POINTER; + } + + dump_AM_MEDIA_TYPE(pmt); + + pin = (BasePin *)This->pOutputPin; + if (pin->pConnectedTo != NULL) + { + hr = IPin_QueryAccept(pin->pConnectedTo, pmt); + TRACE("Would accept: %d\n", hr); + if (hr == S_FALSE) + return VFW_E_INVALIDMEDIATYPE; + } + + hr = qcap_driver_set_format(This->driver_info, pmt); + if (SUCCEEDED(hr) && This->filter.filterInfo.pGraph && pin->pConnectedTo ) + { + hr = IFilterGraph_Reconnect(This->filter.filterInfo.pGraph, This->pOutputPin); + if (SUCCEEDED(hr)) + TRACE("Reconnection completed, with new media format..\n"); + } + TRACE("Returning: %d\n", hr); + return hr; +} + +static HRESULT WINAPI +AMStreamConfig_GetFormat( IAMStreamConfig *iface, AM_MEDIA_TYPE **pmt ) +{ + VfwCapture *This = impl_from_IAMStreamConfig(iface); + + TRACE("%p -> (%p)\n", iface, pmt); + return qcap_driver_get_format(This->driver_info, pmt); +} + +static HRESULT WINAPI +AMStreamConfig_GetNumberOfCapabilities( IAMStreamConfig *iface, int *piCount, + int *piSize ) +{ + FIXME("%p: %p %p - stub, intentional\n", iface, piCount, piSize); + *piCount = 0; + return E_NOTIMPL; /* Not implemented for this interface */ +} + +static HRESULT WINAPI +AMStreamConfig_GetStreamCaps( IAMStreamConfig *iface, int iIndex, + AM_MEDIA_TYPE **pmt, BYTE *pSCC ) +{ + FIXME("%p: %d %p %p - stub, intentional\n", iface, iIndex, pmt, pSCC); + return E_NOTIMPL; /* Not implemented for this interface */ +} + +static const IAMStreamConfigVtbl IAMStreamConfig_VTable = +{ + AMStreamConfig_QueryInterface, + AMStreamConfig_AddRef, + AMStreamConfig_Release, + AMStreamConfig_SetFormat, + AMStreamConfig_GetFormat, + AMStreamConfig_GetNumberOfCapabilities, + AMStreamConfig_GetStreamCaps +}; + +static HRESULT WINAPI AMVideoProcAmp_QueryInterface(IAMVideoProcAmp *iface, REFIID riid, + void **ret_iface) +{ + VfwCapture *This = impl_from_IAMVideoProcAmp(iface); + + return IUnknown_QueryInterface(This->outer_unk, riid, ret_iface); +} + +static ULONG WINAPI AMVideoProcAmp_AddRef(IAMVideoProcAmp * iface) +{ + VfwCapture *This = impl_from_IAMVideoProcAmp(iface); + + return IUnknown_AddRef(This->outer_unk); +} + +static ULONG WINAPI AMVideoProcAmp_Release(IAMVideoProcAmp * iface) +{ + VfwCapture *This = impl_from_IAMVideoProcAmp(iface); + + return IUnknown_Release(This->outer_unk); +} + +static HRESULT WINAPI +AMVideoProcAmp_GetRange( IAMVideoProcAmp * iface, LONG Property, LONG *pMin, + LONG *pMax, LONG *pSteppingDelta, LONG *pDefault, LONG *pCapsFlags ) +{ + VfwCapture *This = impl_from_IAMVideoProcAmp(iface); + + return qcap_driver_get_prop_range( This->driver_info, Property, pMin, pMax, + pSteppingDelta, pDefault, pCapsFlags ); +} + +static HRESULT WINAPI +AMVideoProcAmp_Set( IAMVideoProcAmp * iface, LONG Property, LONG lValue, + LONG Flags ) +{ + VfwCapture *This = impl_from_IAMVideoProcAmp(iface); + + return qcap_driver_set_prop(This->driver_info, Property, lValue, Flags); +} + +static HRESULT WINAPI +AMVideoProcAmp_Get( IAMVideoProcAmp * iface, LONG Property, LONG *lValue, + LONG *Flags ) +{ + VfwCapture *This = impl_from_IAMVideoProcAmp(iface); + + return qcap_driver_get_prop(This->driver_info, Property, lValue, Flags); +} + +static const IAMVideoProcAmpVtbl IAMVideoProcAmp_VTable = +{ + AMVideoProcAmp_QueryInterface, + AMVideoProcAmp_AddRef, + AMVideoProcAmp_Release, + AMVideoProcAmp_GetRange, + AMVideoProcAmp_Set, + AMVideoProcAmp_Get, +}; + +static HRESULT WINAPI PPB_QueryInterface(IPersistPropertyBag *iface, REFIID riid, void **ret_iface) +{ + VfwCapture *This = impl_from_IPersistPropertyBag(iface); + + return IUnknown_QueryInterface(This->outer_unk, riid, ret_iface); +} + +static ULONG WINAPI PPB_AddRef(IPersistPropertyBag * iface) +{ + VfwCapture *This = impl_from_IPersistPropertyBag(iface); + + return IUnknown_AddRef(This->outer_unk); +} + +static ULONG WINAPI PPB_Release(IPersistPropertyBag * iface) +{ + VfwCapture *This = impl_from_IPersistPropertyBag(iface); + + return IUnknown_Release(This->outer_unk); +} + +static HRESULT WINAPI +PPB_GetClassID( IPersistPropertyBag * iface, CLSID * pClassID ) +{ + VfwCapture *This = impl_from_IPersistPropertyBag(iface); + + FIXME("%p - stub\n", This); + + return E_NOTIMPL; +} + +static HRESULT WINAPI PPB_InitNew(IPersistPropertyBag * iface) +{ + VfwCapture *This = impl_from_IPersistPropertyBag(iface); + + FIXME("%p - stub\n", This); + + return E_NOTIMPL; +} + +static HRESULT WINAPI +PPB_Load( IPersistPropertyBag * iface, IPropertyBag *pPropBag, + IErrorLog *pErrorLog ) +{ + VfwCapture *This = impl_from_IPersistPropertyBag(iface); + HRESULT hr; + VARIANT var; + const OLECHAR VFWIndex[] = {'V','F','W','I','n','d','e','x',0}; + + TRACE("%p/%p-> (%p, %p)\n", iface, This, pPropBag, pErrorLog); + + V_VT(&var) = VT_I4; + hr = IPropertyBag_Read(pPropBag, VFWIndex, &var, pErrorLog); + + if (SUCCEEDED(hr)) + { + VfwPinImpl *pin; + + This->driver_info = qcap_driver_init( This->pOutputPin, + var.__VARIANT_NAME_1.__VARIANT_NAME_2.__VARIANT_NAME_3.ulVal ); + if (This->driver_info) + { + pin = (VfwPinImpl *)This->pOutputPin; + pin->parent = This; + This->init = TRUE; + hr = S_OK; + } + else + hr = E_FAIL; + } + + return hr; +} + +static HRESULT WINAPI +PPB_Save( IPersistPropertyBag * iface, IPropertyBag *pPropBag, + BOOL fClearDirty, BOOL fSaveAllProperties ) +{ + VfwCapture *This = impl_from_IPersistPropertyBag(iface); + FIXME("%p - stub\n", This); + return E_NOTIMPL; +} + +static const IPersistPropertyBagVtbl IPersistPropertyBag_VTable = +{ + PPB_QueryInterface, + PPB_AddRef, + PPB_Release, + PPB_GetClassID, + PPB_InitNew, + PPB_Load, + PPB_Save +}; + +/* IKsPropertySet interface */ +static inline VfwPinImpl *impl_from_IKsPropertySet(IKsPropertySet *iface) +{ + return CONTAINING_RECORD(iface, VfwPinImpl, IKsPropertySet_iface); +} + +static HRESULT WINAPI KSP_QueryInterface(IKsPropertySet * iface, REFIID riid, void **ret_iface) +{ + VfwPinImpl *This = impl_from_IKsPropertySet(iface); + + return IPin_QueryInterface(&This->pin.pin.IPin_iface, riid, ret_iface); +} + +static ULONG WINAPI KSP_AddRef(IKsPropertySet * iface) +{ + VfwPinImpl *This = impl_from_IKsPropertySet(iface); + + return IPin_AddRef(&This->pin.pin.IPin_iface); +} + +static ULONG WINAPI KSP_Release(IKsPropertySet * iface) +{ + VfwPinImpl *This = impl_from_IKsPropertySet(iface); + + return IPin_Release(&This->pin.pin.IPin_iface); +} + +static HRESULT WINAPI +KSP_Set( IKsPropertySet * iface, REFGUID guidPropSet, DWORD dwPropID, + LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, + DWORD cbPropData ) +{ + FIXME("%p: stub\n", iface); + return E_NOTIMPL; +} + +static HRESULT WINAPI +KSP_Get( IKsPropertySet * iface, REFGUID guidPropSet, DWORD dwPropID, + LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, + DWORD cbPropData, DWORD *pcbReturned ) +{ + LPGUID pGuid; + + TRACE("()\n"); + + if (!IsEqualIID(guidPropSet, &ROPSETID_Pin)) + return E_PROP_SET_UNSUPPORTED; + if (pPropData == NULL && pcbReturned == NULL) + return E_POINTER; + if (pcbReturned) + *pcbReturned = sizeof(GUID); + if (pPropData == NULL) + return S_OK; + if (cbPropData < sizeof(GUID)) + return E_UNEXPECTED; + pGuid = pPropData; + *pGuid = PIN_CATEGORY_CAPTURE; + FIXME("() Not adding a pin with PIN_CATEGORY_PREVIEW\n"); + return S_OK; +} + +static HRESULT WINAPI +KSP_QuerySupported( IKsPropertySet * iface, REFGUID guidPropSet, + DWORD dwPropID, DWORD *pTypeSupport ) +{ + FIXME("%p: stub\n", iface); + return E_NOTIMPL; +} + +static const IKsPropertySetVtbl IKsPropertySet_VTable = +{ + KSP_QueryInterface, + KSP_AddRef, + KSP_Release, + KSP_Set, + KSP_Get, + KSP_QuerySupported +}; + +static inline VfwPinImpl *impl_from_BasePin(BasePin *pin) +{ + return CONTAINING_RECORD(pin, VfwPinImpl, pin.pin); +} + +static HRESULT WINAPI VfwPin_GetMediaType(BasePin *pin, int iPosition, AM_MEDIA_TYPE *pmt) +{ + VfwPinImpl *This = impl_from_BasePin(pin); + AM_MEDIA_TYPE *vfw_pmt; + HRESULT hr; + + if (iPosition < 0) + return E_INVALIDARG; + if (iPosition > 0) + return VFW_S_NO_MORE_ITEMS; + + hr = qcap_driver_get_format(This->parent->driver_info, &vfw_pmt); + if (SUCCEEDED(hr)) { + CopyMediaType(pmt, vfw_pmt); + DeleteMediaType(vfw_pmt); + } + return hr; +} + +static LONG WINAPI VfwPin_GetMediaTypeVersion(BasePin *iface) +{ + return 1; +} + +static HRESULT WINAPI VfwPin_DecideBufferSize(BaseOutputPin *iface, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest) +{ + ALLOCATOR_PROPERTIES actual; + + /* What we put here doesn't matter, the + driver function should override it then commit */ + if (!ppropInputRequest->cBuffers) + ppropInputRequest->cBuffers = 3; + if (!ppropInputRequest->cbBuffer) + ppropInputRequest->cbBuffer = 230400; + if (!ppropInputRequest->cbAlign) + ppropInputRequest->cbAlign = 1; + + return IMemAllocator_SetProperties(pAlloc, ppropInputRequest, &actual); +} + +static const BaseOutputPinFuncTable output_BaseOutputFuncTable = { + { + NULL, + BaseOutputPinImpl_AttemptConnection, + VfwPin_GetMediaTypeVersion, + VfwPin_GetMediaType + }, + VfwPin_DecideBufferSize, + BaseOutputPinImpl_DecideAllocator, + BaseOutputPinImpl_BreakConnect +}; + +static HRESULT +VfwPin_Construct( IBaseFilter * pBaseFilter, LPCRITICAL_SECTION pCritSec, + IPin ** ppPin ) +{ + static const WCHAR wszOutputPinName[] = { 'O','u','t','p','u','t',0 }; + PIN_INFO piOutput; + HRESULT hr; + + *ppPin = NULL; + + piOutput.dir = PINDIR_OUTPUT; + piOutput.pFilter = pBaseFilter; + lstrcpyW(piOutput.achName, wszOutputPinName); + + hr = BaseOutputPin_Construct(&VfwPin_Vtbl, sizeof(VfwPinImpl), &piOutput, &output_BaseOutputFuncTable, pCritSec, ppPin); + + if (SUCCEEDED(hr)) + { + VfwPinImpl *pPinImpl = (VfwPinImpl*)*ppPin; + pPinImpl->IKsPropertySet_iface.lpVtbl = &IKsPropertySet_VTable; + ObjectRefCount(TRUE); + } + + return hr; +} + +static inline VfwPinImpl *impl_from_IPin(IPin *iface) +{ + return CONTAINING_RECORD(iface, VfwPinImpl, pin.pin.IPin_iface); +} + +static HRESULT WINAPI VfwPin_QueryInterface(IPin * iface, REFIID riid, LPVOID * ppv) +{ + VfwPinImpl *This = impl_from_IPin(iface); + + TRACE("%s %p\n", debugstr_guid(riid), ppv); + + *ppv = NULL; + if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IPin)) + *ppv = This; + else if (IsEqualIID(riid, &IID_IKsPropertySet)) + *ppv = &This->IKsPropertySet_iface; + else if (IsEqualIID(riid, &IID_IAMStreamConfig)) + return IUnknown_QueryInterface((IUnknown *)This->parent, riid, 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 +VfwPin_Release(IPin * iface) +{ + VfwPinImpl *This = impl_from_IPin(iface); + ULONG refCount = InterlockedDecrement(&This->pin.pin.refCount); + + TRACE("() -> new refcount: %u\n", refCount); + + if (!refCount) + { + BaseOutputPin_Destroy(&This->pin); + ObjectRefCount(FALSE); + } + return refCount; +} + +static HRESULT WINAPI +VfwPin_EnumMediaTypes(IPin * iface, IEnumMediaTypes ** ppEnum) +{ + VfwPinImpl *This = impl_from_IPin(iface); + AM_MEDIA_TYPE *pmt; + HRESULT hr; + + hr = qcap_driver_get_format(This->parent->driver_info, &pmt); + if (SUCCEEDED(hr)) { + hr = BasePinImpl_EnumMediaTypes(iface, ppEnum); + DeleteMediaType(pmt); + } + TRACE("%p -- %x\n", This, hr); + return hr; +} + +static HRESULT WINAPI +VfwPin_QueryInternalConnections(IPin * iface, IPin ** apPin, ULONG * cPin) +{ + TRACE("(%p)->(%p, %p)\n", iface, apPin, cPin); + return E_NOTIMPL; +} + +static const IPinVtbl VfwPin_Vtbl = +{ + VfwPin_QueryInterface, + BasePinImpl_AddRef, + VfwPin_Release, + BaseOutputPinImpl_Connect, + BaseOutputPinImpl_ReceiveConnection, + BaseOutputPinImpl_Disconnect, + BasePinImpl_ConnectedTo, + BasePinImpl_ConnectionMediaType, + BasePinImpl_QueryPinInfo, + BasePinImpl_QueryDirection, + BasePinImpl_QueryId, + BasePinImpl_QueryAccept, + VfwPin_EnumMediaTypes, + VfwPin_QueryInternalConnections, + BaseOutputPinImpl_EndOfStream, + BaseOutputPinImpl_BeginFlush, + BaseOutputPinImpl_EndFlush, + BasePinImpl_NewSegment +}; diff --git a/dll/directx/wine/qcap/yuv.c b/dll/directx/wine/qcap/yuv.c new file mode 100644 index 00000000000..7e17add949c --- /dev/null +++ b/dll/directx/wine/qcap/yuv.c @@ -0,0 +1,200 @@ +/* DirectShow capture services (QCAP.DLL) + * + * Copyright 2005 Maarten Lankhorst + * + * This file contains the part of the vfw capture interface that + * does the actual Video4Linux(1/2) stuff required for capturing + * and setting/getting media format.. + * + * 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" +#include + +#include "windef.h" +#include "wingdi.h" +#include "objbase.h" +#include "strmif.h" +#include "qcap_main.h" +#include "wine/debug.h" + +/* This is not used if V4L support is missing */ +#if defined(HAVE_LINUX_VIDEODEV_H) || defined(HAVE_LIBV4L1_H) + +WINE_DEFAULT_DEBUG_CHANNEL(qcap); + +static int yuv_xy[256]; /* Gray value */ +static int yuv_gu[256]; /* Green U */ +static int yuv_bu[256]; /* Blue U */ +static int yuv_rv[256]; /* Red V */ +static int yuv_gv[256]; /* Green V */ +static BOOL initialised = FALSE; + +static inline int ValidRange(int in) { + if (in > 255) in = 255; + if (in < 0) in = 0; + return in; +} + +typedef struct RGB { +#if 0 /* For some reason I have to revert R and B, not sure why */ + unsigned char r, g, b; +#else + unsigned char b, g, r; +#endif +} RGB; + +static inline void YUV2RGB(const unsigned char y_, const unsigned char cb, const unsigned char cr, RGB* retval) { + retval->r = ValidRange(yuv_xy[y_] + yuv_rv[cr]); + retval->g = ValidRange(yuv_xy[y_] + yuv_gu[cb] + yuv_gv[cr]); + retval->b = ValidRange(yuv_xy[y_] + yuv_bu[cb]); +} + +void YUV_Init(void) { + float y, u, v; + int y_, cb, cr; + + if (initialised) return; + initialised = TRUE; + + for (y_ = 0; y_ <= 255; y_++) + { + y = ((float) 255 / 219) * (y_ - 16); + yuv_xy[y_] = y; + } + + for (cb = 0; cb <= 255; cb++) + { + u = ((float) 255 / 224) * (cb - 128); + yuv_gu[cb] = -0.344 * u; + yuv_bu[cb] = 1.772 * u; + } + + for (cr = 0; cr <= 255; cr++) + { + v = ((float) 255 / 224) * (cr - 128); + yuv_rv[cr] = 1.402 * v; + yuv_gv[cr] = -0.714 * v; + } + TRACE("Filled hash table\n"); +} + +static void Parse_YUYV(unsigned char *destbuffer, const unsigned char *input, int width, int height) +{ + const unsigned char *pY, *pCb, *pCr; + int togo = width * height / 2; + pY = input; + pCb = input+1; + pCr = input+3; + while (--togo) { + YUV2RGB(*pY, *pCb, *pCr, (RGB *)destbuffer); + pY += 2; destbuffer += 3; + YUV2RGB(*pY, *pCb, *pCr, (RGB *)destbuffer); + pY += 2; pCb += 4; pCr += 4; destbuffer += 3; + } +} + +static void Parse_UYVY(unsigned char *destbuffer, const unsigned char *input, int width, int height) +{ + const unsigned char *pY, *pCb, *pCr; + int togo = width * height / 2; + pY = input+1; + pCb = input; + pCr = input+2; + while (--togo) { + YUV2RGB(*pY, *pCb, *pCr, (RGB *)destbuffer); + pY += 2; destbuffer += 3; + YUV2RGB(*pY, *pCb, *pCr, (RGB *)destbuffer); + pY += 2; pCb += 4; pCr += 4; destbuffer += 3; + } +} + +static void Parse_UYYVYY(unsigned char *destbuffer, const unsigned char *input, int width, int height) +{ + const unsigned char *pY, *pCb, *pCr; + int togo = width * height / 4; + pY = input+1; + pCb = input; + pCr = input+4; + while (--togo) { + YUV2RGB(*pY, *pCb, *pCr, (RGB *)destbuffer); + destbuffer += 3; pY++; + YUV2RGB(*pY, *pCb, *pCr, (RGB *)destbuffer); + pY += 2; destbuffer += 3; + YUV2RGB(*pY, *pCb, *pCr, (RGB *)destbuffer); + destbuffer += 3; pY++; + YUV2RGB(*pY, *pCb, *pCr, (RGB *)destbuffer); + pY += 2; pCb += 6; pCr += 6; destbuffer += 3; + } +} + +static void Parse_PYUV(unsigned char *destbuffer, const unsigned char *input, int width, int height, int wstep, int hstep) +{ + /* We have 3 pointers, One to Y, one to Cb and 1 to Cr */ + +/* C19 *89* declaration block (Grr julliard for not allowing C99) */ + int ysize, uvsize; + const unsigned char *pY, *pCb, *pCr; + int swstep = 0, shstep = 0; + int ypos = 0, xpos = 0; + int indexUV = 0, cUv; +/* End of Grr */ + + ysize = width * height; + uvsize = (width / wstep) * (height / hstep); + pY = input; + pCb = pY + ysize; + pCr = pCb + uvsize; + /* Bottom down DIB */ + do { + swstep = 0; + cUv = indexUV; + for (xpos = 0; xpos < width; xpos++) { + YUV2RGB(*(pY++), pCb[cUv], pCr[cUv], (RGB *)destbuffer); + destbuffer += 3; + if (++swstep == wstep) { + cUv++; + swstep = 0; + } + } + if (++shstep == hstep) { + shstep = 0; + indexUV = cUv; + } + } while (++ypos < height); +} + +void YUV_To_RGB24(enum YUV_Format format, unsigned char *target, const unsigned char *source, int width, int height) { + int wstep, hstep; + if (format < ENDPLANAR) { + switch (format) { + case YUVP_421: wstep = 2; hstep = 1; break; + case YUVP_422: wstep = 2; hstep = 2; break; + case YUVP_441: wstep = 4; hstep = 1; break; + case YUVP_444: wstep = 4; hstep = 4; break; + default: ERR("Unhandled format \"%d\"\n", format); return; + } + Parse_PYUV(target, source, width, height, wstep, hstep); + } else { + switch (format) { + case YUYV: Parse_YUYV(target, source, width, height); return; + case UYVY: Parse_UYVY(target, source, width, height); return; + case UYYVYY: Parse_UYYVYY(target, source, width, height); return; + default: ERR("Unhandled format \"%d\"\n", format); return; + } + } +} +#endif diff --git a/media/doc/README.WINE b/media/doc/README.WINE index 333a07a1d97..5a0a9cb1a15 100644 --- a/media/doc/README.WINE +++ b/media/doc/README.WINE @@ -39,6 +39,7 @@ dll/directx/wine/dpnhpast # Synced to WineStaging-4.18 dll/directx/wine/dsound # Synced to Wine-1.3.29 dll/directx/wine/dxdiagn # Synced to WineStaging-4.18 dll/directx/wine/msdmo # Synced to WineStaging-4.18 +dll/directx/wine/qcap # Synced to WineStaging-3.3 dll/directx/wine/qedit # Synced to WineStaging-3.17 dll/directx/wine/quartz # Synced to WineStaging-3.9 dll/directx/wine/wined3d # Synced to WineStaging-3.3