This is similar to https://gitlab.winehq.org/wine/wine/-/merge_requests/2684, https://gitlab.winehq.org/wine/wine/-/merge_requests/3004 or https://gitlab.winehq.org/wine/wine/-/merge_requests/3139 but it validates the session transform node behavior with tests.
The tests are added after the changes because they otherwise don't pass and making them pass would be unnecessarily complicated.
I also have some local tests for MF_TOPONODE_WORKQUEUE_ID attributes and how it is supposed to behave. It basically creates new serialized work queues for every source node and assign them to every node downstream. Any sample request or processing operation is done in the associated queue.
When joining streams, queues are assigned downstream one after another and the last assigned queue is used when requesting samples upstream, but when samples are received and processed downstream it looks like the current queue of the source node is used for every downstream operations.
The request behavior seems to be the same when work queues are used, with round robin input requests, and single ProcessInput call followed by ProcessOutput loop until it fails.
This is yet not optimally efficient, and could be improved, for the following reasons:
1) All session operations are serialized together, even unrelated streams, and ProcessInput / ProcessOutput calls may be costly and stalling the pipeline. I believe that native probably allows parallel processing of unrelated stream requests, this needs to be confirmed.
2) MFT_MESSAGE_COMMAND_DRAIN message use isn't ideal, the message forces the transform to process all queued input synchronously, which can take a long time. I haven't checked exactly what native does but I believe it instead uses MFT_MESSAGE_NOTIFY_END_OF_STREAM messages, which would allow us to notify and drain the GStreamer decoder asynchronously.
3) MFT_MESSAGE_COMMAND_DRAIN also doesn't distinguish between input streams and needs to be sent globally. It's unclear how it should be used when multiple input streams are involved, and when one stream ends its segment then start a new segment while other streams don't have yet reached EOS. MFT_MESSAGE_NOTIFY_END_OF_STREAM messages have a stream ID parameter and would be more appropriate to handle separate input streams independently.
-- v3: mf/tests: Add more media session transform tests with multiple inputs. mf/tests: Add more media session transform tests with multiple outputs. mf/tests: Add some media session transform call pattern tests. mf/session: Increase the stream request count when requests are already queued. mf/session: Request more samples from upstream when necessary. mf/session: Push transform input samples one by one to ProcessInput. mf/session: Use helpers to push and pop samples for transform streams. mf/session: Flush requests in transform_node_deliver_samples when drained. mf/session: Use a helper to deliver transform node requested samples. mf/session: Use local variables to access transform node streams.
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/mf/session.c | 48 +++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 555a71dcdf7..c629399d10e 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -3128,6 +3128,8 @@ static HRESULT transform_node_pull_samples(const struct media_session *session, /* Collect returned samples for all streams. */ for (i = 0; i < node->u.transform.output_count; ++i) { + struct transform_stream *stream = &node->u.transform.outputs[i]; + if (buffers[i].pEvents) IMFCollection_Release(buffers[i].pEvents);
@@ -3137,7 +3139,7 @@ static HRESULT transform_node_pull_samples(const struct media_session *session, IMFQualityManager_NotifyProcessOutput(session->quality_manager, node->node, i, buffers[i].pSample);
queued_sample = transform_create_sample(buffers[i].pSample); - list_add_tail(&node->u.transform.outputs[i].samples, &queued_sample->entry); + list_add_tail(&stream->samples, &queued_sample->entry); }
if (buffers[i].pSample) @@ -3189,17 +3191,21 @@ static void session_deliver_sample_to_node(struct media_session *session, IMFTop } break; case MF_TOPOLOGY_TRANSFORM_NODE: + { + struct transform_stream *input_stream = &topo_node->u.transform.inputs[input];
transform_node_pull_samples(session, topo_node);
sample_entry = transform_create_sample(sample); - list_add_tail(&topo_node->u.transform.inputs[input].samples, &sample_entry->entry); + list_add_tail(&input_stream->samples, &sample_entry->entry);
for (i = 0; i < topo_node->u.transform.input_count; ++i) { + struct transform_stream *stream = &topo_node->u.transform.inputs[i]; + stream_id = transform_node_get_stream_id(topo_node, FALSE, i); - LIST_FOR_EACH_ENTRY_SAFE(sample_entry, sample_entry2, &topo_node->u.transform.inputs[i].samples, - struct sample, entry) + + LIST_FOR_EACH_ENTRY_SAFE(sample_entry, sample_entry2, &stream->samples, struct sample, entry) { if (sample_entry->sample) { @@ -3212,7 +3218,7 @@ static void session_deliver_sample_to_node(struct media_session *session, IMFTop } else { - transform_stream_drop_samples(&topo_node->u.transform.inputs[i]); + transform_stream_drop_samples(stream); drain = TRUE; } } @@ -3231,28 +3237,32 @@ static void session_deliver_sample_to_node(struct media_session *session, IMFTop { for (i = 0; i < topo_node->u.transform.output_count; ++i) { - if ((sample_entry = transform_create_sample(NULL))) - list_add_tail(&topo_node->u.transform.outputs[i].samples, &sample_entry->entry); + struct transform_stream *stream = &topo_node->u.transform.outputs[i]; + + if ((sample_entry = transform_create_sample(NULL))) + list_add_tail(&stream->samples, &sample_entry->entry); } }
/* Push down all available output. */ for (i = 0; i < topo_node->u.transform.output_count; ++i) { + struct transform_stream *stream = &topo_node->u.transform.outputs[i]; + if (FAILED(IMFTopologyNode_GetOutput(node, i, &downstream_node, &downstream_input))) { WARN("Failed to get connected node for output %u.\n", i); continue; }
- LIST_FOR_EACH_ENTRY_SAFE(sample_entry, sample_entry2, &topo_node->u.transform.outputs[i].samples, + LIST_FOR_EACH_ENTRY_SAFE(sample_entry, sample_entry2, &stream->samples, struct sample, entry) { - if (!topo_node->u.transform.outputs[i].requests) + if (!stream->requests) break;
session_deliver_sample_to_node(session, downstream_node, downstream_input, sample_entry->sample); - topo_node->u.transform.outputs[i].requests--; + stream->requests--;
transform_release_sample(sample_entry); } @@ -3261,6 +3271,7 @@ static void session_deliver_sample_to_node(struct media_session *session, IMFTop }
break; + } case MF_TOPOLOGY_TEE_NODE: FIXME("Unhandled downstream node type %d.\n", node_type); break; @@ -3293,20 +3304,22 @@ static void session_deliver_pending_samples(struct media_session *session, IMFTo /* Push down all available output. */ for (i = 0; i < topo_node->u.transform.output_count; ++i) { + struct transform_stream *stream = &topo_node->u.transform.outputs[i]; + if (FAILED(IMFTopologyNode_GetOutput(node, i, &downstream_node, &downstream_input))) { WARN("Failed to get connected node for output %u.\n", i); continue; }
- LIST_FOR_EACH_ENTRY_SAFE(sample_entry, sample_entry2, &topo_node->u.transform.outputs[i].samples, + LIST_FOR_EACH_ENTRY_SAFE(sample_entry, sample_entry2, &stream->samples, struct sample, entry) { - if (!topo_node->u.transform.outputs[i].requests) + if (!stream->requests) break;
session_deliver_sample_to_node(session, downstream_node, downstream_input, sample_entry->sample); - topo_node->u.transform.outputs[i].requests--; + stream->requests--;
transform_release_sample(sample_entry); } @@ -3342,14 +3355,16 @@ static HRESULT session_request_sample_from_node(struct media_session *session, I WARN("Sample request failed, hr %#lx.\n", hr); break; case MF_TOPOLOGY_TRANSFORM_NODE: + { + struct transform_stream *stream = &topo_node->u.transform.outputs[output];
- if (list_empty(&topo_node->u.transform.outputs[output].samples)) + if (list_empty(&stream->samples)) { /* Forward request to upstream node. */ if (SUCCEEDED(hr = IMFTopologyNode_GetInput(node, 0 /* FIXME */, &upstream_node, &upstream_output))) { if (SUCCEEDED(hr = session_request_sample_from_node(session, upstream_node, upstream_output))) - topo_node->u.transform.outputs[output].requests++; + stream->requests++; IMFTopologyNode_Release(upstream_node); } } @@ -3357,7 +3372,7 @@ static HRESULT session_request_sample_from_node(struct media_session *session, I { if (SUCCEEDED(hr = IMFTopologyNode_GetOutput(node, output, &downstream_node, &downstream_input))) { - sample = LIST_ENTRY(list_head(&topo_node->u.transform.outputs[output].samples), struct sample, entry); + sample = LIST_ENTRY(list_head(&stream->samples), struct sample, entry); session_deliver_sample_to_node(session, downstream_node, downstream_input, sample->sample); transform_release_sample(sample); IMFTopologyNode_Release(downstream_node); @@ -3365,6 +3380,7 @@ static HRESULT session_request_sample_from_node(struct media_session *session, I }
break; + } case MF_TOPOLOGY_TEE_NODE: FIXME("Unhandled upstream node type %d.\n", node_type); default:
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/mf/session.c | 98 +++++++++++++++++++---------------------------- 1 file changed, 39 insertions(+), 59 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index c629399d10e..254fea6dfe4 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -3151,12 +3151,47 @@ static HRESULT transform_node_pull_samples(const struct media_session *session, return hr; }
+static void session_deliver_sample_to_node(struct media_session *session, IMFTopologyNode *node, unsigned int input, + IMFSample *sample); + +static void transform_node_deliver_samples(struct media_session *session, struct topo_node *topo_node) +{ + IMFTopologyNode *up_node = topo_node->node, *down_node; + struct sample *sample, *next; + DWORD output, input; + HRESULT hr; + + /* Push down all available output. */ + for (output = 0; output < topo_node->u.transform.output_count; ++output) + { + struct transform_stream *stream = &topo_node->u.transform.outputs[output]; + + if (FAILED(hr = IMFTopologyNode_GetOutput(up_node, output, &down_node, &input))) + { + WARN("Failed to node %p/%lu output, hr %#lx.\n", up_node, output, hr); + continue; + } + + LIST_FOR_EACH_ENTRY_SAFE(sample, next, &stream->samples, struct sample, entry) + { + if (!stream->requests) + break; + + session_deliver_sample_to_node(session, down_node, input, sample->sample); + stream->requests--; + + transform_release_sample(sample); + } + + IMFTopologyNode_Release(down_node); + } +} + static void session_deliver_sample_to_node(struct media_session *session, IMFTopologyNode *node, unsigned int input, IMFSample *sample) { struct sample *sample_entry, *sample_entry2; - DWORD stream_id, downstream_input; - IMFTopologyNode *downstream_node; + DWORD stream_id; struct topo_node *topo_node; MF_TOPOLOGY_TYPE node_type; BOOL drain = FALSE; @@ -3244,32 +3279,7 @@ static void session_deliver_sample_to_node(struct media_session *session, IMFTop } }
- /* Push down all available output. */ - for (i = 0; i < topo_node->u.transform.output_count; ++i) - { - struct transform_stream *stream = &topo_node->u.transform.outputs[i]; - - if (FAILED(IMFTopologyNode_GetOutput(node, i, &downstream_node, &downstream_input))) - { - WARN("Failed to get connected node for output %u.\n", i); - continue; - } - - LIST_FOR_EACH_ENTRY_SAFE(sample_entry, sample_entry2, &stream->samples, - struct sample, entry) - { - if (!stream->requests) - break; - - session_deliver_sample_to_node(session, downstream_node, downstream_input, sample_entry->sample); - stream->requests--; - - transform_release_sample(sample_entry); - } - - IMFTopologyNode_Release(downstream_node); - } - + transform_node_deliver_samples(session, topo_node); break; } case MF_TOPOLOGY_TEE_NODE: @@ -3282,13 +3292,9 @@ static void session_deliver_sample_to_node(struct media_session *session, IMFTop
static void session_deliver_pending_samples(struct media_session *session, IMFTopologyNode *node) { - struct sample *sample_entry, *sample_entry2; - IMFTopologyNode *downstream_node; struct topo_node *topo_node; MF_TOPOLOGY_TYPE node_type; - DWORD downstream_input; TOPOID node_id; - unsigned int i;
IMFTopologyNode_GetNodeType(node, &node_type); IMFTopologyNode_GetTopoNodeID(node, &node_id); @@ -3298,34 +3304,8 @@ static void session_deliver_pending_samples(struct media_session *session, IMFTo switch (node_type) { case MF_TOPOLOGY_TRANSFORM_NODE: - transform_node_pull_samples(session, topo_node); - - /* Push down all available output. */ - for (i = 0; i < topo_node->u.transform.output_count; ++i) - { - struct transform_stream *stream = &topo_node->u.transform.outputs[i]; - - if (FAILED(IMFTopologyNode_GetOutput(node, i, &downstream_node, &downstream_input))) - { - WARN("Failed to get connected node for output %u.\n", i); - continue; - } - - LIST_FOR_EACH_ENTRY_SAFE(sample_entry, sample_entry2, &stream->samples, - struct sample, entry) - { - if (!stream->requests) - break; - - session_deliver_sample_to_node(session, downstream_node, downstream_input, sample_entry->sample); - stream->requests--; - - transform_release_sample(sample_entry); - } - - IMFTopologyNode_Release(downstream_node); - } + transform_node_deliver_samples(session, topo_node); break; default: FIXME("Unexpected node type %u.\n", node_type);
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/mf/session.c | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 254fea6dfe4..81198349f66 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -155,6 +155,7 @@ struct transform_stream struct list samples; unsigned int requests; unsigned int min_buffer_size; + BOOL stream_ended; };
enum topo_node_flags @@ -3151,11 +3152,23 @@ static HRESULT transform_node_pull_samples(const struct media_session *session, return hr; }
+static BOOL transform_node_all_streams_ended(struct topo_node *topo_node) +{ + UINT i; + + for (i = 0; i < topo_node->u.transform.input_count; i++) + if (!topo_node->u.transform.inputs[i].stream_ended) + return FALSE; + + return TRUE; +} + static void session_deliver_sample_to_node(struct media_session *session, IMFTopologyNode *node, unsigned int input, IMFSample *sample);
static void transform_node_deliver_samples(struct media_session *session, struct topo_node *topo_node) { + BOOL draining = transform_node_all_streams_ended(topo_node); IMFTopologyNode *up_node = topo_node->node, *down_node; struct sample *sample, *next; DWORD output, input; @@ -3183,6 +3196,12 @@ static void transform_node_deliver_samples(struct media_session *session, struct transform_release_sample(sample); }
+ while (stream->requests && draining) + { + session_deliver_sample_to_node(session, down_node, input, NULL); + stream->requests--; + } + IMFTopologyNode_Release(down_node); } } @@ -3194,7 +3213,6 @@ static void session_deliver_sample_to_node(struct media_session *session, IMFTop DWORD stream_id; struct topo_node *topo_node; MF_TOPOLOGY_TYPE node_type; - BOOL drain = FALSE; TOPOID node_id; unsigned int i; HRESULT hr; @@ -3242,7 +3260,9 @@ static void session_deliver_sample_to_node(struct media_session *session, IMFTop
LIST_FOR_EACH_ENTRY_SAFE(sample_entry, sample_entry2, &stream->samples, struct sample, entry) { - if (sample_entry->sample) + if ((stream->stream_ended = !sample_entry->sample)) + transform_stream_drop_samples(stream); + else { if ((hr = IMFTransform_ProcessInput(topo_node->object.transform, stream_id, sample_entry->sample, 0)) == MF_E_NOTACCEPTING) @@ -3251,34 +3271,16 @@ static void session_deliver_sample_to_node(struct media_session *session, IMFTop WARN("Failed to process input for stream %u/%lu, hr %#lx.\n", i, stream_id, hr); transform_release_sample(sample_entry); } - else - { - transform_stream_drop_samples(stream); - drain = TRUE; - } } }
- if (drain) + if (transform_node_all_streams_ended(topo_node)) { if (FAILED(hr = IMFTransform_ProcessMessage(topo_node->object.transform, MFT_MESSAGE_COMMAND_DRAIN, 0))) WARN("Drain command failed for transform, hr %#lx.\n", hr); }
transform_node_pull_samples(session, topo_node); - - /* Remaining unprocessed input has been discarded, now queue markers for every output. */ - if (drain) - { - for (i = 0; i < topo_node->u.transform.output_count; ++i) - { - struct transform_stream *stream = &topo_node->u.transform.outputs[i]; - - if ((sample_entry = transform_create_sample(NULL))) - list_add_tail(&stream->samples, &sample_entry->entry); - } - } - transform_node_deliver_samples(session, topo_node); break; }
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/mf/session.c | 80 ++++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 36 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 81198349f66..268a930e21d 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -681,12 +681,41 @@ static void transform_release_sample(struct sample *sample) free(sample); }
+static HRESULT transform_stream_push_sample(struct transform_stream *stream, IMFSample *sample) +{ + struct sample *entry; + + if (!(entry = calloc(1, sizeof(*entry)))) + return E_OUTOFMEMORY; + + entry->sample = sample; + IMFSample_AddRef(entry->sample); + + list_add_tail(&stream->samples, &entry->entry); + return S_OK; +} + +static HRESULT transform_stream_pop_sample(struct transform_stream *stream, IMFSample **sample) +{ + struct sample *entry; + struct list *ptr; + + if (!(ptr = list_head(&stream->samples))) + return E_FAIL; + + entry = LIST_ENTRY(ptr, struct sample, entry); + list_remove(&entry->entry); + *sample = entry->sample; + free(entry); + return S_OK; +} + static void transform_stream_drop_samples(struct transform_stream *stream) { - struct sample *sample, *sample2; + IMFSample *sample;
- LIST_FOR_EACH_ENTRY_SAFE(sample, sample2, &stream->samples, struct sample, entry) - transform_release_sample(sample); + while (SUCCEEDED(transform_stream_pop_sample(stream, &sample))) + IMFSample_Release(sample); }
static void release_topo_node(struct topo_node *node) @@ -3035,20 +3064,6 @@ static void session_set_sink_stream_state(struct media_session *session, IMFStre } }
-static struct sample *transform_create_sample(IMFSample *sample) -{ - struct sample *sample_entry = calloc(1, sizeof(*sample_entry)); - - if (sample_entry) - { - sample_entry->sample = sample; - if (sample_entry->sample) - IMFSample_AddRef(sample_entry->sample); - } - - return sample_entry; -} - static HRESULT transform_get_external_output_sample(const struct media_session *session, struct topo_node *transform, unsigned int output_index, const MFT_OUTPUT_STREAM_INFO *stream_info, IMFSample **sample) { @@ -3097,7 +3112,6 @@ static HRESULT transform_node_pull_samples(const struct media_session *session, { MFT_OUTPUT_STREAM_INFO stream_info; MFT_OUTPUT_DATA_BUFFER *buffers; - struct sample *queued_sample; HRESULT hr = E_UNEXPECTED; DWORD status = 0; unsigned int i; @@ -3138,9 +3152,8 @@ static HRESULT transform_node_pull_samples(const struct media_session *session, { if (session->quality_manager) IMFQualityManager_NotifyProcessOutput(session->quality_manager, node->node, i, buffers[i].pSample); - - queued_sample = transform_create_sample(buffers[i].pSample); - list_add_tail(&stream->samples, &queued_sample->entry); + if (FAILED(hr = transform_stream_push_sample(stream, buffers[i].pSample))) + WARN("Failed to queue output sample, hr %#lx\n", hr); }
if (buffers[i].pSample) @@ -3170,8 +3183,8 @@ static void transform_node_deliver_samples(struct media_session *session, struct { BOOL draining = transform_node_all_streams_ended(topo_node); IMFTopologyNode *up_node = topo_node->node, *down_node; - struct sample *sample, *next; DWORD output, input; + IMFSample *sample; HRESULT hr;
/* Push down all available output. */ @@ -3185,15 +3198,11 @@ static void transform_node_deliver_samples(struct media_session *session, struct continue; }
- LIST_FOR_EACH_ENTRY_SAFE(sample, next, &stream->samples, struct sample, entry) + while (stream->requests && SUCCEEDED(hr = transform_stream_pop_sample(stream, &sample))) { - if (!stream->requests) - break; - - session_deliver_sample_to_node(session, down_node, input, sample->sample); + session_deliver_sample_to_node(session, down_node, input, sample); stream->requests--; - - transform_release_sample(sample); + IMFSample_Release(sample); }
while (stream->requests && draining) @@ -3249,8 +3258,7 @@ static void session_deliver_sample_to_node(struct media_session *session, IMFTop
transform_node_pull_samples(session, topo_node);
- sample_entry = transform_create_sample(sample); - list_add_tail(&input_stream->samples, &sample_entry->entry); + transform_stream_push_sample(input_stream, sample);
for (i = 0; i < topo_node->u.transform.input_count; ++i) { @@ -3321,7 +3329,7 @@ static HRESULT session_request_sample_from_node(struct media_session *session, I DWORD downstream_input, upstream_output; struct topo_node *topo_node; MF_TOPOLOGY_TYPE node_type; - struct sample *sample; + IMFSample *sample; TOPOID node_id; HRESULT hr;
@@ -3340,7 +3348,7 @@ static HRESULT session_request_sample_from_node(struct media_session *session, I { struct transform_stream *stream = &topo_node->u.transform.outputs[output];
- if (list_empty(&stream->samples)) + if (FAILED(hr = transform_stream_pop_sample(stream, &sample))) { /* Forward request to upstream node. */ if (SUCCEEDED(hr = IMFTopologyNode_GetInput(node, 0 /* FIXME */, &upstream_node, &upstream_output))) @@ -3354,11 +3362,11 @@ static HRESULT session_request_sample_from_node(struct media_session *session, I { if (SUCCEEDED(hr = IMFTopologyNode_GetOutput(node, output, &downstream_node, &downstream_input))) { - sample = LIST_ENTRY(list_head(&stream->samples), struct sample, entry); - session_deliver_sample_to_node(session, downstream_node, downstream_input, sample->sample); - transform_release_sample(sample); + session_deliver_sample_to_node(session, downstream_node, downstream_input, sample); IMFTopologyNode_Release(downstream_node); } + + IMFSample_Release(sample); }
break;
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/mf/session.c | 71 ++++++++++++++++------------------------------- 1 file changed, 24 insertions(+), 47 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 268a930e21d..863ff81b29f 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -673,14 +673,6 @@ static void session_set_caps(struct media_session *session, DWORD caps) IMFMediaEvent_Release(event); }
-static void transform_release_sample(struct sample *sample) -{ - list_remove(&sample->entry); - if (sample->sample) - IMFSample_Release(sample->sample); - free(sample); -} - static HRESULT transform_stream_push_sample(struct transform_stream *stream, IMFSample *sample) { struct sample *entry; @@ -3176,6 +3168,29 @@ static BOOL transform_node_all_streams_ended(struct topo_node *topo_node) return TRUE; }
+static void transform_node_push_sample(const struct media_session *session, struct topo_node *topo_node, + UINT input, IMFSample *sample) +{ + struct transform_stream *stream = &topo_node->u.transform.inputs[input]; + HRESULT hr; + + if ((stream->stream_ended = !sample)) + { + if (!transform_node_all_streams_ended(topo_node)) + return; + + if (FAILED(hr = IMFTransform_ProcessMessage(topo_node->object.transform, MFT_MESSAGE_COMMAND_DRAIN, 0))) + WARN("Drain command failed for transform, hr %#lx.\n", hr); + } + else + { + UINT id = transform_node_get_stream_id(topo_node, FALSE, input); + if (FAILED(hr = IMFTransform_ProcessInput(topo_node->object.transform, id, sample, 0)) + && FAILED(hr = transform_stream_push_sample(stream, sample))) + WARN("Failed to queue delayed input sample, hr %#lx\n", hr); + } +} + static void session_deliver_sample_to_node(struct media_session *session, IMFTopologyNode *node, unsigned int input, IMFSample *sample);
@@ -3218,12 +3233,9 @@ static void transform_node_deliver_samples(struct media_session *session, struct static void session_deliver_sample_to_node(struct media_session *session, IMFTopologyNode *node, unsigned int input, IMFSample *sample) { - struct sample *sample_entry, *sample_entry2; - DWORD stream_id; struct topo_node *topo_node; MF_TOPOLOGY_TYPE node_type; TOPOID node_id; - unsigned int i; HRESULT hr;
if (session->quality_manager) @@ -3253,45 +3265,10 @@ static void session_deliver_sample_to_node(struct media_session *session, IMFTop } break; case MF_TOPOLOGY_TRANSFORM_NODE: - { - struct transform_stream *input_stream = &topo_node->u.transform.inputs[input]; - - transform_node_pull_samples(session, topo_node); - - transform_stream_push_sample(input_stream, sample); - - for (i = 0; i < topo_node->u.transform.input_count; ++i) - { - struct transform_stream *stream = &topo_node->u.transform.inputs[i]; - - stream_id = transform_node_get_stream_id(topo_node, FALSE, i); - - LIST_FOR_EACH_ENTRY_SAFE(sample_entry, sample_entry2, &stream->samples, struct sample, entry) - { - if ((stream->stream_ended = !sample_entry->sample)) - transform_stream_drop_samples(stream); - else - { - if ((hr = IMFTransform_ProcessInput(topo_node->object.transform, stream_id, - sample_entry->sample, 0)) == MF_E_NOTACCEPTING) - break; - if (FAILED(hr)) - WARN("Failed to process input for stream %u/%lu, hr %#lx.\n", i, stream_id, hr); - transform_release_sample(sample_entry); - } - } - } - - if (transform_node_all_streams_ended(topo_node)) - { - if (FAILED(hr = IMFTransform_ProcessMessage(topo_node->object.transform, MFT_MESSAGE_COMMAND_DRAIN, 0))) - WARN("Drain command failed for transform, hr %#lx.\n", hr); - } - + transform_node_push_sample(session, topo_node, input, sample); 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", node_type); break;
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/mf/session.c | 48 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 863ff81b29f..72d4a0824d5 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -200,6 +200,7 @@ struct topo_node struct transform_stream *inputs; DWORD *input_map; unsigned int input_count; + unsigned int next_input;
struct transform_stream *outputs; DWORD *output_map; @@ -693,7 +694,7 @@ static HRESULT transform_stream_pop_sample(struct transform_stream *stream, IMFS struct list *ptr;
if (!(ptr = list_head(&stream->samples))) - return E_FAIL; + return MF_E_TRANSFORM_NEED_MORE_INPUT;
entry = LIST_ENTRY(ptr, struct sample, entry); list_remove(&entry->entry); @@ -3168,6 +3169,17 @@ static BOOL transform_node_all_streams_ended(struct topo_node *topo_node) return TRUE; }
+static BOOL transform_node_has_requests(struct topo_node *topo_node) +{ + UINT i; + + for (i = 0; i < topo_node->u.transform.output_count; i++) + if (topo_node->u.transform.outputs[i].requests) + return TRUE; + + return FALSE; +} + static void transform_node_push_sample(const struct media_session *session, struct topo_node *topo_node, UINT input, IMFSample *sample) { @@ -3200,10 +3212,10 @@ static void transform_node_deliver_samples(struct media_session *session, struct IMFTopologyNode *up_node = topo_node->node, *down_node; DWORD output, input; IMFSample *sample; - HRESULT hr; + HRESULT hr = S_OK;
/* Push down all available output. */ - for (output = 0; output < topo_node->u.transform.output_count; ++output) + for (output = 0; SUCCEEDED(hr) && output < topo_node->u.transform.output_count; ++output) { struct transform_stream *stream = &topo_node->u.transform.outputs[output];
@@ -3213,8 +3225,17 @@ static void transform_node_deliver_samples(struct media_session *session, struct continue; }
- while (stream->requests && SUCCEEDED(hr = transform_stream_pop_sample(stream, &sample))) + while (stream->requests) { + if (FAILED(hr = transform_stream_pop_sample(stream, &sample))) + { + /* try getting more samples by calling IMFTransform_ProcessOutput */ + if (FAILED(hr = transform_node_pull_samples(session, topo_node))) + break; + if (FAILED(hr = transform_stream_pop_sample(stream, &sample))) + break; + } + session_deliver_sample_to_node(session, down_node, input, sample); stream->requests--; IMFSample_Release(sample); @@ -3228,6 +3249,25 @@ static void transform_node_deliver_samples(struct media_session *session, struct
IMFTopologyNode_Release(down_node); } + + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT && transform_node_has_requests(topo_node)) + { + struct transform_stream *stream; + + input = topo_node->u.transform.next_input++ % topo_node->u.transform.input_count; + 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); + 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 + { + 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); + IMFTopologyNode_Release(up_node); + } + } }
static void session_deliver_sample_to_node(struct media_session *session, IMFTopologyNode *node, unsigned int input,
From: Rémi Bernon rbernon@codeweavers.com
Better matching native behavior. --- dlls/mf/session.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 72d4a0824d5..16330674df2 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -3342,13 +3342,13 @@ static void session_deliver_pending_samples(struct media_session *session, IMFTo
static HRESULT session_request_sample_from_node(struct media_session *session, IMFTopologyNode *node, DWORD output) { - IMFTopologyNode *downstream_node, *upstream_node; - DWORD downstream_input, upstream_output; + IMFTopologyNode *down_node; struct topo_node *topo_node; MF_TOPOLOGY_TYPE node_type; + HRESULT hr = S_OK; IMFSample *sample; TOPOID node_id; - HRESULT hr; + DWORD input;
IMFTopologyNode_GetNodeType(node, &node_type); IMFTopologyNode_GetTopoNodeID(node, &node_id); @@ -3365,27 +3365,29 @@ static HRESULT session_request_sample_from_node(struct media_session *session, I { struct transform_stream *stream = &topo_node->u.transform.outputs[output];
- if (FAILED(hr = transform_stream_pop_sample(stream, &sample))) + if (FAILED(hr = IMFTopologyNode_GetOutput(node, output, &down_node, &input))) { - /* Forward request to upstream node. */ - if (SUCCEEDED(hr = IMFTopologyNode_GetInput(node, 0 /* FIXME */, &upstream_node, &upstream_output))) - { - if (SUCCEEDED(hr = session_request_sample_from_node(session, upstream_node, upstream_output))) - stream->requests++; - IMFTopologyNode_Release(upstream_node); - } + WARN("Failed to node %p/%lu output, hr %#lx.\n", node, output, hr); + break; } - else - { - if (SUCCEEDED(hr = IMFTopologyNode_GetOutput(node, output, &downstream_node, &downstream_input))) - { - session_deliver_sample_to_node(session, downstream_node, downstream_input, sample); - IMFTopologyNode_Release(downstream_node); - }
+ if (SUCCEEDED(transform_stream_pop_sample(stream, &sample))) + { + session_deliver_sample_to_node(session, down_node, input, sample); IMFSample_Release(sample); } + else if (transform_node_has_requests(topo_node)) + { + /* there's already requests pending, just increase the counter */ + stream->requests++; + } + else + { + stream->requests++; + transform_node_deliver_samples(session, topo_node); + }
+ IMFTopologyNode_Release(down_node); break; } case MF_TOPOLOGY_TEE_NODE:
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/mf/tests/mf.c | 767 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 767 insertions(+)
diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index fa538e8ad9f..a8d9807693c 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -7050,6 +7050,772 @@ static void test_mpeg4_media_sink(void) IMFMediaType_Release(audio_type); }
+struct test_transform +{ + IMFTransform IMFTransform_iface; + LONG refcount; + + CRITICAL_SECTION cs; + CONDITION_VARIABLE results_cond; + BOOL shutdown; + + UINT input_count; + IMFMediaType *available_input_types[4]; + IMFMediaType *input_types[4]; + IMFSample *input_samples[4]; + HRESULT input_results[4]; + HANDLE process_input_event[4]; + + UINT output_count; + IMFMediaType *available_output_types[4]; + IMFMediaType *output_types[4]; + IMFSample *output_samples[4]; + HRESULT output_results[4]; + HANDLE process_output_event[4]; +}; + +static HRESULT WINAPI test_transform_QueryInterface(IMFTransform *iface, REFIID iid, void **out) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + + if (IsEqualGUID(iid, &IID_IUnknown) + || IsEqualGUID(iid, &IID_IMFTransform)) + *out = &impl->IMFTransform_iface; + else + { + *out = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)*out); + return S_OK; +} + +static ULONG WINAPI test_transform_AddRef(IMFTransform *iface) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + ULONG refcount = InterlockedIncrement(&impl->refcount); + return refcount; +} + +static ULONG WINAPI test_transform_Release(IMFTransform *iface) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + ULONG refcount = InterlockedDecrement(&impl->refcount); + UINT i; + + if (!refcount) + { + for (i = 0; i < impl->input_count; i++) + CloseHandle(impl->process_input_event[i]); + for (i = 0; i < impl->output_count; i++) + CloseHandle(impl->process_output_event[i]); + DeleteCriticalSection(&impl->cs); + free(impl); + } + + return refcount; +} + +static HRESULT WINAPI test_transform_GetStreamLimits(IMFTransform *iface, DWORD *input_minimum, + DWORD *input_maximum, DWORD *output_minimum, DWORD *output_maximum) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI test_transform_GetStreamCount(IMFTransform *iface, DWORD *inputs, DWORD *outputs) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + *inputs = impl->input_count; + *outputs = impl->output_count; + return S_OK; +} + +static HRESULT WINAPI test_transform_GetStreamIDs(IMFTransform *iface, DWORD input_size, DWORD *inputs, + DWORD output_size, DWORD *outputs) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + UINT i; + + for (i = 0; i < impl->input_count; ++i) + inputs[i] = i; + for (i = 0; i < impl->output_count; ++i) + outputs[i] = i; + + return S_OK; +} + +static HRESULT WINAPI test_transform_GetInputStreamInfo(IMFTransform *iface, DWORD id, MFT_INPUT_STREAM_INFO *info) +{ + info->cbAlignment = 0; + info->cbSize = 0x1000; + info->dwFlags = 0; + info->cbMaxLookahead = 0; + info->hnsMaxLatency = 0; + return S_OK; +} + +static HRESULT WINAPI test_transform_GetOutputStreamInfo(IMFTransform *iface, DWORD id, MFT_OUTPUT_STREAM_INFO *info) +{ + info->cbAlignment = 0; + info->cbSize = 0x1000; + info->dwFlags = MFT_OUTPUT_STREAM_PROVIDES_SAMPLES; + return S_OK; +} + +static HRESULT WINAPI test_transform_GetAttributes(IMFTransform *iface, IMFAttributes **attributes) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI test_transform_GetInputStreamAttributes(IMFTransform *iface, DWORD id, IMFAttributes **attributes) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI test_transform_GetOutputStreamAttributes(IMFTransform *iface, DWORD id, IMFAttributes **attributes) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI test_transform_DeleteInputStream(IMFTransform *iface, DWORD id) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI test_transform_AddInputStreams(IMFTransform *iface, DWORD streams, DWORD *ids) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI test_transform_GetInputAvailableType(IMFTransform *iface, DWORD id, DWORD index, + IMFMediaType **type) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + HRESULT hr; + + EnterCriticalSection(&impl->cs); + if (impl->shutdown) + hr = MF_E_SHUTDOWN; + else if (index >= impl->input_count) + hr = MF_E_NO_MORE_TYPES; + else + { + IMFMediaType_AddRef((*type = impl->available_input_types[index])); + hr = S_OK; + } + LeaveCriticalSection(&impl->cs); + return hr; +} + +static HRESULT WINAPI test_transform_GetOutputAvailableType(IMFTransform *iface, DWORD id, + DWORD index, IMFMediaType **type) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + HRESULT hr; + + EnterCriticalSection(&impl->cs); + if (impl->shutdown) + hr = MF_E_SHUTDOWN; + else if (index >= impl->output_count) + hr = MF_E_NO_MORE_TYPES; + else + { + IMFMediaType_AddRef((*type = impl->available_output_types[index])); + hr = S_OK; + } + LeaveCriticalSection(&impl->cs); + return hr; +} + +static HRESULT WINAPI test_transform_SetInputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + HRESULT hr; + + EnterCriticalSection(&impl->cs); + if (impl->shutdown) + hr = MF_E_SHUTDOWN; + else if (id >= impl->input_count) + hr = MF_E_INVALIDSTREAMNUMBER; + else + { + IMFMediaType_AddRef((impl->input_types[id] = type)); + hr = S_OK; + } + LeaveCriticalSection(&impl->cs); + return hr; +} + +static HRESULT WINAPI test_transform_SetOutputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + HRESULT hr; + + EnterCriticalSection(&impl->cs); + if (impl->shutdown) + hr = MF_E_SHUTDOWN; + else if (id >= impl->output_count) + hr = MF_E_INVALIDSTREAMNUMBER; + else + { + IMFMediaType_AddRef((impl->output_types[id] = type)); + hr = S_OK; + } + LeaveCriticalSection(&impl->cs); + return hr; +} + +static HRESULT WINAPI test_transform_GetInputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **type) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + HRESULT hr; + + EnterCriticalSection(&impl->cs); + if (impl->shutdown) + hr = MF_E_SHUTDOWN; + else if (id >= impl->input_count) + hr = MF_E_INVALIDSTREAMNUMBER; + else if (!impl->input_types[id]) + hr = MF_E_TRANSFORM_TYPE_NOT_SET; + else + { + IMFMediaType_AddRef((*type = impl->input_types[id])); + hr = S_OK; + } + LeaveCriticalSection(&impl->cs); + return hr; +} + +static HRESULT WINAPI test_transform_GetOutputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **type) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + HRESULT hr; + + EnterCriticalSection(&impl->cs); + if (impl->shutdown) + hr = MF_E_SHUTDOWN; + else if (id >= impl->output_count) + hr = MF_E_INVALIDSTREAMNUMBER; + else if (!impl->output_types[id]) + hr = MF_E_TRANSFORM_TYPE_NOT_SET; + else + { + IMFMediaType_AddRef((*type = impl->output_types[id])); + hr = S_OK; + } + LeaveCriticalSection(&impl->cs); + return hr; +} + +static HRESULT WINAPI test_transform_GetInputStatus(IMFTransform *iface, DWORD id, DWORD *flags) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI test_transform_GetOutputStatus(IMFTransform *iface, DWORD *flags) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI test_transform_SetOutputBounds(IMFTransform *iface, LONGLONG lower, LONGLONG upper) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI test_transform_ProcessEvent(IMFTransform *iface, DWORD id, IMFMediaEvent *event) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI test_transform_ProcessMessage(IMFTransform *iface, MFT_MESSAGE_TYPE message, ULONG_PTR param) +{ + return S_OK; +} + +static HRESULT WINAPI test_transform_ProcessInput(IMFTransform *iface, DWORD id, IMFSample *sample, DWORD flags) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + IMFSample *previous; + HRESULT hr; + + EnterCriticalSection(&impl->cs); + + if (impl->shutdown) + hr = MF_E_SHUTDOWN; + else + { + if ((previous = impl->input_samples[id])) + { + /* samples should be received again after MF_E_NOTACCEPTING */ + if (impl->input_results[id] == MF_E_NOTACCEPTING) + ok(previous == sample || broken(previous != sample && !has_video_processor) /* w7 */, + "got different sample\n"); + IMFSample_Release(previous); + } + + impl->input_results[id] = E_PENDING; + IMFSample_AddRef((impl->input_samples[id] = sample)); + SetEvent(impl->process_input_event[id]); + + while ((hr = impl->input_results[id]) == E_PENDING && !impl->shutdown) + SleepConditionVariableCS(&impl->results_cond, &impl->cs, INFINITE); + } + + LeaveCriticalSection(&impl->cs); + return hr; +} + +static HRESULT WINAPI test_transform_ProcessOutput(IMFTransform *iface, DWORD flags, DWORD count, + MFT_OUTPUT_DATA_BUFFER *samples, DWORD *status) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + HRESULT hr, result; + UINT i; + + EnterCriticalSection(&impl->cs); + + if (impl->shutdown) + hr = MF_E_SHUTDOWN; + else + { + for (i = 0; i < count; ++i) + { + UINT id = samples[i].dwStreamID; + ok(!samples[i].pEvents, "got pEvents\n"); + ok(!samples[i].pSample, "got pSample\n"); + ok(!samples[i].dwStatus, "got dwStatus\n"); + ok(id == i, "got dwStreamID %#lx\n", samples[i].dwStreamID); + + impl->output_results[id] = E_PENDING; + SetEvent(impl->process_output_event[id]); + } + + for (i = 0, hr = MF_E_TRANSFORM_NEED_MORE_INPUT; i < count; ++i) + { + UINT id = samples[i].dwStreamID; + while ((result = impl->output_results[id]) == E_PENDING && !impl->shutdown) + SleepConditionVariableCS(&impl->results_cond, &impl->cs, INFINITE); + samples[i].pSample = impl->output_samples[id]; + impl->output_samples[id] = NULL; + if (FAILED(result)) + samples[i].dwStatus = MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE; + else + hr = S_OK; + } + + *status = 0; + } + + LeaveCriticalSection(&impl->cs); + return hr; +} + +static const IMFTransformVtbl test_transform_vtbl = +{ + test_transform_QueryInterface, + test_transform_AddRef, + test_transform_Release, + test_transform_GetStreamLimits, + test_transform_GetStreamCount, + test_transform_GetStreamIDs, + test_transform_GetInputStreamInfo, + test_transform_GetOutputStreamInfo, + test_transform_GetAttributes, + test_transform_GetInputStreamAttributes, + test_transform_GetOutputStreamAttributes, + test_transform_DeleteInputStream, + test_transform_AddInputStreams, + test_transform_GetInputAvailableType, + test_transform_GetOutputAvailableType, + test_transform_SetInputType, + test_transform_SetOutputType, + test_transform_GetInputCurrentType, + test_transform_GetOutputCurrentType, + test_transform_GetInputStatus, + test_transform_GetOutputStatus, + test_transform_SetOutputBounds, + test_transform_ProcessEvent, + test_transform_ProcessMessage, + test_transform_ProcessInput, + test_transform_ProcessOutput, +}; + +HRESULT test_transform_create(UINT input_count, IMFMediaType **input_types, + UINT output_count, IMFMediaType **output_types, IMFTransform **out) +{ + struct test_transform *impl; + UINT i; + + if (!(impl = calloc(1, sizeof(*impl)))) + return E_OUTOFMEMORY; + impl->IMFTransform_iface.lpVtbl = &test_transform_vtbl; + impl->refcount = 1; + + impl->input_count = input_count; + impl->output_count = output_count; + + for (i = 0; i < input_count; i++) + { + IMFMediaType_AddRef((impl->available_input_types[i] = input_types[i])); + impl->process_input_event[i] = CreateEventW(NULL, FALSE, FALSE, NULL); + } + for (i = 0; i < output_count; i++) + { + IMFMediaType_AddRef((impl->available_output_types[i] = output_types[i])); + impl->process_output_event[i] = CreateEventW(NULL, FALSE, FALSE, NULL); + } + + InitializeCriticalSection(&impl->cs); + + *out = &impl->IMFTransform_iface; + return S_OK; +} + +static DWORD test_transform_wait_process_input(IMFTransform *iface, DWORD timeout) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + return WaitForMultipleObjects(impl->input_count, impl->process_input_event, FALSE, timeout); +} + +static void test_transform_get_input(IMFTransform *iface, UINT index, IMFSample **sample) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + + EnterCriticalSection(&impl->cs); + impl->input_results[index] = sample ? S_OK : MF_E_NOTACCEPTING; + if (sample) + { + *sample = impl->input_samples[index]; + impl->input_samples[index] = NULL; + } + LeaveCriticalSection(&impl->cs); + WakeAllConditionVariable(&impl->results_cond); +} + +static DWORD test_transform_wait_process_output(IMFTransform *iface, DWORD timeout) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + return WaitForMultipleObjects(impl->output_count, impl->process_output_event, FALSE, timeout); +} + +static void test_transform_set_output(IMFTransform *iface, UINT index, IMFSample *sample) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + + EnterCriticalSection(&impl->cs); + impl->output_results[index] = sample ? S_OK : MF_E_TRANSFORM_NEED_MORE_INPUT; + if ((impl->output_samples[index] = sample)) + IMFSample_AddRef(sample); + LeaveCriticalSection(&impl->cs); + WakeAllConditionVariable(&impl->results_cond); +} + +static void test_transform_shutdown(IMFTransform *iface) +{ + struct test_transform *impl = CONTAINING_RECORD(iface, struct test_transform, IMFTransform_iface); + UINT i; + + EnterCriticalSection(&impl->cs); + impl->shutdown = TRUE; + for (i = 0; i < impl->input_count; i++) + { + impl->input_results[i] = MF_E_SHUTDOWN; + if (impl->input_samples[i]) + IMFSample_Release(impl->input_samples[i]); + impl->input_samples[i] = NULL; + } + for (i = 0; i < impl->output_count; i++) + { + impl->output_results[i] = MF_E_SHUTDOWN; + if (impl->output_samples[i]) + IMFSample_Release(impl->output_samples[i]); + impl->output_samples[i] = NULL; + } + LeaveCriticalSection(&impl->cs); + WakeAllConditionVariable(&impl->results_cond); +} + +static IMFTopologyNode *test_transform_create_source_node(IMFTopology *topology, + IMFMediaSource *source, UINT index, IMFMediaType **media_type) +{ + IMFPresentationDescriptor *pd; + IMFMediaTypeHandler *handler; + IMFStreamDescriptor *sd; + IMFTopologyNode *node; + BOOL selected; + HRESULT hr; + + /* create source node */ + hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &node); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFTopology_AddNode(topology, node); + ok(hr == S_OK, "got hr %#lx\n", hr); + + hr = IMFMediaSource_CreatePresentationDescriptor(source, &pd); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFPresentationDescriptor_SelectStream(pd, index); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFPresentationDescriptor_GetStreamDescriptorByIndex(pd, index, &selected, &sd); + ok(hr == S_OK, "got hr %#lx\n", hr); + + hr = IMFStreamDescriptor_GetMediaTypeHandler(sd, &handler); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFMediaTypeHandler_GetMediaTypeByIndex(handler, 0, media_type); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFMediaTypeHandler_Release(handler); + + hr = IMFTopologyNode_SetUnknown(node, &MF_TOPONODE_SOURCE, (IUnknown *)source); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFTopologyNode_SetUnknown(node, &MF_TOPONODE_STREAM_DESCRIPTOR, (IUnknown *)sd); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFStreamDescriptor_Release(sd); + hr = IMFTopologyNode_SetUnknown(node, &MF_TOPONODE_PRESENTATION_DESCRIPTOR, (IUnknown *)pd); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFPresentationDescriptor_Release(pd); + + return node; +} + +static IMFTopologyNode *test_transform_create_transform_node(IMFTopology *topology, + UINT input_count, IMFMediaType **input_types, UINT output_count, IMFMediaType **output_types, + IMFTransform **transform) +{ + IMFTopologyNode *node; + HRESULT hr; + + hr = MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &node); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFTopology_AddNode(topology, node); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = test_transform_create(input_count, input_types, output_count, output_types, transform); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFTopologyNode_SetObject(node, (IUnknown *)*transform); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFTopologyNode_SetUINT32(node, &MF_TOPONODE_CONNECT_METHOD, MF_CONNECT_DIRECT); + ok(hr == S_OK, "got hr %#lx\n", hr); + + return node; +} + +static IMFTopologyNode *test_transform_create_output_node(IMFTopology *topology, + IMFMediaType *media_type, IMFSampleGrabberSinkCallback *callback) +{ + IMFStreamSink *stream_sink; + IMFMediaSink *media_sink; + IMFTopologyNode *node; + IMFActivate *activate; + HRESULT hr; + + /* create output node */ + hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &node); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFTopology_AddNode(topology, node); + ok(hr == S_OK, "got hr %#lx\n", hr); + + hr = MFCreateSampleGrabberSinkActivate(media_type, callback, &activate); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFActivate_SetUINT32(activate, &MF_SAMPLEGRABBERSINK_IGNORE_CLOCK, 1); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFActivate_ActivateObject(activate, &IID_IMFMediaSink, (void **)&media_sink); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFActivate_Release(activate); + + hr = IMFMediaSink_GetStreamSinkByIndex(media_sink, 0, &stream_sink); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFMediaSink_Release(media_sink); + hr = IMFTopologyNode_SetObject(node, (IUnknown *)stream_sink); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFStreamSink_Release(stream_sink); + hr = IMFTopologyNode_SetUINT32(node, &MF_TOPONODE_CONNECT_METHOD, MF_CONNECT_DIRECT); + ok(hr == S_OK, "got hr %#lx\n", hr); + + return node; +} + +static void test_media_session_transform_simple(void) +{ + IMFAsyncCallback *media_event_callback; + IMFSampleGrabberSinkCallback *grabber_callback; + IMFTopologyNode *up_node, *node; + IMFMediaType *media_type; + IMFMediaSession *session; + LONGLONG time, duration; + IMFTransform *transform; + IMFMediaSource *source; + UINT32 i; + IMFTopology *topology; + PROPVARIANT propvar; + IMFSample *sample; + HRESULT hr; + DWORD res; + + hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + ok(hr == S_OK, "got 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"); + goto done; + } + + grabber_callback = create_test_grabber_callback(); + media_event_callback = create_test_callback(TRUE); + + hr = MFCreateMediaSession(NULL, &session); + ok(hr == S_OK, "got hr %#lx\n", hr); + + + /* create the topology with 1 source -> MFT -> 1 output */ + + hr = MFCreateTopology(&topology); + ok(hr == S_OK, "got hr %#lx\n", hr); + + up_node = test_transform_create_source_node(topology, source, 0, &media_type); + + node = test_transform_create_transform_node(topology, 1, &media_type, 1, &media_type, &transform); + hr = IMFTopologyNode_ConnectOutput(up_node, 0, node, 0); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFTopologyNode_Release(up_node); + up_node = node; + + node = test_transform_create_output_node(topology, media_type, grabber_callback); + IMFMediaType_Release(media_type); + hr = IMFTopologyNode_ConnectOutput(up_node, 0, node, 0); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFTopologyNode_Release(up_node); + IMFTopologyNode_Release(node); + IMFMediaSource_Release(source); + + + hr = IMFMediaSession_SetTopology(session, 0, topology); + ok(hr == S_OK, "got hr %#lx\n", hr); + + hr = wait_media_event(session, media_event_callback, MESessionTopologySet, 1000, &propvar); + ok(hr == S_OK, "got hr %#lx\n", hr); + ok(propvar.vt == VT_UNKNOWN, "got vt %u\n", propvar.vt); + ok(propvar.punkVal != (IUnknown *)topology, "got punkVal %p\n", propvar.punkVal); + IMFTopology_Release(topology); + IMFTopology_AddRef((topology = (IMFTopology *)propvar.punkVal)); + PropVariantClear(&propvar); + + hr = wait_media_event(session, media_event_callback, MESessionTopologyStatus, 1000, &propvar); + ok(hr == S_OK, "got hr %#lx\n", hr); + ok(propvar.vt == VT_UNKNOWN, "got vt %u\n", propvar.vt); + ok(propvar.punkVal == (IUnknown *)topology, "got punkVal %p\n", propvar.punkVal); + PropVariantClear(&propvar); + + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "got hr %#lx\n", hr); + + + /* starts with a ProcessOutput call */ + + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + + /* ProcessOutput call loops until it fails */ + + for (i = 0, time = 0, duration = 1000; i < 20; i++) + { + IMFMediaBuffer *buffer; + + winetest_push_context("%u", i); + + hr = MFCreateSample(&sample); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = MFCreateMemoryBuffer(0x1000, &buffer); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFSample_AddBuffer(sample, buffer); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFMediaBuffer_Release(buffer); + + hr = IMFSample_SetSampleTime(sample, time + duration); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFSample_SetSampleDuration(sample, duration); + ok(hr == S_OK, "got hr %#lx\n", hr); + + test_transform_set_output(transform, 0, sample); + + IMFSample_Release(sample); + time += duration; + + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + + winetest_pop_context(); + } + + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + + for (i = 0; i < 10; i++) + { + winetest_push_context("%u", i); + + /* a single ProcessInput call */ + res = test_transform_wait_process_input(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + /* take the sample and return S_OK */ + test_transform_get_input(transform, 0, &sample); + + /* ProcessOutput call loop */ + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + test_transform_set_output(transform, 0, sample); + + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + + IMFSample_Release(sample); + + + /* returning MF_E_NOTACCEPTING doesn't change the pattern */ + res = test_transform_wait_process_input(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + /* release the sample and return MF_E_NOTACCEPTING */ + test_transform_get_input(transform, 0, NULL); + + /* ProcessOutput call loop */ + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + /* needs to fail at least twice */ + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + + winetest_pop_context(); + } + + test_transform_shutdown(transform); + + IMFTransform_Release(transform); + hr = IMFMediaSession_Shutdown(session); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFMediaSession_Release(session); + + IMFAsyncCallback_Release(media_event_callback); + +done: + hr = MFShutdown(); + ok(hr == S_OK, "got hr %#lx\n", hr); +} + START_TEST(mf) { init_functions(); @@ -7084,4 +7850,5 @@ START_TEST(mf) test_MFGetTopoNodeCurrentType(); test_MFRequireProtectedEnvironment(); test_mpeg4_media_sink(); + test_media_session_transform_simple(); }
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/mf/tests/mf.c | 208 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+)
diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index a8d9807693c..51fdc19b3ad 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -7816,6 +7816,213 @@ done: ok(hr == S_OK, "got hr %#lx\n", hr); }
+static void test_media_session_transform_multiple_outputs(void) +{ + IMFAsyncCallback *media_event_callback; + IMFSampleGrabberSinkCallback *grabber_callback; + IMFTopologyNode *up_node, *node; + IMFMediaType *media_types[2]; + IMFMediaSession *session; + LONGLONG time, duration; + IMFTransform *transform; + IMFMediaSource *source; + UINT32 i; + IMFTopology *topology; + PROPVARIANT propvar; + IMFSample *sample; + HRESULT hr; + DWORD res; + + hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + ok(hr == S_OK, "got 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"); + goto done; + } + + grabber_callback = create_test_grabber_callback(); + media_event_callback = create_test_callback(TRUE); + + hr = MFCreateMediaSession(NULL, &session); + ok(hr == S_OK, "got hr %#lx\n", hr); + + + /* create the topology with 1 source -> MFT -> 2 output */ + hr = MFCreateTopology(&topology); + ok(hr == S_OK, "got hr %#lx\n", hr); + + up_node = test_transform_create_source_node(topology, source, 0, &media_types[0]); + IMFMediaType_AddRef((media_types[1] = media_types[0])); + + node = test_transform_create_transform_node(topology, 1, media_types, 2, media_types, &transform); + hr = IMFTopologyNode_ConnectOutput(up_node, 0, node, 0); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFTopologyNode_Release(up_node); + up_node = node; + + node = test_transform_create_output_node(topology, media_types[0], grabber_callback); + IMFMediaType_Release(media_types[0]); + hr = IMFTopologyNode_ConnectOutput(up_node, 0, node, 0); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFTopologyNode_Release(node); + + node = test_transform_create_output_node(topology, media_types[1], grabber_callback); + IMFMediaType_Release(media_types[1]); + hr = IMFTopologyNode_ConnectOutput(up_node, 1, node, 0); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFTopologyNode_Release(node); + + IMFTopologyNode_Release(up_node); + IMFMediaSource_Release(source); + + + hr = IMFMediaSession_SetTopology(session, 0, topology); + ok(hr == S_OK, "got hr %#lx\n", hr); + + hr = wait_media_event(session, media_event_callback, MESessionTopologySet, 1000, &propvar); + ok(hr == S_OK, "got hr %#lx\n", hr); + ok(propvar.vt == VT_UNKNOWN, "got vt %u\n", propvar.vt); + ok(propvar.punkVal != (IUnknown *)topology, "got punkVal %p\n", propvar.punkVal); + IMFTopology_Release(topology); + IMFTopology_AddRef((topology = (IMFTopology *)propvar.punkVal)); + PropVariantClear(&propvar); + + hr = wait_media_event(session, media_event_callback, MESessionTopologyStatus, 1000, &propvar); + ok(hr == S_OK, "got hr %#lx\n", hr); + ok(propvar.vt == VT_UNKNOWN, "got vt %u\n", propvar.vt); + ok(propvar.punkVal == (IUnknown *)topology, "got punkVal %p\n", propvar.punkVal); + PropVariantClear(&propvar); + + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "got hr %#lx\n", hr); + + + /* starts with a ProcessOutput call for every stream */ + + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 1000); + ok(res == 1, "got res %#lx\n", res); + + /* ProcessOutput call loops until it fails */ + + for (i = 0, time = 0, duration = 1000; i < 20; i++) + { + IMFMediaBuffer *buffer; + + winetest_push_context("%u", i); + + hr = MFCreateSample(&sample); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = MFCreateMemoryBuffer(0x1000, &buffer); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFSample_AddBuffer(sample, buffer); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFMediaBuffer_Release(buffer); + + hr = IMFSample_SetSampleTime(sample, time + duration); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFSample_SetSampleDuration(sample, duration); + ok(hr == S_OK, "got hr %#lx\n", hr); + + test_transform_set_output(transform, 0, sample); + test_transform_set_output(transform, 1, sample); + IMFSample_Release(sample); + time += duration; + + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 1000); + ok(res == 1, "got res %#lx\n", res); + + winetest_pop_context(); + } + + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + test_transform_set_output(transform, 1, NULL); + + for (i = 0; i < 10; i++) + { + winetest_push_context("%u", i); + + /* a single ProcessInput call */ + res = test_transform_wait_process_input(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + /* take the sample and return S_OK */ + test_transform_get_input(transform, 0, &sample); + + /* ProcessOutput call loop */ + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 1000); + ok(res == 1, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + test_transform_set_output(transform, 1, sample); + + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 1000); + ok(res == 1, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + test_transform_set_output(transform, 1, NULL); + + IMFSample_Release(sample); + + + /* returning MF_E_NOTACCEPTING doesn't change the pattern */ + res = test_transform_wait_process_input(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + /* release the sample and return MF_E_NOTACCEPTING */ + test_transform_get_input(transform, 0, NULL); + + /* ProcessOutput call loop */ + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 1000); + ok(res == 1, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + test_transform_set_output(transform, 1, NULL); + /* needs to fail at least twice */ + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 1000); + ok(res == 1, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + test_transform_set_output(transform, 1, NULL); + + winetest_pop_context(); + } + + test_transform_shutdown(transform); + + IMFTransform_Release(transform); + hr = IMFMediaSession_Shutdown(session); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFMediaSession_Release(session); + + IMFAsyncCallback_Release(media_event_callback); + +done: + hr = MFShutdown(); + ok(hr == S_OK, "got hr %#lx\n", hr); +} + START_TEST(mf) { init_functions(); @@ -7851,4 +8058,5 @@ START_TEST(mf) test_MFRequireProtectedEnvironment(); test_mpeg4_media_sink(); test_media_session_transform_simple(); + test_media_session_transform_multiple_outputs(); }
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/mf/tests/mf.c | 190 +++++++++++++++++++++++++++++ dlls/mf/tests/multiple-streams.mp4 | Bin 0 -> 20789 bytes dlls/mf/tests/resource.rc | 15 +++ 3 files changed, 205 insertions(+) create mode 100644 dlls/mf/tests/multiple-streams.mp4
diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 51fdc19b3ad..32ff76154ea 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -8023,6 +8023,195 @@ done: ok(hr == S_OK, "got hr %#lx\n", hr); }
+static void test_media_session_transform_multiple_sources(void) +{ + IMFAsyncCallback *media_event_callback; + IMFSampleGrabberSinkCallback *grabber_callback; + IMFTopologyNode *up_nodes[2], *node; + IMFMediaType *media_types[2]; + UINT32 i; + IMFMediaSession *session; + LONGLONG time, duration; + IMFTransform *transform; + IMFMediaSource *source; + IMFTopology *topology; + PROPVARIANT propvar; + DWORD res, stream; + IMFSample *sample; + HRESULT hr; + + hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + ok(hr == S_OK, "got hr %#lx\n", hr); + + if (!(source = create_media_source(L"multiple-streams.mp4", L"video/mp4"))) + { + win_skip("MP4 media source is not supported, skipping tests.\n"); + goto done; + } + + grabber_callback = create_test_grabber_callback(); + media_event_callback = create_test_callback(TRUE); + + hr = MFCreateMediaSession(NULL, &session); + ok(hr == S_OK, "got hr %#lx\n", hr); + + + /* create the topology with 2 sources -> MFT -> 1 output */ + hr = MFCreateTopology(&topology); + ok(hr == S_OK, "got hr %#lx\n", hr); + + up_nodes[0] = test_transform_create_source_node(topology, source, 0, &media_types[0]); + up_nodes[1] = test_transform_create_source_node(topology, source, 1, &media_types[1]); + + node = test_transform_create_transform_node(topology, 2, media_types, 1, media_types, &transform); + hr = IMFTopologyNode_ConnectOutput(up_nodes[0], 0, node, 0); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFTopologyNode_Release(up_nodes[0]); + hr = IMFTopologyNode_ConnectOutput(up_nodes[1], 0, node, 1); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFTopologyNode_Release(up_nodes[1]); + up_nodes[0] = node; + + node = test_transform_create_output_node(topology, media_types[0], grabber_callback); + IMFMediaType_Release(media_types[0]); + hr = IMFTopologyNode_ConnectOutput(up_nodes[0], 0, node, 0); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFTopologyNode_Release(up_nodes[0]); + IMFTopologyNode_Release(node); + IMFMediaSource_Release(source); + + + hr = IMFMediaSession_SetTopology(session, 0, topology); + ok(hr == S_OK, "got hr %#lx\n", hr); + + hr = wait_media_event(session, media_event_callback, MESessionTopologySet, 1000, &propvar); + ok(hr == S_OK, "got hr %#lx\n", hr); + ok(propvar.vt == VT_UNKNOWN, "got vt %u\n", propvar.vt); + ok(propvar.punkVal != (IUnknown *)topology, "got punkVal %p\n", propvar.punkVal); + IMFTopology_Release(topology); + IMFTopology_AddRef((topology = (IMFTopology *)propvar.punkVal)); + PropVariantClear(&propvar); + + hr = wait_media_event(session, media_event_callback, MESessionTopologyStatus, 1000, &propvar); + ok(hr == S_OK, "got hr %#lx\n", hr); + ok(propvar.vt == VT_UNKNOWN, "got vt %u\n", propvar.vt); + ok(propvar.punkVal == (IUnknown *)topology, "got punkVal %p\n", propvar.punkVal); + PropVariantClear(&propvar); + + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "got hr %#lx\n", hr); + + + /* starts with a ProcessOutput call */ + + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + + /* ProcessOutput call loops until it fails */ + + for (i = 0, time = 0, duration = 1000; i < 20; i++) + { + IMFMediaBuffer *buffer; + + winetest_push_context("%u", i); + + hr = MFCreateSample(&sample); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = MFCreateMemoryBuffer(0x1000, &buffer); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFSample_AddBuffer(sample, buffer); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFMediaBuffer_Release(buffer); + + hr = IMFSample_SetSampleTime(sample, time + duration); + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFSample_SetSampleDuration(sample, duration); + ok(hr == S_OK, "got hr %#lx\n", hr); + + test_transform_set_output(transform, 0, sample); + + IMFSample_Release(sample); + time += duration; + + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + + winetest_pop_context(); + } + + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + + for (i = 0; i < 10; i++) + { + winetest_push_context("%u", i); + + /* a single ProcessInput call for stream 0 or 1, depending on the media source */ + res = test_transform_wait_process_input(transform, 1000); + ok(res == 0 || res == 1, "got res %#lx\n", res); + stream = res; + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + /* take the sample and return S_OK */ + test_transform_get_input(transform, stream, &sample); + + /* ProcessOutput call loop */ + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + test_transform_set_output(transform, 0, sample); + + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + + IMFSample_Release(sample); + + + /* a single ProcessInput call for stream 0 or 1, depending on the media source */ + res = test_transform_wait_process_input(transform, 1000); + ok(res == 0 || res == 1, "got res %#lx\n", res); + stream = res; + res = test_transform_wait_process_input(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + res = test_transform_wait_process_output(transform, 0); + ok(res == WAIT_TIMEOUT, "got res %#lx\n", res); + /* release the sample and return MF_E_NOTACCEPTING */ + test_transform_get_input(transform, stream, NULL); + + /* ProcessOutput call loop */ + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + + /* needs to fail at least twice */ + res = test_transform_wait_process_output(transform, 1000); + ok(res == 0, "got res %#lx\n", res); + test_transform_set_output(transform, 0, NULL); + + winetest_pop_context(); + } + + test_transform_shutdown(transform); + + IMFTransform_Release(transform); + hr = IMFMediaSession_Shutdown(session); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFMediaSession_Release(session); + + IMFAsyncCallback_Release(media_event_callback); + +done: + hr = MFShutdown(); + ok(hr == S_OK, "got hr %#lx\n", hr); +} + START_TEST(mf) { init_functions(); @@ -8059,4 +8248,5 @@ START_TEST(mf) test_mpeg4_media_sink(); test_media_session_transform_simple(); test_media_session_transform_multiple_outputs(); + test_media_session_transform_multiple_sources(); } diff --git a/dlls/mf/tests/multiple-streams.mp4 b/dlls/mf/tests/multiple-streams.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..ffb333bd89812b050005af0684cccd4d90c65643 GIT binary patch literal 20789 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 W0QddhvkWA$$(6$Yge!?)uKa&B-o<YK
literal 0 HcmV?d00001
diff --git a/dlls/mf/tests/resource.rc b/dlls/mf/tests/resource.rc index d9595c68980..9f9a0d8a4e5 100644 --- a/dlls/mf/tests/resource.rc +++ b/dlls/mf/tests/resource.rc @@ -133,3 +133,18 @@ rgb24frame.bmp RCDATA rgb24frame.bmp */ /* @makedep: test.mp4 */ test.mp4 RCDATA test.mp4 + +/* Generated with: +gst-launch-1.0 \ + audiotestsrc num-buffers=60 ! audio/x-raw,format=F32LE,channels=1 ! \ + taginject tags="language-code=eng" ! \ + audioconvert ! audioresample ! avenc_aac ! mux. \ + \ + audiotestsrc num-buffers=60 ! audio/x-raw,format=S16LE,layout=interleaved ! \ + taginject tags="language-code=fre" ! \ + audioconvert ! audioresample ! lamemp3enc ! mux. \ + \ + mp4mux name=mux ! filesink location=dlls/mf/tests/multiple-streams.mp4 +*/ +/* @makedep: multiple-streams.mp4 */ +multiple-streams.mp4 RCDATA multiple-streams.mp4
v3: Add broken result for Win7 for refused samples check.
Anything I should do here?
Nikolay Sivov (@nsivov) commented about dlls/mf/session.c:
struct list samples; unsigned int requests; unsigned int min_buffer_size;
- BOOL stream_ended;
};
Is this safe in a sense of such flag getting set, but never reset? Or the idea is simply indicate last received sample to be null?
Nikolay Sivov (@nsivov) commented about dlls/mf/session.c:
- if ((stream->stream_ended = !sample))
- {
if (!transform_node_all_streams_ended(topo_node))
return;
if (FAILED(hr = IMFTransform_ProcessMessage(topo_node->object.transform, MFT_MESSAGE_COMMAND_DRAIN, 0)))
WARN("Drain command failed for transform, hr %#lx.\n", hr);
- }
- else
- {
UINT id = transform_node_get_stream_id(topo_node, FALSE, input);
if (FAILED(hr = IMFTransform_ProcessInput(topo_node->object.transform, id, sample, 0))
&& FAILED(hr = transform_stream_push_sample(stream, sample)))
WARN("Failed to queue delayed input sample, hr %#lx\n", hr);
- }
That used to be more explicit, checking only for MF_E_NOT_ACCEPTING. Can we restore that? I'd expect that any other error simply stops all processing, puts session in error state, and sends MEError or something similar.
Nikolay Sivov (@nsivov) commented about dlls/mf/tests/mf.c:
- BOOL shutdown;
- UINT input_count;
- IMFMediaType *available_input_types[4];
- IMFMediaType *input_types[4];
- IMFSample *input_samples[4];
- HRESULT input_results[4];
- HANDLE process_input_event[4];
- UINT output_count;
- IMFMediaType *available_output_types[4];
- IMFMediaType *output_types[4];
- IMFSample *output_samples[4];
- HRESULT output_results[4];
- HANDLE process_output_event[4];
+};
We'll see how reliable this is on Windows. I don't think we really have to test such internal behavior in regular wine tests, but I trust you to deal with any failures from this.
On Wed Aug 2 15:50:08 2023 +0000, Nikolay Sivov wrote:
We'll see how reliable this is on Windows. I don't think we really have to test such internal behavior in regular wine tests, but I trust you to deal with any failures from this.
So far all the windows versions that are checked are following this pattern so it looks reliable to me. I don't understand how we can implement these complex components without having the tests in Wine? For instance how can we implement MF_TOPONODE_WORKQUEUE_ID behavior without this kind of tests?
On Tue Aug 8 09:24:04 2023 +0000, Rémi Bernon wrote:
So far all the windows versions that are checked are following this pattern so it looks reliable to me. I don't understand how we can implement these complex components without having the tests in Wine? For instance how can we implement MF_TOPONODE_WORKQUEUE_ID behavior without this kind of tests?
Tests are of course useful to figure out how it works internally, we can have them as long as they are reliable. Because there is not a lot of reason to chase exact patterns, MFT docs define how these should be used, and internal details do not matter much to us.
Regarding WORKQUEUE_ID, this is interesting in principle, yes. Mostly for the case when branches meet in multi-input MFTs. Otherwise it's probably a matter of returning correct queue id from "IMFAsyncCallback::GetParameters()", could be more complicated. Do we know if something is actually using this attribute?
On Tue Aug 8 11:41:47 2023 +0000, Nikolay Sivov wrote:
Tests are of course useful to figure out how it works internally, we can have them as long as they are reliable. Because there is not a lot of reason to chase exact patterns, MFT docs define how these should be used, and internal details do not matter much to us. Regarding WORKQUEUE_ID, this is interesting in principle, yes. Mostly for the case when branches meet in multi-input MFTs. Otherwise it's probably a matter of returning correct queue id from "IMFAsyncCallback::GetParameters()", could be more complicated. Do we know if something is actually using this attribute?
Nothing that I know about is using it but I thought it could be useful to figure how it works if we want to parallelize transform processing. Right now one blocker point is that all the session async operations are serialized anyway because of the session CS.
However, having written more tests I can also see that the native session default behavior is to serialize everything, including possibly concurrent transform processing when there's completely separate streams (Note: when WORKQUEUE_ID attributes are set, you can have concurrent transform processing).
This feels a bit ineffective and I'm a bit worried that serializing decoders could hurt the playback experience, but maybe I should just wait and see how it goes.
On Tue Aug 8 12:51:36 2023 +0000, Rémi Bernon wrote:
Nothing that I know about is using it but I thought it could be useful to figure how it works if we want to parallelize transform processing. Right now one blocker point is that all the session async operations are serialized anyway because of the session CS. However, having written more tests I can also see that the native session default behavior is to serialize everything, including possibly concurrent transform processing when there's completely separate streams (Note: when WORKQUEUE_ID attributes are set, you can have concurrent transform processing). This feels a bit ineffective and I'm a bit worried that serializing decoders could hurt the playback experience, but maybe I should just wait and see how it goes.
Regarding the request pattern, I understand that we don't have to implement native pattern, but at the same time we have to decide over a strategy for requesting more samples, and native one is as good as any. Having these tests also provides tools for some other MFT behavior testing.
I don't mind reducing them if you feel they are too exhaustive, or maintaining them myself, eventually moving them to transform.c if you feel better about it, just tell me.