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; +}