[FFmpeg-devel] [RFC] AVFilter Parser

vmrsss vmrsss
Wed Mar 26 00:51:11 CET 2008


Hello. I've tried to interpret all that was said in the past few days,  
and would like to make a proposal to pull it all together. This is  
essentially what Vitor said in his last email, somehow simplified,  
plus a hopefully cleaner treatment of feedback.

==1==

I am using the following syntactic elements because I find them  
intuitive, but there is nothing special about them, just a matter of  
taste:

  - comma (,) for sequential composition --
	F,G implies #outstream(F) = #instream(G)

  - star (*) for parallel composition --
	#instream(F*G) = #instreams(F) + #instream(G)
	#outstream(F*G) = #outstreams(F) + #outstream(G)

  - input naming ( ... )
  notice this is a scoping operator, (x)F names x inaccessible outside F

  - output naming  < .... >

  - feedback naming [ ... ]
	notice this is a scoping operator, [x]F names x inaccessible outside F

  - a dummy name _

==2==

In general a filter is described by a signature

	filter{par_1,...,par_k} : n ---> k

that is, its parameters (note I am using curly brackets just not to  
confuse parameters and streams), its number of input streams n, its  
number of output streams k. (Typically n and k can be easily inferred  
by looking at the code, but it's not a big pain to ask the user to  
declare them when writing a user-defined filter.)

Examples:

	crop{w,h,x,y} : 1 --->1
	picInPic{} : 2 ---> 1
	rotate{angle} : 1 ---> 1
	movie{file} : 0 ---> 1


==3==

Here are the four main syntactic categories.

OUTPUT_STREAM ::= < id_1,...,id_k >
	sequence can be empty, < x > * < y > be to read as < x y >

ATOMIC_STREAM ::= all the streams in avfilter, eg crop, vflip,  
scale, ...

USER_DEFINED ::= filter_id{par_1,...,par_k} : n ---> k = FILTER

FILTER ::=
	OUTPUT_STREAM
	ATOMIC_STREAM=val_1,...,val_k	(k = # of parameters of filter)
	USER_DEFINED=val_1,...,val_k 	(k = # of parameters of filter)
	FILTER * FILTER					(as discussed above)
	FILTER, FILTER					(as discussed above)
	(id_1 ... id_k) FILTER
	FILTER [bind_1 ... bind_k]	(bind is either the dummy id _ or a proper  
id))


==4==

Let me now give a few examples to explain the elementary constructs;  
(in fact, the only thing which is slightly different is feedback).

	copy{} = (x)< x >
	split{} = (x)< x x >
	swap{} = (x y)< y x >
	drop{} = (x) < >

	myfilter(angle): 1 ---> 1 =	
		case angle of 90 --> transpose,vflip
					180 --> hflip,vflip
					270 --> vflip,transpose
					else --> rotate_slow=angle


Something slightly more complex:

	filter(): 3 ---> 1 = (a b c) < a b >, picInPic, (t) < t c >, picInPic

where:
	(a b c) names the three inputs of the filter
	< a b > outputs to of them to go straight to picInPit
	(t) names to output of picInPic
	< t c > feeds the final picInPic stage

Note that fully parenthesised this would be:

	(a b c)  ( < a b >, picInPic , (t) (< t c >, picInPic) )

(comma is associative, so parenthesis don't matter there -- I find  
using a comma after < > clumsy though)

Notice that if you don't need to name streams for special treatment,  
you're not required to do it: all simple filter chains can be written  
just as "...,crop=...,scale=...," and so can also a number of more  
complex ones -- as long as they don't require feedback.


==5==

Time for well-formedness rules relative to naming:

(1) all ids in a well-formed filter are bound by either ( ) or [ ]
	this guarantees that a filter has got no visible names, which
	may interfere with other names unintentionally; it is however
	possible to use input/output/feedback naming to use the filter
	not trivially even if one only knows its n ---> k type.

(2) ids appear at most once in a single (...)
	this avoids non-sense like < a b >, (x x)F

(3) the number of names in input (resp output) naming determines
	n (resp k) in the type n ---> k of a filter.

There might be still something missing here, but these are the  
important ones.


==6==

Finally, we come to feedback. The treatment is similar to the one  
proposed by Michael and Vitor, the only difference is that the  
[ .... ] is a scoping operator that at the same time feedbacks output  
to inputs and the same time puts the names out of scope, while keeping  
input/output and feedback mechanisms entirely separated. The basic  
mechanism is as follows.

Let F be a filter 2 ---> 2 and suppose I want to feed its outputs back  
to its inputs. Firstly, I can name F's inputs use the form:

	G = < a b >, F  	(which is 0 --> 2)

Then G [ a b ] says: name a and b the 1st (resp 2nd) output of G to  
the a input named a (resp b). To swap the outputs (ie crop the wires)  
I would write G [ b a ]. Now G [a b] is crap (eg because it's got no  
input nor output), one would like to feedback only some of the outputs  
to some of the inputs. That is where one might use a dummy variable _,  
which is a passthrough sort of mechanism.

  	< a b > F [  _ a ] : 2nd output to 1st input, 1st output pass  
through as filter's output
  	< a b > F [  _ b ] : 2nd output to 2nd input, 1st output pass  
through as filter's output
	< a b > F [  a _ ] : 1st output to 1st input, 2nd output pass through  
as filter's output
	... etc

Importantly, [  _ a ] makes a vanish from output and input (it becomes  
a loop, with no specific name) so that it is no more accessible from  
outside. Note that is very important, as any further access to the  
loop would be wrong. So, the final term that takes F and feeds back  
its 2nd output to its 1st input would be

   	(x) (< A x > F [  _ A ] ) : 1 ---> 1

which says: name x the only input, feed it to F's 2nd input,  
temporarily name A its 1st input, pass through the 1st output and  
feedback the 2nd to A as indicated by the feedback naming [  _ A ].

To conclude, we need to constrain the occurrence of names in F  
[ ... ]. This requires some thinking as to the best way to express it,  
but essentially F must be of the form < ... > F' and: there must be as  
many names in [ ... ] as in < ... >, they must all be distinct, and  
actually appear exactly once in < ... >. The type of < ... >  
F' [ ... ] is n ---> k, where n is

==7==

A few examples with feedback:

These were proposed my Michael:

> 2<X 0<L Filter0 L<0 2>Y ; Y>0 2<L Filter1 L<2 X<0
>
>   _________
>  /         \
>  \          |
>>       /
> ---> Filter0 --->
>>       \
>  /          |
>  \____  ___/
>       \/
>   ____/\___
>  /         \
>  \          |
>>       /
> ---> Filter1 --->
>>       \
>  /          |
>  \_________/
>

(x y ) (< a x b > Filter0 * < c y d > Filter1) [ a _ c b _ d ]

the same as

( x y ) < a x b c y d > (Filter0 * Filter1) [ a _ c b _ d ]

>
> --> picInPic -> split--> split --> scale --> picInPic -->
>                 \        \            >
> /                     \        \_________/
> \_____________________/

> 1<T picInPic, split T<1, split 1>T, scale, T>1 picInPic

(If I still understand the picture)

	 (x) (  < x a > (picInPic, split)[ _ a ],  split, (u v) <u> scale,  
(t) < t v > picInPic



==8==

As a final remark, there are might be more opportunities to introduce  
notational conventions eg involving  _. For instance, the form  
(t ... ) < t ... > will occur many times, where t doesn't really  
matter, it's just continuation-passing. So one might instead write  
( _ ... ) < _ ... >. For instance the previous example would be:

	 (x) (  < x a > (picInPic, split)[ _ a ],  split,  ( _ v) < _ >  
scale, ( _ ) < _ v > picInPic

Also, one could introduce an operator >> which by default passes the  
first output as the first input of the following filter, as suggested  
by Vitor too. Then you'd write:

	 (x) (  < x a > (picInPic, split)[ _ a ],  split  >>  (v) scale  >>   
< v > picInPic

which I'd say looks solid and smooth enough.

Regards to all.
-vmrss





More information about the ffmpeg-devel mailing list