diff mbox series

[FFmpeg-devel] lavfi: get rid of FF_INTERNAL_FIELDS

Message ID 20230130122937.12258-1-anton@khirnov.net
State New
Headers show
Series [FFmpeg-devel] lavfi: get rid of FF_INTERNAL_FIELDS | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Anton Khirnov Jan. 30, 2023, 12:29 p.m. UTC
This hack is used to limit the visibility of some AVFilterLink fields to
only certain files. Replace it with the same pattern that is used e.g.
in lavf AVStream/FFstream and avoid exposing these internal fields in a
public header completely.
---
 libavfilter/avfilter.c       | 191 +++++++++++++++++++++--------------
 libavfilter/avfilter.h       |  45 ---------
 libavfilter/avfiltergraph.c  |   9 +-
 libavfilter/buffersink.c     |   8 +-
 libavfilter/link_internal.h  |  69 +++++++++++++
 libavfilter/tests/filtfmts.c |   9 +-
 6 files changed, 196 insertions(+), 135 deletions(-)
 create mode 100644 libavfilter/link_internal.h

Comments

Paul B Mahol Jan. 30, 2023, 12:32 p.m. UTC | #1
On 1/30/23, Anton Khirnov <anton@khirnov.net> wrote:
> This hack is used to limit the visibility of some AVFilterLink fields to
> only certain files. Replace it with the same pattern that is used e.g.
> in lavf AVStream/FFstream and avoid exposing these internal fields in a
> public header completely.

Looks fine on quick look, but wait for Nicolas comment.
Nicolas George Jan. 30, 2023, 12:40 p.m. UTC | #2
Anton Khirnov (12023-01-30):
> This hack is used to limit the visibility of some AVFilterLink fields to
> only certain files. Replace it with the same pattern that is used e.g.
> in lavf AVStream/FFstream and avoid exposing these internal fields in a
> public header completely.
> ---
>  libavfilter/avfilter.c       | 191 +++++++++++++++++++++--------------
>  libavfilter/avfilter.h       |  45 ---------
>  libavfilter/avfiltergraph.c  |   9 +-
>  libavfilter/buffersink.c     |   8 +-
>  libavfilter/link_internal.h  |  69 +++++++++++++
>  libavfilter/tests/filtfmts.c |   9 +-
>  6 files changed, 196 insertions(+), 135 deletions(-)
>  create mode 100644 libavfilter/link_internal.h

This makes the code more verbose, less readable and harder to maintain,
so no thanks.

If you find a solution that does not require us to remember which field
is private and with field is public to prefix them with link-> or li->,
it would not have this issue; avoiding this requirement was a prime goal
of the current implementation. At least you did not add an indirection
like on some other places.
Anton Khirnov Jan. 31, 2023, 1:50 p.m. UTC | #3
Quoting Nicolas George (2023-01-30 13:40:06)
> Anton Khirnov (12023-01-30):
> > This hack is used to limit the visibility of some AVFilterLink fields to
> > only certain files. Replace it with the same pattern that is used e.g.
> > in lavf AVStream/FFstream and avoid exposing these internal fields in a
> > public header completely.
> > ---
> >  libavfilter/avfilter.c       | 191 +++++++++++++++++++++--------------
> >  libavfilter/avfilter.h       |  45 ---------
> >  libavfilter/avfiltergraph.c  |   9 +-
> >  libavfilter/buffersink.c     |   8 +-
> >  libavfilter/link_internal.h  |  69 +++++++++++++
> >  libavfilter/tests/filtfmts.c |   9 +-
> >  6 files changed, 196 insertions(+), 135 deletions(-)
> >  create mode 100644 libavfilter/link_internal.h
> 
> This makes the code more verbose, less readable and harder to maintain,

I find this a significant improvement in code quality, making it easier
to maintain.

> so no thanks.
> 
> If you find a solution that does not require us to remember which field
> is private and with field is public to prefix them with link-> or li->,
> it would not have this issue; avoiding this requirement was a prime goal
> of the current implementation. At least you did not add an indirection
> like on some other places.

Making it obvious which field is private and which is public is a
feature, not a bug.

I'll also note that
- we've been switching to this precise pattern everywhere else in the
  project
- the most prolific lavfi contributor recently (Paul) has no objections
  to this patch
- the second most prolific lavfi contributor recently (Andreas) told me
  on IRC he planned to write this same patch himself

So if you insist on objecting to this patch, it seems to me that a vote
would be in order.
Nicolas George Jan. 31, 2023, 2:03 p.m. UTC | #4
Anton Khirnov (12023-01-31):
> I find this a significant improvement in code quality, making it easier
> to maintain.

You can say that, you do not maintain it.

> Making it obvious which field is private and which is public is a
> feature, not a bug.

Then I do not want this feature. Making people expend effort for no
reason at all.

> I'll also note that
> - we've been switching to this precise pattern everywhere else in the
>   project

Well, too bad.

> - the most prolific lavfi contributor recently (Paul) has no objections
>   to this patch
> - the second most prolific lavfi contributor recently (Andreas) told me
>   on IRC he planned to write this same patch himself

It is not a matter of volume, it is a matter of complexity. Since
Stefano is no longer involved in the coding, I am the only one who knows
how the tricky bits of libavfilter work.

> So if you insist on objecting to this patch, it seems to me that a vote
> would be in order.

Well, go ahead, but please be aware that a vote cannot force me to
maintain that code.

If this project goes over my objections “that patch makes my maintenance
work harder”, then be very careful to involve in your plans “find
somebody else willing to debug the code of libavfilter”. Good luck.

In fact, I think I will take it very easy with maintenance duty until
further notice. I am really fed up with the direction this project is
taking. Authoritarianism, that was the other side of the fork, and we
all know what happened there. And who is to blame.
Paul B Mahol Jan. 31, 2023, 2:14 p.m. UTC | #5
On 1/31/23, Nicolas George <george@nsup.org> wrote:
> Anton Khirnov (12023-01-31):
>> I find this a significant improvement in code quality, making it easier
>> to maintain.
>
> You can say that, you do not maintain it.
>
>> Making it obvious which field is private and which is public is a
>> feature, not a bug.
>
> Then I do not want this feature. Making people expend effort for no
> reason at all.
>
>> I'll also note that
>> - we've been switching to this precise pattern everywhere else in the
>>   project
>
> Well, too bad.
>
>> - the most prolific lavfi contributor recently (Paul) has no objections
>>   to this patch
>> - the second most prolific lavfi contributor recently (Andreas) told me
>>   on IRC he planned to write this same patch himself
>
> It is not a matter of volume, it is a matter of complexity. Since
> Stefano is no longer involved in the coding, I am the only one who knows
> how the tricky bits of libavfilter work.

No, you do not. Calling your libavfilter framework code "complex" is disgrace
to really complex code in non-framework part of libavfilter or else in
FFmpeg libraries.

libavfilter framework needs serious overhaul.
It have numerous limitations and other stuff too much exposed to user,
that really should
not be.

You do not maintain code at all, you just block patches and never
contribute anymore anything useful besides blocking patches.

>
>> So if you insist on objecting to this patch, it seems to me that a vote
>> would be in order.
>
> Well, go ahead, but please be aware that a vote cannot force me to
> maintain that code.
>
> If this project goes over my objections “that patch makes my maintenance
> work harder”, then be very careful to involve in your plans “find
> somebody else willing to debug the code of libavfilter”. Good luck.
>
> In fact, I think I will take it very easy with maintenance duty until
> further notice. I am really fed up with the direction this project is
> taking. Authoritarianism, that was the other side of the fork, and we
> all know what happened there. And who is to blame.
>
> --
>   Nicolas George
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
Anton Khirnov Jan. 31, 2023, 2:18 p.m. UTC | #6
Quoting Nicolas George (2023-01-31 15:03:34)
> Anton Khirnov (12023-01-31):
> > I find this a significant improvement in code quality, making it easier
> > to maintain.
> 
> You can say that, you do not maintain it.

Do you?

You keep implying in your emails that you are the authority on lavfi,
but I see no objective support for this claim. MAINTAINERS only lists
you as the maintainer for graphdump.c and two filters.
And taken e.g. by the number of commits touching libavfilter/, mine is
similar to yours, and both are far behind other people.

> > - the most prolific lavfi contributor recently (Paul) has no objections
> >   to this patch
> > - the second most prolific lavfi contributor recently (Andreas) told me
> >   on IRC he planned to write this same patch himself
> 
> It is not a matter of volume, it is a matter of complexity. Since
> Stefano is no longer involved in the coding, I am the only one who knows
> how the tricky bits of libavfilter work.

Maybe someone should replace those tricky bits with something simpler
then.

> > So if you insist on objecting to this patch, it seems to me that a vote
> > would be in order.
> 
> Well, go ahead, but please be aware that a vote cannot force me to
> maintain that code.
> 
> If this project goes over my objections “that patch makes my maintenance
> work harder”, then be very careful to involve in your plans “find
> somebody else willing to debug the code of libavfilter”. Good luck.
> 
> In fact, I think I will take it very easy with maintenance duty until
> further notice. I am really fed up with the direction this project is
> taking. Authoritarianism, that was the other side of the fork, and we
> all know what happened there. And who is to blame.

Ah yes, preventing one person from enforcing his opinion over those of
multiple other people is "authoritarianism". I don't think that word
means what you think it means.

So take your pick - either stop opposing this patch or we have a vote
over it and see what other people think.
Nicolas George Jan. 31, 2023, 2:31 p.m. UTC | #7
Anton Khirnov (12023-01-31):
> Do you?
> 
> You keep implying in your emails that you are the authority on lavfi,
> but I see no objective support for this claim. MAINTAINERS only lists
> you as the maintainer for graphdump.c and two filters.
> And taken e.g. by the number of commits touching libavfilter/, mine is
> similar to yours, and both are far behind other people.

I know the code, how it works.

I know how the code needs to evolve to achieve new features without
breaking existing.

I review and apply patches to fix bugs when they come; fortunately it
does not happen frequently.

> Maybe someone should replace those tricky bits with something simpler
> then.

Yes, do that, and I will laugh. The code is tricky because the needs are
tricky. “Something simpler” is precisely what came from libav and I
needed to fix a decade ago.

> Ah yes, preventing one person from enforcing his opinion over those of
> multiple other people is "authoritarianism". I don't think that word
> means what you think it means.
> 
> So take your pick - either stop opposing this patch or we have a vote
> over it and see what other people think.

Go ahead, have a vote.

I no longer care. If it goes against me, that will make one less thing
to consider my responsibility.

We should never have asked ourselves “how should we change to entice
forkers back”, we should have asked “how must the forkers change if they
want to be accepted back”. You did not change.
Nicolas George Jan. 31, 2023, 2:32 p.m. UTC | #8
Paul B Mahol (12023-01-31):
> No, you do not. Calling your libavfilter framework code "complex" is disgrace
> to really complex code in non-framework part of libavfilter or else in
> FFmpeg libraries.
> 
> libavfilter framework needs serious overhaul.
> It have numerous limitations and other stuff too much exposed to user,
> that really should not be.
> 
> You do not maintain code at all, you just block patches and never
> contribute anymore anything useful besides blocking patches.

… says the person who does not know that the duration between pts=3 and
pts=5 must be 2.

This is a bad joke.
Paul B Mahol Jan. 31, 2023, 2:50 p.m. UTC | #9
On 1/31/23, Nicolas George <george@nsup.org> wrote:
> Paul B Mahol (12023-01-31):
>> No, you do not. Calling your libavfilter framework code "complex" is
>> disgrace
>> to really complex code in non-framework part of libavfilter or else in
>> FFmpeg libraries.
>>
>> libavfilter framework needs serious overhaul.
>> It have numerous limitations and other stuff too much exposed to user,
>> that really should not be.
>>
>> You do not maintain code at all, you just block patches and never
>> contribute anymore anything useful besides blocking patches.
>
> … says the person who does not know that the duration between pts=3 and
> pts=5 must be 2.
>
> This is a bad joke.

The patch just returned duration of frame from framesync and not
actual duration of frame.
And I have not tested it extensively at all or even thought about it much.

Why? Because I find whole API including libavfilter very silly and
broken and limited.

At least I contributed numerous filters used in production, otherwise
libavfilter would probably not exist today at all in this form at
least.
It would be already rewritten several times and/or whole idea
abandoned completely because of lack of interest to develop it or poor
usage.
Anton Khirnov Jan. 31, 2023, 3:24 p.m. UTC | #10
Quoting Nicolas George (2023-01-31 15:31:44)
> Anton Khirnov (12023-01-31):
> > Do you?
> > 
> > You keep implying in your emails that you are the authority on lavfi,
> > but I see no objective support for this claim. MAINTAINERS only lists
> > you as the maintainer for graphdump.c and two filters.
> > And taken e.g. by the number of commits touching libavfilter/, mine is
> > similar to yours, and both are far behind other people.
> 
> I know the code, how it works.
> 
> I know how the code needs to evolve to achieve new features without
> breaking existing.
> 
> I review and apply patches to fix bugs when they come; fortunately it
> does not happen frequently.

I still see no objective facts supporting your claim of exclusive
maintainership over the entirety of lavfi generic code and public API.
Considering yourself the maintainer in your own head is not enough.

So to avoid any further pointless bickering, I'm hereby asking the TC to
resolve this.

To summarize my view, this patch is an improvement because:
* it prevents filterlink internals from being visible in a
  public header, where they have no business being
* it is a step towards hiding more of lavfi internals from public
  headers
* the same pattern is already and ever more widely used in the other
  libraries and ffmpeg CLI
* it is supported by Andreas (who submitted a more general analogue of
  this patch over a year ago) and Paul
* I am not aware of anyone other than Nicolas being against it
Nicolas George Jan. 31, 2023, 4:13 p.m. UTC | #11
Anton Khirnov (12023-01-31):
> I still see no objective facts supporting your claim of exclusive
> maintainership over the entirety of lavfi generic code and public API.

The fact is very simple: I am the only one who understand how this code
works.

> So to avoid any further pointless bickering, I'm hereby asking the TC to
> resolve this.

Just so we are clear: you are a party in this, you can therefore not be
a judge.

> To summarize my view, this patch is an improvement because:
> * it prevents filterlink internals from being visible in a
>   public header, where they have no business being
> * it is a step towards hiding more of lavfi internals from public
>   headers
> * the same pattern is already and ever more widely used in the other
>   libraries and ffmpeg CLI
> * it is supported by Andreas (who submitted a more general analogue of
>   this patch over a year ago) and Paul
> * I am not aware of anyone other than Nicolas being against it

It is a worsening because:

* It requires the developers to remember which field is public and which
  field is private, which is not something relevant here (is is relevant
  elsewhere).

Of course, if you think about it two seconds, you realize it affects the
person who knows the code very well and used/wants to work on it
intensively more than the developers who move from one part of the code
to another and have to re-learn everything. But thinking two seconds how
your changes affects other people does not seem like your forte either.

Therefore, I add this point:

* If this change is applied, FFmpeg needs to find somebody else to
  maintain the core of libavfilter. (And I predict you will not.)
Nicolas George Jan. 31, 2023, 4:34 p.m. UTC | #12
Nicolas George (12023-01-31):
> > * it prevents filterlink internals from being visible in a
> >   public header, where they have no business being
> > * it is a step towards hiding more of lavfi internals from public
> >   headers
> > * the same pattern is already and ever more widely used in the other

Note to the TC who will decide: I do not oppose the efforts mentioned in
these two points (that are actually the same points twice), I only
oppose this particular solution because of this drawback:

> * It requires the developers to remember which field is public and which
>   field is private, which is not something relevant here (is is relevant
>   elsewhere).

Without looking very far, I can think of several different ways of
hiding the internal fields better without requiring changes to the
implementation. I would not oppose such a change.
Andreas Rheinhardt Jan. 31, 2023, 9:02 p.m. UTC | #13
Nicolas George:
> Nicolas George (12023-01-31):
>>> * it prevents filterlink internals from being visible in a
>>>   public header, where they have no business being
>>> * it is a step towards hiding more of lavfi internals from public
>>>   headers
>>> * the same pattern is already and ever more widely used in the other
> 
> Note to the TC who will decide: I do not oppose the efforts mentioned in
> these two points (that are actually the same points twice), I only
> oppose this particular solution because of this drawback:
> 
>> * It requires the developers to remember which field is public and which
>>   field is private, which is not something relevant here (is is relevant
>>   elsewhere).
> 
> Without looking very far, I can think of several different ways of
> hiding the internal fields better without requiring changes to the
> implementation. I would not oppose such a change.
> 

Details please. I can only think of the following:
a) Allow the use of -fms-extensions. This allows structs with tags and
typedefs thereof to be used as unnamed struct members with the members
of the unnamed structure being treated as members of the enclosing
structure. One could then use a pointer to the big internal structure
internally and one does not need to remember whether a field is internal
or not. There is still a problem, though: One needs to cast from
AVFilterContext.(inputs|outputs). This should be done via dedicated
inlined getters, but this is a bit more typing. E.g.
"input_from_ctx(ctx, i)" instead of "ctx->inputs[i]". Of course, it
might also be shorter if someone has a short name.
GCC, Clang, MSVC and the IIRC the intel compilers support this.

b) Add a big #define AVFILTERLINK in avfilter.h that expands to the
public part of AVFilterLink and change the declaration of AVFilterLink
to "struct AVFilterLink { AVFILTERLINK };" and use declare the internal
struct via
"struct FilterLinkInternal {
    AVFILTERLINK
    /* actual internal fields */
};"
This has the downside of actually being an aliasing violation and of
adding considerable ugliness to avfilter.h, in particular during
deprecations (like with FF_API_OLD_CHANNEL_LAYOUT -- you can't check via
#if in the implementation of a macro). I also don't know whether it
plays nicely with tools that deal with source code annotations.
c) Wrap the internal part in an #ifdef HAVE_AV_CONFIG_H, optionally
using #if defined(HAVE_AV_CONFIG_H) && defined(BUILDING_avfilter).
d) Same as c), but strip this stuff from installed headers.

I consider b)-d) as inferior to a), which I consider superior to Anton's
proposal, but the big drawback is its reliance on a compiler extension.

- Andreas
Nicolas George Jan. 31, 2023, 9:16 p.m. UTC | #14
Andreas Rheinhardt (12023-01-31):
> Details please.

I was not going to spend time explaining unless I had assurance it was
because the issue of requiring changes in the internal implementation
and developers habits just to hide some fields was taken into account.
Coming from somebody else, I would have expected the question to be only
to find the first excuses to shoot the proposal down.

But you more or less found the same ideas I did anyway.

> I can only think of the following:
> a) Allow the use of -fms-extensions. This allows structs with tags and
> typedefs thereof to be used as unnamed struct members with the members
> of the unnamed structure being treated as members of the enclosing
> structure. One could then use a pointer to the big internal structure
> internally and one does not need to remember whether a field is internal
> or not. There is still a problem, though: One needs to cast from
> AVFilterContext.(inputs|outputs). This should be done via dedicated
> inlined getters, but this is a bit more typing. E.g.
> "input_from_ctx(ctx, i)" instead of "ctx->inputs[i]". Of course, it
> might also be shorter if someone has a short name.
> GCC, Clang, MSVC and the IIRC the intel compilers support this.
> 
> b) Add a big #define AVFILTERLINK in avfilter.h that expands to the
> public part of AVFilterLink and change the declaration of AVFilterLink
> to "struct AVFilterLink { AVFILTERLINK };" and use declare the internal
> struct via
> "struct FilterLinkInternal {
>     AVFILTERLINK
>     /* actual internal fields */
> };"
> This has the downside of actually being an aliasing violation and of
> adding considerable ugliness to avfilter.h, in particular during
> deprecations (like with FF_API_OLD_CHANNEL_LAYOUT -- you can't check via
> #if in the implementation of a macro). I also don't know whether it
> plays nicely with tools that deal with source code annotations.

Or use a “#include "avfilter_link_internal_fields.h" instead of a macro.

> c) Wrap the internal part in an #ifdef HAVE_AV_CONFIG_H, optionally
> using #if defined(HAVE_AV_CONFIG_H) && defined(BUILDING_avfilter).
> d) Same as c), but strip this stuff from installed headers.
> 
> I consider b)-d) as inferior to a), which I consider superior to Anton's
> proposal, but the big drawback is its reliance on a compiler extension.

Once and for all: If the solution requires changing things in the
internal implementation C files and changing the habits of the people
who work on these files (that goes together), then it is inferior to a
solution that does not require that.

Your solution (a) has this flaw, plus relies on an extension that is
probably not available on all supported platforms (Microsoft I am
looking at you).

My favor goes to (d). Probably with a stricter test for the (c) part
because the internal fields are not necessary for the filters and
therefore could be hidden there too.

Regards,
Andreas Rheinhardt Feb. 1, 2023, 12:31 a.m. UTC | #15
Nicolas George:
> Andreas Rheinhardt (12023-01-31):
>> Details please.
> 
> I was not going to spend time explaining unless I had assurance it was
> because the issue of requiring changes in the internal implementation
> and developers habits just to hide some fields was taken into account.
> Coming from somebody else, I would have expected the question to be only
> to find the first excuses to shoot the proposal down.
> 
> But you more or less found the same ideas I did anyway.
> 
>> I can only think of the following:
>> a) Allow the use of -fms-extensions. This allows structs with tags and
>> typedefs thereof to be used as unnamed struct members with the members
>> of the unnamed structure being treated as members of the enclosing
>> structure. One could then use a pointer to the big internal structure
>> internally and one does not need to remember whether a field is internal
>> or not. There is still a problem, though: One needs to cast from
>> AVFilterContext.(inputs|outputs). This should be done via dedicated
>> inlined getters, but this is a bit more typing. E.g.
>> "input_from_ctx(ctx, i)" instead of "ctx->inputs[i]". Of course, it
>> might also be shorter if someone has a short name.
>> GCC, Clang, MSVC and the IIRC the intel compilers support this.
>>
>> b) Add a big #define AVFILTERLINK in avfilter.h that expands to the
>> public part of AVFilterLink and change the declaration of AVFilterLink
>> to "struct AVFilterLink { AVFILTERLINK };" and use declare the internal
>> struct via
>> "struct FilterLinkInternal {
>>     AVFILTERLINK
>>     /* actual internal fields */
>> };"
>> This has the downside of actually being an aliasing violation and of
>> adding considerable ugliness to avfilter.h, in particular during
>> deprecations (like with FF_API_OLD_CHANNEL_LAYOUT -- you can't check via
>> #if in the implementation of a macro). I also don't know whether it
>> plays nicely with tools that deal with source code annotations.
> 
> Or use a “#include "avfilter_link_internal_fields.h" instead of a macro.
> 
>> c) Wrap the internal part in an #ifdef HAVE_AV_CONFIG_H, optionally
>> using #if defined(HAVE_AV_CONFIG_H) && defined(BUILDING_avfilter).
>> d) Same as c), but strip this stuff from installed headers.
>>
>> I consider b)-d) as inferior to a), which I consider superior to Anton's
>> proposal, but the big drawback is its reliance on a compiler extension.
> 
> Once and for all: If the solution requires changing things in the
> internal implementation C files and changing the habits of the people
> who work on these files (that goes together), then it is inferior to a
> solution that does not require that.
> 
> Your solution (a) has this flaw, plus relies on an extension that is
> probably not available on all supported platforms (Microsoft I am
> looking at you).
> 

It's called -fms-extensions for a reason.

> My favor goes to (d). Probably with a stricter test for the (c) part
> because the internal fields are not necessary for the filters and
> therefore could be hidden there too.
> 

This can be accomplished with a), too. All one has to do is use multiple
levels of extensions.

- Andreas

PS: Upon rethinking, it is not only b) that contains undefined
behaviour; it is b)-d) as well as the current code. The reason is that
the type of AVFilterLink as seen in the files with FF_INTERNAL_FIELDS is
not compatible with the type of AVFilterLink from the files without
FF_INTERNAL_FIELDS. This also makes derived types, like the types of
function declarations containing pointers to AVFilterLink incompatible
and this is a violation of 6.2.7(2) of C11. I wonder if this will become
a real problem with lto someday.
Nicolas George Feb. 1, 2023, 7:47 a.m. UTC | #16
Andreas Rheinhardt (12023-02-01):
> PS: Upon rethinking, it is not only b) that contains undefined
> behaviour; it is b)-d) as well as the current code. The reason is that
> the type of AVFilterLink as seen in the files with FF_INTERNAL_FIELDS is
> not compatible with the type of AVFilterLink from the files without
> FF_INTERNAL_FIELDS. This also makes derived types, like the types of
> function declarations containing pointers to AVFilterLink incompatible
> and this is a violation of 6.2.7(2) of C11. I wonder if this will become
> a real problem with lto someday.

No: read the second half of the previous paragraph: two structures with
common first fields are compatible types. What we have been using is a
deliberately supported construct.

Regards,
Andreas Rheinhardt Feb. 1, 2023, 1:48 p.m. UTC | #17
Nicolas George:
> Andreas Rheinhardt (12023-02-01):
>> PS: Upon rethinking, it is not only b) that contains undefined
>> behaviour; it is b)-d) as well as the current code. The reason is that
>> the type of AVFilterLink as seen in the files with FF_INTERNAL_FIELDS is
>> not compatible with the type of AVFilterLink from the files without
>> FF_INTERNAL_FIELDS. This also makes derived types, like the types of
>> function declarations containing pointers to AVFilterLink incompatible
>> and this is a violation of 6.2.7(2) of C11. I wonder if this will become
>> a real problem with lto someday.
> 
> No: read the second half of the previous paragraph: two structures with
> common first fields are compatible types. What we have been using is a
> deliberately supported construct.
> 

"Moreover, two structure, union, or enumerated types declared in
separate translation units are compatible if their tags and members
satisfy the following requirements: If one is declared with a tag, the
other shall be declared with the same tag. If both are completed
anywhere within their respective translation units, then the following
additional requirements apply: there shall be a one-to-one
correspondence between their members such that each pair of
corresponding members are declared with compatible types; if one member
of the pair is declared with an alignment specifier, the other is
declared with an equivalent alignment specifier; and if one member of
the pair is declared with a name, the other is declared with the same
name. For two structures, corresponding members shall be declared in the
same order."

1. There is no one-to-one correspondence (aka a bijection) between the
elements of the complete and of the public structure.
2. In the case of AVFilterLink, there is not even an injection from the
public to the FF_INTERNAL_FIELDS structure (due to the former having a
big reserved array).
3. The fact that these structures share a common initial sequence does
not mean that this is legal. There are some guarantees regarding common
initial sequences in 6.5.2.3 (6):

"One special guarantee is made in order to simplify the use of unions:
if a union contains several structures that share a common initial
sequence (see below), and if the union object currently contains one of
these structures, it is permitted to inspect the common initial part of
any of them anywhere that a declaration of the completed type of the
union is visible. Two structures share a common initial sequence if
corresponding members have compatible types (and, for bit-fields, the
same widths) for a sequence of one or more initial members."

But there just is no union between the FF_INTERNAL_FIELDS
!defined(FF_INTERNAL_FIELDS) structures in the whole codebase.
Furthermore, said guarantee is only for inspecting, i.e. reading. For
example, for the following two structs sharing a common initial sequence,

struct Small {
    uint64_t foo;
    uint32_t bar;
};
struct Big {
    uint64_t foo;
    uint32_t bar;
    uint32_t baz;
};

if one had a union { struct Small; struct Big; }, a write to Small.bar
may clobber Big.baz.

- Andreas
Nicolas George Feb. 3, 2023, 2:45 p.m. UTC | #18
Andreas Rheinhardt (12023-02-01):
> "One special guarantee is made in order to simplify the use of unions:
> if a union contains several structures that share a common initial
> sequence (see below), and if the union object currently contains one of
> these structures, it is permitted to inspect the common initial part of
> any of them anywhere that a declaration of the completed type of the
> union is visible. Two structures share a common initial sequence if
> corresponding members have compatible types (and, for bit-fields, the
> same widths) for a sequence of one or more initial members."
> 
> But there just is no union between the FF_INTERNAL_FIELDS
> !defined(FF_INTERNAL_FIELDS) structures in the whole codebase.

It is not necessary that there is one: it is enough that there could be
one in another compilation unit, the code that handles these structures
does not know what exists in the rest of the code base. This guarantee
was made FOR unions, but it does not REQUIRE unions, it applies to
anything that is semantically equivalent to a union.

> Furthermore, said guarantee is only for inspecting, i.e. reading. For
> example, for the following two structs sharing a common initial sequence,
> 
> struct Small {
>     uint64_t foo;
>     uint32_t bar;
> };
> struct Big {
>     uint64_t foo;
>     uint32_t bar;
>     uint32_t baz;
> };
> 
> if one had a union { struct Small; struct Big; }, a write to Small.bar
> may clobber Big.baz.

That is a good point. It does not apply when the first field of Big is
Small itself, which is the case that I absolutely know is supported,
because in that case Big will embed the padding of Small. I had not
thought of this.

Fortunately, even if we are not 100% in the case that is officially
supported, there are multiple reasons that each should be enough to
guarantee that our code will work:

- Between the public part of the structure and the #ifede
  FF_INTERNAL_FIELDS, there are a lot of "not part of the public API"
  fields", changing ch_layout will not clobber incfg because incfg is
  known at this point.

- Even if there was no fields in between, reserved[] offers the same
  protection.

- And even if there was no gap, we are good because applications, and
  even filters, are not supposed to modify AVFilterLink, only the
  framework and the framework knows the whole structure.

In fact, that last remark makes me think of another solution, for the
slightly longer term: make AVFilterLink entirely opaque. The only
documented reason for application to access AVFilterLink was to get the
parameters of buffersink, but buffersink has a real API to get this
since 2016.

Anyway, if you prefer using a compilation option, I have no objection.
My only concern is that when a developer writes:

	if (link->x == ... &&
	    link->y == ... &&
	    link->status_in == ... &&
	    link->min_samples == ..)

they have to remember that no, status_in is different from all the
others.

And it is especially bad in this particular case because the distinction
is not public / private, which makes some semantic sense and might be
easier to remember, the distinction is actually
public-or-private-because-of-a-comment / private-because-of-a-ifdef.

But even if the distinction really was public / private, I consider it
unacceptable if we can do otherwise. And we can.

Regards,
Andreas Rheinhardt Feb. 4, 2023, 3:27 p.m. UTC | #19
Nicolas George:
> Andreas Rheinhardt (12023-02-01):
>> "One special guarantee is made in order to simplify the use of unions:
>> if a union contains several structures that share a common initial
>> sequence (see below), and if the union object currently contains one of
>> these structures, it is permitted to inspect the common initial part of
>> any of them anywhere that a declaration of the completed type of the
>> union is visible. Two structures share a common initial sequence if
>> corresponding members have compatible types (and, for bit-fields, the
>> same widths) for a sequence of one or more initial members."
>>
>> But there just is no union between the FF_INTERNAL_FIELDS
>> !defined(FF_INTERNAL_FIELDS) structures in the whole codebase.
> 
> It is not necessary that there is one: it is enough that there could be
> one in another compilation unit, the code that handles these structures
> does not know what exists in the rest of the code base. This guarantee
> was made FOR unions, but it does not REQUIRE unions, it applies to
> anything that is semantically equivalent to a union.
> 

1. The common initial sequence rule indeed forced traditional compilers
to use the same offsets for the members of the common initial sequence
of two arbitrary structs, because there might be a union of these two in
another translation unit. But this is no longer true for lto compilers
that see the whole program at once (although I don't think that they
will deviate from the behaviour of traditional compilers in this regard).
2. But even if the offsets are fine, does not mean that the accesses are
fine. Notice the clause "anywhere that a declaration of the completed
type of the union is visible" above? It is accompanied with the
following example:

"The following is not a valid fragment (because the union type is not
visible within function f):
struct t1 { int m; };
struct t2 { int m; };
int f(struct t1 *p1, struct t2 *p2)
{
    if (p1->m < 0)
        p2->m = -p2->m;
    return p1->m;
}
int g()
{
    union {
        struct t1 s1;
        struct t2 s2;
    } u;
    /* ... */
    return f(&u.s1, &u.s2);
}"


>> Furthermore, said guarantee is only for inspecting, i.e. reading. For
>> example, for the following two structs sharing a common initial sequence,
>>
>> struct Small {
>>     uint64_t foo;
>>     uint32_t bar;
>> };
>> struct Big {
>>     uint64_t foo;
>>     uint32_t bar;
>>     uint32_t baz;
>> };
>>
>> if one had a union { struct Small; struct Big; }, a write to Small.bar
>> may clobber Big.baz.
> 
> That is a good point. It does not apply when the first field of Big is
> Small itself, which is the case that I absolutely know is supported,
> because in that case Big will embed the padding of Small. I had not
> thought of this.
> 
> Fortunately, even if we are not 100% in the case that is officially
> supported, there are multiple reasons that each should be enough to
> guarantee that our code will work:
> 

I'd rather have something that is actually supported and spec-compliant.
Anyway, I worry more about lto-compilers than about traditional
compilers accidentally clobbering something (after all, most of our
fields are sizeof(int)-sized or a multiple (double) thereof, so CPU
instruction sets can write this natively and there is no advantage in
touching padding).

> - Between the public part of the structure and the #ifede
>   FF_INTERNAL_FIELDS, there are a lot of "not part of the public API"
>   fields", changing ch_layout will not clobber incfg because incfg is
>   known at this point.
> 
> - Even if there was no fields in between, reserved[] offers the same
>   protection.
> 
> - And even if there was no gap, we are good because applications, and
>   even filters, are not supposed to modify AVFilterLink, only the
>   framework and the framework knows the whole structure.
> 
> In fact, that last remark makes me think of another solution, for the
> slightly longer term: make AVFilterLink entirely opaque. The only
> documented reason for application to access AVFilterLink was to get the
> parameters of buffersink, but buffersink has a real API to get this
> since 2016.
> 
> Anyway, if you prefer using a compilation option, I have no objection.
> My only concern is that when a developer writes:
> 
> 	if (link->x == ... &&
> 	    link->y == ... &&
> 	    link->status_in == ... &&
> 	    link->min_samples == ..)
> 
> they have to remember that no, status_in is different from all the
> others.
> 
> And it is especially bad in this particular case because the distinction
> is not public / private, which makes some semantic sense and might be
> easier to remember, the distinction is actually
> public-or-private-because-of-a-comment / private-because-of-a-ifdef.
> 
> But even if the distinction really was public / private, I consider it
> unacceptable if we can do otherwise. And we can.
> 
> Regards,
>
diff mbox series

Patch

diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index c2ecdffa6f5..2a1f99bd656 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -34,15 +34,14 @@ 
 #include "libavutil/rational.h"
 #include "libavutil/samplefmt.h"
 
-#define FF_INTERNAL_FIELDS 1
-#include "framequeue.h"
-
 #include "audio.h"
 #include "avfilter.h"
 #include "filters.h"
 #include "formats.h"
+#include "framequeue.h"
 #include "framepool.h"
 #include "internal.h"
+#include "link_internal.h"
 
 static void tlog_ref(void *ctx, AVFrame *ref, int end)
 {
@@ -148,6 +147,7 @@  int ff_append_outpad_free_name(AVFilterContext *f, AVFilterPad *p)
 int avfilter_link(AVFilterContext *src, unsigned srcpad,
                   AVFilterContext *dst, unsigned dstpad)
 {
+    FilterLinkInternal *li;
     AVFilterLink *link;
 
     av_assert0(src->graph);
@@ -166,9 +166,10 @@  int avfilter_link(AVFilterContext *src, unsigned srcpad,
         return AVERROR(EINVAL);
     }
 
-    link = av_mallocz(sizeof(*link));
-    if (!link)
+    li = av_mallocz(sizeof(*li));
+    if (!li)
         return AVERROR(ENOMEM);
+    link = &li->l;
 
     src->outputs[srcpad] = dst->inputs[dstpad] = link;
 
@@ -179,17 +180,20 @@  int avfilter_link(AVFilterContext *src, unsigned srcpad,
     link->type    = src->output_pads[srcpad].type;
     av_assert0(AV_PIX_FMT_NONE == -1 && AV_SAMPLE_FMT_NONE == -1);
     link->format  = -1;
-    ff_framequeue_init(&link->fifo, &src->graph->internal->frame_queues);
+    ff_framequeue_init(&li->fifo, &src->graph->internal->frame_queues);
 
     return 0;
 }
 
 void avfilter_link_free(AVFilterLink **link)
 {
+    FilterLinkInternal *li;
+
     if (!*link)
         return;
+    li = ff_link_internal(*link);
 
-    ff_framequeue_free(&(*link)->fifo);
+    ff_framequeue_free(&li->fifo);
     ff_frame_pool_uninit((FFFramePool**)&(*link)->frame_pool);
     av_channel_layout_uninit(&(*link)->ch_layout);
 
@@ -209,29 +213,35 @@  static void filter_unblock(AVFilterContext *filter)
 {
     unsigned i;
 
-    for (i = 0; i < filter->nb_outputs; i++)
-        filter->outputs[i]->frame_blocked_in = 0;
+    for (i = 0; i < filter->nb_outputs; i++) {
+        FilterLinkInternal * const li = ff_link_internal(filter->outputs[i]);
+        li->frame_blocked_in = 0;
+    }
 }
 
 
 void ff_avfilter_link_set_in_status(AVFilterLink *link, int status, int64_t pts)
 {
-    if (link->status_in == status)
+    FilterLinkInternal * const li = ff_link_internal(link);
+
+    if (li->status_in == status)
         return;
-    av_assert0(!link->status_in);
-    link->status_in = status;
-    link->status_in_pts = pts;
+    av_assert0(!li->status_in);
+    li->status_in = status;
+    li->status_in_pts = pts;
     link->frame_wanted_out = 0;
-    link->frame_blocked_in = 0;
+    li->frame_blocked_in = 0;
     filter_unblock(link->dst);
     ff_filter_set_ready(link->dst, 200);
 }
 
 void ff_avfilter_link_set_out_status(AVFilterLink *link, int status, int64_t pts)
 {
+    FilterLinkInternal * const li = ff_link_internal(link);
+
     av_assert0(!link->frame_wanted_out);
-    av_assert0(!link->status_out);
-    link->status_out = status;
+    av_assert0(!li->status_out);
+    li->status_out = status;
     if (pts != AV_NOPTS_VALUE)
         ff_update_link_current_pts(link, pts);
     filter_unblock(link->dst);
@@ -409,13 +419,15 @@  void ff_tlog_link(void *ctx, AVFilterLink *link, int end)
 
 int ff_request_frame(AVFilterLink *link)
 {
+    FilterLinkInternal * const li = ff_link_internal(link);
+
     FF_TPRINTF_START(NULL, request_frame); ff_tlog_link(NULL, link, 1);
 
     av_assert1(!link->dst->filter->activate);
-    if (link->status_out)
-        return link->status_out;
-    if (link->status_in) {
-        if (ff_framequeue_queued_frames(&link->fifo)) {
+    if (li->status_out)
+        return li->status_out;
+    if (li->status_in) {
+        if (ff_framequeue_queued_frames(&li->fifo)) {
             av_assert1(!link->frame_wanted_out);
             av_assert1(link->dst->ready >= 300);
             return 0;
@@ -423,8 +435,8 @@  int ff_request_frame(AVFilterLink *link)
             /* Acknowledge status change. Filters using ff_request_frame() will
                handle the change automatically. Filters can also check the
                status directly but none do yet. */
-            ff_avfilter_link_set_out_status(link, link->status_in, link->status_in_pts);
-            return link->status_out;
+            ff_avfilter_link_set_out_status(link, li->status_in, li->status_in_pts);
+            return li->status_out;
         }
     }
     link->frame_wanted_out = 1;
@@ -437,14 +449,18 @@  static int64_t guess_status_pts(AVFilterContext *ctx, int status, AVRational lin
     unsigned i;
     int64_t r = INT64_MAX;
 
-    for (i = 0; i < ctx->nb_inputs; i++)
-        if (ctx->inputs[i]->status_out == status)
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        FilterLinkInternal * const li = ff_link_internal(ctx->inputs[i]);
+        if (li->status_out == status)
             r = FFMIN(r, av_rescale_q(ctx->inputs[i]->current_pts, ctx->inputs[i]->time_base, link_time_base));
+    }
     if (r < INT64_MAX)
         return r;
     av_log(ctx, AV_LOG_WARNING, "EOF timestamp not reliable\n");
-    for (i = 0; i < ctx->nb_inputs; i++)
-        r = FFMIN(r, av_rescale_q(ctx->inputs[i]->status_in_pts, ctx->inputs[i]->time_base, link_time_base));
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        FilterLinkInternal * const li = ff_link_internal(ctx->inputs[i]);
+        r = FFMIN(r, av_rescale_q(li->status_in_pts, ctx->inputs[i]->time_base, link_time_base));
+    }
     if (r < INT64_MAX)
         return r;
     return AV_NOPTS_VALUE;
@@ -452,17 +468,18 @@  static int64_t guess_status_pts(AVFilterContext *ctx, int status, AVRational lin
 
 static int ff_request_frame_to_filter(AVFilterLink *link)
 {
+    FilterLinkInternal * const li = ff_link_internal(link);
     int ret = -1;
 
     FF_TPRINTF_START(NULL, request_frame_to_filter); ff_tlog_link(NULL, link, 1);
     /* Assume the filter is blocked, let the method clear it if not */
-    link->frame_blocked_in = 1;
+    li->frame_blocked_in = 1;
     if (link->srcpad->request_frame)
         ret = link->srcpad->request_frame(link);
     else if (link->src->inputs[0])
         ret = ff_request_frame(link->src->inputs[0]);
     if (ret < 0) {
-        if (ret != AVERROR(EAGAIN) && ret != link->status_in)
+        if (ret != AVERROR(EAGAIN) && ret != li->status_in)
             ff_avfilter_link_set_in_status(link, ret, guess_status_pts(link->src, ret, link->time_base));
         if (ret == AVERROR_EOF)
             ret = 0;
@@ -977,6 +994,7 @@  fail:
 
 int ff_filter_frame(AVFilterLink *link, AVFrame *frame)
 {
+    FilterLinkInternal * const li = ff_link_internal(link);
     int ret;
     FF_TPRINTF_START(NULL, filter_frame); ff_tlog_link(NULL, link, 1); ff_tlog(NULL, " "); tlog_ref(NULL, frame, 1);
 
@@ -1006,11 +1024,11 @@  int ff_filter_frame(AVFilterLink *link, AVFrame *frame)
         }
     }
 
-    link->frame_blocked_in = link->frame_wanted_out = 0;
+    li->frame_blocked_in = link->frame_wanted_out = 0;
     link->frame_count_in++;
     link->sample_count_in += frame->nb_samples;
     filter_unblock(link->dst);
-    ret = ff_framequeue_add(&link->fifo, frame);
+    ret = ff_framequeue_add(&li->fifo, frame);
     if (ret < 0) {
         av_frame_free(&frame);
         return ret;
@@ -1023,16 +1041,17 @@  error:
     return AVERROR_PATCHWELCOME;
 }
 
-static int samples_ready(AVFilterLink *link, unsigned min)
+static int samples_ready(FilterLinkInternal *link, unsigned min)
 {
     return ff_framequeue_queued_frames(&link->fifo) &&
            (ff_framequeue_queued_samples(&link->fifo) >= min ||
             link->status_in);
 }
 
-static int take_samples(AVFilterLink *link, unsigned min, unsigned max,
+static int take_samples(FilterLinkInternal *li, unsigned min, unsigned max,
                         AVFrame **rframe)
 {
+    AVFilterLink *link = &li->l;
     AVFrame *frame0, *frame, *buf;
     unsigned nb_samples, nb_frames, i, p;
     int ret;
@@ -1040,9 +1059,9 @@  static int take_samples(AVFilterLink *link, unsigned min, unsigned max,
     /* Note: this function relies on no format changes and must only be
        called with enough samples. */
     av_assert1(samples_ready(link, link->min_samples));
-    frame0 = frame = ff_framequeue_peek(&link->fifo, 0);
-    if (!link->fifo.samples_skipped && frame->nb_samples >= min && frame->nb_samples <= max) {
-        *rframe = ff_framequeue_take(&link->fifo);
+    frame0 = frame = ff_framequeue_peek(&li->fifo, 0);
+    if (!li->fifo.samples_skipped && frame->nb_samples >= min && frame->nb_samples <= max) {
+        *rframe = ff_framequeue_take(&li->fifo);
         return 0;
     }
     nb_frames = 0;
@@ -1055,9 +1074,9 @@  static int take_samples(AVFilterLink *link, unsigned min, unsigned max,
         }
         nb_samples += frame->nb_samples;
         nb_frames++;
-        if (nb_frames == ff_framequeue_queued_frames(&link->fifo))
+        if (nb_frames == ff_framequeue_queued_frames(&li->fifo))
             break;
-        frame = ff_framequeue_peek(&link->fifo, nb_frames);
+        frame = ff_framequeue_peek(&li->fifo, nb_frames);
     }
 
     buf = ff_get_audio_buffer(link, nb_samples);
@@ -1071,7 +1090,7 @@  static int take_samples(AVFilterLink *link, unsigned min, unsigned max,
 
     p = 0;
     for (i = 0; i < nb_frames; i++) {
-        frame = ff_framequeue_take(&link->fifo);
+        frame = ff_framequeue_take(&li->fifo);
         av_samples_copy(buf->extended_data, frame->extended_data, p, 0,
                         frame->nb_samples, link->ch_layout.nb_channels, link->format);
         p += frame->nb_samples;
@@ -1079,10 +1098,10 @@  static int take_samples(AVFilterLink *link, unsigned min, unsigned max,
     }
     if (p < nb_samples) {
         unsigned n = nb_samples - p;
-        frame = ff_framequeue_peek(&link->fifo, 0);
+        frame = ff_framequeue_peek(&li->fifo, 0);
         av_samples_copy(buf->extended_data, frame->extended_data, p, 0, n,
                         link->ch_layout.nb_channels, link->format);
-        ff_framequeue_skip_samples(&link->fifo, n, link->time_base);
+        ff_framequeue_skip_samples(&li->fifo, n, link->time_base);
     }
 
     *rframe = buf;
@@ -1091,6 +1110,7 @@  static int take_samples(AVFilterLink *link, unsigned min, unsigned max,
 
 static int ff_filter_frame_to_filter(AVFilterLink *link)
 {
+    FilterLinkInternal * const li = ff_link_internal(link);
     AVFrame *frame = NULL;
     AVFilterContext *dst = link->dst;
     int ret;
@@ -1111,7 +1131,7 @@  static int ff_filter_frame_to_filter(AVFilterLink *link)
        before the frame; ff_filter_frame_framed() will re-increment it. */
     link->frame_count_out--;
     ret = ff_filter_frame_framed(link, frame);
-    if (ret < 0 && ret != link->status_out) {
+    if (ret < 0 && ret != li->status_out) {
         ff_avfilter_link_set_out_status(link, ret, AV_NOPTS_VALUE);
     } else {
         /* Run once again, to see if several frames were available, or if
@@ -1121,18 +1141,21 @@  static int ff_filter_frame_to_filter(AVFilterLink *link)
     return ret;
 }
 
-static int forward_status_change(AVFilterContext *filter, AVFilterLink *in)
+static int forward_status_change(AVFilterContext *filter, FilterLinkInternal *li_in)
 {
+    AVFilterLink *in = &li_in->l;
     unsigned out = 0, progress = 0;
     int ret;
 
-    av_assert0(!in->status_out);
+    av_assert0(!li_in->status_out);
     if (!filter->nb_outputs) {
         /* not necessary with the current API and sinks */
         return 0;
     }
-    while (!in->status_out) {
-        if (!filter->outputs[out]->status_in) {
+    while (!li_in->status_out) {
+        FilterLinkInternal *li_out = ff_link_internal(filter->outputs[out]);
+
+        if (!li_out->status_in) {
             progress++;
             ret = ff_request_frame_to_filter(filter->outputs[out]);
             if (ret < 0)
@@ -1142,7 +1165,7 @@  static int forward_status_change(AVFilterContext *filter, AVFilterLink *in)
             if (!progress) {
                 /* Every output already closed: input no longer interesting
                    (example: overlay in shortest mode, other input closed). */
-                ff_avfilter_link_set_out_status(in, in->status_in, in->status_in_pts);
+                ff_avfilter_link_set_out_status(in, li_in->status_in, li_in->status_in_pts);
                 return 0;
             }
             progress = 0;
@@ -1158,19 +1181,22 @@  static int ff_filter_activate_default(AVFilterContext *filter)
     unsigned i;
 
     for (i = 0; i < filter->nb_inputs; i++) {
-        if (samples_ready(filter->inputs[i], filter->inputs[i]->min_samples)) {
+        if (samples_ready(ff_link_internal(filter->inputs[i]),
+                          filter->inputs[i]->min_samples)) {
             return ff_filter_frame_to_filter(filter->inputs[i]);
         }
     }
     for (i = 0; i < filter->nb_inputs; i++) {
-        if (filter->inputs[i]->status_in && !filter->inputs[i]->status_out) {
-            av_assert1(!ff_framequeue_queued_frames(&filter->inputs[i]->fifo));
-            return forward_status_change(filter, filter->inputs[i]);
+        FilterLinkInternal * const li = ff_link_internal(filter->inputs[i]);
+        if (li->status_in && !li->status_out) {
+            av_assert1(!ff_framequeue_queued_frames(&li->fifo));
+            return forward_status_change(filter, li);
         }
     }
     for (i = 0; i < filter->nb_outputs; i++) {
+        FilterLinkInternal * const li = ff_link_internal(filter->outputs[i]);
         if (filter->outputs[i]->frame_wanted_out &&
-            !filter->outputs[i]->frame_blocked_in) {
+            !li->frame_blocked_in) {
             return ff_request_frame_to_filter(filter->outputs[i]);
         }
     }
@@ -1326,39 +1352,44 @@  int ff_filter_activate(AVFilterContext *filter)
 
 int ff_inlink_acknowledge_status(AVFilterLink *link, int *rstatus, int64_t *rpts)
 {
+    FilterLinkInternal * const li = ff_link_internal(link);
     *rpts = link->current_pts;
-    if (ff_framequeue_queued_frames(&link->fifo))
+    if (ff_framequeue_queued_frames(&li->fifo))
         return *rstatus = 0;
-    if (link->status_out)
-        return *rstatus = link->status_out;
-    if (!link->status_in)
+    if (li->status_out)
+        return *rstatus = li->status_out;
+    if (!li->status_in)
         return *rstatus = 0;
-    *rstatus = link->status_out = link->status_in;
-    ff_update_link_current_pts(link, link->status_in_pts);
+    *rstatus = li->status_out = li->status_in;
+    ff_update_link_current_pts(link, li->status_in_pts);
     *rpts = link->current_pts;
     return 1;
 }
 
 size_t ff_inlink_queued_frames(AVFilterLink *link)
 {
-    return ff_framequeue_queued_frames(&link->fifo);
+    FilterLinkInternal * const li = ff_link_internal(link);
+    return ff_framequeue_queued_frames(&li->fifo);
 }
 
 int ff_inlink_check_available_frame(AVFilterLink *link)
 {
-    return ff_framequeue_queued_frames(&link->fifo) > 0;
+    FilterLinkInternal * const li = ff_link_internal(link);
+    return ff_framequeue_queued_frames(&li->fifo) > 0;
 }
 
 int ff_inlink_queued_samples(AVFilterLink *link)
 {
-    return ff_framequeue_queued_samples(&link->fifo);
+    FilterLinkInternal * const li = ff_link_internal(link);
+    return ff_framequeue_queued_samples(&li->fifo);
 }
 
 int ff_inlink_check_available_samples(AVFilterLink *link, unsigned min)
 {
-    uint64_t samples = ff_framequeue_queued_samples(&link->fifo);
+    FilterLinkInternal * const li = ff_link_internal(link);
+    uint64_t samples = ff_framequeue_queued_samples(&li->fifo);
     av_assert1(min);
-    return samples >= min || (link->status_in && samples);
+    return samples >= min || (li->status_in && samples);
 }
 
 static void consume_update(AVFilterLink *link, const AVFrame *frame)
@@ -1372,18 +1403,19 @@  static void consume_update(AVFilterLink *link, const AVFrame *frame)
 
 int ff_inlink_consume_frame(AVFilterLink *link, AVFrame **rframe)
 {
+    FilterLinkInternal * const li = ff_link_internal(link);
     AVFrame *frame;
 
     *rframe = NULL;
     if (!ff_inlink_check_available_frame(link))
         return 0;
 
-    if (link->fifo.samples_skipped) {
-        frame = ff_framequeue_peek(&link->fifo, 0);
+    if (li->fifo.samples_skipped) {
+        frame = ff_framequeue_peek(&li->fifo, 0);
         return ff_inlink_consume_samples(link, frame->nb_samples, frame->nb_samples, rframe);
     }
 
-    frame = ff_framequeue_take(&link->fifo);
+    frame = ff_framequeue_take(&li->fifo);
     consume_update(link, frame);
     *rframe = frame;
     return 1;
@@ -1392,6 +1424,7 @@  int ff_inlink_consume_frame(AVFilterLink *link, AVFrame **rframe)
 int ff_inlink_consume_samples(AVFilterLink *link, unsigned min, unsigned max,
                             AVFrame **rframe)
 {
+    FilterLinkInternal * const li = ff_link_internal(link);
     AVFrame *frame;
     int ret;
 
@@ -1399,9 +1432,9 @@  int ff_inlink_consume_samples(AVFilterLink *link, unsigned min, unsigned max,
     *rframe = NULL;
     if (!ff_inlink_check_available_samples(link, min))
         return 0;
-    if (link->status_in)
-        min = FFMIN(min, ff_framequeue_queued_samples(&link->fifo));
-    ret = take_samples(link, min, max, &frame);
+    if (li->status_in)
+        min = FFMIN(min, ff_framequeue_queued_samples(&li->fifo));
+    ret = take_samples(li, min, max, &frame);
     if (ret < 0)
         return ret;
     consume_update(link, frame);
@@ -1411,7 +1444,8 @@  int ff_inlink_consume_samples(AVFilterLink *link, unsigned min, unsigned max,
 
 AVFrame *ff_inlink_peek_frame(AVFilterLink *link, size_t idx)
 {
-    return ff_framequeue_peek(&link->fifo, idx);
+    FilterLinkInternal * const li = ff_link_internal(link);
+    return ff_framequeue_peek(&li->fifo, idx);
 }
 
 int ff_inlink_make_frame_writable(AVFilterLink *link, AVFrame **rframe)
@@ -1497,29 +1531,32 @@  void ff_inlink_request_frame(AVFilterLink *link)
 
 void ff_inlink_set_status(AVFilterLink *link, int status)
 {
-    if (link->status_out)
+    FilterLinkInternal * const li = ff_link_internal(link);
+    if (li->status_out)
         return;
     link->frame_wanted_out = 0;
-    link->frame_blocked_in = 0;
+    li->frame_blocked_in = 0;
     ff_avfilter_link_set_out_status(link, status, AV_NOPTS_VALUE);
-    while (ff_framequeue_queued_frames(&link->fifo)) {
-           AVFrame *frame = ff_framequeue_take(&link->fifo);
+    while (ff_framequeue_queued_frames(&li->fifo)) {
+           AVFrame *frame = ff_framequeue_take(&li->fifo);
            av_frame_free(&frame);
     }
-    if (!link->status_in)
-        link->status_in = status;
+    if (!li->status_in)
+        li->status_in = status;
 }
 
 int ff_outlink_get_status(AVFilterLink *link)
 {
-    return link->status_in;
+    FilterLinkInternal * const li = ff_link_internal(link);
+    return li->status_in;
 }
 
 int ff_inoutlink_check_flow(AVFilterLink *inlink, AVFilterLink *outlink)
 {
+    FilterLinkInternal * const li_in = ff_link_internal(inlink);
     return ff_outlink_frame_wanted(outlink) ||
            ff_inlink_check_available_frame(inlink) ||
-           inlink->status_out;
+           li_in->status_out;
 }
 
 
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index c2ec7a4b5fc..5ccab4a3f8f 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -668,51 +668,6 @@  struct AVFilterLink {
      * AVHWFramesContext describing the frames.
      */
     AVBufferRef *hw_frames_ctx;
-
-#ifndef FF_INTERNAL_FIELDS
-
-    /**
-     * Internal structure members.
-     * The fields below this limit are internal for libavfilter's use
-     * and must in no way be accessed by applications.
-     */
-    char reserved[0xF000];
-
-#else /* FF_INTERNAL_FIELDS */
-
-    /**
-     * Queue of frames waiting to be filtered.
-     */
-    FFFrameQueue fifo;
-
-    /**
-     * If set, the source filter can not generate a frame as is.
-     * The goal is to avoid repeatedly calling the request_frame() method on
-     * the same link.
-     */
-    int frame_blocked_in;
-
-    /**
-     * Link input status.
-     * If not zero, all attempts of filter_frame will fail with the
-     * corresponding code.
-     */
-    int status_in;
-
-    /**
-     * Timestamp of the input status change.
-     */
-    int64_t status_in_pts;
-
-    /**
-     * Link output status.
-     * If not zero, all attempts of request_frame will fail with the
-     * corresponding code.
-     */
-    int status_out;
-
-#endif /* FF_INTERNAL_FIELDS */
-
 };
 
 /**
diff --git a/libavfilter/avfiltergraph.c b/libavfilter/avfiltergraph.c
index 53f468494d3..c3280f5f8e7 100644
--- a/libavfilter/avfiltergraph.c
+++ b/libavfilter/avfiltergraph.c
@@ -31,13 +31,13 @@ 
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
 
-#define FF_INTERNAL_FIELDS 1
-#include "framequeue.h"
 
 #include "avfilter.h"
 #include "buffersink.h"
 #include "formats.h"
+#include "framequeue.h"
 #include "internal.h"
+#include "link_internal.h"
 #include "thread.h"
 
 #define OFFSET(x) offsetof(AVFilterGraph, x)
@@ -1326,10 +1326,11 @@  int avfilter_graph_request_oldest(AVFilterGraph *graph)
     av_assert1(oldest->age_index >= 0);
     frame_count = oldest->frame_count_out;
     while (frame_count == oldest->frame_count_out) {
+        FilterLinkInternal * const li = ff_link_internal(oldest);
         r = ff_filter_graph_run_once(graph);
         if (r == AVERROR(EAGAIN) &&
-            !oldest->frame_wanted_out && !oldest->frame_blocked_in &&
-            !oldest->status_in)
+            !oldest->frame_wanted_out && !li->frame_blocked_in &&
+            !li->status_in)
             ff_request_frame(oldest);
         else if (r < 0)
             return r;
diff --git a/libavfilter/buffersink.c b/libavfilter/buffersink.c
index e269cf72d1b..e102bd22f38 100644
--- a/libavfilter/buffersink.c
+++ b/libavfilter/buffersink.c
@@ -30,14 +30,13 @@ 
 #include "libavutil/internal.h"
 #include "libavutil/opt.h"
 
-#define FF_INTERNAL_FIELDS 1
-#include "framequeue.h"
-
 #include "audio.h"
 #include "avfilter.h"
 #include "buffersink.h"
 #include "filters.h"
+#include "framequeue.h"
 #include "internal.h"
+#include "link_internal.h"
 
 typedef struct BufferSinkContext {
     const AVClass *class;
@@ -187,9 +186,10 @@  static av_cold int common_init(AVFilterContext *ctx)
 static int activate(AVFilterContext *ctx)
 {
     BufferSinkContext *buf = ctx->priv;
+    FilterLinkInternal * const li = ff_link_internal(ctx->inputs[0]);
 
     if (buf->warning_limit &&
-        ff_framequeue_queued_frames(&ctx->inputs[0]->fifo) >= buf->warning_limit) {
+        ff_framequeue_queued_frames(&li->fifo) >= buf->warning_limit) {
         av_log(ctx, AV_LOG_WARNING,
                "%d buffers queued in %s, something may be wrong.\n",
                buf->warning_limit,
diff --git a/libavfilter/link_internal.h b/libavfilter/link_internal.h
new file mode 100644
index 00000000000..b5a8ac89ec2
--- /dev/null
+++ b/libavfilter/link_internal.h
@@ -0,0 +1,69 @@ 
+/*
+ * Internal filter link API
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVFILTER_LINK_INTERNAL_H
+#define AVFILTER_LINK_INTERNAL_H
+
+#include <stdint.h>
+
+#include "avfilter.h"
+#include "framequeue.h"
+
+typedef struct FilterLinkInternal {
+    AVFilterLink l;
+
+    /**
+     * Queue of frames waiting to be filtered.
+     */
+    FFFrameQueue fifo;
+
+    /**
+     * If set, the source filter can not generate a frame as is.
+     * The goal is to avoid repeatedly calling the request_frame() method on
+     * the same link.
+     */
+    int frame_blocked_in;
+
+    /**
+     * Link input status.
+     * If not zero, all attempts of filter_frame will fail with the
+     * corresponding code.
+     */
+    int status_in;
+
+    /**
+     * Timestamp of the input status change.
+     */
+    int64_t status_in_pts;
+
+    /**
+     * Link output status.
+     * If not zero, all attempts of request_frame will fail with the
+     * corresponding code.
+     */
+    int status_out;
+} FilterLinkInternal;
+
+static inline FilterLinkInternal *ff_link_internal(AVFilterLink *link)
+{
+    return (FilterLinkInternal*)link;
+}
+
+#endif /* AVFILTER_LINK_INTERNAL_H */
diff --git a/libavfilter/tests/filtfmts.c b/libavfilter/tests/filtfmts.c
index 909c1e8dc96..3451ed891e5 100644
--- a/libavfilter/tests/filtfmts.c
+++ b/libavfilter/tests/filtfmts.c
@@ -25,12 +25,11 @@ 
 #include "libavutil/pixdesc.h"
 #include "libavutil/samplefmt.h"
 
-#define FF_INTERNAL_FIELDS 1
-#include "libavfilter/framequeue.h"
-
 #include "libavfilter/avfilter.h"
 #include "libavfilter/formats.h"
+#include "libavfilter/framequeue.h"
 #include "libavfilter/internal.h"
+#include "libavfilter/link_internal.h"
 
 static void print_formats_internal(AVFilterLink **links, const AVFilterPad *pads,
                                    unsigned nb, size_t fmts_cfg_offset,
@@ -123,7 +122,7 @@  int main(int argc, char **argv)
 
     /* create a link for each of the input pads */
     for (i = 0; i < filter_ctx->nb_inputs; i++) {
-        AVFilterLink *link = av_mallocz(sizeof(AVFilterLink));
+        AVFilterLink *link = av_mallocz(sizeof(FilterLinkInternal));
         if (!link) {
             fprintf(stderr, "Unable to allocate memory for filter input link\n");
             ret = 1;
@@ -133,7 +132,7 @@  int main(int argc, char **argv)
         filter_ctx->inputs[i] = link;
     }
     for (i = 0; i < filter_ctx->nb_outputs; i++) {
-        AVFilterLink *link = av_mallocz(sizeof(AVFilterLink));
+        AVFilterLink *link = av_mallocz(sizeof(FilterLinkInternal));
         if (!link) {
             fprintf(stderr, "Unable to allocate memory for filter output link\n");
             ret = 1;