-- v2: qcap/tests: Test audio capture streaming. qcap/audiorecord: Implement streaming. qcap/audiorecord: Open a winmm device when connecting. qcap/tests: Test audio capture allocator properties. qcap/tests: Add tests for audio capture pin connection. qcap/audiorecord: Implement DecideBufferSize().
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 | 82 +++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+)
diff --git a/dlls/qcap/tests/audiorecord.c b/dlls/qcap/tests/audiorecord.c index 9e7b2f944a7..ca41ffece66 100644 --- a/dlls/qcap/tests/audiorecord.c +++ b/dlls/qcap/tests/audiorecord.c @@ -547,6 +547,86 @@ 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}; + IMemAllocator *allocator; + IMediaSample *sample; + WAVEFORMATEX format; + AM_MEDIA_TYPE mt; + HRESULT 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); + + 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 +647,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 ca41ffece66..b73cbba3548 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, @@ -627,6 +693,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; @@ -648,6 +805,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);
The failure came from a bad patch splitting. It should be fixed now; the only testbot failure left is in urlmon:protocol.