From: Zebediah Figura zfigura@codeweavers.com
--- dlls/qcap/audiorecord.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/dlls/qcap/audiorecord.c b/dlls/qcap/audiorecord.c index d97d7fef19a..01f0fed3288 100644 --- a/dlls/qcap/audiorecord.c +++ b/dlls/qcap/audiorecord.c @@ -132,8 +132,17 @@ static HRESULT audio_record_source_get_media_type(struct strmbase_pin *iface, static HRESULT WINAPI audio_record_source_DecideBufferSize(struct strmbase_source *iface, IMemAllocator *allocator, ALLOCATOR_PROPERTIES *props) { + struct audio_record *filter = impl_from_strmbase_filter(iface->pin.filter); + const WAVEFORMATEX *format = (void *)filter->source.pin.mt.pbFormat; ALLOCATOR_PROPERTIES ret_props;
+ props->cBuffers = 4; + /* This is the algorithm that native uses. The alignment to an even number + * doesn't make much sense, and may be a bug. */ + props->cbBuffer = (format->nAvgBytesPerSec / 2) & ~1; + props->cbAlign = 1; + props->cbPrefix = 0; + return IMemAllocator_SetProperties(allocator, props, &ret_props); }
From: Zebediah Figura zfigura@codeweavers.com
--- dlls/qcap/tests/audiorecord.c | 245 ++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+)
diff --git a/dlls/qcap/tests/audiorecord.c b/dlls/qcap/tests/audiorecord.c index 0438b970ff1..9e7b2f944a7 100644 --- a/dlls/qcap/tests/audiorecord.c +++ b/dlls/qcap/tests/audiorecord.c @@ -467,6 +467,250 @@ static void test_media_types(IBaseFilter *filter) IPin_Release(pin); }
+struct testfilter +{ + struct strmbase_filter filter; + struct strmbase_sink sink; + const AM_MEDIA_TYPE *mt; +}; + +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->sink.pin; + return NULL; +} + +static void testfilter_destroy(struct strmbase_filter *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + strmbase_sink_cleanup(&filter->sink); + 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 testsink_query_interface(struct strmbase_pin *iface, REFIID iid, void **out) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->filter); + + if (IsEqualGUID(iid, &IID_IMemInputPin)) + *out = &filter->sink.IMemInputPin_iface; + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown *)*out); + return S_OK; +} + +static HRESULT testsink_get_media_type(struct strmbase_pin *iface, unsigned int index, AM_MEDIA_TYPE *mt) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->filter); + if (!index && filter->mt) + { + CopyMediaType(mt, filter->mt); + return S_OK; + } + return VFW_S_NO_MORE_ITEMS; +} + +static HRESULT testsink_connect(struct strmbase_sink *iface, IPin *peer, const AM_MEDIA_TYPE *mt) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->pin.filter); + if (filter->mt && !IsEqualGUID(&mt->majortype, &filter->mt->majortype)) + return VFW_E_TYPE_NOT_ACCEPTED; + return S_OK; +} + +static const struct strmbase_sink_ops testsink_ops = +{ + .base.pin_query_interface = testsink_query_interface, + .base.pin_get_media_type = testsink_get_media_type, + .sink_connect = testsink_connect, +}; + +static void testfilter_init(struct testfilter *filter) +{ + static const GUID clsid = {0xabacab}; + memset(filter, 0, sizeof(*filter)); + strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops); + strmbase_sink_init(&filter->sink, &filter->filter, L"sink", &testsink_ops, NULL); +} + +static void test_connect_pin(IBaseFilter *filter) +{ + AM_MEDIA_TYPE mt, req_mt, *source_mt; + struct testfilter testsink; + IEnumMediaTypes *enummt; + IMediaControl *control; + IFilterGraph2 *graph; + IPin *source, *peer; + HRESULT hr; + ULONG ref; + + CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, + &IID_IFilterGraph2, (void **)&graph); + testfilter_init(&testsink); + IFilterGraph2_AddFilter(graph, &testsink.filter.IBaseFilter_iface, L"sink"); + IFilterGraph2_AddFilter(graph, filter, L"source"); + IFilterGraph2_QueryInterface(graph, &IID_IMediaControl, (void **)&control); + + IBaseFilter_FindPin(filter, L"Capture", &source); + + peer = (IPin *)0xdeadbeef; + hr = IPin_ConnectedTo(source, &peer); + ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#lx.\n", hr); + ok(!peer, "Got peer %p.\n", peer); + + hr = IPin_ConnectionMediaType(source, &mt); + ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#lx.\n", hr); + + /* Exact connection. */ + + IPin_EnumMediaTypes(source, &enummt); + IEnumMediaTypes_Next(enummt, 1, &source_mt, NULL); + IEnumMediaTypes_Release(enummt); + CopyMediaType(&req_mt, source_mt); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == VFW_E_NOT_STOPPED, "Got hr %#lx.\n", hr); + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IPin_ConnectedTo(source, &peer); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(peer == &testsink.sink.pin.IPin_iface, "Got peer %p.\n", peer); + IPin_Release(peer); + + hr = IPin_ConnectionMediaType(source, &mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(compare_media_types(&mt, &req_mt), "Media types didn't match.\n"); + ok(compare_media_types(&testsink.sink.pin.mt, &req_mt), "Media types didn't match.\n"); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_Disconnect(graph, source); + ok(hr == VFW_E_NOT_STOPPED, "Got hr %#lx.\n", hr); + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IFilterGraph2_Disconnect(graph, source); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_Disconnect(graph, source); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + ok(testsink.sink.pin.peer == source, "Got peer %p.\n", testsink.sink.pin.peer); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + req_mt.lSampleSize = 999; + req_mt.bTemporalCompression = req_mt.bFixedSizeSamples = TRUE; + req_mt.subtype = MEDIASUBTYPE_RGB8; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(compare_media_types(&testsink.sink.pin.mt, &req_mt), "Media types didn't match.\n"); + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + req_mt.majortype = MEDIATYPE_Stream; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + todo_wine ok(hr == VFW_E_INVALIDMEDIATYPE, "Got hr %#lx.\n", hr); + req_mt.majortype = MEDIATYPE_Audio; + + /* Connection with wildcards. */ + + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(compare_media_types(&testsink.sink.pin.mt, source_mt), "Media types didn't match.\n"); + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + req_mt.majortype = GUID_NULL; + req_mt.subtype = MEDIASUBTYPE_RGB8; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == VFW_E_NO_ACCEPTABLE_TYPES, "Got hr %#lx.\n", hr); + + req_mt.subtype = MEDIASUBTYPE_PCM; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(compare_media_types(&testsink.sink.pin.mt, source_mt), "Media types didn't match.\n"); + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + req_mt.subtype = MEDIASUBTYPE_MPEG1AudioPayload; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == VFW_E_NO_ACCEPTABLE_TYPES, "Got hr %#lx.\n", hr); + + req_mt.subtype = GUID_NULL; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(compare_media_types(&testsink.sink.pin.mt, source_mt), "Media types didn't match.\n"); + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + req_mt.formattype = FORMAT_None; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == VFW_E_NO_ACCEPTABLE_TYPES, "Got hr %#lx.\n", hr); + + req_mt.majortype = MEDIATYPE_Audio; + req_mt.subtype = MEDIASUBTYPE_PCM; + req_mt.formattype = GUID_NULL; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(compare_media_types(&testsink.sink.pin.mt, source_mt), "Media types didn't match.\n"); + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + req_mt.subtype = MEDIASUBTYPE_MPEG1AudioPayload; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == VFW_E_NO_ACCEPTABLE_TYPES, "Got hr %#lx.\n", hr); + + req_mt.subtype = GUID_NULL; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(compare_media_types(&testsink.sink.pin.mt, source_mt), "Media types didn't match.\n"); + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + req_mt.majortype = MEDIATYPE_Stream; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == VFW_E_NO_ACCEPTABLE_TYPES, "Got hr %#lx.\n", hr); + + /* Test enumeration of sink media types. */ + + testsink.mt = &req_mt; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, NULL); + ok(hr == VFW_E_NO_ACCEPTABLE_TYPES, "Got hr %#lx.\n", hr); + + req_mt.majortype = MEDIATYPE_Audio; + req_mt.subtype = MEDIASUBTYPE_PCM; + req_mt.formattype = FORMAT_WaveFormatEx; + req_mt.lSampleSize = 444; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(compare_media_types(&testsink.sink.pin.mt, &req_mt), "Media types didn't match.\n"); + + IPin_Release(source); + IMediaControl_Release(control); + ref = IFilterGraph2_Release(graph); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + ref = IBaseFilter_Release(&testsink.filter.IBaseFilter_iface); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + static void test_stream_config(IBaseFilter *filter) { AUDIO_STREAM_CONFIG_CAPS caps; @@ -639,6 +883,7 @@ START_TEST(audiorecord) test_unconnected_filter_state(filter); test_pin_info(filter); test_media_types(filter); + test_connect_pin(filter); /* This calls SetFormat() and hence should be run last. */ test_stream_config(filter);
From: Zebediah Figura zfigura@codeweavers.com
--- dlls/qcap/tests/audiorecord.c | 85 +++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+)
diff --git a/dlls/qcap/tests/audiorecord.c b/dlls/qcap/tests/audiorecord.c index 9e7b2f944a7..503500e8ef4 100644 --- a/dlls/qcap/tests/audiorecord.c +++ b/dlls/qcap/tests/audiorecord.c @@ -547,6 +547,89 @@ static void testfilter_init(struct testfilter *filter) strmbase_sink_init(&filter->sink, &filter->filter, L"sink", &testsink_ops, NULL); }
+static void test_source_allocator(IFilterGraph2 *graph, IMediaControl *control, + IPin *source, struct testfilter *testsink) +{ + ALLOCATOR_PROPERTIES props, req_props = {2, 3200, 32, 0}; + IAMBufferNegotiation *negotiation; + IMemAllocator *allocator; + IMediaSample *sample; + WAVEFORMATEX format; + AM_MEDIA_TYPE mt; + HRESULT hr; + + IPin_QueryInterface(source, &IID_IAMBufferNegotiation, (void **)&negotiation); + + init_pcm_mt(&mt, &format, 1, 32000, 16); + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink->sink.pin.IPin_iface, &mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + ok(!!testsink->sink.pAllocator, "Expected an allocator.\n"); + hr = IMemAllocator_GetProperties(testsink->sink.pAllocator, &props); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(props.cBuffers == 4, "Got %ld buffers.\n", props.cBuffers); + ok(props.cbBuffer == 32000, "Got size %ld.\n", props.cbBuffer); + ok(props.cbAlign == 1, "Got alignment %ld.\n", props.cbAlign); + ok(!props.cbPrefix, "Got prefix %ld.\n", props.cbPrefix); + + hr = IMemAllocator_GetBuffer(testsink->sink.pAllocator, &sample, NULL, NULL, 0); + ok(hr == VFW_E_NOT_COMMITTED, "Got hr %#lx.\n", hr); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemAllocator_GetBuffer(testsink->sink.pAllocator, &sample, NULL, NULL, AM_GBF_NOWAIT); + todo_wine ok(hr == VFW_E_TIMEOUT, "Got hr %#lx.\n", hr); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemAllocator_GetBuffer(testsink->sink.pAllocator, &sample, NULL, NULL, 0); + ok(hr == VFW_E_NOT_COMMITTED, "Got hr %#lx.\n", hr); + + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink->sink.pin.IPin_iface); + + init_pcm_mt(&mt, &format, 1, 32000, 8); + format.nAvgBytesPerSec = 127; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink->sink.pin.IPin_iface, &mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + ok(!!testsink->sink.pAllocator, "Expected an allocator.\n"); + hr = IMemAllocator_GetProperties(testsink->sink.pAllocator, &props); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(props.cBuffers == 4, "Got %ld buffers.\n", props.cBuffers); + ok(props.cbBuffer == 62, "Got size %ld.\n", props.cbBuffer); + ok(props.cbAlign == 1, "Got alignment %ld.\n", props.cbAlign); + ok(!props.cbPrefix, "Got prefix %ld.\n", props.cbPrefix); + + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink->sink.pin.IPin_iface); + + CoCreateInstance(&CLSID_MemoryAllocator, NULL, CLSCTX_INPROC_SERVER, + &IID_IMemAllocator, (void **)&allocator); + testsink->sink.pAllocator = allocator; + + hr = IMemAllocator_SetProperties(allocator, &req_props, &props); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + init_pcm_mt(&mt, &format, 1, 32000, 16); + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink->sink.pin.IPin_iface, &mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + todo_wine ok(testsink->sink.pAllocator && testsink->sink.pAllocator != allocator, + "Got unexpected allocator %p.\n", testsink->sink.pAllocator); + hr = IMemAllocator_GetProperties(testsink->sink.pAllocator, &props); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(props.cBuffers == 4, "Got %ld buffers.\n", props.cBuffers); + ok(props.cbBuffer == 32000, "Got size %ld.\n", props.cbBuffer); + ok(props.cbAlign == 1, "Got alignment %ld.\n", props.cbAlign); + ok(!props.cbPrefix, "Got prefix %ld.\n", props.cbPrefix); + + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink->sink.pin.IPin_iface); +} + static void test_connect_pin(IBaseFilter *filter) { AM_MEDIA_TYPE mt, req_mt, *source_mt; @@ -567,6 +650,8 @@ static void test_connect_pin(IBaseFilter *filter)
IBaseFilter_FindPin(filter, L"Capture", &source);
+ test_source_allocator(graph, control, source, &testsink); + peer = (IPin *)0xdeadbeef; hr = IPin_ConnectedTo(source, &peer); ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#lx.\n", hr);
From: Zebediah Figura zfigura@codeweavers.com
--- dlls/qcap/Makefile.in | 2 +- dlls/qcap/audiorecord.c | 44 ++++++++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 12 deletions(-)
diff --git a/dlls/qcap/Makefile.in b/dlls/qcap/Makefile.in index b61a4a05ab2..56482c23ced 100644 --- a/dlls/qcap/Makefile.in +++ b/dlls/qcap/Makefile.in @@ -1,6 +1,6 @@ MODULE = qcap.dll UNIXLIB = qcap.so -IMPORTS = strmbase strmiids uuid ole32 oleaut32 +IMPORTS = strmbase strmiids uuid ole32 oleaut32 winmm DELAYIMPORTS = msvfw32
C_SRCS = \ diff --git a/dlls/qcap/audiorecord.c b/dlls/qcap/audiorecord.c index 01f0fed3288..52cbfbdb04f 100644 --- a/dlls/qcap/audiorecord.c +++ b/dlls/qcap/audiorecord.c @@ -30,6 +30,9 @@ struct audio_record
struct strmbase_source source; IAMStreamConfig IAMStreamConfig_iface; + + unsigned int id; + HWAVEIN device; };
static struct audio_record *impl_from_strmbase_filter(struct strmbase_filter *filter) @@ -135,6 +138,8 @@ static HRESULT WINAPI audio_record_source_DecideBufferSize(struct strmbase_sourc struct audio_record *filter = impl_from_strmbase_filter(iface->pin.filter); const WAVEFORMATEX *format = (void *)filter->source.pin.mt.pbFormat; ALLOCATOR_PROPERTIES ret_props; + MMRESULT ret; + HRESULT hr;
props->cBuffers = 4; /* This is the algorithm that native uses. The alignment to an even number @@ -143,7 +148,23 @@ static HRESULT WINAPI audio_record_source_DecideBufferSize(struct strmbase_sourc props->cbAlign = 1; props->cbPrefix = 0;
- return IMemAllocator_SetProperties(allocator, props, &ret_props); + if (FAILED(hr = IMemAllocator_SetProperties(allocator, props, &ret_props))) + return hr; + + if ((ret = waveInOpen(&filter->device, filter->id, format, 0, 0, CALLBACK_NULL)) != MMSYSERR_NOERROR) + { + ERR("Failed to open device %u, error %u.\n", filter->id, ret); + return E_FAIL; + } + + return S_OK; +} + +static void audio_record_source_disconnect(struct strmbase_source *iface) +{ + struct audio_record *filter = impl_from_strmbase_filter(iface->pin.filter); + + waveInClose(filter->device); }
static const struct strmbase_source_ops source_ops = @@ -154,6 +175,7 @@ static const struct strmbase_source_ops source_ops = .pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection, .pfnDecideAllocator = BaseOutputPinImpl_DecideAllocator, .pfnDecideBufferSize = audio_record_source_DecideBufferSize, + .source_disconnect = audio_record_source_disconnect, };
static struct audio_record *impl_from_IAMStreamConfig(IAMStreamConfig *iface) @@ -331,21 +353,21 @@ static HRESULT WINAPI PPB_InitNew(IPersistPropertyBag *iface) return E_NOTIMPL; }
-static HRESULT WINAPI PPB_Load(IPersistPropertyBag *iface, IPropertyBag *pPropBag, - IErrorLog *pErrorLog) +static HRESULT WINAPI PPB_Load(IPersistPropertyBag *iface, IPropertyBag *bag, IErrorLog *error_log) { - struct audio_record *This = impl_from_IPersistPropertyBag(iface); - HRESULT hr; + struct audio_record *filter = impl_from_IPersistPropertyBag(iface); VARIANT var; + HRESULT hr;
- TRACE("(%p/%p)->(%p, %p)\n", iface, This, pPropBag, pErrorLog); + TRACE("filter %p, bag %p, error_log %p.\n", filter, bag, error_log);
V_VT(&var) = VT_I4; - hr = IPropertyBag_Read(pPropBag, L"WaveInID", &var, pErrorLog); - if (SUCCEEDED(hr)) - { - FIXME("FIXME: implement opening waveIn device %ld\n", V_I4(&var)); - } + if (FAILED(hr = IPropertyBag_Read(bag, L"WaveInID", &var, error_log))) + return hr; + + EnterCriticalSection(&filter->filter.filter_cs); + filter->id = V_I4(&var); + LeaveCriticalSection(&filter->filter.filter_cs);
return hr; }
From: Zebediah Figura zfigura@codeweavers.com
--- dlls/qcap/audiorecord.c | 215 ++++++++++++++++++++++++++++++++++++++- dlls/qcap/qcap_private.h | 1 + 2 files changed, 215 insertions(+), 1 deletion(-)
diff --git a/dlls/qcap/audiorecord.c b/dlls/qcap/audiorecord.c index 52cbfbdb04f..3a9339fcc2d 100644 --- a/dlls/qcap/audiorecord.c +++ b/dlls/qcap/audiorecord.c @@ -33,6 +33,15 @@ struct audio_record
unsigned int id; HWAVEIN device; + HANDLE event; + HANDLE thread; + + /* FIXME: It would be nice to avoid duplicating this variable with strmbase. + * However, synchronization is tricky; we need access to be protected by a + * separate lock. */ + FILTER_STATE state; + CONDITION_VARIABLE state_cv; + CRITICAL_SECTION state_cs; };
static struct audio_record *impl_from_strmbase_filter(struct strmbase_filter *filter) @@ -151,7 +160,8 @@ static HRESULT WINAPI audio_record_source_DecideBufferSize(struct strmbase_sourc if (FAILED(hr = IMemAllocator_SetProperties(allocator, props, &ret_props))) return hr;
- if ((ret = waveInOpen(&filter->device, filter->id, format, 0, 0, CALLBACK_NULL)) != MMSYSERR_NOERROR) + if ((ret = waveInOpen(&filter->device, filter->id, format, + (DWORD_PTR)filter->event, 0, CALLBACK_EVENT)) != MMSYSERR_NOERROR) { ERR("Failed to open device %u, error %u.\n", filter->id, ret); return E_FAIL; @@ -293,6 +303,9 @@ static void audio_record_destroy(struct strmbase_filter *iface) { struct audio_record *filter = impl_from_strmbase_filter(iface);
+ filter->state_cs.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&filter->state_cs); + CloseHandle(filter->event); strmbase_source_cleanup(&filter->source); strmbase_filter_cleanup(&filter->filter); free(filter); @@ -311,11 +324,200 @@ static HRESULT audio_record_query_interface(struct strmbase_filter *iface, REFII return S_OK; }
+static DWORD WINAPI stream_thread(void *arg) +{ + struct audio_record *filter = arg; + const WAVEFORMATEX *format = (void *)filter->source.pin.mt.pbFormat; + bool started = false; + MMRESULT ret; + + /* FIXME: We should probably queue several buffers instead of just one. */ + + EnterCriticalSection(&filter->state_cs); + + for (;;) + { + IMediaSample *sample; + WAVEHDR header = {0}; + HRESULT hr; + BYTE *data; + + while (filter->state == State_Paused) + SleepConditionVariableCS(&filter->state_cv, &filter->state_cs, INFINITE); + + if (filter->state == State_Stopped) + break; + + if (FAILED(hr = IMemAllocator_GetBuffer(filter->source.pAllocator, &sample, NULL, NULL, 0))) + { + ERR("Failed to get sample, hr %#lx.\n", hr); + break; + } + + IMediaSample_GetPointer(sample, &data); + + header.lpData = (void *)data; + header.dwBufferLength = IMediaSample_GetSize(sample); + if ((ret = waveInPrepareHeader(filter->device, &header, sizeof(header))) != MMSYSERR_NOERROR) + ERR("Failed to prepare header, error %u.\n", ret); + + if ((ret = waveInAddBuffer(filter->device, &header, sizeof(header))) != MMSYSERR_NOERROR) + ERR("Failed to add buffer, error %u.\n", ret); + + if (!started) + { + if ((ret = waveInStart(filter->device)) != MMSYSERR_NOERROR) + ERR("Failed to start, error %u.\n", ret); + started = true; + } + + while (!(header.dwFlags & WHDR_DONE) && filter->state == State_Running) + { + LeaveCriticalSection(&filter->state_cs); + + if ((ret = WaitForSingleObject(filter->event, INFINITE))) + ERR("Failed to wait, error %u.\n", ret); + + EnterCriticalSection(&filter->state_cs); + } + + if (filter->state != State_Running) + { + TRACE("State is %#x; resetting.\n", filter->state); + if ((ret = waveInReset(filter->device)) != MMSYSERR_NOERROR) + ERR("Failed to reset, error %u.\n", ret); + } + + if ((ret = waveInUnprepareHeader(filter->device, &header, sizeof(header))) != MMSYSERR_NOERROR) + ERR("Failed to unprepare header, error %u.\n", ret); + + IMediaSample_SetActualDataLength(sample, header.dwBytesRecorded); + + if (filter->state == State_Running) + { + REFERENCE_TIME start_pts, end_pts; + MMTIME time; + + time.wType = TIME_BYTES; + if ((ret = waveInGetPosition(filter->device, &time, sizeof(time))) != MMSYSERR_NOERROR) + ERR("Failed to get position, error %u.\n", ret); + if (time.wType != TIME_BYTES) + ERR("Got unexpected type %#x.\n", time.wType); + end_pts = MulDiv(time.u.cb, 10000000, format->nAvgBytesPerSec); + start_pts = MulDiv(time.u.cb - header.dwBytesRecorded, 10000000, format->nAvgBytesPerSec); + IMediaSample_SetTime(sample, &start_pts, &end_pts); + + TRACE("Sending buffer %p.\n", sample); + hr = IMemInputPin_Receive(filter->source.pMemInputPin, sample); + IMediaSample_Release(sample); + if (FAILED(hr)) + { + ERR("IMemInputPin::Receive() returned %#lx.\n", hr); + break; + } + } + } + + LeaveCriticalSection(&filter->state_cs); + + if (started && (ret = waveInStop(filter->device)) != MMSYSERR_NOERROR) + ERR("Failed to stop, error %u.\n", ret); + + return 0; +} + +static HRESULT audio_record_init_stream(struct strmbase_filter *iface) +{ + struct audio_record *filter = impl_from_strmbase_filter(iface); + HRESULT hr; + + if (!filter->source.pin.peer) + return S_OK; + + if (FAILED(hr = IMemAllocator_Commit(filter->source.pAllocator))) + ERR("Failed to commit allocator, hr %#lx.\n", hr); + + EnterCriticalSection(&filter->state_cs); + filter->state = State_Paused; + LeaveCriticalSection(&filter->state_cs); + + filter->thread = CreateThread(NULL, 0, stream_thread, filter, 0, NULL); + + return S_OK; +} + +static HRESULT audio_record_start_stream(struct strmbase_filter *iface, REFERENCE_TIME time) +{ + struct audio_record *filter = impl_from_strmbase_filter(iface); + + if (!filter->source.pin.peer) + return S_OK; + + EnterCriticalSection(&filter->state_cs); + filter->state = State_Running; + LeaveCriticalSection(&filter->state_cs); + WakeConditionVariable(&filter->state_cv); + return S_OK; +} + +static HRESULT audio_record_stop_stream(struct strmbase_filter *iface) +{ + struct audio_record *filter = impl_from_strmbase_filter(iface); + + if (!filter->source.pin.peer) + return S_OK; + + EnterCriticalSection(&filter->state_cs); + filter->state = State_Paused; + LeaveCriticalSection(&filter->state_cs); + SetEvent(filter->event); + return S_OK; +} + +static HRESULT audio_record_cleanup_stream(struct strmbase_filter *iface) +{ + struct audio_record *filter = impl_from_strmbase_filter(iface); + HRESULT hr; + + if (!filter->source.pin.peer) + return S_OK; + + EnterCriticalSection(&filter->state_cs); + filter->state = State_Stopped; + LeaveCriticalSection(&filter->state_cs); + WakeConditionVariable(&filter->state_cv); + SetEvent(filter->event); + + WaitForSingleObject(filter->thread, INFINITE); + CloseHandle(filter->thread); + filter->thread = NULL; + + hr = IMemAllocator_Decommit(filter->source.pAllocator); + if (hr != S_OK && hr != VFW_E_NOT_COMMITTED) + ERR("Failed to decommit allocator, hr %#lx.\n", hr); + + return S_OK; +} + +static HRESULT audio_record_wait_state(struct strmbase_filter *iface, DWORD timeout) +{ + struct audio_record *filter = impl_from_strmbase_filter(iface); + + if (filter->filter.state == State_Paused) + return VFW_S_CANT_CUE; + return S_OK; +} + static const struct strmbase_filter_ops filter_ops = { .filter_get_pin = audio_record_get_pin, .filter_destroy = audio_record_destroy, .filter_query_interface = audio_record_query_interface, + .filter_init_stream = audio_record_init_stream, + .filter_start_stream = audio_record_start_stream, + .filter_stop_stream = audio_record_stop_stream, + .filter_cleanup_stream = audio_record_cleanup_stream, + .filter_wait_state = audio_record_wait_state, };
static HRESULT WINAPI PPB_QueryInterface(IPersistPropertyBag *iface, REFIID riid, LPVOID *ppv) @@ -398,12 +600,23 @@ HRESULT audio_record_create(IUnknown *outer, IUnknown **out) if (!(object = calloc(1, sizeof(*object)))) return E_OUTOFMEMORY;
+ if (!(object->event = CreateEventW(NULL, FALSE, FALSE, NULL))) + { + free(object); + return E_OUTOFMEMORY; + } + object->IPersistPropertyBag_iface.lpVtbl = &PersistPropertyBagVtbl; strmbase_filter_init(&object->filter, outer, &CLSID_AudioRecord, &filter_ops);
strmbase_source_init(&object->source, &object->filter, L"Capture", &source_ops); object->IAMStreamConfig_iface.lpVtbl = &stream_config_vtbl;
+ object->state = State_Stopped; + InitializeConditionVariable(&object->state_cv); + InitializeCriticalSection(&object->state_cs); + object->state_cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": audio_record.state_cs"); + TRACE("Created audio recorder %p.\n", object); *out = &object->filter.IUnknown_inner; return S_OK; diff --git a/dlls/qcap/qcap_private.h b/dlls/qcap/qcap_private.h index 140e56539b5..ae1571b0baf 100644 --- a/dlls/qcap/qcap_private.h +++ b/dlls/qcap/qcap_private.h @@ -24,6 +24,7 @@ #define COBJMACROS #define NONAMELESSSTRUCT #define NONAMELESSUNION +#include <stdbool.h> #include "dshow.h" #include "winternl.h" #include "wine/unixlib.h"
From: Zebediah Figura zfigura@codeweavers.com
--- dlls/qcap/tests/audiorecord.c | 162 +++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 2 deletions(-)
diff --git a/dlls/qcap/tests/audiorecord.c b/dlls/qcap/tests/audiorecord.c index 503500e8ef4..936703f7ebf 100644 --- a/dlls/qcap/tests/audiorecord.c +++ b/dlls/qcap/tests/audiorecord.c @@ -303,7 +303,7 @@ static void test_unconnected_filter_state(IBaseFilter *filter) ok(hr == S_OK, "Got hr %#lx.\n", hr);
hr = IBaseFilter_GetState(filter, 0, &state); - todo_wine ok(hr == VFW_S_CANT_CUE, "Got hr %#lx.\n", hr); + ok(hr == VFW_S_CANT_CUE, "Got hr %#lx.\n", hr); ok(state == State_Paused, "Got state %u.\n", state);
hr = IBaseFilter_Run(filter, 0); @@ -317,7 +317,7 @@ static void test_unconnected_filter_state(IBaseFilter *filter) ok(hr == S_OK, "Got hr %#lx.\n", hr);
hr = IBaseFilter_GetState(filter, 0, &state); - todo_wine ok(hr == VFW_S_CANT_CUE, "Got hr %#lx.\n", hr); + ok(hr == VFW_S_CANT_CUE, "Got hr %#lx.\n", hr); ok(state == State_Paused, "Got state %u.\n", state);
hr = IBaseFilter_Stop(filter); @@ -472,6 +472,8 @@ struct testfilter struct strmbase_filter filter; struct strmbase_sink sink; const AM_MEDIA_TYPE *mt; + HANDLE sample_event, eos_event; + unsigned int sample_count, eos_count; };
static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface) @@ -492,12 +494,24 @@ static void testfilter_destroy(struct strmbase_filter *iface) struct testfilter *filter = impl_from_strmbase_filter(iface); strmbase_sink_cleanup(&filter->sink); strmbase_filter_cleanup(&filter->filter); + CloseHandle(filter->eos_event); + CloseHandle(filter->sample_event); +} + +static HRESULT testfilter_init_stream(struct strmbase_filter *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + + filter->eos_count = 0; + filter->sample_count = 0; + return S_OK; }
static const struct strmbase_filter_ops testfilter_ops = { .filter_get_pin = testfilter_get_pin, .filter_destroy = testfilter_destroy, + .filter_init_stream = testfilter_init_stream, };
static HRESULT testsink_query_interface(struct strmbase_pin *iface, REFIID iid, void **out) @@ -532,11 +546,61 @@ static HRESULT testsink_connect(struct strmbase_sink *iface, IPin *peer, const A return S_OK; }
+static HRESULT WINAPI testsink_Receive(struct strmbase_sink *iface, IMediaSample *sample) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->pin.filter); + REFERENCE_TIME start, end; + AM_MEDIA_TYPE *mt; + HRESULT hr; + + hr = IMediaSample_GetTime(sample, &start, &end); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + if (winetest_debug > 1) + trace("%04lx: Got sample with timestamps %I64d-%I64d.\n", GetCurrentThreadId(), start, end); + + mt = (void *)0xdeadbeef; + hr = IMediaSample_GetMediaType(sample, &mt); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + ok(!mt, "Got unexpected media type %p.\n", mt); + + /* Usually the actual size of the sample is the same as its capacity. + * For unclear reasons, though, this isn't always the case. */ + + ok(!filter->eos_count, "Got a sample after EOS.\n"); + ++filter->sample_count; + SetEvent(filter->sample_event); + return S_OK; +} + +static HRESULT testsink_eos(struct strmbase_sink *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->pin.filter); + + if (winetest_debug > 1) + trace("%04lx: Got EOS.\n", GetCurrentThreadId()); + + ok(!filter->eos_count, "Got %u EOS events.\n", filter->eos_count + 1); + ++filter->eos_count; + SetEvent(filter->eos_event); + return S_OK; +} + +static HRESULT testsink_new_segment(struct strmbase_sink *iface, + REFERENCE_TIME start, REFERENCE_TIME end, double rate) +{ + ok(0, "Unexpected new segment.\n"); + return S_OK; +} + static const struct strmbase_sink_ops testsink_ops = { .base.pin_query_interface = testsink_query_interface, .base.pin_get_media_type = testsink_get_media_type, .sink_connect = testsink_connect, + .pfnReceive = testsink_Receive, + .sink_eos = testsink_eos, + .sink_new_segment = testsink_new_segment, };
static void testfilter_init(struct testfilter *filter) @@ -545,6 +609,8 @@ static void testfilter_init(struct testfilter *filter) memset(filter, 0, sizeof(*filter)); strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops); strmbase_sink_init(&filter->sink, &filter->filter, L"sink", &testsink_ops, NULL); + filter->sample_event = CreateEventW(NULL, FALSE, FALSE, NULL); + filter->eos_event = CreateEventW(NULL, FALSE, FALSE, NULL); }
static void test_source_allocator(IFilterGraph2 *graph, IMediaControl *control, @@ -630,6 +696,97 @@ static void test_source_allocator(IFilterGraph2 *graph, IMediaControl *control, IFilterGraph2_Disconnect(graph, &testsink->sink.pin.IPin_iface); }
+static void test_filter_state(IFilterGraph2 *graph, IMediaControl *control, + IPin *source, struct testfilter *testsink) +{ + IMemAllocator *allocator; + IMediaSample *sample; + OAFilterState state; + WAVEFORMATEX format; + AM_MEDIA_TYPE mt; + HRESULT hr; + DWORD ret; + + init_pcm_mt(&mt, &format, 1, 32000, 16); + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink->sink.pin.IPin_iface, &mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Stopped, "Got state %lu.\n", state); + + allocator = testsink->sink.pAllocator; + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == VFW_S_CANT_CUE, "Got hr %#lx.\n", hr); + ok(state == State_Paused, "Got state %lu.\n", state); + + hr = IMediaControl_Run(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + /* starting up the device can be a little slow */ + ret = WaitForSingleObject(testsink->sample_event, 5000); + ok(!ret, "Got %lu.\n", ret); + + ret = WaitForSingleObject(testsink->sample_event, 1000); + ok(!ret, "Got %lu.\n", ret); + + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Running, "Got state %lu.\n", state); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == VFW_S_CANT_CUE, "Got hr %#lx.\n", hr); + ok(state == State_Paused, "Got state %lu.\n", state); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Stopped, "Got state %lu.\n", state); + + hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0); + ok(hr == VFW_E_NOT_COMMITTED, "Got hr %#lx.\n", hr); + + hr = IMediaControl_Run(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Running, "Got state %lu.\n", state); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Stopped, "Got state %lu.\n", state); + + /* Test committing the allocator before the capture filter does. */ + + hr = IMemAllocator_Commit(allocator); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemAllocator_Decommit(allocator); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink->sink.pin.IPin_iface); +} + static void test_connect_pin(IBaseFilter *filter) { AM_MEDIA_TYPE mt, req_mt, *source_mt; @@ -651,6 +808,7 @@ static void test_connect_pin(IBaseFilter *filter) IBaseFilter_FindPin(filter, L"Capture", &source);
test_source_allocator(graph, control, source, &testsink); + test_filter_state(graph, control, source, &testsink);
peer = (IPin *)0xdeadbeef; hr = IPin_ConnectedTo(source, &peer);
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=132711
Your paranoid android.
=== w7u_2qxl (32 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w7u_adm (32 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w7u_el (32 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w8 (32 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w8adm (32 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w864 (32 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w1064v1507 (32 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w1064v1809 (32 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w1064_tsign (32 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w10pro64 (32 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w11pro64 (32 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w7pro64 (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w864 (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w1064v1507 (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w1064v1809 (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w1064_2qxl (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w1064_adm (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w1064_tsign (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w10pro64 (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w10pro64_en_AE_u8 (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w10pro64_ar (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w10pro64_ja (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.
=== w10pro64_zh_CN (64 bit report) ===
qcap: audiorecord.c:1134: Test failed: Got outstanding refcount 1.