Signed-off-by: Zebediah Figura z.figura12@gmail.com --- dlls/qcap/filewriter.c | 69 ++++++++++++++++++++++++++++++++++++ dlls/qcap/tests/filewriter.c | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/dlls/qcap/filewriter.c b/dlls/qcap/filewriter.c index 2b1d219a08..e56b5f68a4 100644 --- a/dlls/qcap/filewriter.c +++ b/dlls/qcap/filewriter.c @@ -28,6 +28,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(qcap); struct file_writer { struct strmbase_filter filter; + IFileSinkFilter IFileSinkFilter_iface;
struct strmbase_sink sink; }; @@ -60,6 +61,19 @@ static inline struct file_writer *impl_from_strmbase_filter(struct strmbase_filt return CONTAINING_RECORD(iface, struct file_writer, filter); }
+static HRESULT file_writer_query_interface(struct strmbase_filter *iface, REFIID iid, void **out) +{ + struct file_writer *filter = impl_from_strmbase_filter(iface); + + if (IsEqualGUID(iid, &IID_IFileSinkFilter)) + *out = &filter->IFileSinkFilter_iface; + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown *)*out); + return S_OK; +} + static struct strmbase_pin *file_writer_get_pin(struct strmbase_filter *iface, unsigned int index) { struct file_writer *filter = impl_from_strmbase_filter(iface); @@ -80,10 +94,64 @@ static void file_writer_destroy(struct strmbase_filter *iface)
static struct strmbase_filter_ops filter_ops = { + .filter_query_interface = file_writer_query_interface, .filter_get_pin = file_writer_get_pin, .filter_destroy = file_writer_destroy, };
+static inline struct file_writer *impl_from_IFileSinkFilter(IFileSinkFilter *iface) +{ + return CONTAINING_RECORD(iface, struct file_writer, IFileSinkFilter_iface); +} + +static HRESULT WINAPI filesinkfilter_QueryInterface(IFileSinkFilter *iface, REFIID iid, void **out) +{ + struct file_writer *filter = impl_from_IFileSinkFilter(iface); + return IUnknown_QueryInterface(filter->filter.outer_unk, iid, out); +} + +static ULONG WINAPI filesinkfilter_AddRef(IFileSinkFilter *iface) +{ + struct file_writer *filter = impl_from_IFileSinkFilter(iface); + return IUnknown_AddRef(filter->filter.outer_unk); +} + +static ULONG WINAPI filesinkfilter_Release(IFileSinkFilter *iface) +{ + struct file_writer *filter = impl_from_IFileSinkFilter(iface); + return IUnknown_Release(filter->filter.outer_unk); +} + +static HRESULT WINAPI filesinkfilter_SetFileName(IFileSinkFilter *iface, + LPCOLESTR filename, const AM_MEDIA_TYPE *mt) +{ + struct file_writer *filter = impl_from_IFileSinkFilter(iface); + + FIXME("filter %p, filename %s, mt %p, stub!\n", filter, debugstr_w(filename), mt); + strmbase_dump_media_type(mt); + + return E_NOTIMPL; +} + +static HRESULT WINAPI filesinkfilter_GetCurFile(IFileSinkFilter *iface, + LPOLESTR *filename, AM_MEDIA_TYPE *mt) +{ + struct file_writer *filter = impl_from_IFileSinkFilter(iface); + + FIXME("filter %p, filename %p, mt %p, stub!\n", filter, filename, mt); + + return E_NOTIMPL; +} + +static const IFileSinkFilterVtbl filesinkfilter_vtbl = +{ + filesinkfilter_QueryInterface, + filesinkfilter_AddRef, + filesinkfilter_Release, + filesinkfilter_SetFileName, + filesinkfilter_GetCurFile, +}; + HRESULT file_writer_create(IUnknown *outer, IUnknown **out) { static const WCHAR sink_name[] = {'i','n',0}; @@ -93,6 +161,7 @@ HRESULT file_writer_create(IUnknown *outer, IUnknown **out) return E_OUTOFMEMORY;
strmbase_filter_init(&object->filter, outer, &CLSID_FileWriter, &filter_ops); + object->IFileSinkFilter_iface.lpVtbl = &filesinkfilter_vtbl;
strmbase_sink_init(&object->sink, &object->filter, sink_name, &sink_ops, NULL);
diff --git a/dlls/qcap/tests/filewriter.c b/dlls/qcap/tests/filewriter.c index 8201a4d0c7..5d8ce45e0d 100644 --- a/dlls/qcap/tests/filewriter.c +++ b/dlls/qcap/tests/filewriter.c @@ -61,7 +61,7 @@ static void test_interfaces(void)
todo_wine check_interface(filter, &IID_IAMFilterMiscFlags, TRUE); check_interface(filter, &IID_IBaseFilter, TRUE); - todo_wine check_interface(filter, &IID_IFileSinkFilter, TRUE); + check_interface(filter, &IID_IFileSinkFilter, TRUE); todo_wine check_interface(filter, &IID_IFileSinkFilter2, TRUE); check_interface(filter, &IID_IMediaFilter, TRUE); check_interface(filter, &IID_IPersist, TRUE);
Signed-off-by: Zebediah Figura z.figura12@gmail.com --- v2: Don't leak filter->filename if called multiple times; thanks Zhiyi.
dlls/qcap/filewriter.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/dlls/qcap/filewriter.c b/dlls/qcap/filewriter.c index e56b5f68a4..656f72db1a 100644 --- a/dlls/qcap/filewriter.c +++ b/dlls/qcap/filewriter.c @@ -22,6 +22,7 @@ #include "dshow.h" #include "qcap_main.h" #include "wine/debug.h" +#include "wine/unicode.h"
WINE_DEFAULT_DEBUG_CHANNEL(qcap);
@@ -31,6 +32,8 @@ struct file_writer IFileSinkFilter IFileSinkFilter_iface;
struct strmbase_sink sink; + + WCHAR *filename; };
static inline struct file_writer *impl_from_strmbase_pin(struct strmbase_pin *iface) @@ -87,6 +90,7 @@ static void file_writer_destroy(struct strmbase_filter *iface) { struct file_writer *filter = impl_from_strmbase_filter(iface);
+ heap_free(filter->filename); strmbase_sink_cleanup(&filter->sink); strmbase_filter_cleanup(&filter->filter); heap_free(filter); @@ -126,11 +130,21 @@ static HRESULT WINAPI filesinkfilter_SetFileName(IFileSinkFilter *iface, LPCOLESTR filename, const AM_MEDIA_TYPE *mt) { struct file_writer *filter = impl_from_IFileSinkFilter(iface); + WCHAR *new_filename;
- FIXME("filter %p, filename %s, mt %p, stub!\n", filter, debugstr_w(filename), mt); + TRACE("filter %p, filename %s, mt %p, stub!\n", filter, debugstr_w(filename), mt); strmbase_dump_media_type(mt);
- return E_NOTIMPL; + if (mt) + FIXME("Ignoring media type %p.\n", mt); + + if (!(new_filename = heap_alloc((strlenW(filename) + 1) * sizeof(WCHAR)))) + return E_OUTOFMEMORY; + strcpyW(new_filename, filename); + + heap_free(filter->filename); + filter->filename = new_filename; + return S_OK; }
static HRESULT WINAPI filesinkfilter_GetCurFile(IFileSinkFilter *iface,
Signed-off-by: Zebediah Figura z.figura12@gmail.com --- dlls/qcap/filewriter.c | 10 +++ dlls/qcap/tests/filewriter.c | 126 +++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+)
diff --git a/dlls/qcap/filewriter.c b/dlls/qcap/filewriter.c index 656f72db1a..aa58226bd0 100644 --- a/dlls/qcap/filewriter.c +++ b/dlls/qcap/filewriter.c @@ -54,9 +54,19 @@ static HRESULT file_writer_sink_query_interface(struct strmbase_pin *iface, REFI return S_OK; }
+static HRESULT file_writer_sink_query_accept(struct strmbase_pin *iface, const AM_MEDIA_TYPE *mt) +{ + struct file_writer *filter = impl_from_strmbase_pin(iface); + + if (filter->filename && !IsEqualGUID(&mt->majortype, &MEDIATYPE_Stream)) + return S_FALSE; + return S_OK; +} + static const struct strmbase_sink_ops sink_ops = { .base.pin_query_interface = file_writer_sink_query_interface, + .base.pin_query_accept = file_writer_sink_query_accept, };
static inline struct file_writer *impl_from_strmbase_filter(struct strmbase_filter *iface) diff --git a/dlls/qcap/tests/filewriter.c b/dlls/qcap/tests/filewriter.c index 5d8ce45e0d..f5ad897f29 100644 --- a/dlls/qcap/tests/filewriter.c +++ b/dlls/qcap/tests/filewriter.c @@ -31,6 +31,24 @@ static IBaseFilter *create_file_writer(void) return filter; }
+static WCHAR *set_filename(IBaseFilter *filter) +{ + static WCHAR filename[MAX_PATH]; + IFileSinkFilter *filesink; + WCHAR path[MAX_PATH]; + HRESULT hr; + + GetTempPathW(ARRAY_SIZE(path), path); + GetTempFileNameW(path, L"qfw", 0, filename); + DeleteFileW(filename); + + IBaseFilter_QueryInterface(filter, &IID_IFileSinkFilter, (void **)&filesink); + hr = IFileSinkFilter_SetFileName(filesink, filename, NULL); + ok(hr == S_OK, "Got hr %#x.\n", hr); + IFileSinkFilter_Release(filesink); + return filename; +} + static ULONG get_refcount(void *iface) { IUnknown *unknown = iface; @@ -349,6 +367,112 @@ static void test_pin_info(void) ok(!ref, "Got outstanding refcount %d.\n", ref); }
+static void test_media_types(void) +{ + IBaseFilter *filter = create_file_writer(); + AM_MEDIA_TYPE mt = {{0}}, *pmt; + IEnumMediaTypes *enummt; + WCHAR *filename; + HRESULT hr; + ULONG ref; + IPin *pin; + + IBaseFilter_FindPin(filter, L"in", &pin); + + hr = IPin_EnumMediaTypes(pin, &enummt); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IEnumMediaTypes_Next(enummt, 1, &pmt, NULL); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + + IEnumMediaTypes_Release(enummt); + + hr = IPin_QueryAccept(pin, &mt); + ok(hr == S_OK, "Got hr %#x.\n", hr); + mt.majortype = MEDIATYPE_Audio; + mt.subtype = MEDIASUBTYPE_PCM; + mt.formattype = FORMAT_WaveFormatEx; + hr = IPin_QueryAccept(pin, &mt); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + filename = set_filename(filter); + + hr = IPin_EnumMediaTypes(pin, &enummt); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IEnumMediaTypes_Next(enummt, 1, &pmt, NULL); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + + IEnumMediaTypes_Release(enummt); + + memset(&mt, 0, sizeof(AM_MEDIA_TYPE)); + hr = IPin_QueryAccept(pin, &mt); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + mt.majortype = MEDIATYPE_Video; + hr = IPin_QueryAccept(pin, &mt); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + mt.majortype = MEDIATYPE_Audio; + hr = IPin_QueryAccept(pin, &mt); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + mt.majortype = MEDIATYPE_Stream; + hr = IPin_QueryAccept(pin, &mt); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + mt.subtype = MEDIASUBTYPE_PCM; + mt.formattype = FORMAT_WaveFormatEx; + hr = IPin_QueryAccept(pin, &mt); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + IPin_Release(pin); + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %d.\n", ref); + ok(GetFileAttributesW(filename) == INVALID_FILE_ATTRIBUTES, "File should not exist.\n"); +} + +static void test_enum_media_types(void) +{ + IBaseFilter *filter = create_file_writer(); + IEnumMediaTypes *enum1, *enum2; + AM_MEDIA_TYPE *mts[2]; + ULONG ref, count; + HRESULT hr; + IPin *pin; + + IBaseFilter_FindPin(filter, L"in", &pin); + + hr = IPin_EnumMediaTypes(pin, &enum1); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IEnumMediaTypes_Next(enum1, 1, mts, NULL); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + + hr = IEnumMediaTypes_Next(enum1, 1, mts, &count); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + ok(!count, "Got count %u.\n", count); + + hr = IEnumMediaTypes_Reset(enum1); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IEnumMediaTypes_Next(enum1, 1, mts, NULL); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + + hr = IEnumMediaTypes_Clone(enum1, &enum2); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IEnumMediaTypes_Skip(enum1, 1); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + + hr = IEnumMediaTypes_Next(enum2, 1, mts, NULL); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + + IEnumMediaTypes_Release(enum1); + IEnumMediaTypes_Release(enum2); + IPin_Release(pin); + + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %d.\n", ref); +} + START_TEST(filewriter) { CoInitializeEx(NULL, COINIT_MULTITHREADED); @@ -358,6 +482,8 @@ START_TEST(filewriter) test_enum_pins(); test_find_pin(); test_pin_info(); + test_media_types(); + test_enum_media_types();
CoUninitialize(); }
Signed-off-by: Zebediah Figura z.figura12@gmail.com --- dlls/qcap/tests/filewriter.c | 176 +++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+)
diff --git a/dlls/qcap/tests/filewriter.c b/dlls/qcap/tests/filewriter.c index f5ad897f29..cfc9e5d634 100644 --- a/dlls/qcap/tests/filewriter.c +++ b/dlls/qcap/tests/filewriter.c @@ -20,6 +20,7 @@
#define COBJMACROS #include "dshow.h" +#include "wine/strmbase.h" #include "wine/test.h"
static IBaseFilter *create_file_writer(void) @@ -473,6 +474,180 @@ static void test_enum_media_types(void) ok(!ref, "Got outstanding refcount %d.\n", ref); }
+struct testfilter +{ + struct strmbase_filter filter; + struct strmbase_source source; +}; + +static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface) +{ + return CONTAINING_RECORD(iface, struct testfilter, filter); +} + +static struct strmbase_pin *testfilter_get_pin(struct strmbase_filter *iface, unsigned int index) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + if (!index) + return &filter->source.pin; + return NULL; +} + +static void testfilter_destroy(struct strmbase_filter *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + strmbase_source_cleanup(&filter->source); + strmbase_filter_cleanup(&filter->filter); +} + +static const struct strmbase_filter_ops testfilter_ops = +{ + .filter_get_pin = testfilter_get_pin, + .filter_destroy = testfilter_destroy, +}; + +static HRESULT WINAPI testsource_DecideAllocator(struct strmbase_source *iface, + IMemInputPin *peer, IMemAllocator **allocator) +{ + return S_OK; +} + +static const struct strmbase_source_ops testsource_ops = +{ + .pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection, + .pfnDecideAllocator = testsource_DecideAllocator, +}; + +static void testfilter_init(struct testfilter *filter) +{ + static const GUID clsid = {0xabacab}; + strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops); + strmbase_source_init(&filter->source, &filter->filter, L"", &testsource_ops); +} + +static void test_allocator(IMemInputPin *input, IMemAllocator *allocator) +{ + ALLOCATOR_PROPERTIES props, ret_props; + IMemAllocator *ret_allocator; + HRESULT hr; + + memset(&props, 0xcc, sizeof(props)); + hr = IMemInputPin_GetAllocatorRequirements(input, &props); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + if (hr == S_OK) + { + ok(!props.cBuffers, "Got %d buffers.\n", props.cBuffers); + ok(!props.cbBuffer, "Got size %d.\n", props.cbBuffer); + ok(props.cbAlign == 512, "Got alignment %d.\n", props.cbAlign); + ok(!props.cbPrefix, "Got prefix %d.\n", props.cbPrefix); + } + + hr = IMemInputPin_GetAllocator(input, &ret_allocator); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + + hr = IMemInputPin_NotifyAllocator(input, NULL, TRUE); + ok(hr == E_POINTER, "Got hr %#x.\n", hr); + + props.cBuffers = 1; + props.cbBuffer = 256; + props.cbAlign = 512; + props.cbPrefix = 0; + hr = IMemAllocator_SetProperties(allocator, &props, &ret_props); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMemInputPin_NotifyAllocator(input, allocator, TRUE); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMemInputPin_GetAllocator(input, &ret_allocator); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + if (hr == S_OK) + IMemAllocator_Release(ret_allocator); +} + +static void test_connect_pin(void) +{ + AM_MEDIA_TYPE req_mt = + { + .majortype = {0x1111}, + .subtype = {0x2222}, + .formattype = {0x3333}, + }; + + IBaseFilter *filter = create_file_writer(); + struct testfilter source; + IMemAllocator *allocator; + IMemInputPin *meminput; + IFilterGraph2 *graph; + AM_MEDIA_TYPE mt; + IPin *pin, *peer; + HRESULT hr; + ULONG ref; + + set_filename(filter); + + testfilter_init(&source); + CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IFilterGraph2, (void **)&graph); + hr = IFilterGraph2_AddFilter(graph, filter, L"filewriter"); + ok(hr == S_OK, "Got hr %#x.\n", hr); + IFilterGraph2_AddFilter(graph, &source.filter.IBaseFilter_iface, L"source"); + IBaseFilter_FindPin(filter, L"in", &pin); + IPin_QueryInterface(pin, &IID_IMemInputPin, (void **)&meminput); + + peer = (IPin *)0xdeadbeef; + hr = IPin_ConnectedTo(pin, &peer); + ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#x.\n", hr); + ok(!peer, "Got peer %p.\n", peer); + + hr = IPin_ConnectionMediaType(pin, &mt); + ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#x.\n", hr); + + hr = IFilterGraph2_ConnectDirect(graph, &source.source.pin.IPin_iface, pin, &req_mt); + ok(hr == VFW_E_TYPE_NOT_ACCEPTED, "Got hr %#x.\n", hr); + req_mt.majortype = MEDIATYPE_Stream; + hr = IFilterGraph2_ConnectDirect(graph, &source.source.pin.IPin_iface, pin, &req_mt); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IPin_ConnectedTo(pin, &peer); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(peer == &source.source.pin.IPin_iface, "Got peer %p.\n", peer); + IPin_Release(peer); + + hr = IPin_ConnectionMediaType(pin, &mt); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(!memcmp(&mt, &req_mt, sizeof(AM_MEDIA_TYPE)), "Media types didn't match.\n"); + + CoCreateInstance(&CLSID_MemoryAllocator, NULL, CLSCTX_INPROC_SERVER, + &IID_IMemAllocator, (void **)&allocator); + + test_allocator(meminput, allocator); + + hr = IFilterGraph2_Disconnect(graph, pin); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IFilterGraph2_Disconnect(graph, pin); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + ok(source.source.pin.peer == pin, "Got peer %p.\n", source.source.pin.peer); + IFilterGraph2_Disconnect(graph, &source.source.pin.IPin_iface); + + peer = (IPin *)0xdeadbeef; + hr = IPin_ConnectedTo(pin, &peer); + ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#x.\n", hr); + ok(!peer, "Got peer %p.\n", peer); + + hr = IPin_ConnectionMediaType(pin, &mt); + ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#x.\n", hr); + + ref = IFilterGraph2_Release(graph); + ok(!ref, "Got outstanding refcount %d.\n", ref); + IMemInputPin_Release(meminput); + IPin_Release(pin); + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %d.\n", ref); + ref = IMemAllocator_Release(allocator); + ok(!ref, "Got outstanding refcount %d.\n", ref); + ref = IBaseFilter_Release(&source.filter.IBaseFilter_iface); + ok(!ref, "Got outstanding refcount %d.\n", ref); +} + START_TEST(filewriter) { CoInitializeEx(NULL, COINIT_MULTITHREADED); @@ -484,6 +659,7 @@ START_TEST(filewriter) test_pin_info(); test_media_types(); test_enum_media_types(); + test_connect_pin();
CoUninitialize(); }