[PATCH v5 1/8] mf/tests: Add tests for the topology loader.

Sergio Gómez Del Real sdelreal at codeweavers.com
Fri Apr 24 18:10:49 CDT 2020


On 23/04/20 8:15 a. m., Nikolay Sivov wrote:

> Hi,
>
> I'm replying to message 1/8, but it really applies to whole set. Could 
> you explain how this works without going into details too much,
> e.g. some questions that I got so far:
>
> - top level function first identifies pairs of {source, sink} paths as 
> branches, and that seems reasonable for trivial cases of 1:1 
> transforms, and no tee nodes.
>
> How could this be used in generic case when you have some of the 
> above? For tee node case for example, with single source and two sinks 
> that will give two paths/pairs,
> while in fact, assuming sinks use same type for now, you only need to 
> resolve from source to tee node. Same for n:m transform (is that even 
> supported in default loader?),
> that gives (n x m) branches when n sources are connected to m sinks?
>
> I think generic way would be to resolve connections, independently or 
> as independently as transforms allow, not from every source to sink, 
> but from {node1, output_index} -> {node2, input_index}.

Yeah... I had worked under the assumption that loader only works with 
straightforward 1:1 transforms; in general I have seen that the loader 
is simpler than one might suspect. It only solves topologies for 
playback, for example.

However I will do some tests to see whether a more general approach is 
justified.

>
> - resolution seems to start with source nodes (and I apologize if I 
> got it wrong), is that correct? Is there an evidence that it works 
> that way?
>
wouldn't this be an implementation detail? _Load either returns the new 
topology, or nothing at all, so I am not sure how we could see if it 
starts at source or at sink.
> AFAIK user is expected to specify sink types, and that's the goal - to 
> construct a graph that delivers such output type, if possible. 
> Wouldn't it make sense to start from output types instead,
> and follow paths upstream? Sink could only be connected to source 
> directly, transform output, or a tee. First case is trivial, transform 
> will give you a list of what it can output to choose from,
> you can then add more transforms if necessary. For tee node, it will 
> likely be dictated by primary output type.

the thing is, in the case of decoders/converters (which again I suspect 
are the only mfts the loader handles, since it only works for playback), 
it is a general tendency that the mft first expects an input type to be 
set through _SetInputType(), and only when it's set, and as a function 
of the type set, it gives available output types through 
_GetOutputAvailableType(). I remember that at first I intended to do it 
as you say, starting from sink, but I decided the other way round in 
part due to this.

Now you could get a list of mfts for the supported output type, but only 
considering majortype and subtype, which isn't a guarantee that the 
output is compatible with the downstream node. In any case, the detailed 
mediatypes for these mfts are obtained only when setting explicitly its 
input type, but for that we would need the possible output types for its 
upstream node, which may be another mft that would again require an 
input type set on it for it to display the detailed possible output 
types. Continuing like this we would finally get to the source and we 
would be practically be starting from it, which is what we wanted to 
avoid in the first place.

So suppose we have:

source --> sink

Start from sink, get its input media type, and get the mfts that output 
that media type through MFTEnumEx(). We get the mfts that can output 
that majortype and subtype, without further qualification:

source --> mft1 --> sink

we still can't call _GetOutputAvailableType() on mft1 to get detailed 
view of types and call _IsMediaTypeSupported on sink's media type 
handler, so we would need to see if source's output type is compatible 
as input type for mft1. If not, then get another list of mfts with 
output types compatible with mft1 as input through MFTEnumEX():

source --> mft2 --> mft1 --> sink

we have gone this far and we still don't even know if mft1 is good or 
not. Now if source's output is supported by mft2, 
_SetInputType(source_type) on mft2, which will produce the list of 
available outputs, and for each one of these we would call 
_SetInputType(mft2_output) on mft1, and then for each output type 
produced in mft1 we would call _IsMediaTypeSupported() on sink.

The other way round would be start with

source --> sink

we know source has a single output media type, so get mfts that support 
it as input through MFTEnumEx():

source --> mft1 --> sink

iterate over mft1's output types through _GetOutputAvailableType(), 
which we would have right away since we already set its input. Continue 
like this.

So I think that this tendency, that is common in decoder mfts, justifies 
starting from source. But apart from this, as far as I remember when I 
thought about it, I think there isn't much to gain with wherever we 
start the resolution.

> Source nodes however could be in theory more flexible, capture sources 
> might provide several output format and you can pick best suited one, 
> to avoid conversion/resampling/etc.
>
I'm not sure that we can in any case choose the best suited type. If 
MF_TOPOLOGY_ENUMERATE_SOURCE_TYPES attribute is set on topology, the 
loader follows a precise algorithm, which doesn't include deciding the 
mediatype on the basis of convenience. And if 
MF_TOPOLOGY_ENUMERATE_SOURCE_TYPES is not set, it simply picks the 
current media type, and if that is not set, it picks the first available 
type (index 0), and only tries with that one.
> - why is it useful to have lists of whole topology instances?

In each step of the resolution we have to change the state of nodes in 
order to get available types, and also there is the possibility that 
nodes and/or whole branches are optional, which would require reverting 
states.

Suppose we have an initial

source --> mft1 --> sink

the algorithms starts with the pair (source, mft1) and tries to solve that.

A solution to this pair includes much more state than just source node 
and mft1 node. For example, suppose one such resolution is

source --> mft2 --> mft1

State here includes source's output type, mft2 input type, mft2 output 
type and mft1 input type (same as mft2 output). It is also possible that 
another configuration is compatible, maybe with a different output from 
source, and different types for mft2. Then it is also possible that 
other resolutions of the pair (source, mft1) are possible, with other 
intermediate mfts and combination of input/output types.

To simplify this, I construct a topology and configure it with the 
compatible state for each possibility, cloning the nodes and setting 
their respective input/output types, and save a list of these 
topologies. The list is then used as basis for the next iteration (the 
pair (mft1, sink)), trying each compatible configuration/state in turn 
that was gathered in the previous iteration. The essence for justifying 
this, I think, is that in the process we are modifying the state of the 
nodes, in order to set its inputs and get its outputs, so we need to 
preserve the states that turn out to be compatible; going back to those 
states solely by using loops proved to be a nightmare, very unpractical 
and impossible to read. So cloning nodes and creating a new topology for 
each positive resolution that we gather on the way is, I believe, a very 
small price to pay for a much needed simplification.

The other thing is what I mentioned about optional nodes/branches 
(something that currently isn't handled). Suppose we get something like 
source --> mft1 --> mft2 --> ? --> sink, that is, we solved almost all 
the topology, but at the end we couldn't solve the last part. There 
should be the possibility to revert the state in case any node in the 
branch turns out to be optional, maybe go back to mft1 and try to 
connect to other intermediate mfts (if mft2 is optional, for example). 
There is definitely a need to recover previous useful states, for which 
I think a higher-level construct comes very handy. I opted for the 
particular solution of maintaining lists of topologies with the proper 
state, and cloned nodes, since the process needs to alter the original 
node's state to be able to figure out its things. I think this 
particular solution turned out relatively well, but of course I am open 
to discuss and work with other possibilities.

>
> - what does connection method really do. I didn't get this part in 
> particular:
>
>> 2780     hr = IMFAttributes_GetUINT32(attrs_src, 
>> &MF_TOPONODE_CONNECT_METHOD, &method);
>> 2781     if (!enum_src_types || (hr == S_OK && !(method & 
>> MF_CONNECT_RESOLVE_INDEPENDENT_OUTPUTTYPES)))
>> 2782     {
>> 2783         for (method = MF_CONNECT_DIRECT; method < 
>> MF_CONNECT_RESOLVE_INDEPENDENT_OUTPUTTYPES; method++)
>> 2784         {
>> 2785             for (i = 0; i < num_media_types; i++)
>> 2786             {
>> 2787 IMFMediaTypeHandler_SetCurrentMediaType(mth_src, 
>> src_mediatypes[i]);
>> 2788                 if (SUCCEEDED(hr = 
>> topology_loader_resolve_branch_connect_nodes(full_topology, src->id, 
>> method)))
>> 2789                     goto out;
>> 2790             }
>> 2791         }
>> 2792     }
>> 2793     else
>> 2794     {
>> 2795         for (i = 0; i < num_media_types; i++)
>> 2796         {
>> 2797             for (method = MF_CONNECT_DIRECT; method < 
>> MF_CONNECT_RESOLVE_INDEPENDENT_OUTPUTTYPES; method++)
>> 2798             {
>> 2799 IMFMediaTypeHandler_SetCurrentMediaType(mth_src, 
>> src_mediatypes[i]);
>> 2800                 if (SUCCEEDED(hr = 
>> topology_loader_resolve_branch_connect_nodes(full_topology, src->id, 
>> method)))
>> 2801                     goto out;
>> 2802             }
>> 2803         }
>> 2804     }
> First source attribute is checked with 
> MF_CONNECT_RESOLVE_INDEPENDENT_OUTPUTTYPES as a mask, and its actual 
> value then is never used;
> and second - following loops iterate in [0,4), when only defined 
> values are 0,1,3, and 4, plus some higher bits for OPTIONAL flags.
>
here the attribute that is retrieved is for the source. Because 
MF_CONNECT_DIRECT and MF_CONNECT_ALLOW_* applies to connections from 
upstream nodes, they don't apply to source, so here we only care if it 
has MF_CONNECT_RESOLVE_INDEPENDENT_OUTPUTTYPES.

I reuse 'method' to iterate through the methods, but I hadn't noticed 
that 2 isn't defined, so this is definitely a bug.

> I can understand that DIRECT means that no decoders and no converters 
> are allowed, and branch is not optional, so if direct connect fails, 
> whole resolution fails.
> But what happens with method == 2? It's clear what loop reordering 
> does here, and that's how it's documented, but going back to previous 
> point,
> you can do that as last step too, after sinks are connected and 
> configured.

method == 2 shouldn't happen. My bad here since I assumed the enum was 
continuous and I simply wanted to use that (supposed) fact to code the 
iterations.

I must change this to use the correct values from the enum, and first 
try MF_CONNECT_DIRECT, and then the others.

In reality, I really haven't found any difference from _DECODER and 
_CONVERTER, I really don't know what _CONVERTER is supposed to mean, 
since I always see that the category used for MFTEnumEx() is 
MFT_CATEGORY_*_DECODER. I must further investigate this, so in the 
meantime I think I am treating MF_CONNECT_ALLOW_CONVERTER/DECODER as the 
same, but I maintain the distinction at the formal level in the 
iteration so that we can more easily fix that in the future should any 
difference show up.

>
> Also, does MF_CONNECT_RESOLVE_INDEPENDENT_OUTPUTTYPES actually apply 
> to sources only as docs say? Do we apply other methods set with 
> MF_TOPONODE_CONNECT_METHOD for
> other node types?

I haven't tested this attribute with other types of nodes, honestly. We 
can leave open this possibility. I wouldn't count on the loader 
accounting for the complexity it would mean to allow this attribute on 
other nodes, based on the limitations I've seen it has, but I will test 
this to find out.

Currently we don't consider MF_TOPONODE_CONNECT_METHOD for other nodes, 
but I think it will be relatively easy given the base code presented. I 
have considered that we will have to eventually handle this, and other 
things (for example, MF_CONNECT_AS_OPTIONAL), so the design was 
conceived with these extensions in mind. I just want first to have a 
base that works and then start working on these things (there are other 
attributes that require handling, actually).




More information about the wine-devel mailing list