[PATCH 0/2] MR10606: mf/session: Support asynchronous transforms.
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mf/tests/mf.c | 396 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 381 insertions(+), 15 deletions(-) diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 7aca38c7fd2..1c0b8424576 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -10190,6 +10190,8 @@ static void test_media_session_thinning(void) struct test_transform { IMFTransform IMFTransform_iface; + IMFMediaEventGenerator IMFMediaEventGenerator_iface; + IMFShutdown IMFShutdown_iface; LONG refcount; const MFT_OUTPUT_STREAM_INFO *output_stream_info; @@ -10205,13 +10207,50 @@ struct test_transform IMFSample *output; HANDLE flush_event; + + IMFAttributes *attributes; + IMFMediaEventQueue *event_queue; + BOOL async; + BOOL streaming; + BOOL pending_input; + BOOL drain; + BOOL is_shut_down; }; +#define test_transform_check_unlocked(a) test_transform_check_unlocked_(__LINE__, a) +static void test_transform_check_unlocked_(int line, struct test_transform *transform) +{ + UINT32 unlock; + HRESULT hr; + + if (!transform->async) + return; + hr = IMFAttributes_GetUINT32(transform->attributes, &MF_TRANSFORM_ASYNC_UNLOCK, &unlock); + ok_(__FILE__, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine + ok_(__FILE__, line)(unlock, "Transform is locked.\n"); +} + static struct test_transform *test_transform_from_IMFTransform(IMFTransform *iface) { return CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); } +static struct test_transform *test_transform_from_IMFMediaEventGenerator(IMFMediaEventGenerator *iface) +{ + return CONTAINING_RECORD(iface, struct test_transform, IMFMediaEventGenerator_iface); +} + +static struct test_transform *test_transform_from_IMFShutdown(IMFShutdown *iface) +{ + return CONTAINING_RECORD(iface, struct test_transform, IMFShutdown_iface); +} + +static HRESULT test_transform_queue_event(struct test_transform *transform, MediaEventType type) +{ + return IMFMediaEventQueue_QueueEventParamVar(transform->event_queue, type, &GUID_NULL, S_OK, NULL); +} + static HRESULT WINAPI test_transform_QueryInterface(IMFTransform *iface, REFIID iid, void **out) { struct test_transform *transform = test_transform_from_IMFTransform(iface); @@ -10219,13 +10258,24 @@ static HRESULT WINAPI test_transform_QueryInterface(IMFTransform *iface, REFIID if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IMFTransform)) { - IMFTransform_AddRef(&transform->IMFTransform_iface); *out = &transform->IMFTransform_iface; - return S_OK; + } + else if (transform->async && IsEqualIID(iid, &IID_IMFMediaEventGenerator)) + { + *out = &transform->IMFMediaEventGenerator_iface; + } + else if (transform->async && IsEqualIID(iid, &IID_IMFShutdown)) + { + *out = &transform->IMFShutdown_iface; + } + else + { + *out = NULL; + return E_NOINTERFACE; } - *out = NULL; - return E_NOINTERFACE; + IUnknown_AddRef((IUnknown *)*out); + return S_OK; } static ULONG WINAPI test_transform_AddRef(IMFTransform *iface) @@ -10247,6 +10297,13 @@ static ULONG WINAPI test_transform_Release(IMFTransform *iface) if (transform->output_type) IMFMediaType_Release(transform->output_type); CloseHandle(transform->flush_event); + if (transform->async) + { + if (!transform->is_shut_down) + IMFShutdown_Shutdown(&transform->IMFShutdown_iface); + IMFAttributes_Release(transform->attributes); + IMFMediaEventQueue_Release(transform->event_queue); + } free(transform); } @@ -10288,6 +10345,8 @@ static HRESULT WINAPI test_transform_GetOutputStreamInfo(IMFTransform *iface, DW { struct test_transform *transform = test_transform_from_IMFTransform(iface); + test_transform_check_unlocked(transform); + ok(!!transform->output_stream_info, "Unexpected %s iface %p call.\n", __func__, iface); if (!transform->output_stream_info) return E_NOTIMPL; @@ -10298,7 +10357,14 @@ static HRESULT WINAPI test_transform_GetOutputStreamInfo(IMFTransform *iface, DW static HRESULT WINAPI test_transform_GetAttributes(IMFTransform *iface, IMFAttributes **attributes) { - return E_NOTIMPL; + struct test_transform *transform = test_transform_from_IMFTransform(iface); + + if (!transform->async) + return E_NOTIMPL; + + *attributes = transform->attributes; + IMFAttributes_AddRef(*attributes); + return S_OK; } static HRESULT WINAPI test_transform_GetInputStreamAttributes(IMFTransform *iface, DWORD id, IMFAttributes **attributes) @@ -10344,6 +10410,8 @@ static HRESULT WINAPI test_transform_GetOutputAvailableType(IMFTransform *iface, { struct test_transform *transform = test_transform_from_IMFTransform(iface); + test_transform_check_unlocked(transform); + if (index >= transform->output_count) { *type = NULL; @@ -10358,6 +10426,9 @@ static HRESULT WINAPI test_transform_GetOutputAvailableType(IMFTransform *iface, static HRESULT WINAPI test_transform_SetInputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags) { struct test_transform *transform = test_transform_from_IMFTransform(iface); + + test_transform_check_unlocked(transform); + if (flags & MFT_SET_TYPE_TEST_ONLY) return S_OK; if (transform->input_type) @@ -10370,6 +10441,9 @@ static HRESULT WINAPI test_transform_SetInputType(IMFTransform *iface, DWORD id, static HRESULT WINAPI test_transform_SetOutputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags) { struct test_transform *transform = test_transform_from_IMFTransform(iface); + + test_transform_check_unlocked(transform); + if (flags & MFT_SET_TYPE_TEST_ONLY) return S_OK; if (transform->output_type) @@ -10382,6 +10456,9 @@ static HRESULT WINAPI test_transform_SetOutputType(IMFTransform *iface, DWORD id static HRESULT WINAPI test_transform_GetInputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **type) { struct test_transform *transform = test_transform_from_IMFTransform(iface); + + test_transform_check_unlocked(transform); + if (!(*type = transform->input_type)) return MF_E_TRANSFORM_TYPE_NOT_SET; IMFMediaType_AddRef(*type); @@ -10391,6 +10468,9 @@ static HRESULT WINAPI test_transform_GetInputCurrentType(IMFTransform *iface, DW static HRESULT WINAPI test_transform_GetOutputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **type) { struct test_transform *transform = test_transform_from_IMFTransform(iface); + + test_transform_check_unlocked(transform); + if (!(*type = transform->output_type)) return MF_E_TRANSFORM_TYPE_NOT_SET; IMFMediaType_AddRef(*type); @@ -10424,11 +10504,15 @@ static HRESULT WINAPI test_transform_ProcessEvent(IMFTransform *iface, DWORD id, DEFINE_EXPECT(test_transform_ProcessMessage_BEGIN_STREAMING); DEFINE_EXPECT(test_transform_ProcessMessage_START_OF_STREAM); DEFINE_EXPECT(test_transform_ProcessMessage_FLUSH); +DEFINE_EXPECT(test_transform_ProcessInput); +DEFINE_EXPECT(test_transform_ProcessOutput); static HRESULT WINAPI test_transform_ProcessMessage(IMFTransform *iface, MFT_MESSAGE_TYPE message, ULONG_PTR param) { struct test_transform *transform = test_transform_from_IMFTransform(iface); + test_transform_check_unlocked(transform); + switch (message) { case MFT_MESSAGE_NOTIFY_BEGIN_STREAMING: @@ -10439,31 +10523,59 @@ static HRESULT WINAPI test_transform_ProcessMessage(IMFTransform *iface, MFT_MES case MFT_MESSAGE_NOTIFY_START_OF_STREAM: CHECK_EXPECT(test_transform_ProcessMessage_START_OF_STREAM); add_object_state(&actual_object_state_record, MFT_START); + if (transform->async) + { + transform->streaming = TRUE; + transform->pending_input = TRUE; + SET_EXPECT(test_transform_ProcessInput); + return test_transform_queue_event(transform, METransformNeedInput); + } return S_OK; case MFT_MESSAGE_COMMAND_FLUSH: + if (transform->output) + { + IMFSample_Release(transform->output); + transform->output = NULL; + } SetEvent(transform->flush_event); CHECK_EXPECT2(test_transform_ProcessMessage_FLUSH); add_object_state(&actual_object_state_record, MFT_FLUSH); return S_OK; + case MFT_MESSAGE_COMMAND_DRAIN: + if (transform->async) + { + if (!transform->pending_input && !transform->output) + return test_transform_queue_event(transform, METransformDrainComplete); + transform->drain = TRUE; + return S_OK; + } + break; + + case MFT_MESSAGE_NOTIFY_END_STREAMING: + if (transform->async) + return S_OK; + break; + default: - ok(0, "Unexpected %s call %#x.\n", __func__, message); - return E_NOTIMPL; + break; } -} -DEFINE_EXPECT(test_transform_ProcessInput); -DEFINE_EXPECT(test_transform_ProcessOutput); + ok(0, "Unexpected %s call %#x.\n", __func__, message); + return E_NOTIMPL; +} static HRESULT WINAPI test_transform_ProcessInput(IMFTransform *iface, DWORD id, IMFSample *sample, DWORD flags) { struct test_transform *transform = test_transform_from_IMFTransform(iface); HRESULT hr; + test_transform_check_unlocked(transform); + if (expect_test_transform_ProcessInput) { - if (transform->output) + if (transform->output || (transform->async && !transform->pending_input)) { hr = MF_E_NOTACCEPTING; } @@ -10471,6 +10583,12 @@ static HRESULT WINAPI test_transform_ProcessInput(IMFTransform *iface, DWORD id, { IMFSample_AddRef(transform->output = sample); hr = S_OK; + + if (transform->async) + { + transform->pending_input = FALSE; + hr = test_transform_queue_event(transform, METransformHaveOutput); + } } } else @@ -10490,6 +10608,8 @@ static HRESULT WINAPI test_transform_ProcessOutput(IMFTransform *iface, DWORD fl struct test_transform *transform = test_transform_from_IMFTransform(iface); HRESULT hr; + test_transform_check_unlocked(transform); + if (expect_test_transform_ProcessOutput) { if (transform->output) @@ -10498,10 +10618,25 @@ static HRESULT WINAPI test_transform_ProcessOutput(IMFTransform *iface, DWORD fl data->pSample = transform->output; transform->output = NULL; hr = S_OK; + + if (transform->async) + { + if (transform->drain) + { + hr = test_transform_queue_event(transform, METransformDrainComplete); + transform->drain = FALSE; + } + else if (transform->streaming) + { + transform->pending_input = TRUE; + SET_EXPECT(test_transform_ProcessInput); + hr = test_transform_queue_event(transform, METransformNeedInput); + } + } } else { - hr = MF_E_TRANSFORM_NEED_MORE_INPUT; + hr = transform->async ? E_UNEXPECTED : MF_E_TRANSFORM_NEED_MORE_INPUT; } } else @@ -10545,14 +10680,125 @@ static const IMFTransformVtbl test_transform_vtbl = test_transform_ProcessOutput, }; +static HRESULT WINAPI test_transform_events_QueryInterface(IMFMediaEventGenerator *iface, REFIID iid, void **out) +{ + struct test_transform *transform = test_transform_from_IMFMediaEventGenerator(iface); + return IMFTransform_QueryInterface(&transform->IMFTransform_iface, iid, out); +} + +static ULONG WINAPI test_transform_events_AddRef(IMFMediaEventGenerator *iface) +{ + struct test_transform *transform = test_transform_from_IMFMediaEventGenerator(iface); + return IMFTransform_AddRef(&transform->IMFTransform_iface); +} + +static ULONG WINAPI test_transform_events_Release(IMFMediaEventGenerator *iface) +{ + struct test_transform *transform = test_transform_from_IMFMediaEventGenerator(iface); + return IMFTransform_Release(&transform->IMFTransform_iface); +} + +static HRESULT WINAPI test_transform_events_GetEvent(IMFMediaEventGenerator *iface, DWORD flags, IMFMediaEvent **event) +{ + struct test_transform *transform = test_transform_from_IMFMediaEventGenerator(iface); + return IMFMediaEventQueue_GetEvent(transform->event_queue, flags, event); +} + +static HRESULT WINAPI test_transform_events_BeginGetEvent(IMFMediaEventGenerator *iface, IMFAsyncCallback *callback, + IUnknown *state) +{ + struct test_transform *transform = test_transform_from_IMFMediaEventGenerator(iface); + return IMFMediaEventQueue_BeginGetEvent(transform->event_queue, callback, state); +} + +static HRESULT WINAPI test_transform_events_EndGetEvent(IMFMediaEventGenerator *iface, IMFAsyncResult *result, + IMFMediaEvent **event) +{ + struct test_transform *transform = test_transform_from_IMFMediaEventGenerator(iface); + return IMFMediaEventQueue_EndGetEvent(transform->event_queue, result, event); +} + +static HRESULT WINAPI test_transform_events_QueueEvent(IMFMediaEventGenerator *iface, MediaEventType event_type, + REFGUID ext_type, HRESULT hr, const PROPVARIANT *value) +{ + struct test_transform *transform = test_transform_from_IMFMediaEventGenerator(iface); + return IMFMediaEventQueue_QueueEventParamVar(transform->event_queue, event_type, ext_type, hr, value); +} + +static const IMFMediaEventGeneratorVtbl test_transform_events_vtbl = +{ + test_transform_events_QueryInterface, + test_transform_events_AddRef, + test_transform_events_Release, + test_transform_events_GetEvent, + test_transform_events_BeginGetEvent, + test_transform_events_EndGetEvent, + test_transform_events_QueueEvent, +}; + +static HRESULT WINAPI test_transform_shutdown_QueryInterface(IMFShutdown *iface, REFIID iid, void **out) +{ + struct test_transform *transform = test_transform_from_IMFShutdown(iface); + return IMFTransform_QueryInterface(&transform->IMFTransform_iface, iid, out); +} + +static ULONG WINAPI test_transform_shutdown_AddRef(IMFShutdown *iface) +{ + struct test_transform *transform = test_transform_from_IMFShutdown(iface); + return IMFTransform_AddRef(&transform->IMFTransform_iface); +} + +static ULONG WINAPI test_transform_shutdown_Release(IMFShutdown *iface) +{ + struct test_transform *transform = test_transform_from_IMFShutdown(iface); + return IMFTransform_Release(&transform->IMFTransform_iface); +} + +static HRESULT WINAPI test_transform_shutdown_Shutdown(IMFShutdown *iface) +{ + struct test_transform *transform = test_transform_from_IMFShutdown(iface); + + IMFMediaEventQueue_Shutdown(transform->event_queue); + transform->is_shut_down = TRUE; + + return S_OK; +} + +static HRESULT WINAPI test_transform_shutdown_GetShutdownStatus(IMFShutdown *iface, MFSHUTDOWN_STATUS *status) +{ + struct test_transform *transform = test_transform_from_IMFShutdown(iface); + HRESULT hr = S_OK; + + if (!status) + return E_INVALIDARG; + + if (transform->is_shut_down) + *status = MFSHUTDOWN_COMPLETED; + else + hr = MF_E_INVALIDREQUEST; + + return hr; +} + +static const IMFShutdownVtbl test_transform_shutdown_vtbl = +{ + test_transform_shutdown_QueryInterface, + test_transform_shutdown_AddRef, + test_transform_shutdown_Release, + test_transform_shutdown_Shutdown, + test_transform_shutdown_GetShutdownStatus, +}; + static HRESULT WINAPI test_transform_create(UINT input_count, IMFMediaType **input_types, - UINT output_count, IMFMediaType **output_types, BOOL d3d_aware, IMFTransform **out) + UINT output_count, IMFMediaType **output_types, BOOL d3d_aware, BOOL async, IMFTransform **out) { struct test_transform *transform; if (!(transform = calloc(1, sizeof(*transform)))) return E_OUTOFMEMORY; transform->IMFTransform_iface.lpVtbl = &test_transform_vtbl; + transform->IMFMediaEventGenerator_iface.lpVtbl = async ? &test_transform_events_vtbl : NULL; + transform->IMFShutdown_iface.lpVtbl = async ? &test_transform_shutdown_vtbl : NULL; transform->refcount = 1; transform->input_count = input_count; @@ -10566,6 +10812,20 @@ static HRESULT WINAPI test_transform_create(UINT input_count, IMFMediaType **inp transform->flush_event = CreateEventW(NULL, FALSE, FALSE, NULL); ok(!!transform->flush_event, "CreateEventW failed, error %lu\n", GetLastError()); + if ((transform->async = async)) + { + HRESULT hr = MFCreateAttributes(&transform->attributes, 3); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFAttributes_SetUINT32(transform->attributes, &MF_TRANSFORM_ASYNC, TRUE); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFAttributes_SetUINT32(transform->attributes, &MF_TRANSFORM_ASYNC_UNLOCK, FALSE); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFAttributes_SetUINT32(transform->attributes, &MFT_SUPPORT_DYNAMIC_FORMAT_CHANGE, TRUE); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = MFCreateEventQueue(&transform->event_queue); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + } + *out = &transform->IMFTransform_iface; return S_OK; } @@ -10627,7 +10887,7 @@ static void test_media_session_seek(void) hr = IMFMediaType_SetUINT32(type, &MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); mft = NULL; - hr = test_transform_create(1, &type, 1, &type, FALSE, &mft); + hr = test_transform_create(1, &type, 1, &type, FALSE, FALSE, &mft); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); test_transform_set_output_stream_info(mft, &output_stream_info); @@ -10934,7 +11194,7 @@ static void test_media_session_scrubbing(void) hr = IMFMediaType_SetUINT64(type, &MF_MT_FRAME_SIZE, (UINT64)640 << 32 | 480); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); mft = NULL; - hr = test_transform_create(1, &type, 1, &type, FALSE, &mft); + hr = test_transform_create(1, &type, 1, &type, FALSE, FALSE, &mft); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); transform = test_transform_from_IMFTransform(mft); test_transform_set_output_stream_info(mft, &output_stream_info); @@ -11293,6 +11553,111 @@ static void test_media_session_invalid_topology(void) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); } +static void test_async_transform(void) +{ + media_type_desc video_nv12_desc = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_NV12), + }; + MFT_OUTPUT_STREAM_INFO output_stream_info = {0}; + struct test_grabber_callback *grabber_callback; + struct test_transform *transform; + IMFActivate *sink_activate; + IMFAsyncCallback *callback; + IMFMediaType *output_type; + PROPVARIANT propvar = {0}; + IMFMediaSession *session; + IMFTransform *mft = NULL; + IMFMediaSource *source; + IMFTopology *topology; + HRESULT hr; + DWORD ret; + UINT i; + + 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"))) + { + todo_wine /* Gitlab CI Debian runner */ + win_skip("MP4 media source is not supported, skipping tests.\n"); + MFShutdown(); + return; + } + + grabber_callback = impl_from_IMFSampleGrabberSinkCallback(create_test_grabber_callback()); + grabber_callback->ready_event = CreateEventW(NULL, FALSE, FALSE, NULL); + ok(!!grabber_callback->ready_event, "CreateEventW failed, error %lu\n", GetLastError()); + + hr = MFCreateMediaType(&output_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(output_type, video_nv12_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); + + memset(&actual_object_state_record, 0, sizeof(actual_object_state_record)); + SET_EXPECT(test_transform_ProcessMessage_START_OF_STREAM); + SET_EXPECT(test_transform_ProcessMessage_BEGIN_STREAMING); + SET_EXPECT(test_transform_ProcessOutput); + SET_EXPECT(test_transform_ProcessMessage_FLUSH); + + hr = test_transform_create(1, &output_type, 1, &output_type, FALSE, TRUE, &mft); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + test_transform_set_output_stream_info(mft, &output_stream_info); + + hr = MFCreateMediaSession(NULL, &session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + topology = create_test_topology_unk(source, (IUnknown *)sink_activate, (IUnknown *)mft, NULL); + hr = IMFMediaSession_SetTopology(session, 0, topology); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFTopology_Release(topology); + IMFActivate_Release(sink_activate); + + callback = create_test_callback(TRUE); + propvar.vt = VT_EMPTY; + 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); + + /* Check for repeated sample delivery */ + for (i = 0, ret = WAIT_OBJECT_0; i < 10 && ret == WAIT_OBJECT_0; ++i) + { + ret = WaitForSingleObject(grabber_callback->ready_event, 1000); + ok(ret == WAIT_OBJECT_0, "WaitForSingleObject returned %lu\n", ret); + } + + transform = test_transform_from_IMFTransform(mft); + ok(transform->streaming, "Transform is not streaming.\n"); + + 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 = wait_media_event(session, callback, MESessionClosed, 1000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + ok(!transform->is_shut_down, "Transform was shut down.\n"); + + 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); + + ok(transform->is_shut_down, "Transform was not shut down.\n"); + + IMFTransform_Release(mft); + IMFMediaType_Release(output_type); + IMFAsyncCallback_Release(callback); + IMFMediaSession_Release(session); + IMFSampleGrabberSinkCallback_Release(&grabber_callback->IMFSampleGrabberSinkCallback_iface); + IMFMediaSource_Release(source); + + hr = MFShutdown(); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +} + START_TEST(mf) { init_functions(); @@ -11337,4 +11702,5 @@ START_TEST(mf) test_media_session_scrubbing(); test_media_session_sample_request(); test_media_session_invalid_topology(); + test_async_transform(); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10606
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mf/mf_private.h | 1 + dlls/mf/session.c | 122 +++++++++++++++++++++++++++++++++++--- dlls/mf/tests/mf.c | 1 - dlls/mf/topology_loader.c | 32 ++++++++++ 4 files changed, 147 insertions(+), 9 deletions(-) diff --git a/dlls/mf/mf_private.h b/dlls/mf/mf_private.h index 0658db1de15..ad8d730db18 100644 --- a/dlls/mf/mf_private.h +++ b/dlls/mf/mf_private.h @@ -127,6 +127,7 @@ extern HRESULT topology_node_get_type_handler(IMFTopologyNode *node, DWORD strea extern HRESULT topology_node_init_media_type(IMFTopologyNode *node, DWORD stream, BOOL output, IMFMediaType **type); extern BOOL topology_node_is_d3d_aware(IMFTopologyNode *node); extern HRESULT topology_node_set_device_manager(IMFTopologyNode *node, IUnknown *device_manager); +extern IMFAttributes *topology_node_transform_async_get_attributes(IMFTopologyNode *node); extern HRESULT stream_sink_get_device_manager(IMFStreamSink *stream_sink, IUnknown **device_manager); extern HRESULT enum_audio_capture_sources(IMFAttributes *attributes, IMFActivate ***sources, UINT32 *ret_count); diff --git a/dlls/mf/session.c b/dlls/mf/session.c index db837c369d7..927ab072bc6 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -227,6 +227,9 @@ struct topo_node struct transform_stream *outputs; DWORD *output_map; unsigned int output_count; + + BOOL async; + IMFMediaEventGenerator *event_source; } transform; } u; }; @@ -800,6 +803,8 @@ static void release_topo_node(struct topo_node *node) free(node->u.transform.outputs); free(node->u.transform.input_map); free(node->u.transform.output_map); + if (node->u.transform.event_source) + IMFMediaEventGenerator_Release(node->u.transform.event_source); break; case MF_TOPOLOGY_OUTPUT_NODE: if (node->u.sink.allocator) @@ -825,6 +830,7 @@ static void session_shutdown_current_topology(struct media_session *session) { unsigned int shutdown, force_shutdown; IMFStreamSink *stream_sink; + IMFAttributes *attributes; IMFTopology *topology; IMFTopologyNode *node; IMFActivate *activate; @@ -835,11 +841,11 @@ static void session_shutdown_current_topology(struct media_session *session) topology = session->presentation.current_topology; force_shutdown = session->state == SESSION_STATE_SHUT_DOWN; - /* FIXME: should handle async MFTs, but these are not supported by the rest of the pipeline currently. */ - while (SUCCEEDED(IMFTopology_GetNode(topology, idx++, &node))) { - if (topology_node_get_type(node) == MF_TOPOLOGY_OUTPUT_NODE) + MF_TOPOLOGY_TYPE node_type = topology_node_get_type(node); + + if (node_type == MF_TOPOLOGY_OUTPUT_NODE) { shutdown = 1; IMFTopologyNode_GetUINT32(node, &MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, &shutdown); @@ -865,6 +871,21 @@ static void session_shutdown_current_topology(struct media_session *session) } } } + else if (node_type == MF_TOPOLOGY_TRANSFORM_NODE + && (attributes = topology_node_transform_async_get_attributes(node))) + { + IMFShutdown *shutdown; + if (SUCCEEDED(hr = topology_node_get_object(node, &IID_IMFShutdown, (void **)&shutdown))) + { + IMFShutdown_Shutdown(shutdown); + IMFShutdown_Release(shutdown); + } + else + { + WARN("Failed to get shutdown interface, hr %#lx.\n", hr); + } + IMFAttributes_Release(attributes); + } IMFTopologyNode_Release(node); } @@ -1752,6 +1773,7 @@ static HRESULT session_set_transform_stream_info(struct topo_node *node) struct transform_stream *streams; UINT32 bytes_per_second, value; unsigned int block_alignment; + IMFAttributes *attributes; IMFMediaType *media_type; HRESULT hr; @@ -1781,6 +1803,17 @@ static HRESULT session_set_transform_stream_info(struct topo_node *node) node->u.transform.inputs = streams; node->u.transform.input_count = input_count; + if ((attributes = topology_node_transform_async_get_attributes(node->node))) + { + IMFAttributes_SetUINT32(attributes, &MF_TRANSFORM_ASYNC_UNLOCK, TRUE); + IMFAttributes_Release(attributes); + + node->u.transform.async = TRUE; + if (FAILED(hr = IMFTransform_QueryInterface(node->object.transform, &IID_IMFMediaEventGenerator, + (void **)&node->u.transform.event_source))) + WARN("Failed to get event source, hr %#lx.\n", hr); + } + streams = calloc(output_count, sizeof(*streams)); for (i = 0; i < output_count; ++i) { @@ -3233,7 +3266,12 @@ static void session_set_source_object_state(struct media_session *session, IUnkn LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry) { if (node->type == MF_TOPOLOGY_TRANSFORM_NODE) + { + if (node->u.transform.async && FAILED(hr = IMFMediaEventGenerator_BeginGetEvent(node->u.transform.event_source, + &session->events_callback, (IUnknown *)node->u.transform.event_source))) + WARN("Failed to subscribe to transform events, hr %#lx.\n", hr); IMFTransform_ProcessMessage(node->object.transform, MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0); + } } } @@ -3859,7 +3897,8 @@ static HRESULT transform_node_handle_format_change(struct media_session *session IMFAttributes_Release(attributes); } - if (!support_dynamic_format_change) + /* all async MFTs must support dynamic format change, but async is checked here in case a bugged MFT doesn't set the flag */ + if (!support_dynamic_format_change && !topo_node->u.transform.async) { if (SUCCEEDED(hr = IMFTransform_ProcessMessage(transform, MFT_MESSAGE_COMMAND_DRAIN, id))) { @@ -3972,6 +4011,8 @@ static void transform_node_deliver_samples(struct media_session *session, struct if (FAILED(hr = transform_stream_pop_event(stream, &event))) { + if (topo_node->u.transform.async) + break; /* try getting more samples by calling IMFTransform_ProcessOutput */ if (FAILED(hr = transform_node_pull_samples(session, topo_node))) break; @@ -3993,7 +4034,7 @@ static void transform_node_deliver_samples(struct media_session *session, struct } } - if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT && transform_node_has_requests(topo_node)) + if (!topo_node->u.transform.async && hr == MF_E_TRANSFORM_NEED_MORE_INPUT && transform_node_has_requests(topo_node)) { struct transform_stream *stream; @@ -4048,8 +4089,11 @@ static void session_deliver_sample_to_node(struct media_session *session, struct case MF_TOPOLOGY_TRANSFORM_NODE: if (FAILED(hr = transform_node_push_sample(session, topo_node, input, sample))) WARN("Failed to push or queue sample to transform, hr %#lx\n", hr); - transform_node_pull_samples(session, topo_node); - transform_node_deliver_samples(session, topo_node); + if (!topo_node->u.transform.async) + { + transform_node_pull_samples(session, topo_node); + transform_node_deliver_samples(session, topo_node); + } break; case MF_TOPOLOGY_TEE_NODE: FIXME("Unhandled downstream node type %d.\n", topo_node->type); @@ -4102,7 +4146,7 @@ static HRESULT session_request_sample_from_node(struct media_session *session, s ERR("Failed to handle stream event, hr %#lx\n", hr); IMFMediaEvent_Release(event); } - else if (transform_node_has_requests(topo_node)) + else if (transform_node_has_requests(topo_node) || topo_node->u.transform.async) { /* there's already requests pending, just increase the counter */ stream->requests++; @@ -4365,6 +4409,20 @@ static void session_sink_stream_scrub_complete(struct media_session *session, IM } } +static struct topo_node *session_get_async_transform_node(const struct media_session *session, + IMFMediaEventGenerator *event_source) +{ + struct topo_node *node; + + LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry) + { + if (node->type == MF_TOPOLOGY_TRANSFORM_NODE && node->u.transform.event_source == event_source) + return node; + } + FIXME("Failed to get transform.\n"); + return NULL; +} + static HRESULT WINAPI session_events_callback_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) { struct media_session *session = impl_from_events_callback_IMFAsyncCallback(iface); @@ -4579,6 +4637,54 @@ static HRESULT WINAPI session_events_callback_Invoke(IMFAsyncCallback *iface, IM break; + case METransformNeedInput: + { + struct topo_node *up_node, *topo_node = session_get_async_transform_node(session, event_source); + struct transform_stream *stream; + IMFMediaEvent *stream_event; + UINT32 input = 0; + BOOL is_sample; + DWORD output; + + if (FAILED(hr = IMFMediaEvent_GetUINT32(event, &MF_EVENT_MFT_INPUT_STREAM_ID, &input))) + WARN("Failed to get input id, hr %#lx.\n", hr); + + stream = &topo_node->u.transform.inputs[input]; + + for (is_sample = FALSE; !is_sample && SUCCEEDED(hr); ) + { + if (SUCCEEDED(hr = transform_stream_pop_event(stream, &stream_event))) + { + MediaEventType event_type; + if (FAILED(hr = transform_stream_handle_event(session, stream, topo_node, input, stream_event))) + ERR("Failed to handle stream event, hr %#lx\n", hr); + is_sample = SUCCEEDED(hr = IMFMediaEvent_GetType(stream_event, &event_type)) && event_type == MEMediaSample; + IMFMediaEvent_Release(stream_event); + } + } + + if (is_sample) + break; + + if (!(up_node = session_get_topo_node_input(session, topo_node, input, &output))) + WARN("Failed to get node %p/%u input\n", topo_node, input); + else if (FAILED(hr = session_request_sample_from_node(session, up_node, output))) + WARN("Failed to request sample from upstream node %p/%lu, hr %#lx\n", up_node, output, hr); + break; + } + + case METransformHaveOutput: + { + struct topo_node *topo_node = session_get_async_transform_node(session, event_source); + + if (topo_node) + { + transform_node_pull_samples(session, topo_node); + transform_node_deliver_samples(session, topo_node); + } + break; + } + case MEError: /* Wine-specific extension */ EnterCriticalSection(&session->cs); diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 1c0b8424576..af2f6f86da0 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -10227,7 +10227,6 @@ static void test_transform_check_unlocked_(int line, struct test_transform *tran return; hr = IMFAttributes_GetUINT32(transform->attributes, &MF_TRANSFORM_ASYNC_UNLOCK, &unlock); ok_(__FILE__, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok_(__FILE__, line)(unlock, "Transform is locked.\n"); } diff --git a/dlls/mf/topology_loader.c b/dlls/mf/topology_loader.c index 9cf4ee0b28c..b18119b7216 100644 --- a/dlls/mf/topology_loader.c +++ b/dlls/mf/topology_loader.c @@ -804,6 +804,32 @@ BOOL topology_node_is_d3d_aware(IMFTopologyNode *node) return d3d_aware || d3d11_aware; } +IMFAttributes *topology_node_transform_async_get_attributes(IMFTopologyNode *node) +{ + IMFAttributes *attributes; + + if (SUCCEEDED(topology_node_get_object_attributes(node, &attributes))) + { + UINT32 async; + if (SUCCEEDED(IMFAttributes_GetUINT32(attributes, &MF_TRANSFORM_ASYNC, &async)) && async) + return attributes; + IMFAttributes_Release(attributes); + } + + return NULL; +} + +static void topology_node_transform_unlock_async(IMFTopologyNode *node) +{ + IMFAttributes *attributes; + + if ((attributes = topology_node_transform_async_get_attributes(node))) + { + IMFAttributes_SetUINT32(attributes, &MF_TRANSFORM_ASYNC_UNLOCK, TRUE); + IMFAttributes_Release(attributes); + } +} + static HRESULT topology_loader_create_copier(IMFTopologyNode *upstream_node, DWORD upstream_output, IMFTopologyNode *downstream_node, unsigned int downstream_input, IMFTransform **copier) { @@ -1059,6 +1085,12 @@ static HRESULT WINAPI topology_loader_Load(IMFTopoLoader *iface, IMFTopology *in for (i = 0; SUCCEEDED(IMFTopology_GetNode(input_topology, i, &node)); i++) { + MF_TOPOLOGY_TYPE type; + + /* unlock transforms now to enable querying of types */ + if (SUCCEEDED(IMFTopologyNode_GetNodeType(node, &type)) && type == MF_TOPOLOGY_TRANSFORM_NODE) + topology_node_transform_unlock_async(node); + hr = topology_node_list_branches(node, &branches); IMFTopologyNode_Release(node); if (FAILED(hr)) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10606
In practice this case only happen if application provides its own async transform. Is this the case? Or is the intent to switch some of ours transforms to async? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10606#note_135616
On Thu Apr 9 10:46:02 2026 +0000, Nikolay Sivov wrote:
In practice this case only happen if application provides its own async transform. Is this the case? Or is the intent to switch some of ours transforms to async? Yes, this is to support a game which uses one, but we have seen performance issues when playing very high-res videos, so maybe there's something to gain by switching some of ours over.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10606#note_135639
On Thu Apr 9 10:46:02 2026 +0000, Conor McCarthy wrote:
Yes, this is to support a game which uses one, but we have seen performance issues when playing very high-res videos, so maybe there's something to gain by switching some of ours over. @rbernon did some work in this direction with !3139. Could you maybe check if there is anything to be reused from it? I'm not sure if we necessarily need to wrap every transform in such async wrapper, but maybe it's something to consider. If it creates a single code path for all transform types.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10606#note_137223
On Tue Apr 21 13:56:17 2026 +0000, Nikolay Sivov wrote:
@rbernon did some work in this direction with !3139. Could you maybe check if there is anything to be reused from it? I'm not sure if we necessarily need to wrap every transform in such async wrapper, but maybe it's something to consider. If it creates a single code path for all transform types. There isn't much in common there, and I don't see handling of async transform events there. It looks like they were to be added later. The complete branch no longer exists.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10606#note_137309
participants (3)
-
Conor McCarthy -
Conor McCarthy (@cmccarthy) -
Nikolay Sivov (@nsivov)