From 4b93d8b3516883e40759a3b23ac6248db27cd006 Mon Sep 17 00:00:00 2001 From: Bones Date: Mon, 1 Jul 2024 00:14:31 -0400 Subject: [PATCH] rtsp patchset 11 --- Makefile.in | 3 + patches/protonprep-valve-staging.sh | 45 + ...ng-support-for-IMFMediaSession-Start.patch | 128 +++ ...reate_media_session_with_source_sink.patch | 143 ++++ ...-mf-tests-Test-IMFMediaSession-Start.patch | 784 ++++++++++++++++++ ...plement-IMFMediaEngineEx-SetCurrentT.patch | 109 +++ ...sts-Test-IMFMediaEngineEx-SetCurrent.patch | 243 ++++++ ...e-Implement-IMFMediaEngine-IsSeeking.patch | 35 + .../0007-AVPro-Video-seeking-support.patch | 22 + ...-leaking-samples-in-transform_node_d.patch | 28 + patches/wine-gst/0009-Memory-leak-fixes.patch | 21 + ...itialize-condition-variable-of-struc.patch | 25 + ...nnect-autoplug-continue-and-deep-ele.patch | 29 + ...-not-create-a-read-thread-for-uridec.patch | 43 + ...reamer-Ignore-an-assert-in-wg_parser.patch | 37 + ...-Fixate-caps-in-autoplug_continue_cb.patch | 27 + ...ke-wg_parser-report-the-exact-suppor.patch | 73 ++ ...d-more-RTSP-based-URI-schemes-to-GSt.patch | 56 ++ ...ixate-caps-in-the-pad-added-callback.patch | 38 + ...rk-wg_parser-container-bin-as-stream.patch | 26 + ...t-a-clock-for-the-wg_parser-pipeline.patch | 27 + ...t-base-time-on-wg_parser-bin-while-c.patch | 43 + ...t-pipeline-into-PLAYING-state-before.patch | 32 + ...n-t-only-accept-segment-events-when-.patch | 53 ++ ...nvert-buffer-presentation-timestamps.patch | 93 +++ ...-Adjust-buffer-timestamps-after-seek.patch | 53 ++ ...order-parser-initialization-code-a-b.patch | 84 ++ ...-away-with-the-per-stream-condvars-a.patch | 137 +++ ...e-pthread_cond_broadcast-instead-of-.patch | 71 ++ ...-not-fail-caps-negotiation-when-ther.patch | 171 ++++ ...negstreamer-Do-not-seek-live-sources.patch | 86 ++ ...gstreamer-Implement-buffering-events.patch | 313 +++++++ ...equests-for-unused-space-of-sample-q.patch | 87 ++ ...x-race-between-wg_parser_stream_disa.patch | 27 + ...ndle-Gstreamer-pipeline-flushes-grac.patch | 57 ++ ...-waits-for-samples-on-stream-specifi.patch | 646 +++++++++++++++ ...er-Add-a-resampler-to-wg_parser-for-.patch | 28 + ...er-Add-a-videoscale-element-to-wg_pa.patch | 40 + ...ne-Do-not-send-MF_MEDIA_ENGINE_EVENT.patch | 50 ++ ...038-Marker-commit-do-not-put-into-MR.patch | 21 + ...EBUG-winegstreamer-GST_LOG-GST_DEBUG.patch | 169 ++++ ...yt-dlp.exe-redirection-and-cmdline-m.patch | 82 ++ ...ed-timers-for-the-original-time-inst.patch | 72 ++ ...ing-samples-only-at-the-PTS-of-the-f.patch | 290 +++++++ ...ll-instead-of-syscall-for-QueryPerfo.patch | 102 +++ 45 files changed, 4749 insertions(+) create mode 100644 patches/wine-gst/0001-mf-Add-seeking-support-for-IMFMediaSession-Start.patch create mode 100644 patches/wine-gst/0002-mf-tests-Add-a-create_media_session_with_source_sink.patch create mode 100644 patches/wine-gst/0003-mf-tests-Test-IMFMediaSession-Start.patch create mode 100644 patches/wine-gst/0004-mfmediaengine-Implement-IMFMediaEngineEx-SetCurrentT.patch create mode 100644 patches/wine-gst/0005-mfmediaengine-tests-Test-IMFMediaEngineEx-SetCurrent.patch create mode 100644 patches/wine-gst/0006-mfmediaengine-Implement-IMFMediaEngine-IsSeeking.patch create mode 100644 patches/wine-gst/0007-AVPro-Video-seeking-support.patch create mode 100644 patches/wine-gst/0008-mf-session-Avoid-leaking-samples-in-transform_node_d.patch create mode 100644 patches/wine-gst/0009-Memory-leak-fixes.patch create mode 100644 patches/wine-gst/0010-winegstreamer-Initialize-condition-variable-of-struc.patch create mode 100644 patches/wine-gst/0011-winegstreamer-Connect-autoplug-continue-and-deep-ele.patch create mode 100644 patches/wine-gst/0012-winegstreamer-Do-not-create-a-read-thread-for-uridec.patch create mode 100644 patches/wine-gst/0013-winegstreamer-Ignore-an-assert-in-wg_parser.patch create mode 100644 patches/wine-gst/0014-winegstreamer-Fixate-caps-in-autoplug_continue_cb.patch create mode 100644 patches/wine-gst/0015-winegstreamer-Make-wg_parser-report-the-exact-suppor.patch create mode 100644 patches/wine-gst/0016-winegstreamer-Add-more-RTSP-based-URI-schemes-to-GSt.patch create mode 100644 patches/wine-gst/0017-winegstreamer-Fixate-caps-in-the-pad-added-callback.patch create mode 100644 patches/wine-gst/0018-winegstreamer-Mark-wg_parser-container-bin-as-stream.patch create mode 100644 patches/wine-gst/0019-winegstreamer-Set-a-clock-for-the-wg_parser-pipeline.patch create mode 100644 patches/wine-gst/0020-winegstreamer-Set-base-time-on-wg_parser-bin-while-c.patch create mode 100644 patches/wine-gst/0021-winegstreamer-Put-pipeline-into-PLAYING-state-before.patch create mode 100644 patches/wine-gst/0022-winegstreamer-Don-t-only-accept-segment-events-when-.patch create mode 100644 patches/wine-gst/0023-winegstreamer-Convert-buffer-presentation-timestamps.patch create mode 100644 patches/wine-gst/0024-winegstreamer-Adjust-buffer-timestamps-after-seek.patch create mode 100644 patches/wine-gst/0025-winegstreamer-Reorder-parser-initialization-code-a-b.patch create mode 100644 patches/wine-gst/0026-winegstreamer-Do-away-with-the-per-stream-condvars-a.patch create mode 100644 patches/wine-gst/0027-winegstreamer-Use-pthread_cond_broadcast-instead-of-.patch create mode 100644 patches/wine-gst/0028-winegstreamer-Do-not-fail-caps-negotiation-when-ther.patch create mode 100644 patches/wine-gst/0029-winegstreamer-Do-not-seek-live-sources.patch create mode 100644 patches/wine-gst/0030-winegstreamer-Implement-buffering-events.patch create mode 100644 patches/wine-gst/0031-mf-Send-sample-requests-for-unused-space-of-sample-q.patch create mode 100644 patches/wine-gst/0032-winegstreamer-Fix-race-between-wg_parser_stream_disa.patch create mode 100644 patches/wine-gst/0033-winegstreamer-Handle-Gstreamer-pipeline-flushes-grac.patch create mode 100644 patches/wine-gst/0034-winegstreamer-Do-waits-for-samples-on-stream-specifi.patch create mode 100644 patches/wine-gst/0035-HACK-winegstreamer-Add-a-resampler-to-wg_parser-for-.patch create mode 100644 patches/wine-gst/0036-HACK-winegstreamer-Add-a-videoscale-element-to-wg_pa.patch create mode 100644 patches/wine-gst/0037-HACK-mfmediaengine-Do-not-send-MF_MEDIA_ENGINE_EVENT.patch create mode 100644 patches/wine-gst/0038-Marker-commit-do-not-put-into-MR.patch create mode 100644 patches/wine-gst/0039-DEBUG-winegstreamer-GST_LOG-GST_DEBUG.patch create mode 100644 patches/wine-gst/0040-HACK-kernelbase-yt-dlp.exe-redirection-and-cmdline-m.patch create mode 100644 patches/wine-gst/0041-mf-Schedule-stored-timers-for-the-original-time-inst.patch create mode 100644 patches/wine-gst/0042-mf-Start-forwarding-samples-only-at-the-PTS-of-the-f.patch create mode 100644 patches/wine-gst/0043-ntdll-Use-unixcall-instead-of-syscall-for-QueryPerfo.patch diff --git a/Makefile.in b/Makefile.in index c01116e52..31cc5806e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -241,6 +241,9 @@ GST_GOOD_MESON_ARGS := \ -Dflac=enabled \ -Djpeg=enabled \ -Dspeex=enabled \ + -Drtsp=enabled \ + -Drtp=enabled \ + -Drtpmanager=enabled \ -Dorc=enabled GST_GOOD_DEPENDS = gst_orc gstreamer gst_base diff --git a/patches/protonprep-valve-staging.sh b/patches/protonprep-valve-staging.sh index 6ef0dd655..05f21b8e1 100755 --- a/patches/protonprep-valve-staging.sh +++ b/patches/protonprep-valve-staging.sh @@ -306,6 +306,51 @@ echo "WINE: -PENDING- Add options to disable proton media converter." patch -Np1 < ../patches/wine-hotfixes/pending/add-envvar-to-gate-media-converter.patch + echo "WINE: RTSP patch" + patch -Np1 < ../patches/wine-gst/0001-mf-Add-seeking-support-for-IMFMediaSession-Start.patch + patch -Np1 < ../patches/wine-gst/0002-mf-tests-Add-a-create_media_session_with_source_sink.patch + patch -Np1 < ../patches/wine-gst/0003-mf-tests-Test-IMFMediaSession-Start.patch + patch -Np1 < ../patches/wine-gst/0004-mfmediaengine-Implement-IMFMediaEngineEx-SetCurrentT.patch + patch -Np1 < ../patches/wine-gst/0005-mfmediaengine-tests-Test-IMFMediaEngineEx-SetCurrent.patch + patch -Np1 < ../patches/wine-gst/0006-mfmediaengine-Implement-IMFMediaEngine-IsSeeking.patch + patch -Np1 < ../patches/wine-gst/0007-AVPro-Video-seeking-support.patch + patch -Np1 < ../patches/wine-gst/0008-mf-session-Avoid-leaking-samples-in-transform_node_d.patch + patch -Np1 < ../patches/wine-gst/0009-Memory-leak-fixes.patch + patch -Np1 < ../patches/wine-gst/0010-winegstreamer-Initialize-condition-variable-of-struc.patch + patch -Np1 < ../patches/wine-gst/0011-winegstreamer-Connect-autoplug-continue-and-deep-ele.patch + patch -Np1 < ../patches/wine-gst/0012-winegstreamer-Do-not-create-a-read-thread-for-uridec.patch + patch -Np1 < ../patches/wine-gst/0013-winegstreamer-Ignore-an-assert-in-wg_parser.patch + patch -Np1 < ../patches/wine-gst/0014-winegstreamer-Fixate-caps-in-autoplug_continue_cb.patch + patch -Np1 < ../patches/wine-gst/0015-winegstreamer-Make-wg_parser-report-the-exact-suppor.patch + patch -Np1 < ../patches/wine-gst/0016-winegstreamer-Add-more-RTSP-based-URI-schemes-to-GSt.patch + patch -Np1 < ../patches/wine-gst/0017-winegstreamer-Fixate-caps-in-the-pad-added-callback.patch + patch -Np1 < ../patches/wine-gst/0018-winegstreamer-Mark-wg_parser-container-bin-as-stream.patch + patch -Np1 < ../patches/wine-gst/0019-winegstreamer-Set-a-clock-for-the-wg_parser-pipeline.patch + patch -Np1 < ../patches/wine-gst/0020-winegstreamer-Set-base-time-on-wg_parser-bin-while-c.patch + patch -Np1 < ../patches/wine-gst/0021-winegstreamer-Put-pipeline-into-PLAYING-state-before.patch + patch -Np1 < ../patches/wine-gst/0022-winegstreamer-Don-t-only-accept-segment-events-when-.patch + patch -Np1 < ../patches/wine-gst/0023-winegstreamer-Convert-buffer-presentation-timestamps.patch + patch -Np1 < ../patches/wine-gst/0024-winegstreamer-Adjust-buffer-timestamps-after-seek.patch + patch -Np1 < ../patches/wine-gst/0025-winegstreamer-Reorder-parser-initialization-code-a-b.patch + patch -Np1 < ../patches/wine-gst/0026-winegstreamer-Do-away-with-the-per-stream-condvars-a.patch + patch -Np1 < ../patches/wine-gst/0027-winegstreamer-Use-pthread_cond_broadcast-instead-of-.patch + patch -Np1 < ../patches/wine-gst/0028-winegstreamer-Do-not-fail-caps-negotiation-when-ther.patch + patch -Np1 < ../patches/wine-gst/0029-winegstreamer-Do-not-seek-live-sources.patch + patch -Np1 < ../patches/wine-gst/0030-winegstreamer-Implement-buffering-events.patch + patch -Np1 < ../patches/wine-gst/0031-mf-Send-sample-requests-for-unused-space-of-sample-q.patch + patch -Np1 < ../patches/wine-gst/0032-winegstreamer-Fix-race-between-wg_parser_stream_disa.patch + patch -Np1 < ../patches/wine-gst/0033-winegstreamer-Handle-Gstreamer-pipeline-flushes-grac.patch + patch -Np1 < ../patches/wine-gst/0034-winegstreamer-Do-waits-for-samples-on-stream-specifi.patch + patch -Np1 < ../patches/wine-gst/0035-HACK-winegstreamer-Add-a-resampler-to-wg_parser-for-.patch + patch -Np1 < ../patches/wine-gst/0036-HACK-winegstreamer-Add-a-videoscale-element-to-wg_pa.patch + patch -Np1 < ../patches/wine-gst/0037-HACK-mfmediaengine-Do-not-send-MF_MEDIA_ENGINE_EVENT.patch + patch -Np1 < ../patches/wine-gst/0038-Marker-commit-do-not-put-into-MR.patch + patch -Np1 < ../patches/wine-gst/0039-DEBUG-winegstreamer-GST_LOG-GST_DEBUG.patch + patch -Np1 < ../patches/wine-gst/0040-HACK-kernelbase-yt-dlp.exe-redirection-and-cmdline-m.patch + patch -Np1 < ../patches/wine-gst/0041-mf-Schedule-stored-timers-for-the-original-time-inst.patch + patch -Np1 < ../patches/wine-gst/0042-mf-Start-forwarding-samples-only-at-the-PTS-of-the-f.patch + patch -Np1 < ../patches/wine-gst/0043-ntdll-Use-unixcall-instead-of-syscall-for-QueryPerfo.patch + #echo "WINE: -Nvidia Reflex- Support VK_NV_low_latency2" #patch -Np1 < ../patches/proton/83-nv_low_latency_wine.patch diff --git a/patches/wine-gst/0001-mf-Add-seeking-support-for-IMFMediaSession-Start.patch b/patches/wine-gst/0001-mf-Add-seeking-support-for-IMFMediaSession-Start.patch new file mode 100644 index 000000000..81cb7e19a --- /dev/null +++ b/patches/wine-gst/0001-mf-Add-seeking-support-for-IMFMediaSession-Start.patch @@ -0,0 +1,128 @@ +From bc88e82ada1457245f246b6d5621b13b3d9ab760 Mon Sep 17 00:00:00 2001 +From: Zhiyi Zhang +Date: Tue, 1 Aug 2023 10:52:21 +0800 +Subject: [PATCH 01/43] mf: Add seeking support for IMFMediaSession::Start(). + +--- + dlls/mf/session.c | 57 ++++++++++++++++++++++++++++++++++++++++++----- + 1 file changed, 51 insertions(+), 6 deletions(-) + +diff --git a/dlls/mf/session.c b/dlls/mf/session.c +index ef707dea4de..3f3be2b84b0 100644 +--- a/dlls/mf/session.c ++++ b/dlls/mf/session.c +@@ -110,6 +110,7 @@ enum object_state + OBJ_STATE_STARTED, + OBJ_STATE_PAUSED, + OBJ_STATE_PREROLLED, ++ OBJ_STATE_SEEKING, + OBJ_STATE_INVALID, + }; + +@@ -912,10 +913,28 @@ static HRESULT session_subscribe_sources(struct media_session *session) + return hr; + } + ++static void session_set_source_output_nodes_seeking(struct media_session *session) ++{ ++ struct media_source *source; ++ struct topo_node *node; ++ ++ LIST_FOR_EACH_ENTRY(source, &session->presentation.sources, struct media_source, entry) ++ { ++ source->state = OBJ_STATE_SEEKING; ++ } ++ ++ LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry) ++ { ++ if (node->type == MF_TOPOLOGY_SOURCESTREAM_NODE || node->type == MF_TOPOLOGY_OUTPUT_NODE) ++ node->state = OBJ_STATE_SEEKING; ++ } ++} ++ + static void session_start(struct media_session *session, const GUID *time_format, const PROPVARIANT *start_position) + { + struct media_source *source; + struct topo_node *topo_node; ++ MFTIME duration; + HRESULT hr; + UINT i; + +@@ -932,6 +951,13 @@ static void session_start(struct media_session *session, const GUID *time_format + + /* fallthrough */ + case SESSION_STATE_PAUSED: ++ case SESSION_STATE_STARTED: ++ if (session->state == SESSION_STATE_STARTED && !(session->caps & MFSESSIONCAP_SEEK)) ++ { ++ WARN("Seeking is not supported for this session.\n"); ++ session_command_complete(session); ++ return; ++ } + + session->presentation.time_format = *time_format; + session->presentation.start_position.vt = VT_EMPTY; +@@ -945,6 +971,14 @@ static void session_start(struct media_session *session, const GUID *time_format + + LIST_FOR_EACH_ENTRY(source, &session->presentation.sources, struct media_source, entry) + { ++ hr = IMFPresentationDescriptor_GetUINT64(source->pd, &MF_PD_DURATION, (UINT64 *)&duration); ++ if (SUCCEEDED(hr) && start_position->vt == VT_I8&& start_position->hVal.QuadPart > duration) ++ { ++ WARN("Start position %s out of range, hr %#lx.\n", wine_dbgstr_longlong(start_position->hVal.QuadPart), hr); ++ session_command_complete_with_event(session, MESessionStarted, MF_E_INVALID_POSITION, NULL); ++ return; ++ } ++ + if (FAILED(hr = IMFMediaSource_Start(source->source, source->pd, &GUID_NULL, start_position))) + { + WARN("Failed to start media source %p, hr %#lx.\n", source->source, hr); +@@ -965,12 +999,22 @@ static void session_start(struct media_session *session, const GUID *time_format + } + } + ++ if (session->state == SESSION_STATE_STARTED) ++ { ++ struct topo_node *node; ++ ++ LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry) ++ { ++ if (node->type == MF_TOPOLOGY_OUTPUT_NODE) ++ IMFStreamSink_Flush(node->object.sink_stream); ++ else if (node->type == MF_TOPOLOGY_TRANSFORM_NODE) ++ IMFTransform_ProcessMessage(node->object.transform, MFT_MESSAGE_COMMAND_FLUSH, 0); ++ } ++ ++ session_set_source_output_nodes_seeking(session); ++ } + session->state = SESSION_STATE_STARTING_SOURCES; + break; +- case SESSION_STATE_STARTED: +- FIXME("Seeking is not implemented.\n"); +- session_command_complete(session); +- break; + default: + session_command_complete_with_event(session, MESessionStarted, MF_E_INVALIDREQUEST, NULL); + break; +@@ -2206,6 +2250,9 @@ static HRESULT WINAPI mfsession_Start(IMFMediaSession *iface, const GUID *format + if (!start_position) + return E_POINTER; + ++ if (FAILED(hr = session_is_shut_down(session))) ++ return hr; ++ + if (FAILED(hr = create_session_op(SESSION_CMD_START, &op))) + return hr; + +@@ -3714,8 +3761,6 @@ static HRESULT WINAPI session_events_callback_Invoke(IMFAsyncCallback *iface, IM + { + case MESourceSeeked: + case MEStreamSeeked: +- FIXME("Source/stream seeking, semi-stub!\n"); +- /* fallthrough */ + case MESourceStarted: + case MESourcePaused: + case MESourceStopped: +-- +2.45.2 + diff --git a/patches/wine-gst/0002-mf-tests-Add-a-create_media_session_with_source_sink.patch b/patches/wine-gst/0002-mf-tests-Add-a-create_media_session_with_source_sink.patch new file mode 100644 index 000000000..2908341e4 --- /dev/null +++ b/patches/wine-gst/0002-mf-tests-Add-a-create_media_session_with_source_sink.patch @@ -0,0 +1,143 @@ +From 4ba015c45201d9cbdba3237d2c9c3a243c4e70f4 Mon Sep 17 00:00:00 2001 +From: Zhiyi Zhang +Date: Tue, 8 Aug 2023 15:24:34 +0800 +Subject: [PATCH 02/43] mf/tests: Add a create_media_session_with_source_sink() + helper. + +--- + dlls/mf/tests/mf.c | 92 ++++++++++++++++++++++++---------------------- + 1 file changed, 48 insertions(+), 44 deletions(-) + +diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c +index 0a34329bd75..e0bcedac20f 100644 +--- a/dlls/mf/tests/mf.c ++++ b/dlls/mf/tests/mf.c +@@ -4995,6 +4995,53 @@ static void test_sample_grabber_is_mediatype_supported(void) + IMFSampleGrabberSinkCallback_Release(grabber_callback); + } + ++/* create a media session with the specified source and sink */ ++static void create_media_session_with_source_sink(IMFMediaSource *source, IMFActivate *sink_activate, ++ IMFMediaSession **session) ++{ ++ IMFTopologyNode *src_node, *sink_node; ++ IMFPresentationDescriptor *pd; ++ IMFStreamDescriptor *sd; ++ IMFTopology *topology; ++ BOOL selected; ++ HRESULT hr; ++ ++ hr = MFCreateMediaSession(NULL, session); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = MFCreateTopology(&topology); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &sink_node); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &src_node); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFTopology_AddNode(topology, sink_node); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFTopology_AddNode(topology, src_node); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFTopologyNode_ConnectOutput(src_node, 0, sink_node, 0); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFMediaSource_CreatePresentationDescriptor(source, &pd); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFPresentationDescriptor_GetStreamDescriptorByIndex(pd, 0, &selected, &sd); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok(selected, "got selected %u.\n", !!selected); ++ init_source_node(source, -1, src_node, pd, sd); ++ hr = IMFTopologyNode_SetObject(sink_node, (IUnknown *)sink_activate); ++ ok(hr == S_OK, "Failed to set object, hr %#lx.\n", hr); ++ hr = IMFTopologyNode_SetUINT32(sink_node, &MF_TOPONODE_CONNECT_METHOD, MF_CONNECT_ALLOW_DECODER); ++ ok(hr == S_OK, "Failed to set connect method, hr %#lx.\n", hr); ++ hr = IMFTopology_SetUINT32(topology, &MF_TOPOLOGY_ENUMERATE_SOURCE_TYPES, TRUE); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFMediaSession_SetTopology(*session, 0, topology); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ IMFStreamDescriptor_Release(sd); ++ IMFPresentationDescriptor_Release(pd); ++ IMFTopologyNode_Release(src_node); ++ IMFTopologyNode_Release(sink_node); ++ IMFTopology_Release(topology); ++} ++ + static void test_sample_grabber_orientation(GUID subtype) + { + media_type_desc video_rgb32_desc = +@@ -5004,17 +5051,12 @@ static void test_sample_grabber_orientation(GUID subtype) + }; + + struct test_grabber_callback *grabber_callback; +- IMFTopologyNode *src_node, *sink_node; +- IMFPresentationDescriptor *pd; + IMFAsyncCallback *callback; + IMFActivate *sink_activate; + IMFMediaType *output_type; + IMFMediaSession *session; +- IMFStreamDescriptor *sd; + IMFMediaSource *source; +- IMFTopology *topology; + PROPVARIANT propvar; +- BOOL selected; + HRESULT hr; + DWORD res; + +@@ -5035,33 +5077,6 @@ static void test_sample_grabber_orientation(GUID subtype) + grabber_callback->done_event = CreateEventW(NULL, FALSE, FALSE, NULL); + ok(!!grabber_callback->done_event, "CreateEventW failed, error %lu\n", GetLastError()); + +- hr = MFCreateMediaSession(NULL, &session); +- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +- +- hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &sink_node); +- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +- hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &src_node); +- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +- +- hr = MFCreateTopology(&topology); +- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +- hr = IMFTopology_AddNode(topology, sink_node); +- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +- hr = IMFTopology_AddNode(topology, src_node); +- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +- hr = IMFTopologyNode_ConnectOutput(src_node, 0, sink_node, 0); +- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +- +- hr = IMFMediaSource_CreatePresentationDescriptor(source, &pd); +- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +- hr = IMFPresentationDescriptor_GetStreamDescriptorByIndex(pd, 0, &selected, &sd); +- ok(selected, "got selected %u.\n", !!selected); +- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +- init_source_node(source, -1, src_node, pd, sd); +- IMFTopologyNode_Release(src_node); +- IMFPresentationDescriptor_Release(pd); +- IMFStreamDescriptor_Release(sd); +- + hr = MFCreateMediaType(&output_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(output_type, video_rgb32_desc, -1); +@@ -5069,18 +5084,7 @@ static void test_sample_grabber_orientation(GUID subtype) + ok(hr == S_OK, "Failed to create grabber sink, hr %#lx.\n", hr); + IMFMediaType_Release(output_type); + +- hr = IMFTopologyNode_SetObject(sink_node, (IUnknown *)sink_activate); +- ok(hr == S_OK, "Failed to set object, hr %#lx.\n", hr); +- hr = IMFTopologyNode_SetUINT32(sink_node, &MF_TOPONODE_CONNECT_METHOD, MF_CONNECT_ALLOW_DECODER); +- ok(hr == S_OK, "Failed to set connect method, hr %#lx.\n", hr); +- IMFTopologyNode_Release(sink_node); +- +- hr = IMFTopology_SetUINT32(topology, &MF_TOPOLOGY_ENUMERATE_SOURCE_TYPES, TRUE); +- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +- +- hr = IMFMediaSession_SetTopology(session, 0, topology); +- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +- IMFTopology_Release(topology); ++ create_media_session_with_source_sink(source, sink_activate, &session); + + propvar.vt = VT_EMPTY; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); +-- +2.45.2 + diff --git a/patches/wine-gst/0003-mf-tests-Test-IMFMediaSession-Start.patch b/patches/wine-gst/0003-mf-tests-Test-IMFMediaSession-Start.patch new file mode 100644 index 000000000..431850bb4 --- /dev/null +++ b/patches/wine-gst/0003-mf-tests-Test-IMFMediaSession-Start.patch @@ -0,0 +1,784 @@ +From d4553e13b2110b7cfbcc39b5c5cc890e1a8da145 Mon Sep 17 00:00:00 2001 +From: Zhiyi Zhang +Date: Mon, 7 Aug 2023 11:52:20 +0800 +Subject: [PATCH 03/43] mf/tests: Test IMFMediaSession::Start(). + +--- + dlls/mf/tests/mf.c | 704 ++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 696 insertions(+), 8 deletions(-) + +diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c +index e0bcedac20f..f164a04b989 100644 +--- a/dlls/mf/tests/mf.c ++++ b/dlls/mf/tests/mf.c +@@ -2086,6 +2086,448 @@ static IMFMediaSource *create_media_source(const WCHAR *name, const WCHAR *mime) + return source; + } + ++enum source_state ++{ ++ SOURCE_STOPPED = 0, ++ SOURCE_RUNNING, ++}; ++ ++struct test_media_stream ++{ ++ IMFMediaStream IMFMediaStream_iface; ++ IMFMediaEventQueue *event_queue; ++ IMFStreamDescriptor *sd; ++ IMFMediaSource *source; ++ LONGLONG sample_duration; ++ LONGLONG sample_time; ++ BOOL is_new; ++ LONG refcount; ++}; ++ ++static struct test_media_stream *impl_from_IMFMediaStream(IMFMediaStream *iface) ++{ ++ return CONTAINING_RECORD(iface, struct test_media_stream, IMFMediaStream_iface); ++} ++ ++static HRESULT WINAPI test_media_stream_QueryInterface(IMFMediaStream *iface, REFIID riid, void **out) ++{ ++ if (IsEqualIID(riid, &IID_IMFMediaStream) ++ || IsEqualIID(riid, &IID_IMFMediaEventGenerator) ++ || IsEqualIID(riid, &IID_IUnknown)) ++ { ++ *out = iface; ++ } ++ else ++ { ++ *out = NULL; ++ return E_NOINTERFACE; ++ } ++ ++ IMFMediaStream_AddRef(iface); ++ return S_OK; ++} ++ ++static ULONG WINAPI test_media_stream_AddRef(IMFMediaStream *iface) ++{ ++ struct test_media_stream *stream = impl_from_IMFMediaStream(iface); ++ return InterlockedIncrement(&stream->refcount); ++} ++ ++static ULONG WINAPI test_media_stream_Release(IMFMediaStream *iface) ++{ ++ struct test_media_stream *stream = impl_from_IMFMediaStream(iface); ++ ULONG refcount = InterlockedDecrement(&stream->refcount); ++ ++ if (!refcount) ++ { ++ IMFMediaEventQueue_Release(stream->event_queue); ++ free(stream); ++ } ++ ++ return refcount; ++} ++ ++static HRESULT WINAPI test_media_stream_GetEvent(IMFMediaStream *iface, DWORD flags, IMFMediaEvent **event) ++{ ++ struct test_media_stream *stream = impl_from_IMFMediaStream(iface); ++ return IMFMediaEventQueue_GetEvent(stream->event_queue, flags, event); ++} ++ ++static HRESULT WINAPI test_media_stream_BeginGetEvent(IMFMediaStream *iface, IMFAsyncCallback *callback, IUnknown *state) ++{ ++ struct test_media_stream *stream = impl_from_IMFMediaStream(iface); ++ return IMFMediaEventQueue_BeginGetEvent(stream->event_queue, callback, state); ++} ++ ++static HRESULT WINAPI test_media_stream_EndGetEvent(IMFMediaStream *iface, IMFAsyncResult *result, IMFMediaEvent **event) ++{ ++ struct test_media_stream *stream = impl_from_IMFMediaStream(iface); ++ return IMFMediaEventQueue_EndGetEvent(stream->event_queue, result, event); ++} ++ ++static HRESULT WINAPI test_media_stream_QueueEvent(IMFMediaStream *iface, MediaEventType event_type, REFGUID ext_type, ++ HRESULT hr, const PROPVARIANT *value) ++{ ++ struct test_media_stream *stream = impl_from_IMFMediaStream(iface); ++ return IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, event_type, ext_type, hr, value); ++} ++ ++static HRESULT WINAPI test_media_stream_GetMediaSource(IMFMediaStream *iface, IMFMediaSource **source) ++{ ++ struct test_media_stream *stream = impl_from_IMFMediaStream(iface); ++ ++ *source = stream->source; ++ IMFMediaSource_AddRef(*source); ++ ++ return S_OK; ++} ++ ++static HRESULT WINAPI test_media_stream_GetStreamDescriptor(IMFMediaStream *iface, IMFStreamDescriptor **sd) ++{ ++ struct test_media_stream *stream = impl_from_IMFMediaStream(iface); ++ ++ *sd = stream->sd; ++ IMFStreamDescriptor_AddRef(*sd); ++ ++ return S_OK; ++} ++ ++static HRESULT WINAPI test_media_stream_RequestSample(IMFMediaStream *iface, IUnknown *token) ++{ ++ struct test_media_stream *stream = impl_from_IMFMediaStream(iface); ++ IMFMediaBuffer *buffer; ++ IMFSample *sample; ++ HRESULT hr; ++ ++ hr = MFCreateSample(&sample); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ if (stream->sample_duration) ++ { ++ hr = IMFSample_SetSampleDuration(sample, stream->sample_duration); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ hr = IMFSample_SetSampleTime(sample, stream->sample_time); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ stream->sample_time += stream->sample_duration; ++ } ++ else ++ { ++ hr = IMFSample_SetSampleTime(sample, 123); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ hr = IMFSample_SetSampleDuration(sample, 1); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ } ++ ++ if (token) ++ IMFSample_SetUnknown(sample, &MFSampleExtension_Token, token); ++ ++ /* Reader expects buffers, empty samples are considered an error. */ ++ hr = MFCreateMemoryBuffer(8, &buffer); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFSample_AddBuffer(sample, buffer); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ IMFMediaBuffer_Release(buffer); ++ ++ hr = IMFMediaEventQueue_QueueEventParamUnk(stream->event_queue, MEMediaSample, &GUID_NULL, S_OK, ++ (IUnknown *)sample); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ IMFSample_Release(sample); ++ ++ return S_OK; ++} ++ ++static const IMFMediaStreamVtbl test_media_stream_vtbl = ++{ ++ test_media_stream_QueryInterface, ++ test_media_stream_AddRef, ++ test_media_stream_Release, ++ test_media_stream_GetEvent, ++ test_media_stream_BeginGetEvent, ++ test_media_stream_EndGetEvent, ++ test_media_stream_QueueEvent, ++ test_media_stream_GetMediaSource, ++ test_media_stream_GetStreamDescriptor, ++ test_media_stream_RequestSample, ++}; ++ ++#define TEST_SOURCE_NUM_STREAMS 3 ++ ++struct unseekable_source ++{ ++ IMFMediaSource IMFMediaSource_iface; ++ IMFMediaEventQueue *event_queue; ++ IMFPresentationDescriptor *pd; ++ struct test_media_stream *streams[TEST_SOURCE_NUM_STREAMS]; ++ enum source_state state; ++ unsigned stream_count; ++ CRITICAL_SECTION cs; ++ LONG refcount; ++}; ++ ++static struct unseekable_source *impl_unseekable_source_from_IMFMediaSource(IMFMediaSource *iface) ++{ ++ return CONTAINING_RECORD(iface, struct unseekable_source, IMFMediaSource_iface); ++} ++ ++static HRESULT WINAPI unseekable_source_QueryInterface(IMFMediaSource *iface, REFIID riid, void **out) ++{ ++ if (IsEqualIID(riid, &IID_IMFMediaSource) ++ || IsEqualIID(riid, &IID_IMFMediaEventGenerator) ++ || IsEqualIID(riid, &IID_IUnknown)) ++ { ++ *out = iface; ++ } ++ else ++ { ++ *out = NULL; ++ return E_NOINTERFACE; ++ } ++ ++ IMFMediaSource_AddRef(iface); ++ return S_OK; ++} ++ ++static ULONG WINAPI unseekable_source_AddRef(IMFMediaSource *iface) ++{ ++ struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); ++ return InterlockedIncrement(&source->refcount); ++} ++ ++static ULONG WINAPI unseekable_source_Release(IMFMediaSource *iface) ++{ ++ struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); ++ ULONG refcount = InterlockedDecrement(&source->refcount); ++ ++ if (!refcount) ++ { ++ IMFMediaEventQueue_Release(source->event_queue); ++ free(source); ++ } ++ ++ return refcount; ++} ++ ++static HRESULT WINAPI unseekable_source_GetEvent(IMFMediaSource *iface, DWORD flags, IMFMediaEvent **event) ++{ ++ struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); ++ return IMFMediaEventQueue_GetEvent(source->event_queue, flags, event); ++} ++ ++static HRESULT WINAPI unseekable_source_BeginGetEvent(IMFMediaSource *iface, IMFAsyncCallback *callback, IUnknown *state) ++{ ++ struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); ++ return IMFMediaEventQueue_BeginGetEvent(source->event_queue, callback, state); ++} ++ ++static HRESULT WINAPI unseekable_source_EndGetEvent(IMFMediaSource *iface, IMFAsyncResult *result, IMFMediaEvent **event) ++{ ++ struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); ++ return IMFMediaEventQueue_EndGetEvent(source->event_queue, result, event); ++} ++ ++static HRESULT WINAPI unseekable_source_QueueEvent(IMFMediaSource *iface, MediaEventType event_type, REFGUID ext_type, ++ HRESULT hr, const PROPVARIANT *value) ++{ ++ struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); ++ return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, event_type, ext_type, hr, value); ++} ++ ++static HRESULT WINAPI unseekable_source_GetCharacteristics(IMFMediaSource *iface, DWORD *flags) ++{ ++ *flags = 0; ++ return S_OK; ++} ++ ++static HRESULT WINAPI unseekable_source_CreatePresentationDescriptor(IMFMediaSource *iface, IMFPresentationDescriptor **pd) ++{ ++ struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); ++ IMFStreamDescriptor *sds[ARRAY_SIZE(source->streams)]; ++ IMFMediaType *media_type; ++ HRESULT hr = S_OK; ++ int i; ++ ++ EnterCriticalSection(&source->cs); ++ ++ if (source->pd) ++ { ++ *pd = source->pd; ++ IMFPresentationDescriptor_AddRef(*pd); ++ } ++ else ++ { ++ for (i = 0; i < source->stream_count; ++i) ++ { ++ hr = MFCreateMediaType(&media_type); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ hr = IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &MFVideoFormat_RGB32); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_SIZE, (UINT64)640 << 32 | 480); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ hr = MFCreateStreamDescriptor(i, 1, &media_type, &sds[i]); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ IMFMediaType_Release(media_type); ++ } ++ ++ hr = MFCreatePresentationDescriptor(source->stream_count, sds, &source->pd); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFPresentationDescriptor_SetUINT64(source->pd, &MF_PD_DURATION, 10*10000000); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFPresentationDescriptor_SelectStream(source->pd, 0); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ for (i = 0; i < source->stream_count; ++i) ++ IMFStreamDescriptor_Release(sds[i]); ++ ++ *pd = source->pd; ++ IMFPresentationDescriptor_AddRef(*pd); ++ } ++ ++ LeaveCriticalSection(&source->cs); ++ ++ return hr; ++} ++ ++static BOOL is_stream_selected(IMFPresentationDescriptor *pd, DWORD index) ++{ ++ IMFStreamDescriptor *sd; ++ BOOL selected = FALSE; ++ ++ if (SUCCEEDED(IMFPresentationDescriptor_GetStreamDescriptorByIndex(pd, index, &selected, &sd))) ++ IMFStreamDescriptor_Release(sd); ++ ++ return selected; ++} ++ ++static HRESULT WINAPI unseekable_source_Start(IMFMediaSource *iface, IMFPresentationDescriptor *pd, const GUID *time_format, ++ const PROPVARIANT *start_position) ++{ ++ struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); ++ MediaEventType event_type; ++ PROPVARIANT var; ++ HRESULT hr; ++ int i; ++ ++ ok(time_format && IsEqualGUID(time_format, &GUID_NULL), "Unexpected time format %s.\n", ++ wine_dbgstr_guid(time_format)); ++ ok(start_position && (start_position->vt == VT_I8 || start_position->vt == VT_EMPTY), ++ "Unexpected position type.\n"); ++ ++ EnterCriticalSection(&source->cs); ++ ++ event_type = source->state == SOURCE_RUNNING ? MESourceSeeked : MESourceStarted; ++ hr = IMFMediaEventQueue_QueueEventParamVar(source->event_queue, event_type, &GUID_NULL, S_OK, NULL); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ for (i = 0; i < source->stream_count; ++i) ++ { ++ if (!is_stream_selected(pd, i)) ++ continue; ++ ++ var.vt = VT_UNKNOWN; ++ var.punkVal = (IUnknown *)&source->streams[i]->IMFMediaStream_iface; ++ event_type = source->streams[i]->is_new ? MENewStream : MEUpdatedStream; ++ source->streams[i]->is_new = FALSE; ++ hr = IMFMediaEventQueue_QueueEventParamVar(source->event_queue, event_type, &GUID_NULL, S_OK, &var); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ event_type = source->state == SOURCE_RUNNING ? MEStreamSeeked : MEStreamStarted; ++ hr = IMFMediaEventQueue_QueueEventParamVar(source->streams[i]->event_queue, event_type, &GUID_NULL, ++ S_OK, NULL); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ } ++ ++ source->state = SOURCE_RUNNING; ++ ++ LeaveCriticalSection(&source->cs); ++ ++ return S_OK; ++} ++ ++static HRESULT WINAPI unseekable_source_Stop(IMFMediaSource *iface) ++{ ++ return S_OK; ++} ++ ++static HRESULT WINAPI unseekable_source_Pause(IMFMediaSource *iface) ++{ ++ ok(0, "Unexpected call.\n"); ++ return E_NOTIMPL; ++} ++ ++static HRESULT WINAPI unseekable_source_Shutdown(IMFMediaSource *iface) ++{ ++ struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); ++ HRESULT hr; ++ ++ hr = IMFMediaEventQueue_Shutdown(source->event_queue); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ return S_OK; ++} ++ ++static const IMFMediaSourceVtbl unseekable_source_vtbl = ++{ ++ unseekable_source_QueryInterface, ++ unseekable_source_AddRef, ++ unseekable_source_Release, ++ unseekable_source_GetEvent, ++ unseekable_source_BeginGetEvent, ++ unseekable_source_EndGetEvent, ++ unseekable_source_QueueEvent, ++ unseekable_source_GetCharacteristics, ++ unseekable_source_CreatePresentationDescriptor, ++ unseekable_source_Start, ++ unseekable_source_Stop, ++ unseekable_source_Pause, ++ unseekable_source_Shutdown, ++}; ++ ++static struct test_media_stream *create_test_stream(DWORD stream_index, IMFMediaSource *source) ++{ ++ struct test_media_stream *stream; ++ IMFPresentationDescriptor *pd; ++ BOOL selected; ++ HRESULT hr; ++ ++ stream = calloc(1, sizeof(*stream)); ++ stream->IMFMediaStream_iface.lpVtbl = &test_media_stream_vtbl; ++ stream->refcount = 1; ++ hr = MFCreateEventQueue(&stream->event_queue); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ stream->source = source; ++ IMFMediaSource_AddRef(stream->source); ++ stream->is_new = TRUE; ++ ++ IMFMediaSource_CreatePresentationDescriptor(source, &pd); ++ IMFPresentationDescriptor_GetStreamDescriptorByIndex(pd, stream_index, &selected, &stream->sd); ++ IMFPresentationDescriptor_Release(pd); ++ ++ return stream; ++} ++ ++static IMFMediaSource *create_unseekable_source(void) ++{ ++ struct unseekable_source *source; ++ int i; ++ ++ source = calloc(1, sizeof(*source)); ++ source->IMFMediaSource_iface.lpVtbl = &unseekable_source_vtbl; ++ source->refcount = 1; ++ source->stream_count = 1; ++ MFCreateEventQueue(&source->event_queue); ++ InitializeCriticalSection(&source->cs); ++ for (i = 0; i < source->stream_count; ++i) ++ source->streams[i] = create_test_stream(i, &source->IMFMediaSource_iface); ++ ++ return &source->IMFMediaSource_iface; ++} ++ + static void test_media_session_events(void) + { + static const media_type_desc audio_float_44100 = +@@ -2887,27 +3329,27 @@ static ULONG WINAPI test_grabber_callback_Release(IMFSampleGrabberSinkCallback * + + static HRESULT WINAPI test_grabber_callback_OnClockStart(IMFSampleGrabberSinkCallback *iface, MFTIME time, LONGLONG offset) + { +- return E_NOTIMPL; ++ return S_OK; + } + + static HRESULT WINAPI test_grabber_callback_OnClockStop(IMFSampleGrabberSinkCallback *iface, MFTIME time) + { +- return E_NOTIMPL; ++ return S_OK; + } + + static HRESULT WINAPI test_grabber_callback_OnClockPause(IMFSampleGrabberSinkCallback *iface, MFTIME time) + { +- return E_NOTIMPL; ++ return S_OK; + } + + static HRESULT WINAPI test_grabber_callback_OnClockRestart(IMFSampleGrabberSinkCallback *iface, MFTIME time) + { +- return E_NOTIMPL; ++ return S_OK; + } + + static HRESULT WINAPI test_grabber_callback_OnClockSetRate(IMFSampleGrabberSinkCallback *iface, MFTIME time, float rate) + { +- return E_NOTIMPL; ++ return S_OK; + } + + static HRESULT WINAPI test_grabber_callback_OnSetPresentationClock(IMFSampleGrabberSinkCallback *iface, +@@ -4995,9 +5437,9 @@ static void test_sample_grabber_is_mediatype_supported(void) + IMFSampleGrabberSinkCallback_Release(grabber_callback); + } + +-/* create a media session with the specified source and sink */ ++/* create a media session with the specified source and sink, and return duration if required */ + static void create_media_session_with_source_sink(IMFMediaSource *source, IMFActivate *sink_activate, +- IMFMediaSession **session) ++ IMFMediaSession **session, UINT64 *duration) + { + IMFTopologyNode *src_node, *sink_node; + IMFPresentationDescriptor *pd; +@@ -5025,6 +5467,11 @@ static void create_media_session_with_source_sink(IMFMediaSource *source, IMFAct + hr = IMFPresentationDescriptor_GetStreamDescriptorByIndex(pd, 0, &selected, &sd); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(selected, "got selected %u.\n", !!selected); ++ if (duration) ++ { ++ hr = IMFPresentationDescriptor_GetUINT64(pd, &MF_PD_DURATION, duration); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ } + init_source_node(source, -1, src_node, pd, sd); + hr = IMFTopologyNode_SetObject(sink_node, (IUnknown *)sink_activate); + ok(hr == S_OK, "Failed to set object, hr %#lx.\n", hr); +@@ -5084,7 +5531,7 @@ static void test_sample_grabber_orientation(GUID subtype) + ok(hr == S_OK, "Failed to create grabber sink, hr %#lx.\n", hr); + IMFMediaType_Release(output_type); + +- create_media_session_with_source_sink(source, sink_activate, &session); ++ create_media_session_with_source_sink(source, sink_activate, &session, NULL); + + propvar.vt = VT_EMPTY; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); +@@ -7280,6 +7727,246 @@ static void test_MFCreateSequencerSegmentOffset(void) + PropVariantClear(&propvar); + } + ++static void test_media_session_Start(void) ++{ ++ media_type_desc video_rgb32_desc = ++ { ++ ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), ++ ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32), ++ }; ++ static const MFTIME allowed_error = 500000; ++ struct test_grabber_callback *grabber_callback; ++ IMFPresentationClock *presentation_clock; ++ IMFActivate *sink_activate; ++ IMFAsyncCallback *callback; ++ IMFMediaType *output_type; ++ IMFMediaSession *session; ++ IMFMediaSource *source; ++ MFTIME time, old_time; ++ PROPVARIANT propvar; ++ IMFClock *clock; ++ UINT64 duration; ++ DWORD caps; ++ HRESULT hr; ++ ++ hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); ++ ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr); ++ ++ if (!(source = create_media_source(L"test.mp4", L"video/mp4"))) ++ { ++ win_skip("MP4 media source is not supported, skipping tests.\n"); ++ MFShutdown(); ++ return; ++ } ++ ++ grabber_callback = impl_from_IMFSampleGrabberSinkCallback(create_test_grabber_callback()); ++ hr = MFCreateMediaType(&output_type); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ init_media_type(output_type, video_rgb32_desc, -1); ++ hr = MFCreateSampleGrabberSinkActivate(output_type, &grabber_callback->IMFSampleGrabberSinkCallback_iface, &sink_activate); ++ ok(hr == S_OK, "Failed to create grabber sink, hr %#lx.\n", hr); ++ IMFMediaType_Release(output_type); ++ create_media_session_with_source_sink(source, sink_activate, &session, &duration); ++ ++ hr = IMFMediaSession_GetClock(session, &clock); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFClock_QueryInterface(clock, &IID_IMFPresentationClock, (void **)&presentation_clock); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ IMFClock_Release(clock); ++ ++ propvar.vt = VT_EMPTY; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ callback = create_test_callback(TRUE); ++ hr = wait_media_event(session, callback, MESessionStarted, 5000, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ /* Seek to 1s */ ++ propvar.vt = VT_I8; ++ propvar.hVal.QuadPart = 10000000; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFPresentationClock_GetTime(presentation_clock, &time); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok(llabs(time - 10000000) <= allowed_error, "Unexpected time %I64d.\n", time); ++ ++ /* Seek to beyond duration */ ++ propvar.vt = VT_I8; ++ propvar.hVal.QuadPart = duration + 10000000; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); ++ ok(hr == MF_E_INVALID_POSITION, "Unexpected hr %#lx.\n", hr); ++ hr = IMFPresentationClock_GetTime(presentation_clock, &time); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok(llabs(time - 10000000) <= allowed_error, "Unexpected time %I64d.\n", time); ++ ++ /* Seek to negative position */ ++ propvar.vt = VT_I8; ++ propvar.hVal.QuadPart = -10000000; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFPresentationClock_GetTime(presentation_clock, &time); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok(llabs(time - (-10000000)) <= allowed_error, "Unexpected time %I64d.\n", time); ++ ++ /* Seek backwards to 0s */ ++ propvar.vt = VT_I8; ++ propvar.hVal.QuadPart = 0; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFPresentationClock_GetTime(presentation_clock, &time); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok(llabs(time) <= allowed_error, "Unexpected time %I64d.\n", time); ++ ++ /* Seek to 1s while in paused state */ ++ hr = IMFMediaSession_Pause(session); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = wait_media_event(session, callback, MESessionPaused, 1000, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ propvar.vt = VT_I8; ++ propvar.hVal.QuadPart = 10000000; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ hr = IMFPresentationClock_GetTime(presentation_clock, &time); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok(llabs(time - 10000000) <= allowed_error, "Unexpected time %I64d.\n", time); ++ old_time = time; ++ ++ /* Expected the presentation clock is running */ ++ Sleep(100); ++ hr = IMFPresentationClock_GetTime(presentation_clock, &time); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok(time > old_time, "Unexpected time %I64d.\n", time); ++ ++ hr = IMFMediaSession_Stop(session); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFMediaSession_Close(session); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ /* Media session is shut down */ ++ hr = IMFMediaSource_Shutdown(source); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFMediaSession_Shutdown(session); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ propvar.vt = VT_I8; ++ propvar.hVal.QuadPart = 10000000; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, NULL); ++ ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); ++ ++ propvar.vt = VT_I8; ++ propvar.hVal.QuadPart = 10000000; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == MF_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr); ++ ++ propvar.vt = VT_EMPTY; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == MF_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr); ++ ++ IMFPresentationClock_Release(presentation_clock); ++ IMFMediaSession_Release(session); ++ IMFMediaSource_Release(source); ++ IMFActivate_ShutdownObject(sink_activate); ++ IMFActivate_Release(sink_activate); ++ IMFSampleGrabberSinkCallback_Release(&grabber_callback->IMFSampleGrabberSinkCallback_iface); ++ ++ /* Unseekable media source */ ++ source = create_unseekable_source(); ++ hr = IMFMediaSource_GetCharacteristics(source, &caps); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok((caps & MFMEDIASOURCE_CAN_SEEK) == 0, "Got unexpected caps %#lx.\n", caps); ++ grabber_callback = impl_from_IMFSampleGrabberSinkCallback(create_test_grabber_callback()); ++ hr = MFCreateMediaType(&output_type); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ init_media_type(output_type, video_rgb32_desc, -1); ++ hr = MFCreateSampleGrabberSinkActivate(output_type, &grabber_callback->IMFSampleGrabberSinkCallback_iface, &sink_activate); ++ ok(hr == S_OK, "Failed to create grabber sink, hr %#lx.\n", hr); ++ IMFMediaType_Release(output_type); ++ create_media_session_with_source_sink(source, sink_activate, &session, &duration); ++ ++ hr = IMFMediaSession_GetClock(session, &clock); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFClock_QueryInterface(clock, &IID_IMFPresentationClock, (void **)&presentation_clock); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ IMFClock_Release(clock); ++ ++ propvar.vt = VT_EMPTY; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ callback = create_test_callback(TRUE); ++ hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ hr = IMFMediaSession_GetSessionCapabilities(session, &caps); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok((caps & MFSESSIONCAP_SEEK) == 0, "Got unexpected caps %#lx\n", caps); ++ ++ /* Seek to 1s */ ++ propvar.vt = VT_I8; ++ propvar.hVal.QuadPart = 10000000; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ /* Waiting for MESessionStarted will timeout, skip checking MESessionStarted */ ++ hr = IMFPresentationClock_GetTime(presentation_clock, &time); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok(llabs(time) <= allowed_error, "Unexpected time %I64d.\n", time); ++ ++ /* Seek to 0s */ ++ propvar.vt = VT_EMPTY; ++ propvar.hVal.QuadPart = 0; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ /* Waiting for MESessionStarted will timeout, skip checking MESessionStarted */ ++ hr = IMFPresentationClock_GetTime(presentation_clock, &time); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok(llabs(time) <= allowed_error, "Unexpected time %I64d.\n", time); ++ ++ /* Seek backwards to 0s */ ++ Sleep(200); ++ hr = IMFPresentationClock_GetTime(presentation_clock, &time); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok(llabs(time - 2000000) <= allowed_error, "Unexpected time %I64d.\n", time); ++ ++ propvar.vt = VT_I8; ++ propvar.hVal.QuadPart = 0; ++ hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ /* Waiting for MESessionStarted will timeout, skip checking MESessionStarted */ ++ hr = IMFPresentationClock_GetTime(presentation_clock, &time); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ok(llabs(time - 2000000) <= allowed_error, "Unexpected time %I64d.\n", time); ++ ++ hr = IMFMediaSession_Stop(session); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFMediaSession_Close(session); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFMediaSession_Shutdown(session); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ hr = IMFMediaSource_Shutdown(source); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ IMFPresentationClock_Release(presentation_clock); ++ IMFMediaSession_Release(session); ++ IMFMediaSource_Release(source); ++ IMFActivate_ShutdownObject(sink_activate); ++ IMFActivate_Release(sink_activate); ++ IMFSampleGrabberSinkCallback_Release(&grabber_callback->IMFSampleGrabberSinkCallback_iface); ++ ++ hr = MFShutdown(); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++} ++ + START_TEST(mf) + { + init_functions(); +@@ -7316,4 +8003,5 @@ START_TEST(mf) + test_MFRequireProtectedEnvironment(); + test_mpeg4_media_sink(); + test_MFCreateSequencerSegmentOffset(); ++ test_media_session_Start(); + } +-- +2.45.2 + diff --git a/patches/wine-gst/0004-mfmediaengine-Implement-IMFMediaEngineEx-SetCurrentT.patch b/patches/wine-gst/0004-mfmediaengine-Implement-IMFMediaEngineEx-SetCurrentT.patch new file mode 100644 index 000000000..33f66854a --- /dev/null +++ b/patches/wine-gst/0004-mfmediaengine-Implement-IMFMediaEngineEx-SetCurrentT.patch @@ -0,0 +1,109 @@ +From d74ea960a50420934759d2addfa649fae15f878a Mon Sep 17 00:00:00 2001 +From: Zhiyi Zhang +Date: Fri, 28 Jul 2023 18:04:30 +0800 +Subject: [PATCH 04/43] mfmediaengine: Implement + IMFMediaEngineEx::SetCurrentTime/Ex(). + +--- + dlls/mfmediaengine/main.c | 62 +++++++++++++++++++++++++++++---------- + 1 file changed, 47 insertions(+), 15 deletions(-) + +diff --git a/dlls/mfmediaengine/main.c b/dlls/mfmediaengine/main.c +index 9e41d9dad84..28a273616c8 100644 +--- a/dlls/mfmediaengine/main.c ++++ b/dlls/mfmediaengine/main.c +@@ -96,6 +96,7 @@ enum media_engine_flags + FLAGS_ENGINE_NEW_FRAME = 0x8000, + FLAGS_ENGINE_SOURCE_PENDING = 0x10000, + FLAGS_ENGINE_PLAY_PENDING = 0x20000, ++ FLAGS_ENGINE_SEEKING = 0x40000, + }; + + struct vec3 +@@ -982,7 +983,14 @@ static HRESULT WINAPI media_engine_session_events_Invoke(IMFAsyncCallback *iface + break; + } + case MESessionStarted: +- ++ EnterCriticalSection(&engine->cs); ++ if (engine->flags & FLAGS_ENGINE_SEEKING) ++ { ++ media_engine_set_flag(engine, FLAGS_ENGINE_SEEKING | FLAGS_ENGINE_IS_ENDED, FALSE); ++ IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_SEEKED, 0, 0); ++ IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_TIMEUPDATE, 0, 0); ++ } ++ LeaveCriticalSection(&engine->cs); + IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_PLAYING, 0, 0); + break; + case MESessionEnded: +@@ -1846,19 +1854,9 @@ static double WINAPI media_engine_GetCurrentTime(IMFMediaEngineEx *iface) + + static HRESULT WINAPI media_engine_SetCurrentTime(IMFMediaEngineEx *iface, double time) + { +- struct media_engine *engine = impl_from_IMFMediaEngineEx(iface); +- HRESULT hr = E_NOTIMPL; ++ TRACE("%p, %f.\n", iface, time); + +- FIXME("(%p, %f): stub.\n", iface, time); +- +- EnterCriticalSection(&engine->cs); +- +- if (engine->flags & FLAGS_ENGINE_SHUT_DOWN) +- hr = MF_E_SHUTDOWN; +- +- LeaveCriticalSection(&engine->cs); +- +- return hr; ++ return IMFMediaEngineEx_SetCurrentTimeEx(iface, time, MF_MEDIA_ENGINE_SEEK_MODE_NORMAL); + } + + static double WINAPI media_engine_GetStartTime(IMFMediaEngineEx *iface) +@@ -3059,9 +3057,43 @@ static HRESULT WINAPI media_engine_SetRealTimeMode(IMFMediaEngineEx *iface, BOOL + + static HRESULT WINAPI media_engine_SetCurrentTimeEx(IMFMediaEngineEx *iface, double seektime, MF_MEDIA_ENGINE_SEEK_MODE mode) + { +- FIXME("%p, %f, %#x stub.\n", iface, seektime, mode); ++ struct media_engine *engine = impl_from_IMFMediaEngineEx(iface); ++ PROPVARIANT position; ++ BOOL paused; ++ DWORD caps; ++ HRESULT hr; + +- return E_NOTIMPL; ++ TRACE("%p, %f, %#x.\n", iface, seektime, mode); ++ ++ if (mode) ++ FIXME("mode %#x is ignored.\n", mode); ++ ++ EnterCriticalSection(&engine->cs); ++ ++ if (engine->flags & FLAGS_ENGINE_SHUT_DOWN) ++ hr = MF_E_SHUTDOWN; ++ else ++ { ++ hr = IMFMediaSession_GetSessionCapabilities(engine->session, &caps); ++ if (SUCCEEDED(hr) && caps & MFSESSIONCAP_SEEK) ++ { ++ paused = IMFMediaEngineEx_IsPaused(iface); ++ ++ position.vt = VT_I8; ++ position.hVal.QuadPart = min(max(0, seektime), engine->duration) * 10000000; ++ hr = IMFMediaSession_Start(engine->session, &GUID_NULL, &position); ++ if (SUCCEEDED(hr)) ++ { ++ media_engine_set_flag(engine, FLAGS_ENGINE_SEEKING, TRUE); ++ IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_SEEKING, 0, 0); ++ if (paused) ++ hr = IMFMediaSession_Pause(engine->session); ++ } ++ } ++ } ++ ++ LeaveCriticalSection(&engine->cs); ++ return hr; + } + + static HRESULT WINAPI media_engine_EnableTimeUpdateTimer(IMFMediaEngineEx *iface, BOOL enable) +-- +2.45.2 + diff --git a/patches/wine-gst/0005-mfmediaengine-tests-Test-IMFMediaEngineEx-SetCurrent.patch b/patches/wine-gst/0005-mfmediaengine-tests-Test-IMFMediaEngineEx-SetCurrent.patch new file mode 100644 index 000000000..f1f162661 --- /dev/null +++ b/patches/wine-gst/0005-mfmediaengine-tests-Test-IMFMediaEngineEx-SetCurrent.patch @@ -0,0 +1,243 @@ +From e899f59e8a37f644d968ee722874e910019bfeb3 Mon Sep 17 00:00:00 2001 +From: Zhiyi Zhang +Date: Mon, 7 Aug 2023 11:53:41 +0800 +Subject: [PATCH 05/43] mfmediaengine/tests: Test + IMFMediaEngineEx::SetCurrentTime/Ex(). + +--- + dlls/mfmediaengine/tests/mfmediaengine.c | 183 +++++++++++++++++++++++ + 1 file changed, 183 insertions(+) + +diff --git a/dlls/mfmediaengine/tests/mfmediaengine.c b/dlls/mfmediaengine/tests/mfmediaengine.c +index 47130d8e436..29a11d034cd 100644 +--- a/dlls/mfmediaengine/tests/mfmediaengine.c ++++ b/dlls/mfmediaengine/tests/mfmediaengine.c +@@ -2185,6 +2185,9 @@ struct test_seek_notify + { + IMFMediaEngineNotify IMFMediaEngineNotify_iface; + HANDLE playing_event; ++ HANDLE seeking_event; ++ HANDLE seeked_event; ++ HANDLE time_update_event; + HRESULT expected_error; + HRESULT error; + LONG refcount; +@@ -2224,6 +2227,9 @@ static ULONG WINAPI test_seek_notify_Release(IMFMediaEngineNotify *iface) + if (!refcount) + { + CloseHandle(notify->playing_event); ++ CloseHandle(notify->seeking_event); ++ CloseHandle(notify->seeked_event); ++ CloseHandle(notify->time_update_event); + free(notify); + } + +@@ -2240,6 +2246,15 @@ static HRESULT WINAPI test_seek_notify_EventNotify(IMFMediaEngineNotify *iface, + case MF_MEDIA_ENGINE_EVENT_PLAYING: + SetEvent(notify->playing_event); + break; ++ case MF_MEDIA_ENGINE_EVENT_SEEKING: ++ SetEvent(notify->seeking_event); ++ break; ++ case MF_MEDIA_ENGINE_EVENT_SEEKED: ++ SetEvent(notify->seeked_event); ++ break; ++ case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE: ++ SetEvent(notify->time_update_event); ++ break; + case MF_MEDIA_ENGINE_EVENT_ERROR: + ok(param2 == notify->expected_error, "Unexpected error %#lx\n", param2); + notify->error = param2; +@@ -2264,7 +2279,13 @@ static struct test_seek_notify *create_seek_notify(void) + object = calloc(1, sizeof(*object)); + object->IMFMediaEngineNotify_iface.lpVtbl = &test_seek_notify_vtbl; + object->playing_event = CreateEventW(NULL, FALSE, FALSE, NULL); ++ object->seeking_event = CreateEventW(NULL, FALSE, FALSE, NULL); ++ object->seeked_event = CreateEventW(NULL, FALSE, FALSE, NULL); ++ object->time_update_event = CreateEventW(NULL, FALSE, FALSE, NULL); + ok(!!object->playing_event, "Failed to create an event, error %lu.\n", GetLastError()); ++ ok(!!object->seeking_event, "Failed to create an event, error %lu.\n", GetLastError()); ++ ok(!!object->seeked_event, "Failed to create an event, error %lu.\n", GetLastError()); ++ ok(!!object->time_update_event, "Failed to create an event, error %lu.\n", GetLastError()); + object->refcount = 1; + return object; + } +@@ -2510,6 +2531,167 @@ static void test_media_extension(void) + IMFMediaEngineExtension_Release(&extension->IMFMediaEngineExtension_iface); + } + ++#define test_seek_result(a, b, c) _test_seek_result(__LINE__, a, b, c) ++static void _test_seek_result(int line, IMFMediaEngineEx *media_engine, ++ const struct test_seek_notify *notify, double expected_time) ++{ ++ static const double allowed_error = 0.05; ++ static const int timeout = 1000; ++ double time; ++ DWORD res; ++ ++ res = WaitForSingleObject(notify->seeking_event, timeout); ++ ok_(__FILE__, line)(!res, "Waiting for seeking event returned %#lx.\n", res); ++ res = WaitForSingleObject(notify->seeked_event, timeout); ++ ok_(__FILE__, line)(!res, "Waiting for seeked event returned %#lx.\n", res); ++ res = WaitForSingleObject(notify->time_update_event, timeout); ++ ok_(__FILE__, line)(!res, "Waiting for ready event returned %#lx.\n", res); ++ time = IMFMediaEngineEx_GetCurrentTime(media_engine); ++ ok_(__FILE__, line)(compare_double(time, expected_time, allowed_error), "Unexpected time %lf.\n", time); ++} ++ ++static void test_SetCurrentTime(void) ++{ ++ static const double allowed_error = 0.05; ++ static const int timeout = 1000; ++ IMFByteStream *stream, *unseekable_stream = NULL; ++ double time, duration, start, end; ++ struct test_seek_notify *notify; ++ IMFMediaEngineEx *media_engine; ++ HRESULT hr; ++ DWORD res; ++ BOOL ret; ++ BSTR url; ++ ++ notify = create_seek_notify(); ++ media_engine = create_media_engine_ex(¬ify->IMFMediaEngineNotify_iface, NULL, DXGI_FORMAT_B8G8R8X8_UNORM); ++ ok(!!media_engine, "create_media_engine_ex failed.\n"); ++ IMFMediaEngineNotify_Release(¬ify->IMFMediaEngineNotify_iface); ++ ++ stream = load_resource(L"i420-64x64.avi", L"video/avi"); ++ url = SysAllocString(L"i420-64x64.avi"); ++ hr = IMFMediaEngineEx_SetSourceFromByteStream(media_engine, stream, url); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ hr = IMFMediaEngineEx_Play(media_engine); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ res = WaitForSingleObject(notify->playing_event, 5000); ++ ok(!res, "Unexpected res %#lx.\n", res); ++ ++ duration = IMFMediaEngineEx_GetDuration(media_engine); ++ ok(duration > 0, "Got invalid duration.\n"); ++ start = 0; ++ end = duration; ++ ++ /* Test playing state */ ++ hr = IMFMediaEngineEx_SetCurrentTime(media_engine, end); ++ ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); ++ if (hr == S_OK) ++ test_seek_result(media_engine, notify, end); ++ ++ /* Test seeking with a negative position */ ++ hr = IMFMediaEngineEx_SetCurrentTime(media_engine, -1); ++ ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); ++ if (hr == S_OK) ++ test_seek_result(media_engine, notify, 0); ++ ++ /* Test seeking beyond duration */ ++ hr = IMFMediaEngineEx_SetCurrentTime(media_engine, end + 1); ++ ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); ++ if (hr == S_OK) ++ test_seek_result(media_engine, notify, end); ++ ++ hr = IMFMediaEngineEx_SetCurrentTimeEx(media_engine, start, MF_MEDIA_ENGINE_SEEK_MODE_NORMAL); ++ ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); ++ if (hr == S_OK) ++ test_seek_result(media_engine, notify, start); ++ ++ hr = IMFMediaEngineEx_SetCurrentTimeEx(media_engine, end, MF_MEDIA_ENGINE_SEEK_MODE_APPROXIMATE); ++ ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); ++ if (hr == S_OK) ++ test_seek_result(media_engine, notify, end); ++ ++ /* Test paused state */ ++ hr = IMFMediaEngineEx_Pause(media_engine); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ hr = IMFMediaEngineEx_SetCurrentTime(media_engine, start); ++ ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); ++ if (hr == S_OK) ++ { ++ res = WaitForSingleObject(notify->seeking_event, timeout); ++ ok(!res, "Unexpected res %#lx.\n", res); ++ res = WaitForSingleObject(notify->seeked_event, timeout); ++ ok(res == WAIT_TIMEOUT || res == 0, /* No timeout sometimes on Win10+ */ ++ "Unexpected res %#lx.\n", res); ++ res = WaitForSingleObject(notify->time_update_event, timeout); ++ ok(!res, "Unexpected res %#lx.\n", res); ++ time = IMFMediaEngineEx_GetCurrentTime(media_engine); ++ ok(compare_double(time, start, allowed_error), "Unexpected time %lf.\n", time); ++ } ++ ++ Sleep(end * 1000); ++ ++ ret = IMFMediaEngineEx_IsPaused(media_engine); ++ ok(ret, "Unexpected ret %d.\n", ret); ++ time = IMFMediaEngineEx_GetCurrentTime(media_engine); ++ ok(compare_double(time, start, allowed_error) ++ || broken(time >= end), /* Windows 11 21H2 AMD GPU TestBot */ ++ "Unexpected time %lf.\n", time); ++ ++ hr = IMFMediaEngineEx_Play(media_engine); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ res = WaitForSingleObject(notify->seeked_event, timeout); ++ ok(res == WAIT_TIMEOUT, "Unexpected res %#lx.\n", res); ++ ++ /* Media engine is shut down */ ++ hr = IMFMediaEngineEx_Shutdown(media_engine); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ ++ hr = IMFMediaEngineEx_SetCurrentTime(media_engine, start); ++ ok(hr == MF_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr); ++ hr = IMFMediaEngineEx_SetCurrentTimeEx(media_engine, start, MF_MEDIA_ENGINE_SEEK_MODE_NORMAL); ++ ok(hr == MF_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr); ++ ++ IMFMediaEngineEx_Release(media_engine); ++ ++ /* Unseekable bytestreams */ ++ notify = create_seek_notify(); ++ media_engine = create_media_engine_ex(¬ify->IMFMediaEngineNotify_iface, NULL, DXGI_FORMAT_B8G8R8X8_UNORM); ++ IMFMediaEngineNotify_Release(¬ify->IMFMediaEngineNotify_iface); ++ unseekable_stream = create_unseekable_stream(stream); ++ hr = IMFMediaEngineEx_SetSourceFromByteStream(media_engine, unseekable_stream, url); ++ todo_wine_if(hr == MF_E_BYTESTREAM_NOT_SEEKABLE) ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ if (FAILED(hr)) ++ goto done; ++ ++ hr = IMFMediaEngineEx_Play(media_engine); ++ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ++ notify->expected_error = MF_E_INVALIDREQUEST; ++ res = WaitForSingleObject(notify->playing_event, 5000); ++ ok(res == S_OK, "Unexpected res %#lx.\n", res); ++ ++ hr = IMFMediaEngineEx_SetCurrentTime(media_engine, end); ++ ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); ++ if (hr == S_OK) ++ { ++ res = WaitForSingleObject(notify->seeking_event, timeout); ++ ok(res == WAIT_TIMEOUT, "Unexpected res %#lx.\n", res); ++ res = WaitForSingleObject(notify->seeked_event, timeout); ++ ok(res == WAIT_TIMEOUT, "Unexpected res %#lx.\n", res); ++ res = WaitForSingleObject(notify->time_update_event, timeout); ++ ok(!res, "Unexpected res %#lx.\n", res); ++ } ++ ++done: ++ IMFMediaEngineEx_Shutdown(media_engine); ++ IMFMediaEngineEx_Release(media_engine); ++ IMFByteStream_Release(unseekable_stream); ++ SysFreeString(url); ++ IMFByteStream_Release(stream); ++} ++ + START_TEST(mfmediaengine) + { + HRESULT hr; +@@ -2545,6 +2727,7 @@ START_TEST(mfmediaengine) + test_GetDuration(); + test_GetSeekable(); + test_media_extension(); ++ test_SetCurrentTime(); + + IMFMediaEngineClassFactory_Release(factory); + +-- +2.45.2 + diff --git a/patches/wine-gst/0006-mfmediaengine-Implement-IMFMediaEngine-IsSeeking.patch b/patches/wine-gst/0006-mfmediaengine-Implement-IMFMediaEngine-IsSeeking.patch new file mode 100644 index 000000000..185894094 --- /dev/null +++ b/patches/wine-gst/0006-mfmediaengine-Implement-IMFMediaEngine-IsSeeking.patch @@ -0,0 +1,35 @@ +From eaba25d360906caaa566ab0e086a954d17e8f4a5 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Fri, 14 Jun 2024 03:09:34 +0200 +Subject: [PATCH 06/43] mfmediaengine: Implement IMFMediaEngine::IsSeeking. + +--- + dlls/mfmediaengine/main.c | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +diff --git a/dlls/mfmediaengine/main.c b/dlls/mfmediaengine/main.c +index 28a273616c8..a40f8d64f58 100644 +--- a/dlls/mfmediaengine/main.c ++++ b/dlls/mfmediaengine/main.c +@@ -1825,9 +1825,16 @@ static USHORT WINAPI media_engine_GetReadyState(IMFMediaEngineEx *iface) + + static BOOL WINAPI media_engine_IsSeeking(IMFMediaEngineEx *iface) + { +- FIXME("(%p): stub.\n", iface); ++ struct media_engine *engine = impl_from_IMFMediaEngineEx(iface); ++ BOOL ret; + +- return FALSE; ++ TRACE("%p.\n", iface); ++ ++ EnterCriticalSection(&engine->cs); ++ ret = !!(engine->flags & FLAGS_ENGINE_SEEKING); ++ LeaveCriticalSection(&engine->cs); ++ ++ return ret; + } + + static double WINAPI media_engine_GetCurrentTime(IMFMediaEngineEx *iface) +-- +2.45.2 + diff --git a/patches/wine-gst/0007-AVPro-Video-seeking-support.patch b/patches/wine-gst/0007-AVPro-Video-seeking-support.patch new file mode 100644 index 000000000..91f37933c --- /dev/null +++ b/patches/wine-gst/0007-AVPro-Video-seeking-support.patch @@ -0,0 +1,22 @@ +From 20481d9dc941adbe88d9090625246e0af49197e6 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 10 Apr 2024 18:57:59 +0200 +Subject: [PATCH 07/43] === AVPro Video seeking support === + +--- + MAINTAINERS | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/MAINTAINERS b/MAINTAINERS +index 59873f6804c..ab42dff2884 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -436,3 +436,5 @@ P: Zebediah Figura + P: Paul Gofman + P: Erich E. Hoover + W: https://wine-staging.com/ ++ ++seek +-- +2.45.2 + diff --git a/patches/wine-gst/0008-mf-session-Avoid-leaking-samples-in-transform_node_d.patch b/patches/wine-gst/0008-mf-session-Avoid-leaking-samples-in-transform_node_d.patch new file mode 100644 index 000000000..f594d35ac --- /dev/null +++ b/patches/wine-gst/0008-mf-session-Avoid-leaking-samples-in-transform_node_d.patch @@ -0,0 +1,28 @@ +From 377f775c4cda142df5c938b0c92ca99f6838364c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?R=C3=A9mi=20Bernon?= +Date: Wed, 13 Mar 2024 10:27:26 +0100 +Subject: [PATCH 08/43] mf/session: Avoid leaking samples in + transform_node_deliver_samples. + +--- + dlls/mf/session.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/dlls/mf/session.c b/dlls/mf/session.c +index 3f3be2b84b0..925566d6baf 100644 +--- a/dlls/mf/session.c ++++ b/dlls/mf/session.c +@@ -3337,7 +3337,10 @@ static void transform_node_deliver_samples(struct media_session *session, struct + stream = &topo_node->u.transform.inputs[input]; + + if (SUCCEEDED(transform_stream_pop_sample(stream, &sample))) ++ { + session_deliver_sample_to_node(session, topo_node->node, input, sample); ++ IMFSample_Release(sample); ++ } + else if (FAILED(hr = IMFTopologyNode_GetInput(topo_node->node, input, &up_node, &output))) + WARN("Failed to get node %p/%lu input, hr %#lx\n", topo_node->node, input, hr); + else +-- +2.45.2 + diff --git a/patches/wine-gst/0009-Memory-leak-fixes.patch b/patches/wine-gst/0009-Memory-leak-fixes.patch new file mode 100644 index 000000000..d6bdadd65 --- /dev/null +++ b/patches/wine-gst/0009-Memory-leak-fixes.patch @@ -0,0 +1,21 @@ +From 54ce917fe1589a95a612dc1469a4ef086168aab3 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 10 Apr 2024 18:59:43 +0200 +Subject: [PATCH 09/43] === Memory leak fixes === + +--- + MAINTAINERS | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/MAINTAINERS b/MAINTAINERS +index ab42dff2884..2291c99447c 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -438,3 +438,4 @@ P: Erich E. Hoover + W: https://wine-staging.com/ + + seek ++leak +-- +2.45.2 + diff --git a/patches/wine-gst/0010-winegstreamer-Initialize-condition-variable-of-struc.patch b/patches/wine-gst/0010-winegstreamer-Initialize-condition-variable-of-struc.patch new file mode 100644 index 000000000..4b453395d --- /dev/null +++ b/patches/wine-gst/0010-winegstreamer-Initialize-condition-variable-of-struc.patch @@ -0,0 +1,25 @@ +From bb59ff4a88faa481a4bceae9464d0302ed1733e1 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Fri, 14 Jun 2024 03:15:57 +0200 +Subject: [PATCH 10/43] winegstreamer: Initialize condition variable of struct + media_stream. + +--- + dlls/winegstreamer/media_source.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c +index a4ac0085dea..93a4425dd06 100644 +--- a/dlls/winegstreamer/media_source.c ++++ b/dlls/winegstreamer/media_source.c +@@ -1172,6 +1172,7 @@ static HRESULT media_stream_create(IMFMediaSource *source, IMFStreamDescriptor * + object->active = TRUE; + object->eos = FALSE; + object->wg_stream = wg_stream; ++ InitializeConditionVariable(&object->cond); + + TRACE("Created stream object %p.\n", object); + +-- +2.45.2 + diff --git a/patches/wine-gst/0011-winegstreamer-Connect-autoplug-continue-and-deep-ele.patch b/patches/wine-gst/0011-winegstreamer-Connect-autoplug-continue-and-deep-ele.patch new file mode 100644 index 000000000..8114d187a --- /dev/null +++ b/patches/wine-gst/0011-winegstreamer-Connect-autoplug-continue-and-deep-ele.patch @@ -0,0 +1,29 @@ +From 044c331fc36bd87ca51f02c106614f37fa9d5d5e Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Sun, 31 Mar 2024 15:49:33 +0200 +Subject: [PATCH 11/43] winegstreamer: Connect autoplug-continue and + deep-element-added callbacks to uridecodebin-based parsers too. + +--- + dlls/winegstreamer/wg_parser.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 6a7fd6d1c71..48a1d658232 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -2085,9 +2085,11 @@ static BOOL uridecodebin_parser_init_gst(struct wg_parser *parser) + g_object_set(parser->decodebin, "uri", parser->uri, NULL); + g_signal_connect(element, "pad-added", G_CALLBACK(pad_added_cb), parser); + g_signal_connect(element, "pad-removed", G_CALLBACK(pad_removed_cb), parser); ++ g_signal_connect(element, "autoplug-continue", G_CALLBACK(autoplug_continue_cb), parser); + g_signal_connect(element, "autoplug-select", G_CALLBACK(autoplug_select_cb), parser); + g_signal_connect(element, "autoplug-sort", G_CALLBACK(autoplug_sort_cb), parser); + g_signal_connect(element, "no-more-pads", G_CALLBACK(no_more_pads_cb), parser); ++ g_signal_connect(element, "deep-element-added", G_CALLBACK(deep_element_added_cb), parser); + + pthread_mutex_lock(&parser->mutex); + parser->no_more_pads = false; +-- +2.45.2 + diff --git a/patches/wine-gst/0012-winegstreamer-Do-not-create-a-read-thread-for-uridec.patch b/patches/wine-gst/0012-winegstreamer-Do-not-create-a-read-thread-for-uridec.patch new file mode 100644 index 000000000..041549223 --- /dev/null +++ b/patches/wine-gst/0012-winegstreamer-Do-not-create-a-read-thread-for-uridec.patch @@ -0,0 +1,43 @@ +From dbab61189aced7b41c3e348d70a3712c5e88841f Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Mon, 25 Mar 2024 09:19:18 +0100 +Subject: [PATCH 12/43] winegstreamer: Do not create a read thread for + uridecodebin-based media sources. + +--- + dlls/winegstreamer/media_source.c | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c +index 93a4425dd06..6709af89444 100644 +--- a/dlls/winegstreamer/media_source.c ++++ b/dlls/winegstreamer/media_source.c +@@ -1625,9 +1625,12 @@ static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface) + + wg_parser_disconnect(source->wg_parser); + +- source->read_thread_shutdown = true; +- WaitForSingleObject(source->read_thread, INFINITE); +- CloseHandle(source->read_thread); ++ if (source->read_thread) ++ { ++ source->read_thread_shutdown = true; ++ WaitForSingleObject(source->read_thread, INFINITE); ++ CloseHandle(source->read_thread); ++ } + + IMFMediaEventQueue_Shutdown(source->event_queue); + IMFByteStream_Close(source->byte_stream); +@@ -1722,7 +1725,8 @@ static HRESULT media_source_create(struct object_context *context, IMFMediaSourc + } + object->wg_parser = parser; + +- object->read_thread = CreateThread(NULL, 0, read_thread, object, 0, NULL); ++ if (context->type != WG_PARSER_URIDECODEBIN) ++ object->read_thread = CreateThread(NULL, 0, read_thread, object, 0, NULL); + + object->state = SOURCE_OPENING; + +-- +2.45.2 + diff --git a/patches/wine-gst/0013-winegstreamer-Ignore-an-assert-in-wg_parser.patch b/patches/wine-gst/0013-winegstreamer-Ignore-an-assert-in-wg_parser.patch new file mode 100644 index 000000000..5f54c3d48 --- /dev/null +++ b/patches/wine-gst/0013-winegstreamer-Ignore-an-assert-in-wg_parser.patch @@ -0,0 +1,37 @@ +From 37b969e05df0e637af3656c272cefc969593c24f Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Tue, 19 Mar 2024 04:06:37 +0100 +Subject: [PATCH 13/43] winegstreamer: Ignore an assert in wg_parser. + +This gets hit when a wg_parser receives GST_EVENT_FLUSH_START between the wg_parser_stream_get_buffer function return +and the wg_parser_stream_release_buffer call. In this case the NULL buffer can be ignored, it does no harm and there +is no memory leak from this. +--- + dlls/winegstreamer/wg_parser.c | 11 ++++++----- + 1 file changed, 6 insertions(+), 5 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 48a1d658232..60d6d2a669e 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -409,11 +409,12 @@ static NTSTATUS wg_parser_stream_release_buffer(void *args) + + pthread_mutex_lock(&parser->mutex); + +- assert(stream->buffer); +- +- gst_buffer_unmap(stream->buffer, &stream->map_info); +- gst_buffer_unref(stream->buffer); +- stream->buffer = NULL; ++ if (stream->buffer) ++ { ++ gst_buffer_unmap(stream->buffer, &stream->map_info); ++ gst_buffer_unref(stream->buffer); ++ stream->buffer = NULL; ++ } + + pthread_mutex_unlock(&parser->mutex); + pthread_cond_signal(&stream->event_empty_cond); +-- +2.45.2 + diff --git a/patches/wine-gst/0014-winegstreamer-Fixate-caps-in-autoplug_continue_cb.patch b/patches/wine-gst/0014-winegstreamer-Fixate-caps-in-autoplug_continue_cb.patch new file mode 100644 index 000000000..f9ab05128 --- /dev/null +++ b/patches/wine-gst/0014-winegstreamer-Fixate-caps-in-autoplug_continue_cb.patch @@ -0,0 +1,27 @@ +From 8b59d7c91a2f9d03296aaf00295af2ea803d337c Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Tue, 19 Mar 2024 10:19:09 +0100 +Subject: [PATCH 14/43] winegstreamer: Fixate caps in autoplug_continue_cb. + +Not strictly necessary but avoids an assert in gstreamer code. +--- + dlls/winegstreamer/wg_parser.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 60d6d2a669e..1ff4da1971c 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -527,7 +527,9 @@ static gboolean autoplug_continue_cb(GstElement * decodebin, GstPad *pad, GstCap + { + struct wg_format format; + ++ caps = gst_caps_fixate(gst_caps_copy_nth(caps, 0)); + wg_format_from_caps(&format, caps); ++ gst_caps_unref(caps); + + return !format_is_compressed(&format); + } +-- +2.45.2 + diff --git a/patches/wine-gst/0015-winegstreamer-Make-wg_parser-report-the-exact-suppor.patch b/patches/wine-gst/0015-winegstreamer-Make-wg_parser-report-the-exact-suppor.patch new file mode 100644 index 000000000..8b8ee58d5 --- /dev/null +++ b/patches/wine-gst/0015-winegstreamer-Make-wg_parser-report-the-exact-suppor.patch @@ -0,0 +1,73 @@ +From 56ca2f6922227bb0378b748925eefd3533278215 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Tue, 20 Feb 2024 23:17:28 +0100 +Subject: [PATCH 15/43] winegstreamer: Make wg_parser report the exact + supported formats to gstreamer instead of just ANY. + +--- + dlls/winegstreamer/wg_parser.c | 42 +++++++++++++++++++++++++++++++++- + 1 file changed, 41 insertions(+), 1 deletion(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 1ff4da1971c..089d659e50d 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -835,6 +835,43 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu + return GST_FLOW_OK; + } + ++static GstCaps *get_supported_formats(void) ++{ ++ GstCaps *caps = gst_caps_new_empty(), *temp; ++ GstAudioInfo ainfo; ++ GstVideoInfo vinfo; ++ gsize i; ++ ++ /* video/x-raw */ ++ gst_video_info_set_format(&vinfo, GST_VIDEO_FORMAT_BGRA, 1, 1); ++ temp = gst_video_info_to_caps(&vinfo); ++ for (i = 0; i < gst_caps_get_size(temp); ++i) ++ gst_structure_remove_fields(gst_caps_get_structure(temp, i), ++ "format", "width", "height", "framerate", "colorimetry", NULL); ++ gst_caps_append(caps, temp); ++ /* other formats */ ++ gst_caps_append(caps, gst_caps_new_empty_simple("video/x-cinepak")); ++ gst_caps_append(caps, gst_caps_new_simple("video/x-h264", "stream-format", G_TYPE_STRING, "byte-stream", ++ "alignment", G_TYPE_STRING, "au", NULL)); ++ gst_caps_append(caps, gst_caps_new_empty_simple("video/x-wmv")); ++ gst_caps_append(caps, gst_caps_new_empty_simple("video/x-indeo")); ++ ++ /* audio/x-raw */ ++ gst_audio_info_set_format(&ainfo, GST_AUDIO_FORMAT_S16LE, 1, 1, NULL); ++ temp = gst_audio_info_to_caps(&ainfo); ++ for (i = 0; i < gst_caps_get_size(temp); ++i) ++ gst_structure_remove_fields(gst_caps_get_structure(temp, i), ++ "format", "channels", "rate", "channel-mask", NULL); ++ gst_caps_append(caps, temp); ++ /* other formats */ ++ gst_caps_append(caps, gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, ++ "parsed", G_TYPE_BOOLEAN, TRUE, NULL)); ++ gst_caps_append(caps, gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 4, NULL)); ++ gst_caps_append(caps, gst_caps_new_empty_simple("audio/x-xma")); ++ ++ return caps; ++} ++ + static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) + { + struct wg_parser_stream *stream = gst_pad_get_element_private(pad); +@@ -852,7 +889,10 @@ static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) + gst_query_parse_caps(query, &filter); + + pthread_mutex_lock(&parser->mutex); +- caps = wg_format_to_caps(&stream->current_format); ++ if (stream->current_format.major_type != WG_MAJOR_TYPE_UNKNOWN) ++ caps = wg_format_to_caps(&stream->current_format); ++ else ++ caps = get_supported_formats(); + pthread_mutex_unlock(&parser->mutex); + + if (!caps) +-- +2.45.2 + diff --git a/patches/wine-gst/0016-winegstreamer-Add-more-RTSP-based-URI-schemes-to-GSt.patch b/patches/wine-gst/0016-winegstreamer-Add-more-RTSP-based-URI-schemes-to-GSt.patch new file mode 100644 index 000000000..1cff6f99f --- /dev/null +++ b/patches/wine-gst/0016-winegstreamer-Add-more-RTSP-based-URI-schemes-to-GSt.patch @@ -0,0 +1,56 @@ +From 9e7ea98869537829196b7da0b6e701f474c57039 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 31 Jan 2024 17:42:54 +0100 +Subject: [PATCH 16/43] winegstreamer: Add more RTSP-based URI schemes to + GStreamer scheme handler. + +--- + dlls/winegstreamer/winegstreamer.rgs | 32 ++++++++++++++++++++++++++++ + 1 file changed, 32 insertions(+) + +diff --git a/dlls/winegstreamer/winegstreamer.rgs b/dlls/winegstreamer/winegstreamer.rgs +index c50d3a05747..d0c0265f65a 100644 +--- a/dlls/winegstreamer/winegstreamer.rgs ++++ b/dlls/winegstreamer/winegstreamer.rgs +@@ -35,6 +35,38 @@ HKLM + { + val '{587eeb6a-7336-4ebd-a4f2-91c948de622c}' = s 'GStreamer Scheme Handler' + } ++ 'rtspu:' ++ { ++ val '{587eeb6a-7336-4ebd-a4f2-91c948de622c}' = s 'GStreamer Scheme Handler' ++ } ++ 'rtspt:' ++ { ++ val '{587eeb6a-7336-4ebd-a4f2-91c948de622c}' = s 'GStreamer Scheme Handler' ++ } ++ 'rtsph:' ++ { ++ val '{587eeb6a-7336-4ebd-a4f2-91c948de622c}' = s 'GStreamer Scheme Handler' ++ } ++ 'rtsp-sdp:' ++ { ++ val '{587eeb6a-7336-4ebd-a4f2-91c948de622c}' = s 'GStreamer Scheme Handler' ++ } ++ 'rtsps:' ++ { ++ val '{587eeb6a-7336-4ebd-a4f2-91c948de622c}' = s 'GStreamer Scheme Handler' ++ } ++ 'rtspsu:' ++ { ++ val '{587eeb6a-7336-4ebd-a4f2-91c948de622c}' = s 'GStreamer Scheme Handler' ++ } ++ 'rtspst:' ++ { ++ val '{587eeb6a-7336-4ebd-a4f2-91c948de622c}' = s 'GStreamer Scheme Handler' ++ } ++ 'rtspsh:' ++ { ++ val '{587eeb6a-7336-4ebd-a4f2-91c948de622c}' = s 'GStreamer Scheme Handler' ++ } + } + } + } +-- +2.45.2 + diff --git a/patches/wine-gst/0017-winegstreamer-Fixate-caps-in-the-pad-added-callback.patch b/patches/wine-gst/0017-winegstreamer-Fixate-caps-in-the-pad-added-callback.patch new file mode 100644 index 000000000..3122e1449 --- /dev/null +++ b/patches/wine-gst/0017-winegstreamer-Fixate-caps-in-the-pad-added-callback.patch @@ -0,0 +1,38 @@ +From 8f8a63ac48264dac08febf3374f0a37a5df01318 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Mon, 22 Apr 2024 11:44:37 +0200 +Subject: [PATCH 17/43] winegstreamer: Fixate caps in the pad-added callback. + +Pad caps might not be fixed caps, and non-fixed caps can't be transformed to a wg_format. +--- + dlls/winegstreamer/wg_parser.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 089d659e50d..3a39a754b81 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -1209,7 +1209,7 @@ static void pad_added_cb(GstElement *element, GstPad *pad, gpointer user) + { + struct wg_parser_stream *stream; + struct wg_parser *parser = user; +- GstCaps *caps; ++ GstCaps *caps, *caps2; + + GST_LOG("parser %p, element %p, pad %p.", parser, element, pad); + +@@ -1220,7 +1220,10 @@ static void pad_added_cb(GstElement *element, GstPad *pad, gpointer user) + return; + + caps = gst_pad_query_caps(pad, NULL); +- wg_format_from_caps(&stream->codec_format, caps); ++ /* uridecodebin can provide non-fixed pad caps, fixate them here */ ++ caps2 = gst_caps_fixate(gst_caps_copy_nth(caps, 0)); ++ wg_format_from_caps(&stream->codec_format, caps2); ++ gst_caps_unref(caps2); + gst_caps_unref(caps); + + /* For compressed stream, create an extra decodebin to decode it. */ +-- +2.45.2 + diff --git a/patches/wine-gst/0018-winegstreamer-Mark-wg_parser-container-bin-as-stream.patch b/patches/wine-gst/0018-winegstreamer-Mark-wg_parser-container-bin-as-stream.patch new file mode 100644 index 000000000..a73031ea0 --- /dev/null +++ b/patches/wine-gst/0018-winegstreamer-Mark-wg_parser-container-bin-as-stream.patch @@ -0,0 +1,26 @@ +From e4bf2619eda4d8612be1d87faf7247c926f651ee Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Mon, 22 Apr 2024 11:43:45 +0200 +Subject: [PATCH 18/43] winegstreamer: Mark wg_parser container bin as + streams-aware. + +Mirroring wg_source. +--- + dlls/winegstreamer/wg_parser.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 3a39a754b81..b89a25fd9ef 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -1873,6 +1873,7 @@ static NTSTATUS wg_parser_connect(void *args) + } + + parser->container = gst_bin_new(NULL); ++ GST_OBJECT_FLAG_SET(parser->container, GST_BIN_FLAG_STREAMS_AWARE); + gst_element_set_bus(parser->container, parser->bus); + if (parser->context) + gst_element_set_context(parser->container, parser->context); +-- +2.45.2 + diff --git a/patches/wine-gst/0019-winegstreamer-Set-a-clock-for-the-wg_parser-pipeline.patch b/patches/wine-gst/0019-winegstreamer-Set-a-clock-for-the-wg_parser-pipeline.patch new file mode 100644 index 000000000..b0463b146 --- /dev/null +++ b/patches/wine-gst/0019-winegstreamer-Set-a-clock-for-the-wg_parser-pipeline.patch @@ -0,0 +1,27 @@ +From ee8ab4183cffaa51ea9b4940f6c4e9643dc29721 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 31 Jan 2024 17:42:55 +0100 +Subject: [PATCH 19/43] winegstreamer: Set a clock for the wg_parser pipeline. + +Some elements under uridecodebin (e.g. the RTSP demuxer) require a valid clock to function properly. +GstPipeline does this so let's do this too. +--- + dlls/winegstreamer/wg_parser.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index b89a25fd9ef..cd193b8f2af 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -1885,6 +1885,8 @@ static NTSTATUS wg_parser_connect(void *args) + gst_pad_set_event_function(parser->my_src, src_event_cb); + gst_pad_set_element_private(parser->my_src, parser); + ++ gst_element_set_clock(parser->container, gst_system_clock_obtain()); ++ + parser->start_offset = parser->next_offset = parser->stop_offset = 0; + parser->next_pull_offset = 0; + parser->error = false; +-- +2.45.2 + diff --git a/patches/wine-gst/0020-winegstreamer-Set-base-time-on-wg_parser-bin-while-c.patch b/patches/wine-gst/0020-winegstreamer-Set-base-time-on-wg_parser-bin-while-c.patch new file mode 100644 index 000000000..b9757d51e --- /dev/null +++ b/patches/wine-gst/0020-winegstreamer-Set-base-time-on-wg_parser-bin-while-c.patch @@ -0,0 +1,43 @@ +From 5351fa9f5ebf8941563398e4dfb2368be7980459 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 31 Jan 2024 17:42:55 +0100 +Subject: [PATCH 20/43] winegstreamer: Set base time on wg_parser bin while + connecting. + +--- + dlls/winegstreamer/wg_parser.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index cd193b8f2af..fdbce6b5f25 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -1849,6 +1849,7 @@ static NTSTATUS wg_parser_connect(void *args) + const struct wg_parser_connect_params *params = args; + struct wg_parser *parser = get_parser(params->parser); + const WCHAR *uri = params->uri; ++ GstClock *clock; + unsigned int i; + int ret; + +@@ -1885,7 +1886,8 @@ static NTSTATUS wg_parser_connect(void *args) + gst_pad_set_event_function(parser->my_src, src_event_cb); + gst_pad_set_element_private(parser->my_src, parser); + +- gst_element_set_clock(parser->container, gst_system_clock_obtain()); ++ clock = gst_system_clock_obtain(); ++ gst_element_set_clock(parser->container, clock); + + parser->start_offset = parser->next_offset = parser->stop_offset = 0; + parser->next_pull_offset = 0; +@@ -1894,6 +1896,7 @@ static NTSTATUS wg_parser_connect(void *args) + if (!parser->init_gst(parser)) + goto out; + ++ gst_element_set_base_time(parser->container, gst_clock_get_time(clock)); + gst_element_set_state(parser->container, GST_STATE_PAUSED); + ret = gst_element_get_state(parser->container, NULL, NULL, -1); + +-- +2.45.2 + diff --git a/patches/wine-gst/0021-winegstreamer-Put-pipeline-into-PLAYING-state-before.patch b/patches/wine-gst/0021-winegstreamer-Put-pipeline-into-PLAYING-state-before.patch new file mode 100644 index 000000000..b89115d82 --- /dev/null +++ b/patches/wine-gst/0021-winegstreamer-Put-pipeline-into-PLAYING-state-before.patch @@ -0,0 +1,32 @@ +From 87cbcd34173216b7363db897b1cb6b6a668a1a8e Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 31 Jan 2024 17:42:55 +0100 +Subject: [PATCH 21/43] winegstreamer: Put pipeline into PLAYING state before + waiting for the no-more-pads callback. + +Some elements (e.g. uridecodebin with an RTSP URI) won't send no-more-pads in READY/PAUSED state. +--- + dlls/winegstreamer/wg_parser.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index fdbce6b5f25..83786a4aa4c 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -1914,6 +1914,13 @@ static NTSTATUS wg_parser_connect(void *args) + + pthread_mutex_lock(&parser->mutex); + ++ if (parser->uri) ++ { ++ gst_element_set_state(parser->container, GST_STATE_PLAYING); ++ ret = gst_element_get_state(parser->container, NULL, NULL, -1); ++ if (ret == GST_STATE_CHANGE_FAILURE) ++ goto out; ++ } + while (!parser_no_more_pads(parser) && !parser->error) + pthread_cond_wait(&parser->init_cond, &parser->mutex); + if (parser->error) +-- +2.45.2 + diff --git a/patches/wine-gst/0022-winegstreamer-Don-t-only-accept-segment-events-when-.patch b/patches/wine-gst/0022-winegstreamer-Don-t-only-accept-segment-events-when-.patch new file mode 100644 index 000000000..11c1f9aa6 --- /dev/null +++ b/patches/wine-gst/0022-winegstreamer-Don-t-only-accept-segment-events-when-.patch @@ -0,0 +1,53 @@ +From fa40f81575e94d0ae3bc18fddb79d21dc540f20e Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Tue, 23 Apr 2024 19:30:17 +0200 +Subject: [PATCH 22/43] winegstreamer: Don't only accept segment events when + streams are enabled. + +I don't know why this was done previously but this just creates a race condition, with no to me apparent benefit. +--- + dlls/winegstreamer/wg_parser.c | 24 ++++++++++-------------- + 1 file changed, 10 insertions(+), 14 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 83786a4aa4c..3eb4134b931 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -683,24 +683,20 @@ static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) + switch (event->type) + { + case GST_EVENT_SEGMENT: +- pthread_mutex_lock(&parser->mutex); +- if (stream->enabled) ++ { ++ const GstSegment *segment; ++ ++ gst_event_parse_segment(event, &segment); ++ if (segment->format != GST_FORMAT_TIME) + { +- const GstSegment *segment; +- +- gst_event_parse_segment(event, &segment); +- +- if (segment->format != GST_FORMAT_TIME) +- { +- pthread_mutex_unlock(&parser->mutex); +- GST_FIXME("Unhandled format \"%s\".", gst_format_get_name(segment->format)); +- break; +- } +- +- gst_segment_copy_into(segment, &stream->segment); ++ GST_FIXME("Unhandled format \"%s\".", gst_format_get_name(segment->format)); ++ break; + } ++ pthread_mutex_lock(&parser->mutex); ++ gst_segment_copy_into(segment, &stream->segment); + pthread_mutex_unlock(&parser->mutex); + break; ++ } + + case GST_EVENT_EOS: + pthread_mutex_lock(&parser->mutex); +-- +2.45.2 + diff --git a/patches/wine-gst/0023-winegstreamer-Convert-buffer-presentation-timestamps.patch b/patches/wine-gst/0023-winegstreamer-Convert-buffer-presentation-timestamps.patch new file mode 100644 index 000000000..3ea5d85f9 --- /dev/null +++ b/patches/wine-gst/0023-winegstreamer-Convert-buffer-presentation-timestamps.patch @@ -0,0 +1,93 @@ +From 6d0bfd1a5883ccc81ceea1bd467ea5c9e10cb112 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 31 Jan 2024 17:42:55 +0100 +Subject: [PATCH 23/43] winegstreamer: Convert buffer presentation timestamps + into running time. + +--- + dlls/winegstreamer/wg_parser.c | 46 +++++++++++++++++++++++----------- + 1 file changed, 31 insertions(+), 15 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 3eb4134b931..e6170b96cb9 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -306,6 +306,18 @@ static GstBuffer *wait_parser_stream_buffer(struct wg_parser *parser, struct wg_ + return buffer; + } + ++static void release_buffer(struct wg_parser *parser, struct wg_parser_stream *stream) ++{ ++ if (stream->buffer) ++ { ++ gst_buffer_unmap(stream->buffer, &stream->map_info); ++ gst_buffer_unref(stream->buffer); ++ stream->buffer = NULL; ++ } ++ ++ pthread_cond_signal(&stream->event_empty_cond); ++} ++ + static NTSTATUS wg_parser_stream_get_buffer(void *args) + { + const struct wg_parser_stream_get_buffer_params *params = args; +@@ -317,6 +329,7 @@ static NTSTATUS wg_parser_stream_get_buffer(void *args) + + pthread_mutex_lock(&parser->mutex); + ++retry: + if (stream) + buffer = wait_parser_stream_buffer(parser, stream); + else +@@ -359,13 +372,24 @@ static NTSTATUS wg_parser_stream_get_buffer(void *args) + return S_FALSE; + } + +- /* FIXME: Should we use gst_segment_to_stream_time_full()? Under what +- * circumstances is the stream time not equal to the buffer PTS? Note +- * that this will need modification to wg_parser_stream_notify_qos() as +- * well. */ +- + if ((wg_buffer->has_pts = GST_BUFFER_PTS_IS_VALID(buffer))) +- wg_buffer->pts = GST_BUFFER_PTS(buffer) / 100; ++ { ++ if (stream->segment.format != GST_FORMAT_TIME) ++ { ++ /* Some types of streams (e.g. HLS) don't provide a segment. Assume that these start their PTS at zero. */ ++ wg_buffer->pts = GST_BUFFER_PTS(buffer) / 100; ++ } ++ else ++ { ++ guint64 stream_time = gst_segment_to_running_time(&stream->segment, GST_FORMAT_TIME, GST_BUFFER_PTS(buffer)); ++ if (stream_time == -1) ++ { ++ release_buffer(parser, stream); ++ goto retry; ++ } ++ wg_buffer->pts = stream_time / 100; ++ } ++ } + if ((wg_buffer->has_duration = GST_BUFFER_DURATION_IS_VALID(buffer))) + wg_buffer->duration = GST_BUFFER_DURATION(buffer) / 100; + wg_buffer->discontinuity = GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DISCONT); +@@ -408,16 +432,8 @@ static NTSTATUS wg_parser_stream_release_buffer(void *args) + struct wg_parser *parser = stream->parser; + + pthread_mutex_lock(&parser->mutex); +- +- if (stream->buffer) +- { +- gst_buffer_unmap(stream->buffer, &stream->map_info); +- gst_buffer_unref(stream->buffer); +- stream->buffer = NULL; +- } +- ++ release_buffer(parser, stream); + pthread_mutex_unlock(&parser->mutex); +- pthread_cond_signal(&stream->event_empty_cond); + + return S_OK; + } +-- +2.45.2 + diff --git a/patches/wine-gst/0024-winegstreamer-Adjust-buffer-timestamps-after-seek.patch b/patches/wine-gst/0024-winegstreamer-Adjust-buffer-timestamps-after-seek.patch new file mode 100644 index 000000000..fb6af30c8 --- /dev/null +++ b/patches/wine-gst/0024-winegstreamer-Adjust-buffer-timestamps-after-seek.patch @@ -0,0 +1,53 @@ +From 4af6bcfd88a8531d958175246a612285f819f88e Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Tue, 19 Mar 2024 05:11:11 +0100 +Subject: [PATCH 24/43] winegstreamer: Adjust buffer timestamps after seek. + +--- + dlls/winegstreamer/wg_parser.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index e6170b96cb9..6da8dd3067c 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -109,6 +109,8 @@ struct wg_parser + bool use_mediaconv; + bool use_opengl; + GstContext *context; ++ ++ guint64 seek_pos; + }; + static const unsigned int input_cache_chunk_size = 512 << 10; + +@@ -377,7 +379,7 @@ retry: + if (stream->segment.format != GST_FORMAT_TIME) + { + /* Some types of streams (e.g. HLS) don't provide a segment. Assume that these start their PTS at zero. */ +- wg_buffer->pts = GST_BUFFER_PTS(buffer) / 100; ++ wg_buffer->pts = (GST_BUFFER_PTS(buffer) + parser->seek_pos) / 100; + } + else + { +@@ -387,7 +389,7 @@ retry: + release_buffer(parser, stream); + goto retry; + } +- wg_buffer->pts = stream_time / 100; ++ wg_buffer->pts = (stream_time + parser->seek_pos) / 100; + } + } + if ((wg_buffer->has_duration = GST_BUFFER_DURATION_IS_VALID(buffer))) +@@ -492,6 +494,9 @@ static NTSTATUS wg_parser_stream_seek(void *args) + flags, start_type, params->start_pos * 100, stop_type, + params->stop_pos == stream->duration ? -1 : params->stop_pos * 100))) + GST_ERROR("Failed to seek."); ++ else ++ /* Pushing a seek event on one stream changes the decoding position of all streams */ ++ stream->parser->seek_pos = params->start_pos * 100; + + return S_OK; + } +-- +2.45.2 + diff --git a/patches/wine-gst/0025-winegstreamer-Reorder-parser-initialization-code-a-b.patch b/patches/wine-gst/0025-winegstreamer-Reorder-parser-initialization-code-a-b.patch new file mode 100644 index 000000000..2ef18465d --- /dev/null +++ b/patches/wine-gst/0025-winegstreamer-Reorder-parser-initialization-code-a-b.patch @@ -0,0 +1,84 @@ +From 1cce3c0a5d870b3e6b27ffaaa5d45daa89116ad5 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 31 Jan 2024 17:42:55 +0100 +Subject: [PATCH 25/43] winegstreamer: Reorder parser initialization code a bit + to prevent race conditions. + +--- + dlls/winegstreamer/wg_parser.c | 28 ++++++++++++++-------------- + 1 file changed, 14 insertions(+), 14 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 6da8dd3067c..6d2044d692c 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -2129,7 +2129,10 @@ static BOOL decodebin_parser_init_gst(struct wg_parser *parser) + gst_bin_add(GST_BIN(parser->container), element); + parser->decodebin = element; + +- g_object_set(element, "max-size-bytes", G_MAXUINT, NULL); ++ pthread_mutex_lock(&parser->mutex); ++ parser->no_more_pads = false; ++ pthread_mutex_unlock(&parser->mutex); ++ + g_signal_connect(element, "pad-added", G_CALLBACK(pad_added_cb), parser); + g_signal_connect(element, "pad-removed", G_CALLBACK(pad_removed_cb), parser); + g_signal_connect(element, "autoplug-continue", G_CALLBACK(autoplug_continue_cb), parser); +@@ -2137,10 +2140,7 @@ static BOOL decodebin_parser_init_gst(struct wg_parser *parser) + g_signal_connect(element, "autoplug-sort", G_CALLBACK(autoplug_sort_cb), parser); + g_signal_connect(element, "no-more-pads", G_CALLBACK(no_more_pads_cb), parser); + g_signal_connect(element, "deep-element-added", G_CALLBACK(deep_element_added_cb), parser); +- +- pthread_mutex_lock(&parser->mutex); +- parser->no_more_pads = false; +- pthread_mutex_unlock(&parser->mutex); ++ g_object_set(element, "max-size-bytes", G_MAXUINT, NULL); + + if (!link_src_to_element(parser->my_src, element)) + return FALSE; +@@ -2158,7 +2158,10 @@ static BOOL uridecodebin_parser_init_gst(struct wg_parser *parser) + gst_bin_add(GST_BIN(parser->container), element); + parser->decodebin = element; + +- g_object_set(parser->decodebin, "uri", parser->uri, NULL); ++ pthread_mutex_lock(&parser->mutex); ++ parser->no_more_pads = false; ++ pthread_mutex_unlock(&parser->mutex); ++ + g_signal_connect(element, "pad-added", G_CALLBACK(pad_added_cb), parser); + g_signal_connect(element, "pad-removed", G_CALLBACK(pad_removed_cb), parser); + g_signal_connect(element, "autoplug-continue", G_CALLBACK(autoplug_continue_cb), parser); +@@ -2166,10 +2169,7 @@ static BOOL uridecodebin_parser_init_gst(struct wg_parser *parser) + g_signal_connect(element, "autoplug-sort", G_CALLBACK(autoplug_sort_cb), parser); + g_signal_connect(element, "no-more-pads", G_CALLBACK(no_more_pads_cb), parser); + g_signal_connect(element, "deep-element-added", G_CALLBACK(deep_element_added_cb), parser); +- +- pthread_mutex_lock(&parser->mutex); +- parser->no_more_pads = false; +- pthread_mutex_unlock(&parser->mutex); ++ g_object_set(parser->decodebin, "uri", parser->uri, NULL); + + return TRUE; + } +@@ -2183,14 +2183,14 @@ static BOOL avi_parser_init_gst(struct wg_parser *parser) + + gst_bin_add(GST_BIN(parser->container), element); + +- g_signal_connect(element, "pad-added", G_CALLBACK(pad_added_cb), parser); +- g_signal_connect(element, "pad-removed", G_CALLBACK(pad_removed_cb), parser); +- g_signal_connect(element, "no-more-pads", G_CALLBACK(no_more_pads_cb), parser); +- + pthread_mutex_lock(&parser->mutex); + parser->no_more_pads = false; + pthread_mutex_unlock(&parser->mutex); + ++ g_signal_connect(element, "pad-added", G_CALLBACK(pad_added_cb), parser); ++ g_signal_connect(element, "pad-removed", G_CALLBACK(pad_removed_cb), parser); ++ g_signal_connect(element, "no-more-pads", G_CALLBACK(no_more_pads_cb), parser); ++ + if (!link_src_to_element(parser->my_src, element)) + return FALSE; + +-- +2.45.2 + diff --git a/patches/wine-gst/0026-winegstreamer-Do-away-with-the-per-stream-condvars-a.patch b/patches/wine-gst/0026-winegstreamer-Do-away-with-the-per-stream-condvars-a.patch new file mode 100644 index 000000000..8e5c80cf3 --- /dev/null +++ b/patches/wine-gst/0026-winegstreamer-Do-away-with-the-per-stream-condvars-a.patch @@ -0,0 +1,137 @@ +From f329cd493e7579347b7da8f3aa6d9f29132c72f4 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 31 Jan 2024 17:42:54 +0100 +Subject: [PATCH 26/43] winegstreamer: Do away with the per-stream condvars and + use one parser-wide condvar instead. + +--- + dlls/winegstreamer/wg_parser.c | 28 +++++++++++----------------- + 1 file changed, 11 insertions(+), 17 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 6d2044d692c..c8409616aad 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -90,6 +90,7 @@ struct wg_parser + bool err_on, warn_on; + + pthread_cond_t read_cond, read_done_cond; ++ pthread_cond_t stream_event_cond; + struct + { + GstBuffer *buffer; +@@ -124,7 +125,6 @@ struct wg_parser_stream + GstSegment segment; + struct wg_format preferred_format, current_format, codec_format; + +- pthread_cond_t event_cond, event_empty_cond; + GstBuffer *buffer; + GstMapInfo map_info; + +@@ -290,8 +290,7 @@ static NTSTATUS wg_parser_stream_disable(void *args) + stream->enabled = false; + stream->current_format.major_type = WG_MAJOR_TYPE_UNKNOWN; + pthread_mutex_unlock(&parser->mutex); +- pthread_cond_signal(&stream->event_cond); +- pthread_cond_signal(&stream->event_empty_cond); ++ pthread_cond_signal(&parser->stream_event_cond); + return S_OK; + } + +@@ -303,7 +302,7 @@ static GstBuffer *wait_parser_stream_buffer(struct wg_parser *parser, struct wg_ + * must return the buffer. */ + + while (stream->enabled && !(buffer = stream->buffer) && !stream->eos) +- pthread_cond_wait(&stream->event_cond, &parser->mutex); ++ pthread_cond_wait(&parser->stream_event_cond, &parser->mutex); + + return buffer; + } +@@ -317,7 +316,7 @@ static void release_buffer(struct wg_parser *parser, struct wg_parser_stream *st + stream->buffer = NULL; + } + +- pthread_cond_signal(&stream->event_empty_cond); ++ pthread_cond_signal(&parser->stream_event_cond); + } + + static NTSTATUS wg_parser_stream_get_buffer(void *args) +@@ -723,7 +722,7 @@ static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) + pthread_mutex_lock(&parser->mutex); + stream->eos = true; + if (stream->enabled) +- pthread_cond_signal(&stream->event_cond); ++ pthread_cond_signal(&parser->stream_event_cond); + else + pthread_cond_signal(&parser->init_cond); + pthread_mutex_unlock(&parser->mutex); +@@ -735,7 +734,7 @@ static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) + if (stream->enabled) + { + stream->flushing = true; +- pthread_cond_signal(&stream->event_empty_cond); ++ pthread_cond_signal(&parser->stream_event_cond); + + if (stream->buffer) + { +@@ -813,7 +812,7 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu + * implementing a queue object here. */ + + while (stream->enabled && !stream->flushing && stream->buffer) +- pthread_cond_wait(&stream->event_empty_cond, &parser->mutex); ++ pthread_cond_wait(&parser->stream_event_cond, &parser->mutex); + + if (!stream->enabled) + { +@@ -842,7 +841,7 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu + stream->buffer = buffer; + + pthread_mutex_unlock(&parser->mutex); +- pthread_cond_signal(&stream->event_cond); ++ pthread_cond_signal(&parser->stream_event_cond); + + /* The chain callback is given a reference to the buffer. Transfer that + * reference to the stream object, which will release it in +@@ -986,8 +985,7 @@ static struct wg_parser_stream *create_stream(struct wg_parser *parser, char *id + stream->number = parser->stream_count; + stream->no_more_pads = true; + stream->current_format.major_type = WG_MAJOR_TYPE_UNKNOWN; +- pthread_cond_init(&stream->event_cond, NULL); +- pthread_cond_init(&stream->event_empty_cond, NULL); ++ stream->next_format.major_type = WG_MAJOR_TYPE_UNKNOWN; + + sprintf(pad_name, "qz_sink_%u", parser->stream_count); + stream->my_sink = gst_pad_new(pad_name, GST_PAD_SINK); +@@ -1019,9 +1017,6 @@ static void free_stream(struct wg_parser_stream *stream) + stream->buffer = NULL; + } + +- pthread_cond_destroy(&stream->event_cond); +- pthread_cond_destroy(&stream->event_empty_cond); +- + for (i = 0; i < ARRAY_SIZE(stream->tags); ++i) + { + if (stream->tags[i]) +@@ -2080,10 +2075,8 @@ static NTSTATUS wg_parser_disconnect(void *args) + /* Unblock all of our streams. */ + pthread_mutex_lock(&parser->mutex); + for (i = 0; i < parser->stream_count; ++i) +- { + parser->streams[i]->flushing = true; +- pthread_cond_signal(&parser->streams[i]->event_empty_cond); +- } ++ pthread_cond_signal(&parser->stream_event_cond); + pthread_mutex_unlock(&parser->mutex); + + gst_element_set_state(parser->container, GST_STATE_NULL); +@@ -2259,6 +2252,7 @@ static NTSTATUS wg_parser_create(void *args) + pthread_cond_init(&parser->init_cond, NULL); + pthread_cond_init(&parser->read_cond, NULL); + pthread_cond_init(&parser->read_done_cond, NULL); ++ pthread_cond_init(&parser->stream_event_cond, NULL); + parser->init_gst = init_funcs[params->type]; + parser->output_compressed = params->output_compressed; + parser->err_on = params->err_on; +-- +2.45.2 + diff --git a/patches/wine-gst/0027-winegstreamer-Use-pthread_cond_broadcast-instead-of-.patch b/patches/wine-gst/0027-winegstreamer-Use-pthread_cond_broadcast-instead-of-.patch new file mode 100644 index 000000000..c8df8c2cf --- /dev/null +++ b/patches/wine-gst/0027-winegstreamer-Use-pthread_cond_broadcast-instead-of-.patch @@ -0,0 +1,71 @@ +From 01e1e742376e04a8b0436c6543725c4d8f1b0974 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Mon, 18 Mar 2024 10:18:07 +0100 +Subject: [PATCH 27/43] winegstreamer: Use pthread_cond_broadcast instead of + pthread_cond_signal for stream_event_cond. + +--- + dlls/winegstreamer/wg_parser.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index c8409616aad..3e047e02de5 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -290,7 +290,7 @@ static NTSTATUS wg_parser_stream_disable(void *args) + stream->enabled = false; + stream->current_format.major_type = WG_MAJOR_TYPE_UNKNOWN; + pthread_mutex_unlock(&parser->mutex); +- pthread_cond_signal(&parser->stream_event_cond); ++ pthread_cond_broadcast(&parser->stream_event_cond); + return S_OK; + } + +@@ -316,7 +316,7 @@ static void release_buffer(struct wg_parser *parser, struct wg_parser_stream *st + stream->buffer = NULL; + } + +- pthread_cond_signal(&parser->stream_event_cond); ++ pthread_cond_broadcast(&parser->stream_event_cond); + } + + static NTSTATUS wg_parser_stream_get_buffer(void *args) +@@ -722,7 +722,7 @@ static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) + pthread_mutex_lock(&parser->mutex); + stream->eos = true; + if (stream->enabled) +- pthread_cond_signal(&parser->stream_event_cond); ++ pthread_cond_broadcast(&parser->stream_event_cond); + else + pthread_cond_signal(&parser->init_cond); + pthread_mutex_unlock(&parser->mutex); +@@ -734,7 +734,7 @@ static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) + if (stream->enabled) + { + stream->flushing = true; +- pthread_cond_signal(&parser->stream_event_cond); ++ pthread_cond_broadcast(&parser->stream_event_cond); + + if (stream->buffer) + { +@@ -841,7 +841,7 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu + stream->buffer = buffer; + + pthread_mutex_unlock(&parser->mutex); +- pthread_cond_signal(&parser->stream_event_cond); ++ pthread_cond_broadcast(&parser->stream_event_cond); + + /* The chain callback is given a reference to the buffer. Transfer that + * reference to the stream object, which will release it in +@@ -2076,7 +2076,7 @@ static NTSTATUS wg_parser_disconnect(void *args) + pthread_mutex_lock(&parser->mutex); + for (i = 0; i < parser->stream_count; ++i) + parser->streams[i]->flushing = true; +- pthread_cond_signal(&parser->stream_event_cond); ++ pthread_cond_broadcast(&parser->stream_event_cond); + pthread_mutex_unlock(&parser->mutex); + + gst_element_set_state(parser->container, GST_STATE_NULL); +-- +2.45.2 + diff --git a/patches/wine-gst/0028-winegstreamer-Do-not-fail-caps-negotiation-when-ther.patch b/patches/wine-gst/0028-winegstreamer-Do-not-fail-caps-negotiation-when-ther.patch new file mode 100644 index 000000000..efde586c0 --- /dev/null +++ b/patches/wine-gst/0028-winegstreamer-Do-not-fail-caps-negotiation-when-ther.patch @@ -0,0 +1,171 @@ +From b0d53a974c18d412a1755896410b177b1b47a36a Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Tue, 19 Mar 2024 10:09:59 +0100 +Subject: [PATCH 28/43] winegstreamer: Do not fail caps negotiation when + there's a concurrent reconfigure. + +If wg_parser_stream_enable is called between the time our sink's GST_QUERY_CAPS returns and the time our sink gets the +GST_QUERY_ACCEPT_CAPS query to finalize caps negotiation, GST_QUERY_ACCEPT_CAPS would likely return FALSE because the +caps that are being negotiated would not meet the requirements of the new stream->current_format set by +wg_parser_stream_enable. + +Instead, accept both the old and the new format as valid caps in GST_QUERY_ACCEPT_CAPS, but discard buffers until the +correct caps have been negotiated (signalled by a GST_EVENT_CAPS). + +Also try to omit the reconfigure event in some cases. +--- + dlls/winegstreamer/wg_parser.c | 69 +++++++++++++++++++++++++++++++--- + 1 file changed, 63 insertions(+), 6 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 3e047e02de5..78a5314b508 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -123,7 +123,7 @@ struct wg_parser_stream + GstPad *my_sink; + GstElement *flip, *decodebin; + GstSegment segment; +- struct wg_format preferred_format, current_format, codec_format; ++ struct wg_format preferred_format, current_format, next_format, codec_format; + + GstBuffer *buffer; + GstMapInfo map_info; +@@ -262,10 +262,27 @@ static NTSTATUS wg_parser_stream_enable(void *args) + struct wg_parser_stream *stream = get_stream(params->stream); + const struct wg_format *format = params->format; + struct wg_parser *parser = stream->parser; ++ BOOL need_reconf = FALSE; + + pthread_mutex_lock(&parser->mutex); + +- stream->current_format = *format; ++ while (stream->next_format.major_type != WG_MAJOR_TYPE_UNKNOWN && !parser->error) ++ pthread_cond_wait(&parser->stream_event_cond, &parser->mutex); ++ ++ if (parser->error) ++ { ++ pthread_mutex_unlock(&parser->mutex); ++ return E_FAIL; ++ } ++ ++ if (!wg_format_compare(&stream->current_format, format) ++ && !(stream->has_caps && wg_format_compare(&stream->preferred_format, format))) ++ { ++ stream->next_format = *format; ++ need_reconf = stream->has_caps; ++ } ++ else ++ stream->current_format = *format; + stream->enabled = true; + + pthread_mutex_unlock(&parser->mutex); +@@ -273,11 +290,18 @@ static NTSTATUS wg_parser_stream_enable(void *args) + if (format->major_type == WG_MAJOR_TYPE_VIDEO) + { + bool flip = (params->flags & STREAM_ENABLE_FLAG_FLIP_RGB) && (format->u.video.height < 0); ++ GstVideoOrientationMethod old_method = -1, new_method = flip ? GST_VIDEO_ORIENTATION_VERT : GST_VIDEO_ORIENTATION_IDENTITY; + +- gst_util_set_object_arg(G_OBJECT(stream->flip), "method", flip ? "vertical-flip" : "none"); ++ g_object_get(G_OBJECT(stream->flip), "method", &old_method, NULL); ++ if (new_method != old_method) ++ { ++ g_object_set(G_OBJECT(stream->flip), "method", new_method, NULL); ++ need_reconf = TRUE; ++ } + } + +- push_event(stream->my_sink, gst_event_new_reconfigure()); ++ if (need_reconf) ++ push_event(stream->my_sink, gst_event_new_reconfigure()); + return S_OK; + } + +@@ -289,6 +313,7 @@ static NTSTATUS wg_parser_stream_disable(void *args) + pthread_mutex_lock(&parser->mutex); + stream->enabled = false; + stream->current_format.major_type = WG_MAJOR_TYPE_UNKNOWN; ++ stream->next_format.major_type = WG_MAJOR_TYPE_UNKNOWN; + pthread_mutex_unlock(&parser->mutex); + pthread_cond_broadcast(&parser->stream_event_cond); + return S_OK; +@@ -774,6 +799,17 @@ static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) + pthread_mutex_lock(&parser->mutex); + wg_format_from_caps(&stream->preferred_format, caps); + stream->has_caps = true; ++ if (stream->next_format.major_type != WG_MAJOR_TYPE_UNKNOWN) ++ { ++ if (!wg_format_compare(&stream->preferred_format, &stream->next_format)) ++ push_event(stream->my_sink, gst_event_new_reconfigure()); ++ else ++ { ++ stream->current_format = stream->next_format; ++ stream->next_format.major_type = WG_MAJOR_TYPE_UNKNOWN; ++ pthread_cond_broadcast(&parser->stream_event_cond); ++ } ++ } + pthread_mutex_unlock(&parser->mutex); + pthread_cond_signal(&parser->init_cond); + break; +@@ -802,6 +838,14 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu + + pthread_mutex_lock(&parser->mutex); + ++ if (stream->next_format.major_type != WG_MAJOR_TYPE_UNKNOWN) ++ { ++ GST_DEBUG("Stream is changing format; discarding buffer."); ++ pthread_mutex_unlock(&parser->mutex); ++ gst_buffer_unref(buffer); ++ return GST_FLOW_OK; ++ } ++ + if (!stream->has_buffer) + { + stream->has_buffer = true; +@@ -905,7 +949,17 @@ static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) + gst_query_parse_caps(query, &filter); + + pthread_mutex_lock(&parser->mutex); +- if (stream->current_format.major_type != WG_MAJOR_TYPE_UNKNOWN) ++ if (stream->next_format.major_type != WG_MAJOR_TYPE_UNKNOWN) ++ { ++ caps = wg_format_to_caps(&stream->next_format); ++ if (caps && stream->current_format.major_type != WG_MAJOR_TYPE_UNKNOWN) ++ { ++ temp = wg_format_to_caps(&stream->current_format); ++ if (temp) ++ gst_caps_append(caps, temp); ++ } ++ } ++ else if (stream->current_format.major_type != WG_MAJOR_TYPE_UNKNOWN) + caps = wg_format_to_caps(&stream->current_format); + else + caps = get_supported_formats(); +@@ -941,7 +995,8 @@ static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) + + pthread_mutex_lock(&parser->mutex); + +- if (stream->current_format.major_type == WG_MAJOR_TYPE_UNKNOWN) ++ if (stream->current_format.major_type == WG_MAJOR_TYPE_UNKNOWN ++ || stream->next_format.major_type != WG_MAJOR_TYPE_UNKNOWN) + { + pthread_mutex_unlock(&parser->mutex); + gst_query_set_accept_caps_result(query, TRUE); +@@ -1646,6 +1701,7 @@ static GstBusSyncReply bus_handler_cb(GstBus *bus, GstMessage *msg, gpointer use + parser->error = true; + pthread_mutex_unlock(&parser->mutex); + pthread_cond_signal(&parser->init_cond); ++ pthread_cond_broadcast(&parser->stream_event_cond); + break; + + case GST_MESSAGE_WARNING: +@@ -1676,6 +1732,7 @@ static GstBusSyncReply bus_handler_cb(GstBus *bus, GstMessage *msg, gpointer use + GST_WARNING("Autoplugged element failed to initialise, trying again with protonvideoconvert."); + parser->error = true; + pthread_cond_signal(&parser->init_cond); ++ pthread_cond_broadcast(&parser->stream_event_cond); + } + pthread_mutex_unlock(&parser->mutex); + } +-- +2.45.2 + diff --git a/patches/wine-gst/0029-winegstreamer-Do-not-seek-live-sources.patch b/patches/wine-gst/0029-winegstreamer-Do-not-seek-live-sources.patch new file mode 100644 index 000000000..76f56f751 --- /dev/null +++ b/patches/wine-gst/0029-winegstreamer-Do-not-seek-live-sources.patch @@ -0,0 +1,86 @@ +From e34d7e2409002e9643d669786fb09b1cec36fec8 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Sun, 14 Apr 2024 06:12:55 +0200 +Subject: [PATCH 29/43] winegstreamer: Do not seek live sources. + +souphttpsrc seems to break from seeking sometimes: + + winegstreamer error: source: Server does not support seeking. + winegstreamer error: source: ../src-gst_good/ext/soup/gstsouphttpsrc.c(1948): gst_soup_http_src_do_request (): /GstBin:bin1/GstURIDecodeBin:uridecodebin1/GstSoupHTTPSrc:source: + Server does not accept Range HTTP header, URL: https://stream.vrcdn.live/live/djzombiepunk.live.ts, Redirect to: (NULL) + +Seeking a live source doesn't make sense anyway. +--- + dlls/winegstreamer/wg_parser.c | 21 +++++++++++++++++++-- + 1 file changed, 19 insertions(+), 2 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 78a5314b508..204d7fa5672 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -112,6 +112,8 @@ struct wg_parser + GstContext *context; + + guint64 seek_pos; ++ ++ bool is_live; + }; + static const unsigned int input_cache_chunk_size = 512 << 10; + +@@ -502,6 +504,9 @@ static NTSTATUS wg_parser_stream_seek(void *args) + + stream = get_stream(params->stream); + ++ if (stream->parser->is_live) ++ return S_OK; ++ + if (start_flags & AM_SEEKING_SeekToKeyFrame) + flags |= GST_SEEK_FLAG_KEY_UNIT; + if (start_flags & AM_SEEKING_Segment) +@@ -697,6 +702,7 @@ static void no_more_pads_cb(GstElement *element, gpointer user) + + static void deep_element_added_cb(GstBin *self, GstBin *sub_bin, GstElement *element, gpointer user) + { ++ struct wg_parser *parser = user; + GstElementFactory *factory = NULL; + const char *name = NULL; + +@@ -716,6 +722,9 @@ static void deep_element_added_cb(GstBin *self, GstBin *sub_bin, GstElement *ele + g_object_set(element, "n-threads", G_GINT64_CONSTANT(1), NULL); + #endif + } ++ ++ if (name && (strstr(name, "HLS") || strstr(name, "RTP") || strstr(name, "RTSP"))) ++ parser->is_live = TRUE; + } + + static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) +@@ -1966,7 +1975,11 @@ static NTSTATUS wg_parser_connect(void *args) + goto out; + + gst_element_set_base_time(parser->container, gst_clock_get_time(clock)); +- gst_element_set_state(parser->container, GST_STATE_PAUSED); ++ if (gst_element_set_state(parser->container, GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL && parser->uri) ++ { ++ GST_DEBUG("Stream is live."); ++ parser->is_live = TRUE; ++ } + ret = gst_element_get_state(parser->container, NULL, NULL, -1); + + if (ret == GST_STATE_CHANGE_FAILURE) +@@ -1985,7 +1998,11 @@ static NTSTATUS wg_parser_connect(void *args) + + if (parser->uri) + { +- gst_element_set_state(parser->container, GST_STATE_PLAYING); ++ if (gst_element_set_state(parser->container, GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL && !parser->is_live) ++ { ++ GST_DEBUG("Stream is live."); ++ parser->is_live = TRUE; ++ } + ret = gst_element_get_state(parser->container, NULL, NULL, -1); + if (ret == GST_STATE_CHANGE_FAILURE) + goto out; +-- +2.45.2 + diff --git a/patches/wine-gst/0030-winegstreamer-Implement-buffering-events.patch b/patches/wine-gst/0030-winegstreamer-Implement-buffering-events.patch new file mode 100644 index 000000000..1dfa2d423 --- /dev/null +++ b/patches/wine-gst/0030-winegstreamer-Implement-buffering-events.patch @@ -0,0 +1,313 @@ +From 6caf81d40fa889abb34c059203e1de45c87547f9 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 10 Apr 2024 15:11:02 +0200 +Subject: [PATCH 30/43] winegstreamer: Implement buffering events. + +--- + dlls/winegstreamer/gst_private.h | 3 ++ + dlls/winegstreamer/main.c | 21 ++++++++++ + dlls/winegstreamer/media_source.c | 54 ++++++++++++++++++++++++ + dlls/winegstreamer/unixlib.h | 12 ++++++ + dlls/winegstreamer/wg_parser.c | 68 +++++++++++++++++++++++++++++++ + 5 files changed, 158 insertions(+) + +diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h +index bce9b5d2fe4..3bdd53dd36c 100644 +--- a/dlls/winegstreamer/gst_private.h ++++ b/dlls/winegstreamer/gst_private.h +@@ -79,6 +79,9 @@ void wg_parser_disconnect(wg_parser_t parser); + bool wg_parser_get_next_read_offset(wg_parser_t parser, uint64_t *offset, uint32_t *size); + void wg_parser_push_data(wg_parser_t parser, const void *data, uint32_t size); + ++bool wg_parser_get_next_event(wg_parser_t parser, int *stream, MediaEventType *event_type, ++ GUID *ext_type, HRESULT *evt_hr, PROPVARIANT *data); ++ + uint32_t wg_parser_get_stream_count(wg_parser_t parser); + wg_parser_stream_t wg_parser_get_stream(wg_parser_t parser, uint32_t index); + +diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c +index 992f6b3ae98..2d04b83ae9d 100644 +--- a/dlls/winegstreamer/main.c ++++ b/dlls/winegstreamer/main.c +@@ -133,6 +133,27 @@ bool wg_parser_get_next_read_offset(wg_parser_t parser, uint64_t *offset, uint32 + return true; + } + ++bool wg_parser_get_next_event(wg_parser_t parser, int *stream, MediaEventType *event_type, ++ GUID *ext_type, HRESULT *evt_hr, PROPVARIANT *data) ++{ ++ struct wg_parser_get_next_event_params params = ++ { ++ .parser = parser, ++ .data = data, ++ }; ++ ++ TRACE("parser %#I64x, stream %p, event_type %p, ext_type %p, evt_hr %p, data %p.\n", ++ parser, stream, event_type, ext_type, evt_hr, data); ++ ++ if (WINE_UNIX_CALL(unix_wg_parser_get_next_event, ¶ms)) ++ return false; ++ *stream = params.stream; ++ *event_type = params.event_type; ++ *ext_type = params.ext_type; ++ *evt_hr = params.evt_hr; ++ return true; ++} ++ + void wg_parser_push_data(wg_parser_t parser, const void *data, uint32_t size) + { + struct wg_parser_push_data_params params = +diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c +index 6709af89444..8aea5d77172 100644 +--- a/dlls/winegstreamer/media_source.c ++++ b/dlls/winegstreamer/media_source.c +@@ -207,6 +207,9 @@ struct media_source + + HANDLE read_thread; + bool read_thread_shutdown; ++ ++ HANDLE event_thread; ++ bool event_thread_shutdown; + }; + + static inline struct media_stream *impl_from_IMFMediaStream(IMFMediaStream *iface) +@@ -825,6 +828,7 @@ static HRESULT wait_on_sample(struct media_stream *stream, IUnknown *token) + + stream->busy = TRUE; + LeaveCriticalSection(&source->cs); ++ memset(&buffer, 0, sizeof(buffer)); + ret = wg_parser_stream_get_buffer(source->wg_parser, stream->wg_stream, &buffer); + EnterCriticalSection(&source->cs); + stream->busy = FALSE; +@@ -961,6 +965,41 @@ static DWORD CALLBACK read_thread(void *arg) + return 0; + } + ++static DWORD CALLBACK event_thread(void *arg) ++{ ++ struct media_source *source = arg; ++ ++ TRACE("Starting event thread for media source %p.\n", source); ++ ++ while (!source->event_thread_shutdown) ++ { ++ PROPVARIANT data = {.vt = VT_EMPTY}; ++ MediaEventType event_type = 0; ++ IMFMediaEventQueue *queue; ++ GUID ext_type = GUID_NULL; ++ HRESULT hr, evt_hr = E_FAIL; ++ int stream = -1; ++ ++ if (!wg_parser_get_next_event(source->wg_parser, &stream, &event_type, &ext_type, &evt_hr, &data)) ++ continue; ++ ++ if (!event_type) ++ continue; ++ ++ if (stream != -1) ++ queue = source->streams[stream]->event_queue; ++ else ++ queue = source->event_queue; ++ ++ if (FAILED(hr = IMFMediaEventQueue_QueueEventParamVar(queue, event_type, &ext_type, evt_hr, &data))) ++ ERR("Failed to enqueue event %lx, ext_type %s, evt_hr %lx, data %s, hr %#lx.\n", event_type, ++ debugstr_guid(&ext_type), evt_hr, wine_dbgstr_variant((VARIANT *)&data), hr); ++ } ++ ++ TRACE("Media source is shutting down; exiting.\n"); ++ return 0; ++} ++ + static HRESULT WINAPI media_stream_QueryInterface(IMFMediaStream *iface, REFIID riid, void **out) + { + struct media_stream *stream = impl_from_IMFMediaStream(iface); +@@ -1625,6 +1664,13 @@ static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface) + + wg_parser_disconnect(source->wg_parser); + ++ if (source->event_thread) ++ { ++ source->event_thread_shutdown = true; ++ WaitForSingleObject(source->event_thread, INFINITE); ++ CloseHandle(source->event_thread); ++ } ++ + if (source->read_thread) + { + source->read_thread_shutdown = true; +@@ -1728,6 +1774,8 @@ static HRESULT media_source_create(struct object_context *context, IMFMediaSourc + if (context->type != WG_PARSER_URIDECODEBIN) + object->read_thread = CreateThread(NULL, 0, read_thread, object, 0, NULL); + ++ object->event_thread = CreateThread(NULL, 0, event_thread, object, 0, NULL); ++ + object->state = SOURCE_OPENING; + + if (FAILED(hr = wg_parser_connect(parser, object->file_size, context->url))) +@@ -1812,6 +1860,12 @@ fail: + + if (stream_count != UINT_MAX) + wg_parser_disconnect(object->wg_parser); ++ if (object->event_thread) ++ { ++ object->event_thread_shutdown = true; ++ WaitForSingleObject(object->event_thread, INFINITE); ++ CloseHandle(object->event_thread); ++ } + if (object->read_thread) + { + object->read_thread_shutdown = true; +diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h +index fe21a24f6f5..154469a096a 100644 +--- a/dlls/winegstreamer/unixlib.h ++++ b/dlls/winegstreamer/unixlib.h +@@ -274,6 +274,16 @@ struct wg_parser_push_data_params + UINT32 size; + }; + ++struct wg_parser_get_next_event_params ++{ ++ wg_parser_t parser; ++ int stream; ++ UINT32 event_type; ++ GUID ext_type; ++ HRESULT evt_hr; ++ PROPVARIANT *data; ++}; ++ + struct wg_parser_get_stream_count_params + { + wg_parser_t parser; +@@ -526,6 +536,8 @@ enum unix_funcs + unix_wg_parser_get_next_read_offset, + unix_wg_parser_push_data, + ++ unix_wg_parser_get_next_event, ++ + unix_wg_parser_get_stream_count, + unix_wg_parser_get_stream, + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 204d7fa5672..ceaf66c92da 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -43,6 +43,12 @@ + #include "winternl.h" + #include "dshow.h" + ++#define COBJMACROS ++#include "mfapi.h" ++#include "mferror.h" ++#include "mfobjects.h" ++#include "mfidl.h" ++ + #include "unix_private.h" + + extern GstGLDisplay *gl_display; +@@ -114,6 +120,9 @@ struct wg_parser + guint64 seek_pos; + + bool is_live; ++ ++ bool buffering; ++ bool sent_buffering_event; + }; + static const unsigned int input_cache_chunk_size = 512 << 10; + +@@ -239,6 +248,47 @@ static NTSTATUS wg_parser_push_data(void *args) + return S_OK; + } + ++static NTSTATUS wg_parser_get_next_event(void *args) ++{ ++ struct wg_parser_get_next_event_params *params = args; ++ struct wg_parser *parser = get_parser(params->parser); ++ HRESULT hr = S_FALSE; ++ ++ pthread_mutex_lock(&parser->mutex); ++ ++ while (parser->sink_connected && (parser->buffering == parser->sent_buffering_event)) ++ pthread_cond_wait(&parser->stream_event_cond, &parser->mutex); ++ ++ if (!parser->sink_connected) ++ { ++ pthread_mutex_unlock(&parser->mutex); ++ return VFW_E_WRONG_STATE; ++ } ++ ++ params->stream = -1; ++ params->event_type = 0; ++ memset(¶ms->ext_type, 0, sizeof(params->ext_type)); ++ params->evt_hr = S_OK; ++ params->data->vt = VT_EMPTY; ++ ++ if (parser->buffering && !parser->sent_buffering_event) ++ { ++ params->event_type = MEBufferingStarted; ++ parser->sent_buffering_event = TRUE; ++ hr = S_OK; ++ } ++ else if (!parser->buffering && parser->sent_buffering_event) ++ { ++ params->event_type = MEBufferingStopped; ++ parser->sent_buffering_event = FALSE; ++ hr = S_OK; ++ } ++ ++ pthread_mutex_unlock(&parser->mutex); ++ ++ return hr; ++} ++ + static NTSTATUS wg_parser_stream_get_preferred_format(void *args) + { + const struct wg_parser_stream_get_preferred_format_params *params = args; +@@ -1765,6 +1815,19 @@ static GstBusSyncReply bus_handler_cb(GstBus *bus, GstMessage *msg, gpointer use + break; + } + ++ case GST_MESSAGE_BUFFERING: ++ { ++ gint percent; ++ ++ gst_message_parse_buffering(msg, &percent); ++ pthread_mutex_lock(&parser->mutex); ++ parser->buffering = percent < 100; ++ pthread_mutex_unlock(&parser->mutex); ++ pthread_cond_broadcast(&parser->stream_event_cond); ++ GST_DEBUG("parser %p, message %s, buffering %d.", parser, GST_MESSAGE_TYPE_NAME(msg), parser->buffering); ++ break; ++ } ++ + default: + break; + } +@@ -2161,6 +2224,7 @@ static NTSTATUS wg_parser_disconnect(void *args) + parser->sink_connected = false; + pthread_mutex_unlock(&parser->mutex); + pthread_cond_signal(&parser->read_cond); ++ pthread_cond_broadcast(&parser->stream_event_cond); + + for (i = 0; i < parser->stream_count; ++i) + free_stream(parser->streams[i]); +@@ -2374,6 +2438,8 @@ const unixlib_entry_t __wine_unix_call_funcs[] = + X(wg_parser_get_next_read_offset), + X(wg_parser_push_data), + ++ X(wg_parser_get_next_event), ++ + X(wg_parser_get_stream_count), + X(wg_parser_get_stream), + +@@ -2819,6 +2885,8 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = + X(wg_parser_get_next_read_offset), + X64(wg_parser_push_data), + ++ X(wg_parser_get_next_event), ++ + X(wg_parser_get_stream_count), + X(wg_parser_get_stream), + +-- +2.45.2 + diff --git a/patches/wine-gst/0031-mf-Send-sample-requests-for-unused-space-of-sample-q.patch b/patches/wine-gst/0031-mf-Send-sample-requests-for-unused-space-of-sample-q.patch new file mode 100644 index 000000000..dd3603fe0 --- /dev/null +++ b/patches/wine-gst/0031-mf-Send-sample-requests-for-unused-space-of-sample-q.patch @@ -0,0 +1,87 @@ +From 3dac384018f5b28f0afa2679fcffc06278c98602 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Tue, 21 May 2024 08:25:47 +0200 +Subject: [PATCH 31/43] mf: Send sample requests for unused space of sample + queue on pause => restart. + +--- + dlls/mf/samplegrabber.c | 27 ++++++++++++++------------- + 1 file changed, 14 insertions(+), 13 deletions(-) + +diff --git a/dlls/mf/samplegrabber.c b/dlls/mf/samplegrabber.c +index de599139736..77a05a6adc1 100644 +--- a/dlls/mf/samplegrabber.c ++++ b/dlls/mf/samplegrabber.c +@@ -91,6 +91,7 @@ struct sample_grabber + CRITICAL_SECTION cs; + UINT32 sample_count; + IMFSample *samples[MAX_SAMPLE_QUEUE_LENGTH]; ++ UINT32 samples_requested; + }; + + static IMFSampleGrabberSinkCallback *sample_grabber_get_callback(const struct sample_grabber *sink) +@@ -411,7 +412,8 @@ static HRESULT stream_queue_sample(struct sample_grabber *grabber, IMFSample *sa + + static void sample_grabber_stream_request_sample(struct sample_grabber *grabber) + { +- IMFStreamSink_QueueEvent(&grabber->IMFStreamSink_iface, MEStreamSinkRequestSample, &GUID_NULL, S_OK, NULL); ++ if (SUCCEEDED(IMFStreamSink_QueueEvent(&grabber->IMFStreamSink_iface, MEStreamSinkRequestSample, &GUID_NULL, S_OK, NULL))) ++ grabber->samples_requested++; + } + + static HRESULT WINAPI sample_grabber_stream_ProcessSample(IMFStreamSink *iface, IMFSample *sample) +@@ -423,12 +425,13 @@ static HRESULT WINAPI sample_grabber_stream_ProcessSample(IMFStreamSink *iface, + + TRACE("%p, %p.\n", iface, sample); + +- if (!sample) +- return S_OK; +- + EnterCriticalSection(&grabber->cs); + +- if (grabber->is_shut_down) ++ grabber->samples_requested--; ++ ++ if (!sample) ++ hr = S_OK; ++ else if (grabber->is_shut_down) + hr = MF_E_STREAMSINK_REMOVED; + else if (grabber->state == SINK_STATE_RUNNING || (grabber->state == SINK_STATE_PAUSED && grabber->ignore_clock)) + { +@@ -780,7 +783,7 @@ static HRESULT WINAPI sample_grabber_stream_timer_callback_Invoke(IMFAsyncCallba + } + } + } +- if (sample_delivered) ++ if (sample_delivered && grabber->samples_requested < MAX_SAMPLE_QUEUE_LENGTH) + sample_grabber_stream_request_sample(grabber); + + LeaveCriticalSection(&grabber->cs); +@@ -1195,19 +1198,17 @@ static HRESULT sample_grabber_set_state(struct sample_grabber *grabber, enum sin + + if (state == SINK_STATE_RUNNING && grabber->state != SINK_STATE_RUNNING) + { +- /* Every transition to running state sends a bunch requests to build up initial queue. */ +- for (i = 0; i < grabber->sample_count; ++i) ++ if (grabber->state == SINK_STATE_PAUSED && offset == PRESENTATION_CURRENT_POSITION) + { +- if (grabber->state == SINK_STATE_PAUSED && offset == PRESENTATION_CURRENT_POSITION) ++ for (i = 0; i < grabber->sample_count; ++i) + { + assert(grabber->samples[i]); + stream_queue_sample(grabber, grabber->samples[i]); + } +- else +- { +- sample_grabber_stream_request_sample(grabber); +- } + } ++ /* Every transition to running state sends a bunch requests to build up initial queue. */ ++ for (i = grabber->samples_requested; i < MAX_SAMPLE_QUEUE_LENGTH; ++i) ++ sample_grabber_stream_request_sample(grabber); + release_samples(grabber); + grabber->sample_count = 0; + } +-- +2.45.2 + diff --git a/patches/wine-gst/0032-winegstreamer-Fix-race-between-wg_parser_stream_disa.patch b/patches/wine-gst/0032-winegstreamer-Fix-race-between-wg_parser_stream_disa.patch new file mode 100644 index 000000000..c1ac50ff1 --- /dev/null +++ b/patches/wine-gst/0032-winegstreamer-Fix-race-between-wg_parser_stream_disa.patch @@ -0,0 +1,27 @@ +From ea06ee8ca40513d2d5194b2396bccc59253e4b98 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Mon, 3 Jun 2024 11:05:38 +0200 +Subject: [PATCH 32/43] winegstreamer: Fix race between + wg_parser_stream_disable and GST_EVENT_FLUSH_STOP. + +--- + dlls/winegstreamer/wg_parser.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index ceaf66c92da..703346ab887 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -843,8 +843,7 @@ static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) + pthread_mutex_lock(&parser->mutex); + + stream->eos = false; +- if (stream->enabled) +- stream->flushing = false; ++ stream->flushing = false; + + pthread_mutex_unlock(&parser->mutex); + break; +-- +2.45.2 + diff --git a/patches/wine-gst/0033-winegstreamer-Handle-Gstreamer-pipeline-flushes-grac.patch b/patches/wine-gst/0033-winegstreamer-Handle-Gstreamer-pipeline-flushes-grac.patch new file mode 100644 index 000000000..6688e0c3b --- /dev/null +++ b/patches/wine-gst/0033-winegstreamer-Handle-Gstreamer-pipeline-flushes-grac.patch @@ -0,0 +1,57 @@ +From f647e86f21ba2088736bde5b8a75048a17159135 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Fri, 14 Jun 2024 03:11:44 +0200 +Subject: [PATCH 33/43] winegstreamer: Handle Gstreamer pipeline flushes + gracefully. + +If a GST_EVENT_FLUSH_START event is received between the calls from PE code to wg_parser_stream_get_buffer +and wg_parser_stream_copy_buffer, the latter function will return an error, resulting in the sample request being +dropped without having delivered a sample. + +If wg_parser_stream_copy_buffer returns an error, retry the whole wait_on_sample procedure. +--- + dlls/winegstreamer/media_source.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c +index 8aea5d77172..7380515a050 100644 +--- a/dlls/winegstreamer/media_source.c ++++ b/dlls/winegstreamer/media_source.c +@@ -764,6 +764,7 @@ static HRESULT media_stream_send_sample(struct media_stream *stream, const struc + + if (!wg_parser_stream_copy_buffer(stream->wg_stream, data, 0, wg_buffer->size)) + { ++ hr = HRESULT_FROM_WIN32(ERROR_RETRY); + wg_parser_stream_release_buffer(stream->wg_stream); + IMFMediaBuffer_Unlock(buffer); + goto out; +@@ -822,10 +823,10 @@ static HRESULT wait_on_sample(struct media_stream *stream, IUnknown *token) + { + struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + struct wg_parser_buffer buffer; ++ HRESULT hr; + BOOL ret; + +- TRACE("%p, %p\n", stream, token); +- ++retry: + stream->busy = TRUE; + LeaveCriticalSection(&source->cs); + memset(&buffer, 0, sizeof(buffer)); +@@ -841,7 +842,12 @@ static HRESULT wait_on_sample(struct media_stream *stream, IUnknown *token) + } + + if (ret) +- return media_stream_send_sample(stream, &buffer, token); ++ { ++ hr = media_stream_send_sample(stream, &buffer, token); ++ if (hr == HRESULT_FROM_WIN32(ERROR_RETRY)) ++ goto retry; ++ return hr; ++ } + return media_stream_send_eos(source, stream); + } + +-- +2.45.2 + diff --git a/patches/wine-gst/0034-winegstreamer-Do-waits-for-samples-on-stream-specifi.patch b/patches/wine-gst/0034-winegstreamer-Do-waits-for-samples-on-stream-specifi.patch new file mode 100644 index 000000000..ecf58594d --- /dev/null +++ b/patches/wine-gst/0034-winegstreamer-Do-waits-for-samples-on-stream-specifi.patch @@ -0,0 +1,646 @@ +From 305f54c3db23d9b15386ad151faa1e6d8426e5ab Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Fri, 14 Jun 2024 03:08:48 +0200 +Subject: [PATCH 34/43] winegstreamer: Do waits for samples on stream-specific + work queues. + +--- + dlls/winegstreamer/media_source.c | 181 +++++++++++++++++++++----- + dlls/winegstreamer/new_media_source.c | 179 ++++++++++++++++++++----- + 2 files changed, 296 insertions(+), 64 deletions(-) + +diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c +index 7380515a050..3f84c1a5a5d 100644 +--- a/dlls/winegstreamer/media_source.c ++++ b/dlls/winegstreamer/media_source.c +@@ -121,9 +121,29 @@ static HRESULT object_context_create(DWORD flags, IMFByteStream *stream, const W + return S_OK; + } + ++enum stream_async_op ++{ ++ STREAM_ASYNC_REQUEST_SAMPLE, ++}; ++ ++struct stream_async_command ++{ ++ IUnknown IUnknown_iface; ++ LONG refcount; ++ enum stream_async_op op; ++ union ++ { ++ struct ++ { ++ IUnknown *token; ++ } request_sample; ++ } u; ++}; ++ + struct media_stream + { + IMFMediaStream IMFMediaStream_iface; ++ IMFAsyncCallback async_commands_callback; + LONG ref; + + IMFMediaSource *media_source; +@@ -132,6 +152,8 @@ struct media_stream + + wg_parser_stream_t wg_stream; + ++ DWORD async_commands_queue; ++ + IUnknown **token_queue; + LONG token_queue_count; + LONG token_queue_cap; +@@ -149,7 +171,6 @@ enum source_async_op + SOURCE_ASYNC_START, + SOURCE_ASYNC_PAUSE, + SOURCE_ASYNC_STOP, +- SOURCE_ASYNC_REQUEST_SAMPLE, + }; + + struct source_async_command +@@ -165,11 +186,6 @@ struct source_async_command + GUID format; + PROPVARIANT position; + } start; +- struct +- { +- struct media_stream *stream; +- IUnknown *token; +- } request_sample; + } u; + }; + +@@ -217,6 +233,16 @@ static inline struct media_stream *impl_from_IMFMediaStream(IMFMediaStream *ifac + return CONTAINING_RECORD(iface, struct media_stream, IMFMediaStream_iface); + } + ++static inline struct media_stream *impl_from_stream_async_commands_callback_IMFAsyncCallback(IMFAsyncCallback *iface) ++{ ++ return CONTAINING_RECORD(iface, struct media_stream, async_commands_callback); ++} ++ ++static inline struct stream_async_command *impl_from_stream_async_command_IUnknown(IUnknown *iface) ++{ ++ return CONTAINING_RECORD(iface, struct stream_async_command, IUnknown_iface); ++} ++ + static inline struct media_source *impl_from_IMFMediaSource(IMFMediaSource *iface) + { + return CONTAINING_RECORD(iface, struct media_source, IMFMediaSource_iface); +@@ -247,7 +273,7 @@ static inline struct source_async_command *impl_from_async_command_IUnknown(IUnk + return CONTAINING_RECORD(iface, struct source_async_command, IUnknown_iface); + } + +-static HRESULT WINAPI source_async_command_QueryInterface(IUnknown *iface, REFIID riid, void **obj) ++static HRESULT WINAPI async_command_QueryInterface(IUnknown *iface, REFIID riid, void **obj) + { + if (IsEqualIID(riid, &IID_IUnknown)) + { +@@ -261,6 +287,52 @@ static HRESULT WINAPI source_async_command_QueryInterface(IUnknown *iface, REFII + return E_NOINTERFACE; + } + ++static ULONG WINAPI stream_async_command_AddRef(IUnknown *iface) ++{ ++ struct stream_async_command *command = impl_from_stream_async_command_IUnknown(iface); ++ return InterlockedIncrement(&command->refcount); ++} ++ ++static ULONG WINAPI stream_async_command_Release(IUnknown *iface) ++{ ++ struct stream_async_command *command = impl_from_stream_async_command_IUnknown(iface); ++ ULONG refcount = InterlockedDecrement(&command->refcount); ++ ++ if (!refcount) ++ { ++ if (command->op == STREAM_ASYNC_REQUEST_SAMPLE) ++ { ++ if (command->u.request_sample.token) ++ IUnknown_Release(command->u.request_sample.token); ++ } ++ free(command); ++ } ++ ++ return refcount; ++} ++ ++static const IUnknownVtbl stream_async_command_vtbl = ++{ ++ async_command_QueryInterface, ++ stream_async_command_AddRef, ++ stream_async_command_Release, ++}; ++ ++static HRESULT stream_create_async_op(enum stream_async_op op, IUnknown **out) ++{ ++ struct stream_async_command *command; ++ ++ if (!(command = calloc(1, sizeof(*command)))) ++ return E_OUTOFMEMORY; ++ ++ command->IUnknown_iface.lpVtbl = &stream_async_command_vtbl; ++ command->refcount = 1; ++ command->op = op; ++ ++ *out = &command->IUnknown_iface; ++ return S_OK; ++} ++ + static ULONG WINAPI source_async_command_AddRef(IUnknown *iface) + { + struct source_async_command *command = impl_from_async_command_IUnknown(iface); +@@ -279,11 +351,6 @@ static ULONG WINAPI source_async_command_Release(IUnknown *iface) + IMFPresentationDescriptor_Release(command->u.start.descriptor); + PropVariantClear(&command->u.start.position); + } +- else if (command->op == SOURCE_ASYNC_REQUEST_SAMPLE) +- { +- if (command->u.request_sample.token) +- IUnknown_Release(command->u.request_sample.token); +- } + free(command); + } + +@@ -292,7 +359,7 @@ static ULONG WINAPI source_async_command_Release(IUnknown *iface) + + static const IUnknownVtbl source_async_command_vtbl = + { +- source_async_command_QueryInterface, ++ async_command_QueryInterface, + source_async_command_AddRef, + source_async_command_Release, + }; +@@ -568,7 +635,6 @@ static BOOL enqueue_token(struct media_stream *stream, IUnknown *token) + + static void flush_token_queue(struct media_stream *stream, BOOL send) + { +- struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + LONG i; + + for (i = 0; i < stream->token_queue_count; i++) +@@ -578,13 +644,12 @@ static void flush_token_queue(struct media_stream *stream, BOOL send) + IUnknown *op; + HRESULT hr; + +- if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &op))) ++ if (SUCCEEDED(hr = stream_create_async_op(STREAM_ASYNC_REQUEST_SAMPLE, &op))) + { +- struct source_async_command *command = impl_from_async_command_IUnknown(op); +- command->u.request_sample.stream = stream; ++ struct stream_async_command *command = impl_from_stream_async_command_IUnknown(op); + command->u.request_sample.token = stream->token_queue[i]; + +- hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); ++ hr = MFPutWorkItem(stream->async_commands_queue, &stream->async_commands_callback, op); + IUnknown_Release(op); + } + if (FAILED(hr)) +@@ -884,15 +949,6 @@ static HRESULT WINAPI source_async_commands_Invoke(IMFAsyncCallback *iface, IMFA + if (FAILED(hr = media_source_stop(source))) + WARN("Failed to stop source %p, hr %#lx\n", source, hr); + break; +- case SOURCE_ASYNC_REQUEST_SAMPLE: +- if (source->state == SOURCE_PAUSED) +- enqueue_token(command->u.request_sample.stream, command->u.request_sample.token); +- else if (source->state == SOURCE_RUNNING) +- { +- if (FAILED(hr = wait_on_sample(command->u.request_sample.stream, command->u.request_sample.token))) +- WARN("Failed to request sample, hr %#lx\n", hr); +- } +- break; + } + + LeaveCriticalSection(&source->cs); +@@ -911,6 +967,61 @@ static const IMFAsyncCallbackVtbl source_async_commands_callback_vtbl = + source_async_commands_Invoke, + }; + ++static ULONG WINAPI stream_async_commands_callback_AddRef(IMFAsyncCallback *iface) ++{ ++ struct media_stream *stream = impl_from_stream_async_commands_callback_IMFAsyncCallback(iface); ++ return IMFMediaStream_AddRef(&stream->IMFMediaStream_iface); ++} ++ ++static ULONG WINAPI stream_async_commands_callback_Release(IMFAsyncCallback *iface) ++{ ++ struct media_stream *stream = impl_from_stream_async_commands_callback_IMFAsyncCallback(iface); ++ return IMFMediaStream_Release(&stream->IMFMediaStream_iface); ++} ++ ++static HRESULT WINAPI stream_async_commands_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) ++{ ++ struct media_stream *stream = impl_from_stream_async_commands_callback_IMFAsyncCallback(iface); ++ struct media_source *source = impl_from_IMFMediaSource(stream->media_source); ++ struct stream_async_command *command; ++ IUnknown *state; ++ HRESULT hr; ++ ++ if (FAILED(hr = IMFAsyncResult_GetState(result, &state))) ++ return hr; ++ ++ EnterCriticalSection(&source->cs); ++ ++ command = impl_from_stream_async_command_IUnknown(state); ++ switch (command->op) ++ { ++ case STREAM_ASYNC_REQUEST_SAMPLE: ++ if (source->state == SOURCE_PAUSED) ++ enqueue_token(stream, command->u.request_sample.token); ++ else if (source->state == SOURCE_RUNNING) ++ { ++ if (FAILED(hr = wait_on_sample(stream, command->u.request_sample.token))) ++ WARN("Failed to request sample, hr %#lx\n", hr); ++ } ++ break; ++ } ++ ++ LeaveCriticalSection(&source->cs); ++ ++ IUnknown_Release(state); ++ ++ return S_OK; ++} ++ ++static const IMFAsyncCallbackVtbl stream_async_commands_callback_vtbl = ++{ ++ callback_QueryInterface, ++ stream_async_commands_callback_AddRef, ++ stream_async_commands_callback_Release, ++ callback_GetParameters, ++ stream_async_commands_Invoke, ++}; ++ + static DWORD CALLBACK read_thread(void *arg) + { + struct media_source *source = arg; +@@ -1158,15 +1269,14 @@ static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown + hr = MF_E_MEDIA_SOURCE_WRONGSTATE; + else if (stream->eos) + hr = MF_E_END_OF_STREAM; +- else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &op))) ++ else if (SUCCEEDED(hr = stream_create_async_op(STREAM_ASYNC_REQUEST_SAMPLE, &op))) + { +- struct source_async_command *command = impl_from_async_command_IUnknown(op); +- command->u.request_sample.stream = stream; ++ struct stream_async_command *command = impl_from_stream_async_command_IUnknown(op); + if (token) + IUnknown_AddRef(token); + command->u.request_sample.token = token; + +- hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); ++ hr = MFPutWorkItem(stream->async_commands_queue, &stream->async_commands_callback, op); + IUnknown_Release(op); + } + +@@ -1201,9 +1311,11 @@ static HRESULT media_stream_create(IMFMediaSource *source, IMFStreamDescriptor * + return E_OUTOFMEMORY; + + object->IMFMediaStream_iface.lpVtbl = &media_stream_vtbl; ++ object->async_commands_callback.lpVtbl = &stream_async_commands_callback_vtbl; + object->ref = 1; + +- if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) ++ if (FAILED(hr = MFCreateEventQueue(&object->event_queue)) || ++ FAILED(hr = MFAllocateWorkQueue(&object->async_commands_queue))) + { + free(object); + return hr; +@@ -1692,6 +1804,7 @@ static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface) + struct media_stream *stream = source->streams[source->stream_count]; + IMFStreamDescriptor_Release(source->descriptors[source->stream_count]); + IMFMediaEventQueue_Shutdown(stream->event_queue); ++ MFUnlockWorkQueue(stream->async_commands_queue); + IMFMediaStream_Release(&stream->IMFMediaStream_iface); + } + free(source->descriptors); +@@ -1854,7 +1967,11 @@ fail: + for (i = 0; i < stream_count; i++) + { + if (object->streams && object->streams[i]) ++ { ++ if (object->streams[i]->async_commands_queue) ++ MFUnlockWorkQueue(object->streams[i]->async_commands_queue); + IMFMediaStream_Release(&object->streams[i]->IMFMediaStream_iface); ++ } + } + for (i = 0; i < stream_count; i++) + { +diff --git a/dlls/winegstreamer/new_media_source.c b/dlls/winegstreamer/new_media_source.c +index efaf6a3e087..7444fc1c788 100644 +--- a/dlls/winegstreamer/new_media_source.c ++++ b/dlls/winegstreamer/new_media_source.c +@@ -239,15 +239,37 @@ static HRESULT create_media_source_fallback(struct object_context *context, IUnk + return hr; + } + ++enum stream_async_op ++{ ++ STREAM_ASYNC_REQUEST_SAMPLE, ++}; ++ ++struct stream_async_command ++{ ++ IUnknown IUnknown_iface; ++ LONG refcount; ++ enum stream_async_op op; ++ union ++ { ++ struct ++ { ++ IUnknown *token; ++ } request_sample; ++ } u; ++}; ++ + struct media_stream + { + IMFMediaStream IMFMediaStream_iface; ++ IMFAsyncCallback async_commands_callback; + LONG ref; + + IMFMediaSource *media_source; + IMFMediaEventQueue *event_queue; + IMFStreamDescriptor *descriptor; + ++ DWORD async_commands_queue; ++ + IUnknown **token_queue; + LONG token_queue_count; + LONG token_queue_cap; +@@ -261,7 +283,6 @@ enum source_async_op + SOURCE_ASYNC_START, + SOURCE_ASYNC_PAUSE, + SOURCE_ASYNC_STOP, +- SOURCE_ASYNC_REQUEST_SAMPLE, + }; + + struct source_async_command +@@ -277,11 +298,6 @@ struct source_async_command + GUID format; + PROPVARIANT position; + } start; +- struct +- { +- struct media_stream *stream; +- IUnknown *token; +- } request_sample; + } u; + }; + +@@ -326,6 +342,16 @@ static inline struct media_stream *impl_from_IMFMediaStream(IMFMediaStream *ifac + return CONTAINING_RECORD(iface, struct media_stream, IMFMediaStream_iface); + } + ++static inline struct media_stream *impl_from_stream_async_commands_callback_IMFAsyncCallback(IMFAsyncCallback *iface) ++{ ++ return CONTAINING_RECORD(iface, struct media_stream, async_commands_callback); ++} ++ ++static inline struct stream_async_command *impl_from_stream_async_command_IUnknown(IUnknown *iface) ++{ ++ return CONTAINING_RECORD(iface, struct stream_async_command, IUnknown_iface); ++} ++ + static inline struct media_source *impl_from_IMFMediaSource(IMFMediaSource *iface) + { + return CONTAINING_RECORD(iface, struct media_source, IMFMediaSource_iface); +@@ -356,7 +382,7 @@ static inline struct source_async_command *impl_from_async_command_IUnknown(IUnk + return CONTAINING_RECORD(iface, struct source_async_command, IUnknown_iface); + } + +-static HRESULT WINAPI source_async_command_QueryInterface(IUnknown *iface, REFIID riid, void **obj) ++static HRESULT WINAPI async_command_QueryInterface(IUnknown *iface, REFIID riid, void **obj) + { + if (IsEqualIID(riid, &IID_IUnknown)) + { +@@ -370,6 +396,52 @@ static HRESULT WINAPI source_async_command_QueryInterface(IUnknown *iface, REFII + return E_NOINTERFACE; + } + ++static ULONG WINAPI stream_async_command_AddRef(IUnknown *iface) ++{ ++ struct stream_async_command *command = impl_from_stream_async_command_IUnknown(iface); ++ return InterlockedIncrement(&command->refcount); ++} ++ ++static ULONG WINAPI stream_async_command_Release(IUnknown *iface) ++{ ++ struct stream_async_command *command = impl_from_stream_async_command_IUnknown(iface); ++ ULONG refcount = InterlockedDecrement(&command->refcount); ++ ++ if (!refcount) ++ { ++ if (command->op == STREAM_ASYNC_REQUEST_SAMPLE) ++ { ++ if (command->u.request_sample.token) ++ IUnknown_Release(command->u.request_sample.token); ++ } ++ free(command); ++ } ++ ++ return refcount; ++} ++ ++static const IUnknownVtbl stream_async_command_vtbl = ++{ ++ async_command_QueryInterface, ++ stream_async_command_AddRef, ++ stream_async_command_Release, ++}; ++ ++static HRESULT stream_create_async_op(enum stream_async_op op, IUnknown **out) ++{ ++ struct stream_async_command *command; ++ ++ if (!(command = calloc(1, sizeof(*command)))) ++ return E_OUTOFMEMORY; ++ ++ command->IUnknown_iface.lpVtbl = &stream_async_command_vtbl; ++ command->refcount = 1; ++ command->op = op; ++ ++ *out = &command->IUnknown_iface; ++ return S_OK; ++} ++ + static ULONG WINAPI source_async_command_AddRef(IUnknown *iface) + { + struct source_async_command *command = impl_from_async_command_IUnknown(iface); +@@ -388,11 +460,6 @@ static ULONG WINAPI source_async_command_Release(IUnknown *iface) + IMFPresentationDescriptor_Release(command->u.start.descriptor); + PropVariantClear(&command->u.start.position); + } +- else if (command->op == SOURCE_ASYNC_REQUEST_SAMPLE) +- { +- if (command->u.request_sample.token) +- IUnknown_Release(command->u.request_sample.token); +- } + free(command); + } + +@@ -401,7 +468,7 @@ static ULONG WINAPI source_async_command_Release(IUnknown *iface) + + static const IUnknownVtbl source_async_command_vtbl = + { +- source_async_command_QueryInterface, ++ async_command_QueryInterface, + source_async_command_AddRef, + source_async_command_Release, + }; +@@ -568,7 +635,6 @@ static BOOL enqueue_token(struct media_stream *stream, IUnknown *token) + + static void flush_token_queue(struct media_stream *stream, BOOL send) + { +- struct media_source *source = impl_from_IMFMediaSource(stream->media_source); + LONG i; + + for (i = 0; i < stream->token_queue_count; i++) +@@ -578,13 +644,12 @@ static void flush_token_queue(struct media_stream *stream, BOOL send) + IUnknown *op; + HRESULT hr; + +- if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &op))) ++ if (SUCCEEDED(hr = stream_create_async_op(STREAM_ASYNC_REQUEST_SAMPLE, &op))) + { +- struct source_async_command *command = impl_from_async_command_IUnknown(op); +- command->u.request_sample.stream = stream; ++ struct stream_async_command *command = impl_from_stream_async_command_IUnknown(op); + command->u.request_sample.token = stream->token_queue[i]; + +- hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); ++ hr = MFPutWorkItem(stream->async_commands_queue, &stream->async_commands_callback, op); + IUnknown_Release(op); + } + if (FAILED(hr)) +@@ -844,15 +909,6 @@ static HRESULT WINAPI source_async_commands_Invoke(IMFAsyncCallback *iface, IMFA + if (FAILED(hr = media_source_stop(source))) + WARN("Failed to stop source %p, hr %#lx\n", source, hr); + break; +- case SOURCE_ASYNC_REQUEST_SAMPLE: +- if (source->state == SOURCE_PAUSED) +- enqueue_token(command->u.request_sample.stream, command->u.request_sample.token); +- else if (source->state == SOURCE_RUNNING) +- { +- if (FAILED(hr = wait_on_sample(command->u.request_sample.stream, command->u.request_sample.token))) +- WARN("Failed to request sample, hr %#lx\n", hr); +- } +- break; + } + + LeaveCriticalSection(&source->cs); +@@ -871,6 +927,61 @@ static const IMFAsyncCallbackVtbl source_async_commands_callback_vtbl = + source_async_commands_Invoke, + }; + ++static ULONG WINAPI stream_async_commands_callback_AddRef(IMFAsyncCallback *iface) ++{ ++ struct media_stream *stream = impl_from_stream_async_commands_callback_IMFAsyncCallback(iface); ++ return IMFMediaStream_AddRef(&stream->IMFMediaStream_iface); ++} ++ ++static ULONG WINAPI stream_async_commands_callback_Release(IMFAsyncCallback *iface) ++{ ++ struct media_stream *stream = impl_from_stream_async_commands_callback_IMFAsyncCallback(iface); ++ return IMFMediaStream_Release(&stream->IMFMediaStream_iface); ++} ++ ++static HRESULT WINAPI stream_async_commands_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) ++{ ++ struct media_stream *stream = impl_from_stream_async_commands_callback_IMFAsyncCallback(iface); ++ struct media_source *source = impl_from_IMFMediaSource(stream->media_source); ++ struct stream_async_command *command; ++ IUnknown *state; ++ HRESULT hr; ++ ++ if (FAILED(hr = IMFAsyncResult_GetState(result, &state))) ++ return hr; ++ ++ EnterCriticalSection(&source->cs); ++ ++ command = impl_from_stream_async_command_IUnknown(state); ++ switch (command->op) ++ { ++ case STREAM_ASYNC_REQUEST_SAMPLE: ++ if (source->state == SOURCE_PAUSED) ++ enqueue_token(stream, command->u.request_sample.token); ++ else if (source->state == SOURCE_RUNNING) ++ { ++ if (FAILED(hr = wait_on_sample(stream, command->u.request_sample.token))) ++ WARN("Failed to request sample, hr %#lx\n", hr); ++ } ++ break; ++ } ++ ++ LeaveCriticalSection(&source->cs); ++ ++ IUnknown_Release(state); ++ ++ return S_OK; ++} ++ ++static const IMFAsyncCallbackVtbl stream_async_commands_callback_vtbl = ++{ ++ callback_QueryInterface, ++ stream_async_commands_callback_AddRef, ++ stream_async_commands_callback_Release, ++ callback_GetParameters, ++ stream_async_commands_Invoke, ++}; ++ + static HRESULT WINAPI media_stream_QueryInterface(IMFMediaStream *iface, REFIID riid, void **out) + { + struct media_stream *stream = impl_from_IMFMediaStream(iface); +@@ -1023,15 +1134,14 @@ static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown + hr = MF_E_MEDIA_SOURCE_WRONGSTATE; + else if (stream->eos) + hr = MF_E_END_OF_STREAM; +- else if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &op))) ++ else if (SUCCEEDED(hr = stream_create_async_op(STREAM_ASYNC_REQUEST_SAMPLE, &op))) + { +- struct source_async_command *command = impl_from_async_command_IUnknown(op); +- command->u.request_sample.stream = stream; ++ struct stream_async_command *command = impl_from_stream_async_command_IUnknown(op); + if (token) + IUnknown_AddRef(token); + command->u.request_sample.token = token; + +- hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, op); ++ hr = MFPutWorkItem(stream->async_commands_queue, &stream->async_commands_callback, op); + IUnknown_Release(op); + } + +@@ -1065,9 +1175,11 @@ static HRESULT media_stream_create(IMFMediaSource *source, IMFStreamDescriptor * + return E_OUTOFMEMORY; + + object->IMFMediaStream_iface.lpVtbl = &media_stream_vtbl; ++ object->async_commands_callback.lpVtbl = &stream_async_commands_callback_vtbl; + object->ref = 1; + +- if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) ++ if (FAILED(hr = MFCreateEventQueue(&object->event_queue)) || ++ FAILED(hr = MFAllocateWorkQueue(&object->async_commands_queue))) + { + free(object); + return hr; +@@ -1517,6 +1629,7 @@ static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface) + struct media_stream *stream = source->streams[source->stream_count]; + IMFStreamDescriptor_Release(source->descriptors[source->stream_count]); + IMFMediaEventQueue_Shutdown(stream->event_queue); ++ MFUnlockWorkQueue(stream->async_commands_queue); + IMFMediaStream_Release(&stream->IMFMediaStream_iface); + } + free(source->stream_map); +@@ -1731,6 +1844,8 @@ fail: + { + struct media_stream *stream = object->streams[object->stream_count]; + IMFStreamDescriptor_Release(object->descriptors[object->stream_count]); ++ if (stream->async_commands_queue) ++ MFUnlockWorkQueue(stream->async_commands_queue); + IMFMediaStream_Release(&stream->IMFMediaStream_iface); + } + free(object->stream_map); +-- +2.45.2 + diff --git a/patches/wine-gst/0035-HACK-winegstreamer-Add-a-resampler-to-wg_parser-for-.patch b/patches/wine-gst/0035-HACK-winegstreamer-Add-a-resampler-to-wg_parser-for-.patch new file mode 100644 index 000000000..eefb5a900 --- /dev/null +++ b/patches/wine-gst/0035-HACK-winegstreamer-Add-a-resampler-to-wg_parser-for-.patch @@ -0,0 +1,28 @@ +From 0d56779fda98158f125c1adcae8bbd535a97329b Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 31 Jan 2024 17:42:54 +0100 +Subject: [PATCH 35/43] [HACK] winegstreamer: Add a resampler to wg_parser for + raw audio. + +--- + dlls/winegstreamer/wg_parser.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 703346ab887..3d9e3b78a2c 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -1266,6 +1266,10 @@ static bool stream_create_post_processing_elements(GstPad *pad, struct wg_parser + if (!(element = create_element("audioconvert", "base")) + || !append_element(parser->container, element, &first, &last)) + return false; ++ /* Input sample rate may not match requested sample rate. */ ++ if (!(element = create_element("audioresample", "base")) ++ || !append_element(parser->container, element, &first, &last)) ++ return false; + + if (!link_src_to_element(pad, first) || !link_element_to_sink(last, stream->my_sink)) + return false; +-- +2.45.2 + diff --git a/patches/wine-gst/0036-HACK-winegstreamer-Add-a-videoscale-element-to-wg_pa.patch b/patches/wine-gst/0036-HACK-winegstreamer-Add-a-videoscale-element-to-wg_pa.patch new file mode 100644 index 000000000..4fb0c27a3 --- /dev/null +++ b/patches/wine-gst/0036-HACK-winegstreamer-Add-a-videoscale-element-to-wg_pa.patch @@ -0,0 +1,40 @@ +From 420c1729c09a1366a90ec60405b88ad938d3a2e5 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 31 Jan 2024 17:42:54 +0100 +Subject: [PATCH 36/43] [HACK] winegstreamer: Add a videoscale element to + wg_parser for raw video. + +--- + dlls/winegstreamer/wg_parser.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 3d9e3b78a2c..5cbc38b0e18 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -1174,6 +1174,10 @@ static bool stream_create_post_processing_elements(GstPad *pad, struct wg_parser + if (!(element = create_element("gldownload", "base")) + || !append_element(parser->container, element, &first, &last)) + return false; ++ /* Input resolution may not match requested resolution. */ ++ if (!(element = create_element("videoscale", "base")) ++ || !append_element(parser->container, element, &first, &last)) ++ return false; + + if (!link_src_to_element(pad, first) || !link_element_to_sink(last, stream->my_sink)) + return false; +@@ -1254,6 +1258,11 @@ static bool stream_create_post_processing_elements(GstPad *pad, struct wg_parser + /* Let GStreamer choose a default number of threads. */ + gst_util_set_object_arg(G_OBJECT(element), "n-threads", "0"); + ++ /* Input resolution may not match requested resolution. */ ++ if (!(element = create_element("videoscale", "base")) ++ || !append_element(parser->container, element, &first, &last)) ++ return false; ++ + if (!link_src_to_element(pad, first) || !link_element_to_sink(last, stream->my_sink)) + return false; + } +-- +2.45.2 + diff --git a/patches/wine-gst/0037-HACK-mfmediaengine-Do-not-send-MF_MEDIA_ENGINE_EVENT.patch b/patches/wine-gst/0037-HACK-mfmediaengine-Do-not-send-MF_MEDIA_ENGINE_EVENT.patch new file mode 100644 index 000000000..5b33e6380 --- /dev/null +++ b/patches/wine-gst/0037-HACK-mfmediaengine-Do-not-send-MF_MEDIA_ENGINE_EVENT.patch @@ -0,0 +1,50 @@ +From a78b4f13a004a9a4b52dc5ad7806186c9fd0b770 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Sat, 2 Mar 2024 21:19:25 +0100 +Subject: [PATCH 37/43] [HACK] mfmediaengine: Do not send + MF_MEDIA_ENGINE_EVENT_ERROR for VRChat. + +Breaks AVPro Video (used by VRChat) when a IMFMediaEngine::GetError call inside the event handler doesn't return an +IMFMediaError instance. The event handler code then tries to call a function on the non-existant IMFMediaError instance, +which segfaults. + +I don't know why IMFMediaEngine::GetError returns a NULL instance inside the error callback. This should be fixed +properly. In the meantime, this hack works around the problem. +--- + dlls/mfmediaengine/main.c | 20 +++++++++++++++----- + 1 file changed, 15 insertions(+), 5 deletions(-) + +diff --git a/dlls/mfmediaengine/main.c b/dlls/mfmediaengine/main.c +index a40f8d64f58..4180ba116bc 100644 +--- a/dlls/mfmediaengine/main.c ++++ b/dlls/mfmediaengine/main.c +@@ -1468,11 +1468,21 @@ static HRESULT WINAPI media_engine_load_handler_Invoke(IMFAsyncCallback *iface, + } + else + { +- engine->network_state = MF_MEDIA_ENGINE_NETWORK_NO_SOURCE; +- engine->error_code = MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED; +- engine->extended_code = hr; +- IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_ERROR, engine->error_code, +- engine->extended_code); ++ static unsigned int once; ++ char sgi[64]; ++ ++ if (GetEnvironmentVariableA("SteamGameId", sgi, sizeof(sgi)) && !strcmp(sgi, "438100")) ++ { ++ if (!once++) FIXME("HACK: not sending MF_MEDIA_ENGINE_EVENT_ERROR event.\n"); ++ } ++ else ++ { ++ engine->network_state = MF_MEDIA_ENGINE_NETWORK_NO_SOURCE; ++ engine->error_code = MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED; ++ engine->extended_code = hr; ++ IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_ERROR, engine->error_code, ++ engine->extended_code); ++ } + } + + LeaveCriticalSection(&engine->cs); +-- +2.45.2 + diff --git a/patches/wine-gst/0038-Marker-commit-do-not-put-into-MR.patch b/patches/wine-gst/0038-Marker-commit-do-not-put-into-MR.patch new file mode 100644 index 000000000..ce7710f7a --- /dev/null +++ b/patches/wine-gst/0038-Marker-commit-do-not-put-into-MR.patch @@ -0,0 +1,21 @@ +From bfc2cb62dcc4f234aad78e1a324704e5929815e6 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Mon, 25 Mar 2024 09:58:04 +0100 +Subject: [PATCH 38/43] === Marker commit, do not put into MR === + +--- + MAINTAINERS | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/MAINTAINERS b/MAINTAINERS +index 2291c99447c..2ed85ed4657 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -439,3 +439,4 @@ W: https://wine-staging.com/ + + seek + leak ++tmp +-- +2.45.2 + diff --git a/patches/wine-gst/0039-DEBUG-winegstreamer-GST_LOG-GST_DEBUG.patch b/patches/wine-gst/0039-DEBUG-winegstreamer-GST_LOG-GST_DEBUG.patch new file mode 100644 index 000000000..36651ad9f --- /dev/null +++ b/patches/wine-gst/0039-DEBUG-winegstreamer-GST_LOG-GST_DEBUG.patch @@ -0,0 +1,169 @@ +From d922f71679240a984ab0bdeb85d79eb841081b4a Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Tue, 19 Mar 2024 10:20:49 +0100 +Subject: [PATCH 39/43] [DEBUG] winegstreamer: GST_LOG -> GST_DEBUG. + +--- + dlls/winegstreamer/wg_parser.c | 34 +++++++++++++++++----------------- + 1 file changed, 17 insertions(+), 17 deletions(-) + +diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c +index 5cbc38b0e18..97e247788ca 100644 +--- a/dlls/winegstreamer/wg_parser.c ++++ b/dlls/winegstreamer/wg_parser.c +@@ -599,7 +599,7 @@ static NTSTATUS wg_parser_stream_notify_qos(void *args) + /* 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."); ++ GST_DEBUG("Ignoring QoS event."); + return S_OK; + } + if (!(event = gst_event_new_qos(params->underflow ? GST_QOS_TYPE_UNDERFLOW : GST_QOS_TYPE_OVERFLOW, +@@ -782,7 +782,7 @@ static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) + struct wg_parser_stream *stream = gst_pad_get_element_private(pad); + struct wg_parser *parser = stream->parser; + +- GST_LOG("stream %p, type \"%s\".", stream, GST_EVENT_TYPE_NAME(event)); ++ GST_DEBUG("stream %p, type \"%s\".", stream, GST_EVENT_TYPE_NAME(event)); + + switch (event->type) + { +@@ -892,7 +892,7 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu + struct wg_parser_stream *stream = gst_pad_get_element_private(pad); + struct wg_parser *parser = stream->parser; + +- GST_LOG("stream %p, buffer %p.", stream, buffer); ++ GST_DEBUG("stream %p, buffer %p.", stream, buffer); + + pthread_mutex_lock(&parser->mutex); + +@@ -918,7 +918,7 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu + + if (!stream->enabled) + { +- GST_LOG("Stream is disabled; discarding buffer."); ++ GST_DEBUG("Stream is disabled; discarding buffer."); + pthread_mutex_unlock(&parser->mutex); + gst_buffer_unref(buffer); + return GST_FLOW_OK; +@@ -949,7 +949,7 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu + * reference to the stream object, which will release it in + * wg_parser_stream_release_buffer(). */ + +- GST_LOG("Buffer queued."); ++ GST_DEBUG("Buffer queued."); + return GST_FLOW_OK; + } + +@@ -995,7 +995,7 @@ static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) + struct wg_parser_stream *stream = gst_pad_get_element_private(pad); + struct wg_parser *parser = stream->parser; + +- GST_LOG("stream %p, type \"%s\".", stream, gst_query_type_get_name(query->type)); ++ GST_DEBUG("stream %p, type \"%s\".", stream, gst_query_type_get_name(query->type)); + + switch (query->type) + { +@@ -1031,7 +1031,7 @@ static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) + gst_structure_remove_fields(gst_caps_get_structure(caps, i), + "framerate", "pixel-aspect-ratio", NULL); + +- GST_LOG("Stream caps are \"%" GST_PTR_FORMAT "\".", caps); ++ GST_DEBUG("Stream caps are \"%" GST_PTR_FORMAT "\".", caps); + + if (filter) + { +@@ -1309,7 +1309,7 @@ static void stream_decodebin_pad_added_cb(GstElement *element, GstPad *pad, gpoi + struct wg_parser_stream *stream = user; + struct wg_parser *parser = stream->parser; + +- GST_LOG("stream %p, parser %p, element %p, pad %p.", stream, parser, element, pad); ++ GST_DEBUG("stream %p, parser %p, element %p, pad %p.", stream, parser, element, pad); + + if (gst_pad_is_linked(pad)) + return; +@@ -1323,7 +1323,7 @@ static bool stream_decodebin_create(struct wg_parser_stream *stream) + { + struct wg_parser *parser = stream->parser; + +- GST_LOG("stream %p, parser %p.", stream, parser); ++ GST_DEBUG("stream %p, parser %p.", stream, parser); + + if (!(stream->decodebin = create_element("decodebin", "base"))) + return false; +@@ -1338,7 +1338,7 @@ static bool stream_decodebin_create(struct wg_parser_stream *stream) + pthread_mutex_unlock(&parser->mutex); + gst_element_sync_state_with_parent(stream->decodebin); + +- GST_LOG("Created stream decodebin %p for %u.", stream->decodebin, stream->number); ++ GST_DEBUG("Created stream decodebin %p for %u.", stream->decodebin, stream->number); + + return true; + } +@@ -1349,7 +1349,7 @@ static void pad_added_cb(GstElement *element, GstPad *pad, gpointer user) + struct wg_parser *parser = user; + GstCaps *caps, *caps2; + +- GST_LOG("parser %p, element %p, pad %p.", parser, element, pad); ++ GST_DEBUG("parser %p, element %p, pad %p.", parser, element, pad); + + if (gst_pad_is_linked(pad)) + return; +@@ -1392,7 +1392,7 @@ static void pad_removed_cb(GstElement *element, GstPad *pad, gpointer user) + unsigned int i; + char *name; + +- GST_LOG("parser %p, element %p, pad %p.", parser, element, pad); ++ GST_DEBUG("parser %p, element %p, pad %p.", parser, element, pad); + + for (i = 0; i < parser->stream_count; ++i) + { +@@ -1451,7 +1451,7 @@ static GstFlowReturn issue_read_request(struct wg_parser *parser, guint64 offset + + pthread_mutex_unlock(&parser->mutex); + +- GST_LOG("Request returned %s.", gst_flow_get_name(ret)); ++ GST_DEBUG("Request returned %s.", gst_flow_get_name(ret)); + + return ret; + } +@@ -1560,7 +1560,7 @@ static GstFlowReturn src_getrange_cb(GstPad *pad, GstObject *parent, + { + struct wg_parser *parser = gst_pad_get_element_private(pad); + +- GST_LOG("pad %p, offset %" G_GINT64_MODIFIER "u, size %u, buffer %p.", pad, offset, size, *buffer); ++ GST_DEBUG("pad %p, offset %" G_GINT64_MODIFIER "u, size %u, buffer %p.", pad, offset, size, *buffer); + + if (offset == GST_BUFFER_OFFSET_NONE) + offset = parser->next_pull_offset; +@@ -1574,7 +1574,7 @@ static GstFlowReturn src_getrange_cb(GstPad *pad, GstObject *parent, + if (!*buffer) + *buffer = gst_buffer_new_and_alloc(0); + gst_buffer_set_size(*buffer, 0); +- GST_LOG("Returning empty buffer."); ++ GST_DEBUG("Returning empty buffer."); + return GST_FLOW_OK; + } + +@@ -1595,7 +1595,7 @@ static gboolean src_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) + struct wg_parser *parser = gst_pad_get_element_private(pad); + GstFormat format; + +- GST_LOG("parser %p, type %s.", parser, GST_QUERY_TYPE_NAME(query)); ++ GST_DEBUG("parser %p, type %s.", parser, GST_QUERY_TYPE_NAME(query)); + + switch (GST_QUERY_TYPE(query)) + { +@@ -1899,7 +1899,7 @@ static gboolean src_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) + struct wg_parser *parser = gst_pad_get_element_private(pad); + gboolean ret = TRUE; + +- GST_LOG("parser %p, type \"%s\".", parser, GST_EVENT_TYPE_NAME(event)); ++ GST_DEBUG("parser %p, type \"%s\".", parser, GST_EVENT_TYPE_NAME(event)); + + switch (event->type) + { +-- +2.45.2 + diff --git a/patches/wine-gst/0040-HACK-kernelbase-yt-dlp.exe-redirection-and-cmdline-m.patch b/patches/wine-gst/0040-HACK-kernelbase-yt-dlp.exe-redirection-and-cmdline-m.patch new file mode 100644 index 000000000..0e27ced90 --- /dev/null +++ b/patches/wine-gst/0040-HACK-kernelbase-yt-dlp.exe-redirection-and-cmdline-m.patch @@ -0,0 +1,82 @@ +From 06cdae196eddcae63ec30665ea78033b2cb4e33a Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Thu, 28 Mar 2024 16:43:07 +0100 +Subject: [PATCH 40/43] [HACK] kernelbase: yt-dlp.exe redirection and cmdline + modification to read cookies from browser. + +--- + dlls/kernelbase/process.c | 51 +++++++++++++++++++++++++++++++++++++++ + 1 file changed, 51 insertions(+) + +diff --git a/dlls/kernelbase/process.c b/dlls/kernelbase/process.c +index 0898e30f603..958de08bfc3 100644 +--- a/dlls/kernelbase/process.c ++++ b/dlls/kernelbase/process.c +@@ -584,6 +584,54 @@ static int battleye_launcher_redirect_hack( const WCHAR *app_name, WCHAR *new_na + return 1; + } + ++static int ytdlp_hack( const WCHAR *app_name, WCHAR *new_name, DWORD new_name_len, WCHAR **cmdline ) ++{ ++ static const WCHAR ytdlp_exeW[] = L"yt-dlp.exe"; ++ static const size_t ytdlp_exeW_len = ARRAY_SIZE(ytdlp_exeW) - 1; ++ static const WCHAR cookiesfrombrowserW[] = L" --cookies-from-browser "; ++ static const size_t cookiesfrombrowserW_len = ARRAY_SIZE(cookiesfrombrowserW) - 1; ++ ++ WCHAR ytdlp_path[MAX_PATH], browser[32 + MAX_PATH]; ++ WCHAR *new_cmdline = NULL; ++ size_t len; ++ ++ if (!app_name || !*cmdline) ++ return 0; ++ ++ len = wcslen( app_name ); ++ if (len < ytdlp_exeW_len + 1) ++ return 0; ++ if (app_name[len - ytdlp_exeW_len - 1] != '\\' && app_name[len - ytdlp_exeW_len - 1] != '/') ++ return 0; ++ if (wcscmp( app_name + (len - ytdlp_exeW_len), ytdlp_exeW )) ++ return 0; ++ ++ ytdlp_path[0] = 0; ++ GetEnvironmentVariableW(L"PROTON_YTDLP_PATH", ytdlp_path, ARRAY_SIZE(ytdlp_path)); ++ if (wcslen( ytdlp_path ) >= new_name_len) ++ return 0; ++ ++ browser[0] = 0; ++ GetEnvironmentVariableW(L"PROTON_YTDLP_BROWSER", browser, ARRAY_SIZE(browser)); ++ if (browser[0]) ++ { ++ if (!(new_cmdline = RtlAllocateHeap( GetProcessHeap(), 0, (wcslen( *cmdline ) + cookiesfrombrowserW_len + wcslen( browser ) + 1) * sizeof(WCHAR) ))) ++ return 0; ++ } ++ ++ if (ytdlp_path[0]) ++ wcscpy( new_name, ytdlp_path ); ++ if (browser[0] && new_cmdline) ++ { ++ wcscpy( new_cmdline, *cmdline ); ++ wcscat( new_cmdline, cookiesfrombrowserW ); ++ wcscat( new_cmdline, browser ); ++ HeapFree( GetProcessHeap(), 0, *cmdline ); ++ *cmdline = new_cmdline; ++ } ++ return ytdlp_path[0]; ++} ++ + static const WCHAR *hack_append_command_line( const WCHAR *cmd ) + { + static const struct +@@ -712,6 +760,9 @@ BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessInternalW( HANDLE token, const WCHAR + if (battleye_launcher_redirect_hack( app_name, name, ARRAY_SIZE(name), &orig_app_name )) + app_name = name; + ++ if (ytdlp_hack( app_name, name, ARRAY_SIZE(name), &tidy_cmdline )) ++ app_name = name; ++ + /* Warn if unsupported features are used */ + + if (flags & (IDLE_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | REALTIME_PRIORITY_CLASS | +-- +2.45.2 + diff --git a/patches/wine-gst/0041-mf-Schedule-stored-timers-for-the-original-time-inst.patch b/patches/wine-gst/0041-mf-Schedule-stored-timers-for-the-original-time-inst.patch new file mode 100644 index 000000000..98d48f95c --- /dev/null +++ b/patches/wine-gst/0041-mf-Schedule-stored-timers-for-the-original-time-inst.patch @@ -0,0 +1,72 @@ +From f987de460921dbe30dc2d283ed7e4b571ac51778 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Fri, 23 Feb 2024 21:45:44 +0100 +Subject: [PATCH 41/43] mf: Schedule stored timers for the original time + instead of calling callbacks immediately when changing presentation clock + state. + +--- + dlls/mf/clock.c | 19 ++++++++++--------- + 1 file changed, 10 insertions(+), 9 deletions(-) + +diff --git a/dlls/mf/clock.c b/dlls/mf/clock.c +index e6be05d2794..474874330bf 100644 +--- a/dlls/mf/clock.c ++++ b/dlls/mf/clock.c +@@ -74,6 +74,8 @@ struct clock_timer + LONG refcount; + IMFAsyncResult *result; + IMFAsyncCallback *callback; ++ DWORD flags; ++ LONGLONG time; + MFWORKITEM_KEY key; + struct list entry; + }; +@@ -556,6 +558,9 @@ static HRESULT clock_call_state_change(MFTIME system_time, struct clock_state_ch + return hr; + } + ++static HRESULT present_clock_schedule_timer(struct presentation_clock *clock, DWORD flags, LONGLONG time, ++ struct clock_timer *timer); ++ + static HRESULT clock_change_state(struct presentation_clock *clock, enum clock_command command, + struct clock_state_change_param param) + { +@@ -583,7 +588,6 @@ static HRESULT clock_change_state(struct presentation_clock *clock, enum clock_c + enum clock_notification notification; + struct clock_sink *sink; + MFCLOCK_STATE old_state; +- IMFAsyncResult *result; + MFTIME system_time; + HRESULT hr; + +@@ -623,14 +627,9 @@ static HRESULT clock_change_state(struct presentation_clock *clock, enum clock_c + { + LIST_FOR_EACH_ENTRY_SAFE(timer, timer2, &clock->timers, struct clock_timer, entry) + { +- list_remove(&timer->entry); +- hr = MFCreateAsyncResult(&timer->IUnknown_iface, &clock->timer_callback, NULL, &result); +- IUnknown_Release(&timer->IUnknown_iface); +- if (SUCCEEDED(hr)) +- { +- MFPutWorkItemEx(MFASYNC_CALLBACK_QUEUE_TIMER, result); +- IMFAsyncResult_Release(result); +- } ++ hr = present_clock_schedule_timer(clock, timer->flags, timer->time, timer); ++ if (FAILED(hr)) ++ list_remove(&timer->entry); + } + } + else +@@ -902,6 +901,8 @@ static HRESULT WINAPI present_clock_timer_SetTimer(IMFTimer *iface, DWORD flags, + clock_timer->IUnknown_iface.lpVtbl = &clock_timer_vtbl; + clock_timer->refcount = 1; + clock_timer->callback = callback; ++ clock_timer->flags = flags; ++ clock_timer->time = time; + IMFAsyncCallback_AddRef(clock_timer->callback); + + EnterCriticalSection(&clock->cs); +-- +2.45.2 + diff --git a/patches/wine-gst/0042-mf-Start-forwarding-samples-only-at-the-PTS-of-the-f.patch b/patches/wine-gst/0042-mf-Start-forwarding-samples-only-at-the-PTS-of-the-f.patch new file mode 100644 index 000000000..eb64f7167 --- /dev/null +++ b/patches/wine-gst/0042-mf-Start-forwarding-samples-only-at-the-PTS-of-the-f.patch @@ -0,0 +1,290 @@ +From e950094cf2d13e5ad3ccbd04b4635127db6d1e8d Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Fri, 23 Feb 2024 21:50:22 +0100 +Subject: [PATCH 42/43] mf: Start forwarding samples only at the PTS of the + first sample. + +Hides desynchronization in applications that ignore the PTS, e.g. VRChat. +--- + dlls/mf/session.c | 179 ++++++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 172 insertions(+), 7 deletions(-) + +diff --git a/dlls/mf/session.c b/dlls/mf/session.c +index 925566d6baf..5b0cc5a344c 100644 +--- a/dlls/mf/session.c ++++ b/dlls/mf/session.c +@@ -163,6 +163,7 @@ enum topo_node_flags + { + TOPO_NODE_END_OF_STREAM = 0x1, + TOPO_NODE_SCRUB_SAMPLE_COMPLETE = 0x2, ++ TOPO_NODE_HAS_WAITED_FOR_PTS = 0x4, + }; + + struct topo_node +@@ -188,6 +189,9 @@ struct topo_node + { + IMFMediaSource *source; + DWORD stream_id; ++ IUnknown *cancel_key; ++ struct list samples; ++ IMFAsyncCallback timer_callback; + } source; + struct + { +@@ -237,6 +241,7 @@ struct media_session + IMFPresentationClock *clock; + IMFPresentationTimeSource *system_time_source; + IMFRateControl *clock_rate_control; ++ IMFTimer *timer; + IMFTopoLoader *topo_loader; + IMFQualityManager *quality_manager; + struct +@@ -313,6 +318,11 @@ static struct session_op *impl_op_from_IUnknown(IUnknown *iface) + return CONTAINING_RECORD(iface, struct session_op, IUnknown_iface); + } + ++static struct topo_node *impl_node_from_IMFAsyncCallback(IMFAsyncCallback *iface) ++{ ++ return CONTAINING_RECORD(iface, struct topo_node, u.source.timer_callback); ++} ++ + static struct topo_node *impl_node_from_IMFVideoSampleAllocatorNotify(IMFVideoSampleAllocatorNotify *iface) + { + return CONTAINING_RECORD(iface, struct topo_node, u.sink.notify_cb); +@@ -712,6 +722,29 @@ static void transform_stream_drop_samples(struct transform_stream *stream) + IMFSample_Release(sample); + } + ++static HRESULT topo_node_sourcestream_pop_sample(struct topo_node *source_node, IMFSample **sample) ++{ ++ struct sample *entry; ++ struct list *ptr; ++ ++ if (!(ptr = list_head(&source_node->u.source.samples))) ++ return MF_E_TRANSFORM_NEED_MORE_INPUT; ++ ++ entry = LIST_ENTRY(ptr, struct sample, entry); ++ list_remove(&entry->entry); ++ *sample = entry->sample; ++ free(entry); ++ return S_OK; ++} ++ ++static void topo_node_sourcestream_drop_samples(struct topo_node *source_node) ++{ ++ IMFSample *sample; ++ ++ while (SUCCEEDED(topo_node_sourcestream_pop_sample(source_node, &sample))) ++ IMFSample_Release(sample); ++} ++ + static void release_topo_node(struct topo_node *node) + { + unsigned int i; +@@ -721,6 +754,12 @@ static void release_topo_node(struct topo_node *node) + case MF_TOPOLOGY_SOURCESTREAM_NODE: + if (node->u.source.source) + IMFMediaSource_Release(node->u.source.source); ++ topo_node_sourcestream_drop_samples(node); ++ if (node->u.source.cancel_key) ++ { ++ IMFTimer_CancelTimer(node->session->timer, node->u.source.cancel_key); ++ IUnknown_Release(node->u.source.cancel_key); ++ } + break; + case MF_TOPOLOGY_TRANSFORM_NODE: + for (i = 0; i < node->u.transform.input_count; ++i) +@@ -1640,6 +1679,68 @@ static HRESULT session_get_stream_sink_type(IMFStreamSink *sink, IMFMediaType ** + return hr; + } + ++static HRESULT WINAPI node_timer_callback_QueryInterface(IMFAsyncCallback *iface, ++ REFIID riid, void **obj) ++{ ++ if (IsEqualIID(riid, &IID_IMFAsyncCallback) || ++ IsEqualIID(riid, &IID_IUnknown)) ++ { ++ *obj = iface; ++ IMFAsyncCallback_AddRef(iface); ++ return S_OK; ++ } ++ ++ *obj = NULL; ++ return E_NOINTERFACE; ++} ++ ++static ULONG WINAPI node_timer_callback_AddRef(IMFAsyncCallback *iface) ++{ ++ return 2; ++} ++ ++static ULONG WINAPI node_timer_callback_Release(IMFAsyncCallback *iface) ++{ ++ return 1; ++} ++ ++static HRESULT WINAPI node_timer_callback_GetParameters(IMFAsyncCallback *iface, ++ DWORD *flags, DWORD *queue) ++{ ++ return E_NOTIMPL; ++} ++ ++static void session_deliver_sample_from_source(struct media_session *session, struct topo_node *source_node, ++ IMFSample *sample); ++ ++static HRESULT WINAPI node_timer_callback_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) ++{ ++ struct topo_node *source_node = impl_node_from_IMFAsyncCallback(iface); ++ struct media_session *session = source_node->session; ++ IMFSample *sample; ++ ++ TRACE("%p %p\n", iface, result); ++ ++ EnterCriticalSection(&session->cs); ++ while (SUCCEEDED(topo_node_sourcestream_pop_sample(source_node, &sample))) ++ { ++ session_deliver_sample_from_source(session, source_node, sample); ++ IMFSample_Release(sample); ++ } ++ LeaveCriticalSection(&session->cs); ++ ++ return S_OK; ++} ++ ++static const IMFAsyncCallbackVtbl node_timer_callback_vtbl = ++{ ++ node_timer_callback_QueryInterface, ++ node_timer_callback_AddRef, ++ node_timer_callback_Release, ++ node_timer_callback_GetParameters, ++ node_timer_callback_Invoke, ++}; ++ + static HRESULT WINAPI node_sample_allocator_cb_QueryInterface(IMFVideoSampleAllocatorNotify *iface, + REFIID riid, void **obj) + { +@@ -1737,6 +1838,9 @@ static HRESULT session_append_node(struct media_session *session, IMFTopologyNod + + break; + case MF_TOPOLOGY_SOURCESTREAM_NODE: ++ list_init(&topo_node->u.source.samples); ++ topo_node->u.source.timer_callback.lpVtbl = &node_timer_callback_vtbl; ++ + if (FAILED(IMFTopologyNode_GetUnknown(node, &MF_TOPONODE_SOURCE, &IID_IMFMediaSource, + (void **)&topo_node->u.source.source))) + { +@@ -2039,6 +2143,8 @@ static ULONG WINAPI mfsession_Release(IMFMediaSession *iface) + IMFPresentationTimeSource_Release(session->system_time_source); + if (session->clock_rate_control) + IMFRateControl_Release(session->clock_rate_control); ++ if (session->timer) ++ IMFTimer_Release(session->timer); + if (session->topo_loader) + IMFTopoLoader_Release(session->topo_loader); + if (session->quality_manager) +@@ -3513,11 +3619,41 @@ static void session_request_sample(struct media_session *session, IMFStreamSink + IMFTopologyNode_Release(upstream_node); + } + ++static HRESULT topo_node_sourcestream_push_sample(struct topo_node *source_node, IMFSample *sample) ++{ ++ struct sample *entry; ++ ++ if (!(entry = calloc(1, sizeof(*entry)))) ++ return E_OUTOFMEMORY; ++ ++ entry->sample = sample; ++ IMFSample_AddRef(entry->sample); ++ ++ list_add_tail(&source_node->u.source.samples, &entry->entry); ++ return S_OK; ++} ++ ++static void session_deliver_sample_from_source(struct media_session *session, struct topo_node *source_node, ++ IMFSample *sample) ++{ ++ IMFTopologyNode *downstream_node; ++ DWORD downstream_input; ++ HRESULT hr; ++ ++ if (FAILED(hr = IMFTopologyNode_GetOutput(source_node->node, 0, &downstream_node, &downstream_input))) ++ { ++ WARN("Failed to get downstream node connection, hr %#lx.\n", hr); ++ return; ++ } ++ ++ session_deliver_sample_to_node(session, downstream_node, downstream_input, sample); ++ IMFTopologyNode_Release(downstream_node); ++} ++ + static void session_deliver_sample(struct media_session *session, IMFMediaStream *stream, const PROPVARIANT *value) + { + struct topo_node *source_node = NULL, *node; +- IMFTopologyNode *downstream_node; +- DWORD downstream_input; ++ IMFSample *sample; + HRESULT hr; + + if (value && (value->vt != VT_UNKNOWN || !value->punkVal)) +@@ -3538,17 +3674,42 @@ static void session_deliver_sample(struct media_session *session, IMFMediaStream + if (!source_node) + return; + ++ sample = value ? (IMFSample *)value->punkVal : NULL; ++ + if (!value) + source_node->flags |= TOPO_NODE_END_OF_STREAM; +- +- if (FAILED(hr = IMFTopologyNode_GetOutput(source_node->node, 0, &downstream_node, &downstream_input))) ++ else if (!(source_node->flags & TOPO_NODE_HAS_WAITED_FOR_PTS)) + { +- WARN("Failed to get downstream node connection, hr %#lx.\n", hr); ++ LONGLONG sampletime; ++ ++ source_node->flags |= TOPO_NODE_HAS_WAITED_FOR_PTS; ++ ++ if (SUCCEEDED(hr = IMFSample_GetSampleTime(sample, &sampletime))) ++ { ++ if (source_node->u.source.cancel_key) ++ { ++ IUnknown_Release(source_node->u.source.cancel_key); ++ source_node->u.source.cancel_key = NULL; ++ } ++ ++ if (SUCCEEDED(hr = IMFTimer_SetTimer(session->timer, 0, sampletime, &source_node->u.source.timer_callback, ++ NULL, &source_node->u.source.cancel_key))) ++ { ++ topo_node_sourcestream_push_sample(source_node, sample); ++ return; ++ } ++ ++ source_node->u.source.cancel_key = NULL; ++ } ++ } ++ ++ if (!list_empty(&source_node->u.source.samples)) ++ { ++ topo_node_sourcestream_push_sample(source_node, sample); + return; + } + +- session_deliver_sample_to_node(session, downstream_node, downstream_input, value ? (IMFSample *)value->punkVal : NULL); +- IMFTopologyNode_Release(downstream_node); ++ session_deliver_sample_from_source(session, source_node, sample); + } + + static void session_sink_invalidated(struct media_session *session, IMFMediaEvent *event, IMFStreamSink *sink) +@@ -4342,6 +4503,10 @@ HRESULT WINAPI MFCreateMediaSession(IMFAttributes *config, IMFMediaSession **ses + goto failed; + } + ++ if (FAILED(hr = IMFPresentationClock_QueryInterface(object->clock, &IID_IMFTimer, ++ (void **)&object->timer))) ++ goto failed; ++ + if (config) + { + GUID clsid; +-- +2.45.2 + diff --git a/patches/wine-gst/0043-ntdll-Use-unixcall-instead-of-syscall-for-QueryPerfo.patch b/patches/wine-gst/0043-ntdll-Use-unixcall-instead-of-syscall-for-QueryPerfo.patch new file mode 100644 index 000000000..60a8a57b8 --- /dev/null +++ b/patches/wine-gst/0043-ntdll-Use-unixcall-instead-of-syscall-for-QueryPerfo.patch @@ -0,0 +1,102 @@ +From 65fe0ddaa16cecb85d84ea59bd47354c8654b720 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 24 Apr 2024 22:00:49 +0200 +Subject: [PATCH 43/43] ntdll: Use unixcall instead of syscall for + QueryPerformanceCounter. + +--- + dlls/ntdll/time.c | 3 +-- + dlls/ntdll/unix/loader.c | 2 ++ + dlls/ntdll/unix/sync.c | 10 +++++++++- + dlls/ntdll/unix/unix_private.h | 2 ++ + dlls/ntdll/unixlib.h | 1 + + 5 files changed, 15 insertions(+), 3 deletions(-) + +diff --git a/dlls/ntdll/time.c b/dlls/ntdll/time.c +index 105a6cf5bc8..9bd182e4cb2 100644 +--- a/dlls/ntdll/time.c ++++ b/dlls/ntdll/time.c +@@ -382,8 +382,7 @@ LONGLONG WINAPI RtlGetSystemTimePrecise( void ) + */ + BOOL WINAPI DECLSPEC_HOTPATCH RtlQueryPerformanceCounter( LARGE_INTEGER *counter ) + { +- NtQueryPerformanceCounter( counter, NULL ); +- return TRUE; ++ return !WINE_UNIX_CALL( unix_query_performance_counter, counter ); + } + + /****************************************************************************** +diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c +index f8695a70deb..04f3fda6d4d 100644 +--- a/dlls/ntdll/unix/loader.c ++++ b/dlls/ntdll/unix/loader.c +@@ -1203,6 +1203,7 @@ static const unixlib_entry_t unix_call_funcs[] = + unixcall_wine_server_handle_to_fd, + unixcall_wine_spawnvp, + system_time_precise, ++ query_performance_counter, + steamclient_setup_trampolines, + is_pc_in_native_so, + debugstr_pc, +@@ -1224,6 +1225,7 @@ const unixlib_entry_t unix_call_wow64_funcs[] = + wow64_wine_server_handle_to_fd, + wow64_wine_spawnvp, + system_time_precise, ++ query_performance_counter, + }; + + #endif /* _WIN64 */ +diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c +index 23f17e72e08..355f4658fca 100644 +--- a/dlls/ntdll/unix/sync.c ++++ b/dlls/ntdll/unix/sync.c +@@ -1706,12 +1706,20 @@ NTSTATUS WINAPI NtDelayExecution( BOOLEAN alertable, const LARGE_INTEGER *timeou + } + + ++NTSTATUS query_performance_counter( void *args ) ++{ ++ LARGE_INTEGER *counter = args; ++ counter->QuadPart = monotonic_counter(); ++ return STATUS_SUCCESS; ++} ++ ++ + /****************************************************************************** + * NtQueryPerformanceCounter (NTDLL.@) + */ + NTSTATUS WINAPI NtQueryPerformanceCounter( LARGE_INTEGER *counter, LARGE_INTEGER *frequency ) + { +- counter->QuadPart = monotonic_counter(); ++ query_performance_counter( counter ); + if (frequency) frequency->QuadPart = TICKSPERSEC; + return STATUS_SUCCESS; + } +diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h +index 2b62d5c7754..02d6a8a0d41 100644 +--- a/dlls/ntdll/unix/unix_private.h ++++ b/dlls/ntdll/unix/unix_private.h +@@ -265,6 +265,8 @@ extern unsigned int alloc_object_attributes( const OBJECT_ATTRIBUTES *attr, stru + data_size_t *ret_len ); + extern NTSTATUS system_time_precise( void *args ); + ++extern NTSTATUS query_performance_counter( void *args ); ++ + extern void *steamclient_handle_fault( LPCVOID addr, DWORD err ); + extern void *anon_mmap_fixed( void *start, size_t size, int prot, int flags ); + extern void *anon_mmap_alloc( size_t size, int prot ); +diff --git a/dlls/ntdll/unixlib.h b/dlls/ntdll/unixlib.h +index e0870584a68..030801de407 100644 +--- a/dlls/ntdll/unixlib.h ++++ b/dlls/ntdll/unixlib.h +@@ -89,6 +89,7 @@ enum ntdll_unix_funcs + unix_wine_server_handle_to_fd, + unix_wine_spawnvp, + unix_system_time_precise, ++ unix_query_performance_counter, + unix_steamclient_setup_trampolines, + unix_is_pc_in_native_so, + unix_debugstr_pc, +-- +2.45.2 +