From: Anton Baskanov baskanov@gmail.com
--- dlls/amstream/tests/amstream.c | 295 +++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+)
diff --git a/dlls/amstream/tests/amstream.c b/dlls/amstream/tests/amstream.c index f1e17ef49cf..abf0428140b 100644 --- a/dlls/amstream/tests/amstream.c +++ b/dlls/amstream/tests/amstream.c @@ -386,6 +386,7 @@ static void test_interfaces(void) check_interface(stream, &IID_IMediaStreamFilter, FALSE); check_interface(stream, &IID_IMultiMediaStream, FALSE); check_interface(stream, &IID_IPersist, FALSE); + check_interface(stream, &IID_IQualityControl, FALSE);
IMediaStream_Release(stream);
@@ -408,6 +409,7 @@ static void test_interfaces(void) check_interface(stream, &IID_IMediaStreamFilter, FALSE); check_interface(stream, &IID_IMultiMediaStream, FALSE); check_interface(stream, &IID_IPersist, FALSE); + check_interface(stream, &IID_IQualityControl, FALSE);
IMediaStream_Release(stream);
@@ -1017,10 +1019,13 @@ struct testfilter struct strmbase_filter filter; struct strmbase_source source; IMediaSeeking IMediaSeeking_iface; + IQualityControl IQualityControl_iface; LONGLONG current_position; LONGLONG stop_position; const AM_MEDIA_TYPE *preferred_mt; HANDLE wait_state_event; + IBaseFilter *qc_notify_sender; + Quality qc_notify_quality; HRESULT get_duration_hr; HRESULT get_stop_position_hr; HRESULT set_positions_hr; @@ -1028,6 +1033,7 @@ struct testfilter HRESULT cleanup_stream_hr; HRESULT wait_state_hr; HRESULT is_format_supported_hr; + HRESULT qc_notify_hr; };
static inline struct testfilter *impl_from_BaseFilter(struct strmbase_filter *iface) @@ -1123,6 +1129,8 @@ static HRESULT testsource_query_interface(struct strmbase_pin *iface, REFIID iid
if (IsEqualGUID(iid, &IID_IMediaSeeking) && filter->IMediaSeeking_iface.lpVtbl) *out = &filter->IMediaSeeking_iface; + else if (IsEqualGUID(iid, &IID_IQualityControl) && filter->IQualityControl_iface.lpVtbl) + *out = &filter->IQualityControl_iface; else return E_NOINTERFACE;
@@ -1358,6 +1366,54 @@ static const IMediaSeekingVtbl testsource_seeking_vtbl = testsource_seeking_GetPreroll, };
+static inline struct testfilter *impl_from_IQualityControl(IQualityControl *iface) +{ + return CONTAINING_RECORD(iface, struct testfilter, IQualityControl_iface); +} + +static HRESULT WINAPI testsource_qc_QueryInterface(IQualityControl *iface, REFIID iid, void **out) +{ + struct testfilter *filter = impl_from_IQualityControl(iface); + return IBaseFilter_QueryInterface(&filter->filter.IBaseFilter_iface, iid, out); +} + +static ULONG WINAPI testsource_qc_AddRef(IQualityControl *iface) +{ + struct testfilter *filter = impl_from_IQualityControl(iface); + return IBaseFilter_AddRef(&filter->filter.IBaseFilter_iface); +} + +static ULONG WINAPI testsource_qc_Release(IQualityControl *iface) +{ + struct testfilter *filter = impl_from_IQualityControl(iface); + return IBaseFilter_Release(&filter->filter.IBaseFilter_iface); +} + +static HRESULT WINAPI testsource_qc_Notify(IQualityControl *iface, IBaseFilter *sender, Quality q) +{ + struct testfilter *filter = impl_from_IQualityControl(iface); + + filter->qc_notify_sender = sender; + filter->qc_notify_quality = q; + + return filter->qc_notify_hr; +} + +static HRESULT WINAPI testsource_qc_SetSink(IQualityControl *iface, IQualityControl *sink) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static const IQualityControlVtbl testsource_qc_vtbl = +{ + testsource_qc_QueryInterface, + testsource_qc_AddRef, + testsource_qc_Release, + testsource_qc_Notify, + testsource_qc_SetSink, +}; + #define check_get_stream(a,b,c,d) check_get_stream_(__LINE__,a,b,c,d) static void check_get_stream_(int line, IAMMultiMediaStream *mmstream, IMediaStreamFilter *filter, const GUID *mspid, IMediaStream *expect) @@ -8190,6 +8246,244 @@ static void test_ddrawstream_create_sample(void) desc.ddckCKSrcBlt.dwColorSpaceHighValue); }
+static void test_ddrawstream_qc(void) +{ + static const BYTE test_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + IAMMultiMediaStream *mmstream = create_ammultimediastream(); + struct advise_time_cookie cookie = { 0 }; + IDirectDrawStreamSample *stream_sample; + IDirectDrawMediaStream *ddraw_stream; + IMediaFilter *graph_media_filter; + STREAM_TIME filter_start_time; + IMemInputPin *mem_input_pin; + IMediaStreamFilter *filter; + struct testfilter source; + struct testclock clock; + STREAM_TIME start_time; + STREAM_TIME end_time; + IGraphBuilder *graph; + IMediaStream *stream; + VIDEOINFO video_info; + AM_MEDIA_TYPE mt; + HANDLE thread; + HRESULT hr; + ULONG ref; + IPin *pin; + + hr = IAMMultiMediaStream_Initialize(mmstream, STREAMTYPE_READ, 0, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IAMMultiMediaStream_GetFilter(mmstream, &filter); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(!!filter, "Expected non-null filter.\n"); + hr = IAMMultiMediaStream_AddMediaStream(mmstream, NULL, &MSPID_PrimaryVideo, 0, &stream); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaStream_QueryInterface(stream, &IID_IDirectDrawMediaStream, (void **)&ddraw_stream); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaStream_QueryInterface(stream, &IID_IPin, (void **)&pin); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaStream_QueryInterface(stream, &IID_IMemInputPin, (void **)&mem_input_pin); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IAMMultiMediaStream_GetFilterGraph(mmstream, &graph); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(graph != NULL, "Expected non-NULL graph.\n"); + hr = IGraphBuilder_QueryInterface(graph, &IID_IMediaFilter, (void **)&graph_media_filter); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + testfilter_init(&source); + source.IQualityControl_iface.lpVtbl = &testsource_qc_vtbl; + hr = IGraphBuilder_AddFilter(graph, &source.filter.IBaseFilter_iface, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + testclock_init(&clock); + cookie.advise_time_called_event = CreateEventW(NULL, FALSE, FALSE, NULL); + ok(cookie.advise_time_called_event != NULL, "Expected non-NULL event."); + + video_info = rgb32_video_info; + video_info.bmiHeader.biWidth = 3; + video_info.bmiHeader.biHeight = 1; + mt = rgb32_mt; + mt.pbFormat = (BYTE *)&video_info; + hr = IGraphBuilder_ConnectDirect(graph, &source.source.pin.IPin_iface, pin, &mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IDirectDrawMediaStream_CreateSample(ddraw_stream, NULL, NULL, 0, &stream_sample); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + /* There are no quality control messages without a sync source. */ + hr = IMediaFilter_SetSyncSource(graph_media_filter, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_RUN); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + source.qc_notify_sender = (IBaseFilter *)0xdeadbeef; + source.qc_notify_quality.Type = Flood; + source.qc_notify_quality.Proportion = 0xdeadbeef; + source.qc_notify_quality.Late = 0xdeadbeef; + source.qc_notify_quality.TimeStamp = 0xdeadbeef; + + thread = ammediastream_async_receive_time(&source, + 11111111, 11111111, test_data, sizeof(test_data)); + ok(WaitForSingleObject(thread, 100) == WAIT_TIMEOUT, "Receive returned prematurely.\n"); + + hr = IDirectDrawStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + ok(!WaitForSingleObject(thread, 2000), "Wait timed out.\n"); + CloseHandle(thread); + + ok(source.qc_notify_sender == (IBaseFilter *)0xdeadbeef, "Got sender %p.\n", + source.qc_notify_sender); + ok(source.qc_notify_quality.Type == Flood, "Got type %d.\n", + source.qc_notify_quality.Type); + ok(source.qc_notify_quality.Proportion == 0xdeadbeef, "Got proportion %ld.\n", + source.qc_notify_quality.Proportion); + ok(source.qc_notify_quality.Late == 0xdeadbeef, + "Got late %s.\n", wine_dbgstr_longlong(source.qc_notify_quality.Late)); + ok(source.qc_notify_quality.TimeStamp == 0xdeadbeef, "Got time stamp %s.\n", + wine_dbgstr_longlong(source.qc_notify_quality.TimeStamp)); + + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_STOP); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaFilter_SetSyncSource(graph_media_filter, &clock.IReferenceClock_iface); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + clock.time = 12345678; + + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_RUN); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaStreamFilter_GetCurrentStreamTime(filter, &filter_start_time); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + /* Quality control message is sent on update completion, q.Late is computed + * as a difference between sample start time and update completion time. */ + + /* Test Update() before Reveive(). */ + source.qc_notify_sender = (IBaseFilter *)0xdeadbeef; + source.qc_notify_quality.Type = Flood; + source.qc_notify_quality.Proportion = 0xdeadbeef; + source.qc_notify_quality.Late = 0xdeadbeef; + source.qc_notify_quality.TimeStamp = 0xdeadbeef; + + clock.time = 12345678 - filter_start_time + 11111111; + + hr = IDirectDrawStreamSample_Update(stream_sample, SSUPDATE_ASYNC, NULL, NULL, 0); + ok(hr == MS_S_PENDING, "Got hr %#lx.\n", hr); + + clock.advise_time_cookie = &cookie; + clock.time = 12345678 - filter_start_time + 11111111 + 100000; + + start_time = 11111111 + 200000; + end_time = 11111111 + 200000; + thread = ammediastream_async_receive_time(&source, + start_time, end_time, test_data, sizeof(test_data)); + ok(!WaitForSingleObject(cookie.advise_time_called_event, 2000), "Wait timed out.\n"); + + ok(source.qc_notify_sender == (IBaseFilter *)0xdeadbeef, "Got sender %p.\n", + source.qc_notify_sender); + ok(source.qc_notify_quality.Type == Flood, "Got type %d.\n", + source.qc_notify_quality.Type); + ok(source.qc_notify_quality.Proportion == 0xdeadbeef, "Got proportion %ld.\n", + source.qc_notify_quality.Proportion); + ok(source.qc_notify_quality.Late == 0xdeadbeef, + "Got late %s.\n", wine_dbgstr_longlong(source.qc_notify_quality.Late)); + ok(source.qc_notify_quality.TimeStamp == 0xdeadbeef, "Got time stamp %s.\n", + wine_dbgstr_longlong(source.qc_notify_quality.TimeStamp)); + + clock.time = 12345678 - filter_start_time + 11111111 + 200000; + + SetEvent(cookie.event); + + ok(!WaitForSingleObject(thread, 2000), "Wait timed out.\n"); + CloseHandle(thread); + + hr = IDirectDrawStreamSample_CompletionStatus(stream_sample, 0, 0); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + todo_wine ok(source.qc_notify_sender == (IBaseFilter *)filter, "Got sender %p.\n", + source.qc_notify_sender); + todo_wine ok(source.qc_notify_quality.Type == Famine, "Got type %d.\n", + source.qc_notify_quality.Type); + todo_wine ok(source.qc_notify_quality.Proportion == 1000, "Got proportion %ld.\n", + source.qc_notify_quality.Proportion); + todo_wine ok(source.qc_notify_quality.Late == 0, "Got late %s.\n", + wine_dbgstr_longlong(source.qc_notify_quality.Late)); + todo_wine ok(source.qc_notify_quality.TimeStamp == start_time, "Got time stamp %s.\n", + wine_dbgstr_longlong(source.qc_notify_quality.TimeStamp)); + + /* Test Update() after Reveive(). */ + source.qc_notify_sender = (IBaseFilter *)0xdeadbeef; + source.qc_notify_quality.Type = Flood; + source.qc_notify_quality.Proportion = 0xdeadbeef; + source.qc_notify_quality.Late = 0xdeadbeef; + source.qc_notify_quality.TimeStamp = 0xdeadbeef; + + clock.advise_time_cookie = &cookie; + clock.time = 12345678 - filter_start_time + 11111111 + 300000; + + start_time = 11111111 + 400000; + end_time = 11111111 + 400000; + thread = ammediastream_async_receive_time(&source, + start_time, end_time, test_data, sizeof(test_data)); + ok(!WaitForSingleObject(cookie.advise_time_called_event, 2000), "Wait timed out.\n"); + + clock.time = 12345678 - filter_start_time + 11111111 + 400000; + SetEvent(cookie.event); + + ok(WaitForSingleObject(thread, 100) == WAIT_TIMEOUT, "Receive returned prematurely.\n"); + + ok(source.qc_notify_sender == (IBaseFilter *)0xdeadbeef, "Got sender %p.\n", + source.qc_notify_sender); + ok(source.qc_notify_quality.Type == Flood, "Got type %d.\n", + source.qc_notify_quality.Type); + ok(source.qc_notify_quality.Proportion == 0xdeadbeef, "Got proportion %ld.\n", + source.qc_notify_quality.Proportion); + ok(source.qc_notify_quality.Late == 0xdeadbeef, + "Got late %s.\n", wine_dbgstr_longlong(source.qc_notify_quality.Late)); + ok(source.qc_notify_quality.TimeStamp == 0xdeadbeef, "Got time stamp %s.\n", + wine_dbgstr_longlong(source.qc_notify_quality.TimeStamp)); + + clock.time = 12345678 - filter_start_time + 11111111 + 500000; + + hr = IDirectDrawStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + ok(!WaitForSingleObject(thread, 2000), "Wait timed out.\n"); + CloseHandle(thread); + + todo_wine ok(source.qc_notify_sender == (IBaseFilter *)filter, "Got sender %p.\n", + source.qc_notify_sender); + todo_wine ok(source.qc_notify_quality.Type == Famine, "Got type %d.\n", + source.qc_notify_quality.Type); + todo_wine ok(source.qc_notify_quality.Proportion == 1000, "Got proportion %ld.\n", + source.qc_notify_quality.Proportion); + todo_wine ok(source.qc_notify_quality.Late == 100000, "Got late %s.\n", + wine_dbgstr_longlong(source.qc_notify_quality.Late)); + todo_wine ok(source.qc_notify_quality.TimeStamp == start_time, "Got time stamp %s.\n", + wine_dbgstr_longlong(source.qc_notify_quality.TimeStamp)); + + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_STOP); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + IGraphBuilder_Disconnect(graph, pin); + IGraphBuilder_Disconnect(graph, &source.source.pin.IPin_iface); + + CloseHandle(cookie.advise_time_called_event); + ref = IDirectDrawStreamSample_Release(stream_sample); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + ref = IAMMultiMediaStream_Release(mmstream); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + IMediaFilter_Release(graph_media_filter); + ref = IGraphBuilder_Release(graph); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + ref = IMediaStreamFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + IPin_Release(pin); + IMemInputPin_Release(mem_input_pin); + IDirectDrawMediaStream_Release(ddraw_stream); + ref = IMediaStream_Release(stream); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + static void test_ddrawstreamsample_get_media_stream(void) { IAMMultiMediaStream *mmstream = create_ammultimediastream(); @@ -9516,6 +9810,7 @@ START_TEST(amstream) test_ddrawstream_begin_flush_end_flush(); test_ddrawstream_new_segment(); test_ddrawstream_get_time_per_frame(); + test_ddrawstream_qc();
test_ddrawstreamsample_get_media_stream(); test_ddrawstreamsample_update();
From: Anton Baskanov baskanov@gmail.com
--- dlls/amstream/ddrawstream.c | 14 ++++++++++++++ dlls/amstream/tests/amstream.c | 20 ++++++++++---------- 2 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/dlls/amstream/ddrawstream.c b/dlls/amstream/ddrawstream.c index b31466803f4..dc77cb87813 100644 --- a/dlls/amstream/ddrawstream.c +++ b/dlls/amstream/ddrawstream.c @@ -1409,6 +1409,7 @@ static HRESULT WINAPI ddraw_meminput_Receive(IMemInputPin *iface, IMediaSample * if (!list_empty(&stream->update_queue)) { struct ddraw_sample *sample = LIST_ENTRY(list_head(&stream->update_queue), struct ddraw_sample, entry); + IQualityControl *qc;
sample->update_hr = process_update(sample, top_down_stride, top_down_pointer, start_stream_time, end_stream_time); @@ -1422,6 +1423,19 @@ static HRESULT WINAPI ddraw_meminput_Receive(IMemInputPin *iface, IMediaSample * { remove_queued_update(sample); } + + if (S_OK == IMediaStreamFilter_GetCurrentStreamTime(filter, ¤t_time) + && SUCCEEDED(IPin_QueryInterface(stream->peer, &IID_IQualityControl, (void **)&qc))) + { + Quality q; + q.Type = Famine; + q.Proportion = 1000; + q.Late = current_time - start_time; + q.TimeStamp = start_time; + IQualityControl_Notify(qc, (IBaseFilter *)stream->filter, q); + IQualityControl_Release(qc); + } + LeaveCriticalSection(&stream->cs); return S_OK; } diff --git a/dlls/amstream/tests/amstream.c b/dlls/amstream/tests/amstream.c index abf0428140b..b796dbd64f8 100644 --- a/dlls/amstream/tests/amstream.c +++ b/dlls/amstream/tests/amstream.c @@ -8400,15 +8400,15 @@ static void test_ddrawstream_qc(void) hr = IDirectDrawStreamSample_CompletionStatus(stream_sample, 0, 0); ok(hr == S_OK, "Got hr %#lx.\n", hr);
- todo_wine ok(source.qc_notify_sender == (IBaseFilter *)filter, "Got sender %p.\n", + ok(source.qc_notify_sender == (IBaseFilter *)filter, "Got sender %p.\n", source.qc_notify_sender); - todo_wine ok(source.qc_notify_quality.Type == Famine, "Got type %d.\n", + ok(source.qc_notify_quality.Type == Famine, "Got type %d.\n", source.qc_notify_quality.Type); - todo_wine ok(source.qc_notify_quality.Proportion == 1000, "Got proportion %ld.\n", + ok(source.qc_notify_quality.Proportion == 1000, "Got proportion %ld.\n", source.qc_notify_quality.Proportion); - todo_wine ok(source.qc_notify_quality.Late == 0, "Got late %s.\n", + ok(source.qc_notify_quality.Late == 0, "Got late %s.\n", wine_dbgstr_longlong(source.qc_notify_quality.Late)); - todo_wine ok(source.qc_notify_quality.TimeStamp == start_time, "Got time stamp %s.\n", + ok(source.qc_notify_quality.TimeStamp == start_time, "Got time stamp %s.\n", wine_dbgstr_longlong(source.qc_notify_quality.TimeStamp));
/* Test Update() after Reveive(). */ @@ -8451,15 +8451,15 @@ static void test_ddrawstream_qc(void) ok(!WaitForSingleObject(thread, 2000), "Wait timed out.\n"); CloseHandle(thread);
- todo_wine ok(source.qc_notify_sender == (IBaseFilter *)filter, "Got sender %p.\n", + ok(source.qc_notify_sender == (IBaseFilter *)filter, "Got sender %p.\n", source.qc_notify_sender); - todo_wine ok(source.qc_notify_quality.Type == Famine, "Got type %d.\n", + ok(source.qc_notify_quality.Type == Famine, "Got type %d.\n", source.qc_notify_quality.Type); - todo_wine ok(source.qc_notify_quality.Proportion == 1000, "Got proportion %ld.\n", + ok(source.qc_notify_quality.Proportion == 1000, "Got proportion %ld.\n", source.qc_notify_quality.Proportion); - todo_wine ok(source.qc_notify_quality.Late == 100000, "Got late %s.\n", + ok(source.qc_notify_quality.Late == 100000, "Got late %s.\n", wine_dbgstr_longlong(source.qc_notify_quality.Late)); - todo_wine ok(source.qc_notify_quality.TimeStamp == start_time, "Got time stamp %s.\n", + ok(source.qc_notify_quality.TimeStamp == start_time, "Got time stamp %s.\n", wine_dbgstr_longlong(source.qc_notify_quality.TimeStamp));
hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_STOP);
From: Anton Baskanov baskanov@gmail.com
--- dlls/quartz/tests/mpegvideo.c | 162 ++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+)
diff --git a/dlls/quartz/tests/mpegvideo.c b/dlls/quartz/tests/mpegvideo.c index f66d69e99b8..52c174098a9 100644 --- a/dlls/quartz/tests/mpegvideo.c +++ b/dlls/quartz/tests/mpegvideo.c @@ -718,11 +718,118 @@ static void test_media_types(void) ok(!ref, "Got outstanding refcount %ld.\n", ref); }
+struct testqc +{ + IQualityControl IQualityControl_iface; + IUnknown IUnknown_inner; + IUnknown *outer_unk; + LONG refcount; + IBaseFilter *notify_sender; + Quality notify_quality; + HRESULT notify_hr; +}; + +static struct testqc *impl_from_IQualityControl(IQualityControl *iface) +{ + return CONTAINING_RECORD(iface, struct testqc, IQualityControl_iface); +} + +static HRESULT WINAPI testqc_QueryInterface(IQualityControl *iface, REFIID iid, void **out) +{ + struct testqc *qc = impl_from_IQualityControl(iface); + return IUnknown_QueryInterface(qc->outer_unk, iid, out); +} + +static ULONG WINAPI testqc_AddRef(IQualityControl *iface) +{ + struct testqc *qc = impl_from_IQualityControl(iface); + return IUnknown_AddRef(qc->outer_unk); +} + +static ULONG WINAPI testqc_Release(IQualityControl *iface) +{ + struct testqc *qc = impl_from_IQualityControl(iface); + return IUnknown_Release(qc->outer_unk); +} + +static HRESULT WINAPI testqc_Notify(IQualityControl *iface, IBaseFilter *sender, Quality q) +{ + struct testqc *qc = impl_from_IQualityControl(iface); + + qc->notify_sender = sender; + qc->notify_quality = q; + + return qc->notify_hr; +} + +static HRESULT WINAPI testqc_SetSink(IQualityControl *iface, IQualityControl *sink) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static const IQualityControlVtbl testqc_vtbl = +{ + testqc_QueryInterface, + testqc_AddRef, + testqc_Release, + testqc_Notify, + testqc_SetSink, +}; + +static struct testqc *impl_from_qc_IUnknown(IUnknown *iface) +{ + return CONTAINING_RECORD(iface, struct testqc, IUnknown_inner); +} + +static HRESULT WINAPI testqc_inner_QueryInterface(IUnknown *iface, REFIID iid, void **out) +{ + struct testqc *qc = impl_from_qc_IUnknown(iface); + + if (IsEqualIID(iid, &IID_IUnknown)) + *out = iface; + else if (IsEqualIID(iid, &IID_IQualityControl)) + *out = &qc->IQualityControl_iface; + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown *)*out); + return S_OK; +} + +static ULONG WINAPI testqc_inner_AddRef(IUnknown *iface) +{ + struct testqc *qc = impl_from_qc_IUnknown(iface); + return InterlockedIncrement(&qc->refcount); +} + +static ULONG WINAPI testqc_inner_Release(IUnknown *iface) +{ + struct testqc *qc = impl_from_qc_IUnknown(iface); + return InterlockedDecrement(&qc->refcount); +} + +static const IUnknownVtbl testqc_inner_vtbl = +{ + testqc_inner_QueryInterface, + testqc_inner_AddRef, + testqc_inner_Release, +}; + +static void testqc_init(struct testqc *qc, IUnknown *outer) +{ + memset(qc, 0, sizeof(*qc)); + qc->IQualityControl_iface.lpVtbl = &testqc_vtbl; + qc->IUnknown_inner.lpVtbl = &testqc_inner_vtbl; + qc->outer_unk = outer ? outer : &qc->IUnknown_inner; +} + struct testfilter { struct strmbase_filter filter; struct strmbase_source source; struct strmbase_sink sink; + struct testqc *qc; const AM_MEDIA_TYPE *mt; unsigned int got_sample, got_new_segment, got_eos, got_begin_flush, got_end_flush; REFERENCE_TIME expected_start_time; @@ -1042,6 +1149,59 @@ static void test_source_allocator(IFilterGraph2 *graph, IMediaControl *control, IFilterGraph2_Disconnect(graph, &testsource->source.pin.IPin_iface); }
+static void test_quality_control(IFilterGraph2 *graph, IBaseFilter *filter, + IPin *sink, IPin *source, struct testfilter *testsource, struct testfilter *testsink) +{ + struct testqc testsource_qc; + IQualityControl *source_qc; + IQualityControl *sink_qc; + Quality quality = {0}; + struct testqc qc; + HRESULT hr; + + testqc_init(&testsource_qc, testsource->filter.outer_unk); + testqc_init(&qc, NULL); + + testsource->qc = &testsource_qc; + + hr = IPin_QueryInterface(sink, &IID_IQualityControl, (void **)&sink_qc); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IPin_QueryInterface(source, &IID_IQualityControl, (void **)&source_qc); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IQualityControl_Notify(source_qc, &testsink->filter.IBaseFilter_iface, quality); + todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IFilterGraph2_ConnectDirect(graph, &testsource->source.pin.IPin_iface, sink, &mpeg_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + testsource_qc.notify_sender = (IBaseFilter *)0xdeadbeef; + hr = IQualityControl_Notify(source_qc, &testsink->filter.IBaseFilter_iface, quality); + todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(testsource_qc.notify_sender == (IBaseFilter *)0xdeadbeef, "Got sender %p.\n", + testsource_qc.notify_sender); + + hr = IQualityControl_SetSink(sink_qc, &qc.IQualityControl_iface); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + qc.notify_sender = (IBaseFilter *)0xdeadbeef; + hr = IQualityControl_Notify(source_qc, &testsink->filter.IBaseFilter_iface, quality); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + todo_wine ok(qc.notify_sender == (IBaseFilter *)0xdeadbeef, "Got sender %p.\n", qc.notify_sender); + + qc.notify_hr = E_FAIL; + hr = IQualityControl_Notify(source_qc, &testsink->filter.IBaseFilter_iface, quality); + todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + qc.notify_hr = S_OK; + + IFilterGraph2_Disconnect(graph, sink); + IFilterGraph2_Disconnect(graph, &testsource->source.pin.IPin_iface); + + IQualityControl_Release(source_qc); + IQualityControl_Release(sink_qc); + + testsource->qc = NULL; +} + static void test_send_sample(IMemInputPin *input, IMediaSample *sample, const BYTE *data, LONG len, BOOL todo) { BYTE *target_data; @@ -1322,6 +1482,8 @@ static void test_connect_pin(void) IPin_QueryInterface(sink, &IID_IMemInputPin, (void **)&meminput); IFilterGraph2_QueryInterface(graph, &IID_IMediaControl, (void **)&control);
+ test_quality_control(graph, filter, sink, source, &testsource, &testsink); + for (i=0;i<ARRAY_SIZE(video_types);++i) { BOOL should_work;
From: Anton Baskanov baskanov@gmail.com
Based on the code from quartz_parser and wg_parser. --- dlls/quartz/tests/mpegvideo.c | 8 +-- dlls/winegstreamer/gst_private.h | 2 + dlls/winegstreamer/main.c | 18 ++++++ dlls/winegstreamer/quartz_transform.c | 88 ++++++++++++++++++++++----- dlls/winegstreamer/unix_private.h | 1 + dlls/winegstreamer/unixlib.h | 10 +++ dlls/winegstreamer/wg_parser.c | 2 + dlls/winegstreamer/wg_transform.c | 30 +++++++++ 8 files changed, 139 insertions(+), 20 deletions(-)
diff --git a/dlls/quartz/tests/mpegvideo.c b/dlls/quartz/tests/mpegvideo.c index 52c174098a9..1f17ddbe636 100644 --- a/dlls/quartz/tests/mpegvideo.c +++ b/dlls/quartz/tests/mpegvideo.c @@ -1170,14 +1170,14 @@ static void test_quality_control(IFilterGraph2 *graph, IBaseFilter *filter, ok(hr == S_OK, "Got hr %#lx.\n", hr);
hr = IQualityControl_Notify(source_qc, &testsink->filter.IBaseFilter_iface, quality); - todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(hr == S_OK, "Got hr %#lx.\n", hr);
hr = IFilterGraph2_ConnectDirect(graph, &testsource->source.pin.IPin_iface, sink, &mpeg_mt); ok(hr == S_OK, "Got hr %#lx.\n", hr);
testsource_qc.notify_sender = (IBaseFilter *)0xdeadbeef; hr = IQualityControl_Notify(source_qc, &testsink->filter.IBaseFilter_iface, quality); - todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(hr == S_OK, "Got hr %#lx.\n", hr); ok(testsource_qc.notify_sender == (IBaseFilter *)0xdeadbeef, "Got sender %p.\n", testsource_qc.notify_sender);
@@ -1186,11 +1186,11 @@ static void test_quality_control(IFilterGraph2 *graph, IBaseFilter *filter, qc.notify_sender = (IBaseFilter *)0xdeadbeef; hr = IQualityControl_Notify(source_qc, &testsink->filter.IBaseFilter_iface, quality); ok(hr == S_OK, "Got hr %#lx.\n", hr); - todo_wine ok(qc.notify_sender == (IBaseFilter *)0xdeadbeef, "Got sender %p.\n", qc.notify_sender); + ok(qc.notify_sender == (IBaseFilter *)0xdeadbeef, "Got sender %p.\n", qc.notify_sender);
qc.notify_hr = E_FAIL; hr = IQualityControl_Notify(source_qc, &testsink->filter.IBaseFilter_iface, quality); - todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(hr == S_OK, "Got hr %#lx.\n", hr); qc.notify_hr = S_OK;
IFilterGraph2_Disconnect(graph, sink); diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index a7f2b8b1e6b..5e6cbbab5d5 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -109,6 +109,8 @@ bool wg_transform_set_output_format(wg_transform_t transform, struct wg_format * bool wg_transform_get_status(wg_transform_t transform, bool *accepts_input); HRESULT wg_transform_drain(wg_transform_t transform); HRESULT wg_transform_flush(wg_transform_t transform); +void wg_transform_notify_qos(wg_transform_t transform, + bool underflow, double proportion, int64_t diff, uint64_t timestamp);
HRESULT wg_muxer_create(const char *format, wg_muxer_t *muxer); void wg_muxer_destroy(wg_muxer_t muxer); diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c index d264b9a77e0..74a6eee5b1d 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -457,6 +457,24 @@ HRESULT wg_transform_flush(wg_transform_t transform) return S_OK; }
+void wg_transform_notify_qos(wg_transform_t transform, + bool underflow, double proportion, int64_t diff, uint64_t timestamp) +{ + struct wg_transform_notify_qos_params params = + { + .transform = transform, + .underflow = underflow, + .proportion = proportion, + .diff = diff, + .timestamp = timestamp, + }; + + TRACE("transform %#I64x, underflow %d, proportion %.16e, diff %I64d, timestamp %I64u.\n", + transform, underflow, proportion, diff, timestamp); + + WINE_UNIX_CALL(unix_wg_transform_notify_qos, ¶ms); +} + HRESULT wg_muxer_create(const char *format, wg_muxer_t *muxer) { struct wg_muxer_create_params params = diff --git a/dlls/winegstreamer/quartz_transform.c b/dlls/winegstreamer/quartz_transform.c index fee275bb1ee..5189c0b22d3 100644 --- a/dlls/winegstreamer/quartz_transform.c +++ b/dlls/winegstreamer/quartz_transform.c @@ -52,6 +52,7 @@ struct transform_ops HRESULT (*source_query_accept)(struct transform *filter, const AM_MEDIA_TYPE *mt); HRESULT (*source_get_media_type)(struct transform *filter, unsigned int index, AM_MEDIA_TYPE *mt); HRESULT (*source_decide_buffer_size)(struct transform *filter, IMemAllocator *allocator, ALLOCATOR_PROPERTIES *props); + HRESULT (*source_qc_notify)(struct transform *filter, IBaseFilter *sender, Quality q); };
static inline struct transform *impl_from_strmbase_filter(struct strmbase_filter *iface) @@ -505,23 +506,8 @@ static ULONG WINAPI source_quality_control_Release(IQualityControl *iface) static HRESULT WINAPI source_quality_control_Notify(IQualityControl *iface, IBaseFilter *sender, Quality q) { struct transform *filter = impl_from_source_IQualityControl(iface); - IQualityControl *peer; - HRESULT hr = VFW_E_NOT_FOUND; - - TRACE("filter %p, sender %p, type %#x, proportion %ld, late %s, timestamp %s.\n", - filter, sender, q.Type, q.Proportion, debugstr_time(q.Late), debugstr_time(q.TimeStamp));
- if (filter->qc_sink) - return IQualityControl_Notify(filter->qc_sink, &filter->filter.IBaseFilter_iface, q); - - if (filter->sink.pin.peer - && SUCCEEDED(IPin_QueryInterface(filter->sink.pin.peer, &IID_IQualityControl, (void **)&peer))) - { - hr = IQualityControl_Notify(peer, &filter->filter.IBaseFilter_iface, q); - IQualityControl_Release(peer); - } - - return hr; + return filter->ops->source_qc_notify(filter, sender, q); }
static HRESULT WINAPI source_quality_control_SetSink(IQualityControl *iface, IQualityControl *sink) @@ -567,6 +553,73 @@ static HRESULT transform_create(IUnknown *outer, const CLSID *clsid, const struc return S_OK; }
+static HRESULT passthrough_source_qc_notify(struct transform *filter, IBaseFilter *sender, Quality q) +{ + IQualityControl *peer; + HRESULT hr = VFW_E_NOT_FOUND; + + TRACE("filter %p, sender %p, type %s, proportion %ld, late %s, timestamp %s.\n", + filter, sender, q.Type == Famine ? "Famine" : "Flood", q.Proportion, + debugstr_time(q.Late), debugstr_time(q.TimeStamp)); + + if (filter->qc_sink) + return IQualityControl_Notify(filter->qc_sink, &filter->filter.IBaseFilter_iface, q); + + if (filter->sink.pin.peer + && SUCCEEDED(IPin_QueryInterface(filter->sink.pin.peer, &IID_IQualityControl, (void **)&peer))) + { + hr = IQualityControl_Notify(peer, &filter->filter.IBaseFilter_iface, q); + IQualityControl_Release(peer); + } + + return hr; +} + +static HRESULT handle_source_qc_notify(struct transform *filter, IBaseFilter *sender, Quality q) +{ + uint64_t timestamp; + int64_t diff; + + TRACE("filter %p, sender %p, type %s, proportion %ld, late %s, timestamp %s.\n", + filter, sender, q.Type == Famine ? "Famine" : "Flood", q.Proportion, + debugstr_time(q.Late), debugstr_time(q.TimeStamp)); + + /* DirectShow filters sometimes pass negative timestamps (Audiosurf uses the + * current time instead of the time of the last buffer). GstClockTime is + * unsigned, so clamp it to 0. */ + timestamp = max(q.TimeStamp, 0); + + /* The documentation specifies that timestamp + diff must be nonnegative. */ + diff = q.Late; + if (diff < 0 && timestamp < (uint64_t)-diff) + diff = -timestamp; + + /* DirectShow "Proportion" describes what percentage of buffers the upstream + * filter should keep (i.e. dropping the rest). If frames are late, the + * proportion will be less than 1. For example, a proportion of 500 means + * that the element should drop half of its frames, essentially because + * frames are taking twice as long as they should to arrive. + * + * GStreamer "proportion" is the inverse of this; it describes how much + * faster the upstream element should produce frames. I.e. if frames are + * taking twice as long as they should to arrive, we want the frames to be + * decoded twice as fast, and so we pass 2.0 to GStreamer. */ + + if (!q.Proportion) + { + WARN("Ignoring quality message with zero proportion.\n"); + return S_OK; + } + + /* GST_QOS_TYPE_OVERFLOW is also used for buffers that arrive on time, but + * DirectShow filters might use Famine, so check that there actually is an + * underrun. */ + wg_transform_notify_qos(filter->transform, q.Type == Famine && q.Proportion < 1000, + 1000.0 / q.Proportion, diff, timestamp); + + return S_OK; +} + static HRESULT mpeg_audio_codec_sink_query_accept(struct transform *filter, const AM_MEDIA_TYPE *mt) { const MPEG1WAVEFORMAT *format; @@ -686,6 +739,7 @@ static const struct transform_ops mpeg_audio_codec_transform_ops = mpeg_audio_codec_source_query_accept, mpeg_audio_codec_source_get_media_type, mpeg_audio_codec_source_decide_buffer_size, + passthrough_source_qc_notify, };
HRESULT mpeg_audio_codec_create(IUnknown *outer, IUnknown **out) @@ -837,6 +891,7 @@ static const struct transform_ops mpeg_video_codec_transform_ops = mpeg_video_codec_source_query_accept, mpeg_video_codec_source_get_media_type, mpeg_video_codec_source_decide_buffer_size, + handle_source_qc_notify, };
HRESULT mpeg_video_codec_create(IUnknown *outer, IUnknown **out) @@ -962,6 +1017,7 @@ static const struct transform_ops mpeg_layer3_decoder_transform_ops = mpeg_layer3_decoder_source_query_accept, mpeg_layer3_decoder_source_get_media_type, mpeg_layer3_decoder_source_decide_buffer_size, + passthrough_source_qc_notify, };
HRESULT mpeg_layer3_decoder_create(IUnknown *outer, IUnknown **out) diff --git a/dlls/winegstreamer/unix_private.h b/dlls/winegstreamer/unix_private.h index f3c24311ac7..bb2cb864735 100644 --- a/dlls/winegstreamer/unix_private.h +++ b/dlls/winegstreamer/unix_private.h @@ -62,6 +62,7 @@ extern NTSTATUS wg_transform_read_data(void *args); extern NTSTATUS wg_transform_get_status(void *args); extern NTSTATUS wg_transform_drain(void *args); extern NTSTATUS wg_transform_flush(void *args); +extern NTSTATUS wg_transform_notify_qos(void *args);
/* wg_muxer.c */
diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index 86bd380c351..eea31be2036 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -372,6 +372,15 @@ struct wg_transform_get_status_params UINT32 accepts_input; };
+struct wg_transform_notify_qos_params +{ + wg_transform_t transform; + UINT8 underflow; + DOUBLE proportion; + INT64 diff; + UINT64 timestamp; +}; + struct wg_muxer_create_params { wg_muxer_t muxer; @@ -439,6 +448,7 @@ enum unix_funcs unix_wg_transform_get_status, unix_wg_transform_drain, unix_wg_transform_flush, + unix_wg_transform_notify_qos,
unix_wg_muxer_create, unix_wg_muxer_destroy, diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index dde469bac69..7193062e89b 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -1921,6 +1921,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = X(wg_transform_get_status), X(wg_transform_drain), X(wg_transform_flush), + X(wg_transform_notify_qos),
X(wg_muxer_create), X(wg_muxer_destroy), @@ -2253,6 +2254,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = X(wg_transform_get_status), X(wg_transform_drain), X(wg_transform_flush), + X(wg_transform_notify_qos),
X64(wg_muxer_create), X(wg_muxer_destroy), diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index 6b2910740d3..ab5ce381fc5 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -930,3 +930,33 @@ NTSTATUS wg_transform_flush(void *args)
return STATUS_SUCCESS; } + +NTSTATUS wg_transform_notify_qos(void *args) +{ + const struct wg_transform_notify_qos_params *params = args; + struct wg_transform *transform = get_transform(params->transform); + GstClockTimeDiff diff = params->diff * 100; + GstClockTime stream_time; + GstEvent *event; + + /* We return timestamps in stream time, i.e. relative to the start of the + * file (or other medium), but gst_event_new_qos() expects the timestamp in + * running time. */ + stream_time = gst_segment_to_running_time(&transform->segment, GST_FORMAT_TIME, params->timestamp * 100); + if (diff < (GstClockTimeDiff)-stream_time) + diff = -stream_time; + if (stream_time == -1) + { + /* This can happen legitimately if the sample falls outside of the + * segment bounds. GStreamer elements shouldn't present the sample in + * that case, but DirectShow doesn't care. */ + GST_LOG("Ignoring QoS event."); + return S_OK; + } + if (!(event = gst_event_new_qos(params->underflow ? GST_QOS_TYPE_UNDERFLOW : GST_QOS_TYPE_OVERFLOW, + params->proportion, diff, stream_time))) + GST_ERROR("Failed to create QOS event."); + push_event(transform->my_sink, event); + + return S_OK; +}
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=140698
Your paranoid android.
=== w11pro64_amd (64 bit report) ===
quartz: mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 90ca6e, expected 56ce. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 90ca6e, expected 56ce. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 90ca6e, expected 56ce. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 90ca6e, expected 56ce. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 90ca6e, expected 56ce. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 90ca6e, expected 56ce. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 90ca6e, expected 56ce. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 90ca6e, expected 56ce. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0. mpegvideo.c:946: Test failed: Got stop time 9073a0, expected 0.
This merge request was approved by Zebediah Figura.
This merge request was approved by Rémi Bernon.