diff mbox series

[FFmpeg-devel,1/3] doc: Explain what "context" means

Message ID 20240418150614.3952107-1-ffmpeg-devel@pileofstuff.org
State New
Headers show
Series [FFmpeg-devel,1/3] doc: Explain what "context" means | 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

Andrew Sayers April 18, 2024, 3:06 p.m. UTC
Based largely on the explanation by Stefano Sabatini:
https://ffmpeg.org/pipermail/ffmpeg-devel/2024-April/325854.html
---
 doc/jargon.md | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 96 insertions(+)
 create mode 100644 doc/jargon.md

Comments

Stefano Sabatini April 20, 2024, 7:25 a.m. UTC | #1
On date Thursday 2024-04-18 16:06:12 +0100, Andrew Sayers wrote:
> Based largely on the explanation by Stefano Sabatini:
> https://ffmpeg.org/pipermail/ffmpeg-devel/2024-April/325854.html
> ---
>  doc/jargon.md | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 96 insertions(+)
>  create mode 100644 doc/jargon.md
> 
> diff --git a/doc/jargon.md b/doc/jargon.md
> new file mode 100644
> index 0000000000..3b78ffb61f

> --- /dev/null
> +++ b/doc/jargon.md

We currently have a single .md file in doc (for historical reason we
still stick to texinfo). Also how is this integrated into doxygen?


> @@ -0,0 +1,96 @@
> +# Jargon
> +
> +Terms used throughout the code that developers may need to know.
> +
> +@anchor context
> +
> +## Context
> +
> +A design pattern that stores the context (e.g. configuration) for a series
> +of operations in a "context" structure, and moves other information elsewhere.
> +
> +Consider a trivial program to print uppercase text:
> +
> +```c

> +/*
> + * Contextual information about where to print a series of messages
> + */

Style:
/**
 * Contextual information about where to print a series of messages
 */

> +struct UpperCasePrinterContext {
> +    FILE* out;

here and below, use:
VAR *out;

for overall consistency with the project style.

> +};
> +
> +/*
> + * Extra information about messages to print.
> + * This could be used multiple times in a single context,
> + * or reused several times across multiple contexts.
> + */
> +struct PrintInfo {
> +    char* str;
> +};
> +
> +void print(
> +    struct UpperCasePrinterContext * ctx,
> +    struct PrintInfo * info
> +) {
> +    for ( char* c = info->str; *c; ++c ) {
> +        char C = toupper(*c);
> +        fwrite( &C, 1, 1, ctx->out );
> +    }
> +}
> +

> +int main()
> +{
> +    struct PrintInfo hello, world;
> +    struct UpperCasePrinterContext ctx;
> +
> +    hello.str = "hello, ";
> +    world.str = "world!\n";
> +
> +    ctx.out = stdout;
> +
> +    print( &ctx, &hello );
> +    print( &ctx, &world );
> +
> +    return 0;
> +}

I'm not sure this is a fitting example. Usually the context is a
public structure and the internal context (which corresponds to the
PrintInfo struct) is private to the implementation. In this case the
API user is not interested at all at its implmentation.

You can think the context provides the "object"/instance where some
operations are done - this is alike in object oriented programming,
where the context corresponds to the self, so that you create/allocate
the object, initialize it, and invoke operations on the object.

So using this analogy, the example would be:

struct UpperCasePrinterContext {...};

// this corresponds to a "method" defined on the context/object
void uppercase_printer_print(UpperCasePrinterContext *ctx, const char *str);

Or maybe you want to define the property in the context itself, so you
do:
uppercase_ctx.str = "foobar";

then you have:
void uppercase_printer_print(UpperCasePrinterContext *ctx);

On a typical FFmpeg context you typically do (see
doc/examples/encode.c example):
    // create the context
    c = avcodec_alloc_context3(codec);

    // set parameters, either in the context or by using the av_opt API
    c->bit_rate = 400000;
    c->width = 352;
    c->height = 288;
    c->time_base = (AVRational){1, 25};
    c->framerate = (AVRational){25, 1};
    // ...

    // invoke the methods defined in the context
    ret = avcodec_send_frame(enc_ctx, frame);
    if (ret < 0) {
        fprintf(stderr, "Error sending a frame for encoding\n");
        exit(1);
    }

    while (ret >= 0) {
        ret = avcodec_receive_packet(enc_ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error during encoding\n");
            exit(1);
        }
        ...
        av_packet_unref(pkt);
    }

[...]

Note also that "context" in the FFmpeg jargon is not very specific
because it might be different depending on the implementation, for
example it might not have an av_opt_* interface (for example
libavutil/hash.h).

> +```
> +

> +The `UpperCasePrinterContext` object contains the information that's about
> +the context of the current job (i.e. printing things to standard output).

I find this confusing, it is essentially saying that the context (the
"object") is the context of the current job.

> +Information with a lifetime different than that of the context is moved
> +to the `PrintInfo` object.
> +

> +FFmpeg's main context structures all happen to face some common problems:
> +
> +- querying, setting and getting options
> +- handling "private" internal context, including options for
> +  a particular instance of the generic context
> +- configuring log message verbosity and content
> +

> +FFmpeg gradually converged on the AVClass struct to store this information,
> +then converged on the @ref avoptions "AVOptions" system to manipulate it,
> +so modern code often uses the terms "context", "AVClass context structure"
> +and "AVOptions-enabled struct" interchangeably.  But it is occasionally
> +necessary to distinguish between them - for example, AVMediaCodecContext
> +is a context that does not use AVClass.
> +
> +To understand how this all works, consider some requirements for the
> +`libx264` encoder:
> +
> +- it has to support common encoder options like "bitrate"
> +- it has to support encoder-specific options like "profile"
> +- it has to provide useful feedback about unsupported options
> +
> +Common encoder options like "bitrate" are stored in the AVCodecContext class,
> +while encoder-specific options like "profile" are stored in an X264Context
> +instance in AVCodecContext::priv_data.  These options are then exposed to
> +users through a tree of AVOption objects, which include user-visible help
> +text and machine-readable information about the memory location to read/write
> +each option.  Common @ref avoptions "AVOptions" functionality lets you get

> +and set those values, and provides readable feedback about errors.

> Although X264Context can be set by users,

Can it? The X264Context is defined within the implementation, so it is
not exposed to the user, the only way to set it is through the private
options to be set through the AVCodecContext options through the
av_opt API - in other words you cannot set the context directly as you
do with the "generic" options defined in the AVCodecContext.

> it is not part of the public interface, so new releases can modify
> it without affecting the API version.

[...]
Andrew Sayers April 20, 2024, 12:18 p.m. UTC | #2
On 20/04/2024 08:25, Stefano Sabatini wrote:
> On date Thursday 2024-04-18 16:06:12 +0100, Andrew Sayers wrote:
>> Based largely on the explanation by Stefano Sabatini:
>> https://ffmpeg.org/pipermail/ffmpeg-devel/2024-April/325854.html
>> ---
>>   doc/jargon.md | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++
>>   1 file changed, 96 insertions(+)
>>   create mode 100644 doc/jargon.md
>>
>> diff --git a/doc/jargon.md b/doc/jargon.md
>> new file mode 100644
>> index 0000000000..3b78ffb61f
>> --- /dev/null
>> +++ b/doc/jargon.md
> We currently have a single .md file in doc (for historical reason we
> still stick to texinfo). Also how is this integrated into doxygen?

Doxygen automatically renders all /*.md and /doc/*.md files to pages at [1]
which is the only place I'd know to look for this sort of thing. it seems
like texinfo is more for man pages etc., which would be hard to link from
doxygen?  By the way, a file called "jargon" seemed like a better idea than
e.g. a "design_patterns" file or a section in AVClass.  I've rewritten the
document completely based on your feedback - same markdown file for now,
but happy to move/reformat.

The points below should be addressed by the new patch, so I'll let that
speak for itself.  But there's a general issue that's worth mentioning...

Technically, it sounds like a more accurate description would be "Context
is an FFmpeg convention that has become fairly rigorous over the years".
But IMHO readers would uncharitably read that as "Context is some weird
FFmpeg thing they're stuck with because they picked a pre-OOP language".
Arguing "Context is a design pattern that groups objects by lifespan"
emphasises the lessons a newbie can take from FFmpeg and use elsewhere,
so they get more value from the time they spent reading the document.
I've tried to write the document to start with the more useful argument,
then gradually ease in to the more accurate one.

Note: I previously sent this from the wrong e-mail address - apologies for
spam if the other one makes it past moderation.

[1] https://ffmpeg.org/doxygen/trunk/pages.html

>
>> @@ -0,0 +1,96 @@
>> +# Jargon
>> +
>> +Terms used throughout the code that developers may need to know.
>> +
>> +@anchor context
>> +
>> +## Context
>> +
>> +A design pattern that stores the context (e.g. configuration) for a series
>> +of operations in a "context" structure, and moves other information elsewhere.
>> +
>> +Consider a trivial program to print uppercase text:
>> +
>> +```c
>> +/*
>> + * Contextual information about where to print a series of messages
>> + */
> Style:
> /**
>   * Contextual information about where to print a series of messages
>   */
>
>> +struct UpperCasePrinterContext {
>> +    FILE* out;
> here and below, use:
> VAR *out;
>
> for overall consistency with the project style.
>
>> +};
>> +
>> +/*
>> + * Extra information about messages to print.
>> + * This could be used multiple times in a single context,
>> + * or reused several times across multiple contexts.
>> + */
>> +struct PrintInfo {
>> +    char* str;
>> +};
>> +
>> +void print(
>> +    struct UpperCasePrinterContext * ctx,
>> +    struct PrintInfo * info
>> +) {
>> +    for ( char* c = info->str; *c; ++c ) {
>> +        char C = toupper(*c);
>> +        fwrite( &C, 1, 1, ctx->out );
>> +    }
>> +}
>> +
>> +int main()
>> +{
>> +    struct PrintInfo hello, world;
>> +    struct UpperCasePrinterContext ctx;
>> +
>> +    hello.str = "hello, ";
>> +    world.str = "world!\n";
>> +
>> +    ctx.out = stdout;
>> +
>> +    print( &ctx, &hello );
>> +    print( &ctx, &world );
>> +
>> +    return 0;
>> +}
> I'm not sure this is a fitting example. Usually the context is a
> public structure and the internal context (which corresponds to the
> PrintInfo struct) is private to the implementation. In this case the
> API user is not interested at all at its implmentation.
>
> You can think the context provides the "object"/instance where some
> operations are done - this is alike in object oriented programming,
> where the context corresponds to the self, so that you create/allocate
> the object, initialize it, and invoke operations on the object.
>
> So using this analogy, the example would be:
>
> struct UpperCasePrinterContext {...};
>
> // this corresponds to a "method" defined on the context/object
> void uppercase_printer_print(UpperCasePrinterContext *ctx, const char *str);
>
> Or maybe you want to define the property in the context itself, so you
> do:
> uppercase_ctx.str = "foobar";
>
> then you have:
> void uppercase_printer_print(UpperCasePrinterContext *ctx);
>
> On a typical FFmpeg context you typically do (see
> doc/examples/encode.c example):
>      // create the context
>      c = avcodec_alloc_context3(codec);
>
>      // set parameters, either in the context or by using the av_opt API
>      c->bit_rate = 400000;
>      c->width = 352;
>      c->height = 288;
>      c->time_base = (AVRational){1, 25};
>      c->framerate = (AVRational){25, 1};
>      // ...
>
>      // invoke the methods defined in the context
>      ret = avcodec_send_frame(enc_ctx, frame);
>      if (ret < 0) {
>          fprintf(stderr, "Error sending a frame for encoding\n");
>          exit(1);
>      }
>
>      while (ret >= 0) {
>          ret = avcodec_receive_packet(enc_ctx, pkt);
>          if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
>              return;
>          else if (ret < 0) {
>              fprintf(stderr, "Error during encoding\n");
>              exit(1);
>          }
>          ...
>          av_packet_unref(pkt);
>      }
>
> [...]
>
> Note also that "context" in the FFmpeg jargon is not very specific
> because it might be different depending on the implementation, for
> example it might not have an av_opt_* interface (for example
> libavutil/hash.h).
>
>> +```
>> +
>> +The `UpperCasePrinterContext` object contains the information that's about
>> +the context of the current job (i.e. printing things to standard output).
> I find this confusing, it is essentially saying that the context (the
> "object") is the context of the current job.
>
>> +Information with a lifetime different than that of the context is moved
>> +to the `PrintInfo` object.
>> +
>> +FFmpeg's main context structures all happen to face some common problems:
>> +
>> +- querying, setting and getting options
>> +- handling "private" internal context, including options for
>> +  a particular instance of the generic context
>> +- configuring log message verbosity and content
>> +
>> +FFmpeg gradually converged on the AVClass struct to store this information,
>> +then converged on the @ref avoptions "AVOptions" system to manipulate it,
>> +so modern code often uses the terms "context", "AVClass context structure"
>> +and "AVOptions-enabled struct" interchangeably.  But it is occasionally
>> +necessary to distinguish between them - for example, AVMediaCodecContext
>> +is a context that does not use AVClass.
>> +
>> +To understand how this all works, consider some requirements for the
>> +`libx264` encoder:
>> +
>> +- it has to support common encoder options like "bitrate"
>> +- it has to support encoder-specific options like "profile"
>> +- it has to provide useful feedback about unsupported options
>> +
>> +Common encoder options like "bitrate" are stored in the AVCodecContext class,
>> +while encoder-specific options like "profile" are stored in an X264Context
>> +instance in AVCodecContext::priv_data.  These options are then exposed to
>> +users through a tree of AVOption objects, which include user-visible help
>> +text and machine-readable information about the memory location to read/write
>> +each option.  Common @ref avoptions "AVOptions" functionality lets you get
>> +and set those values, and provides readable feedback about errors.
>> Although X264Context can be set by users,
> Can it? The X264Context is defined within the implementation, so it is
> not exposed to the user, the only way to set it is through the private
> options to be set through the AVCodecContext options through the
> av_opt API - in other words you cannot set the context directly as you
> do with the "generic" options defined in the AVCodecContext.
>
>> it is not part of the public interface, so new releases can modify
>> it without affecting the API version.
> [...]
> _______________________________________________
> 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".
>
Stefano Sabatini April 20, 2024, 4:13 p.m. UTC | #3
On date Saturday 2024-04-20 13:18:29 +0100, Andrew Sayers wrote:
> On 20/04/2024 08:25, Stefano Sabatini wrote:
> > On date Thursday 2024-04-18 16:06:12 +0100, Andrew Sayers wrote:
> > > Based largely on the explanation by Stefano Sabatini:
> > > https://ffmpeg.org/pipermail/ffmpeg-devel/2024-April/325854.html
> > > ---
> > >   doc/jargon.md | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++
> > >   1 file changed, 96 insertions(+)
> > >   create mode 100644 doc/jargon.md
> > > 
> > > diff --git a/doc/jargon.md b/doc/jargon.md
> > > new file mode 100644
> > > index 0000000000..3b78ffb61f
> > > --- /dev/null
> > > +++ b/doc/jargon.md
> > We currently have a single .md file in doc (for historical reason we
> > still stick to texinfo). Also how is this integrated into doxygen?
> 

> Doxygen automatically renders all /*.md and /doc/*.md files to pages at [1]
> which is the only place I'd know to look for this sort of thing. it seems
> like texinfo is more for man pages etc., which would be hard to link from
> doxygen?  By the way, a file called "jargon" seemed like a better idea than
> e.g. a "design_patterns" file or a section in AVClass.  I've rewritten the
> document completely based on your feedback - same markdown file for now,
> but happy to move/reformat.

If this is the case, probably we should move the files to a dedicated
directory (doxygen?) to avoid to mix too many things in the same
bundle (can be done as a separated patch though since we might think
about what we really want to include in the doxygen docs).

> The points below should be addressed by the new patch, so I'll let that
> speak for itself.  But there's a general issue that's worth mentioning...
> 

> Technically, it sounds like a more accurate description would be "Context
> is an FFmpeg convention that has become fairly rigorous over the years".
> But IMHO readers would uncharitably read that as "Context is some weird
> FFmpeg thing they're stuck with because they picked a pre-OOP language".
> Arguing "Context is a design pattern that groups objects by lifespan"
> emphasises the lessons a newbie can take from FFmpeg and use elsewhere,
> so they get more value from the time they spent reading the document.
> I've tried to write the document to start with the more useful argument,
> then gradually ease in to the more accurate one.

But, I don't think this is really something very rigourous, because it
is used in different "contexts" with sligthly different features
(e.g. you can have a structure named "context" but without an AVClass
or options).

And it's not really about lifespan, and not really specific for
FFmpeg, in C programming this is used to provide a "context" for
several "methods" operating on a given struct - so basically a light
implementation of an object-oriented-based API.

[...]
diff mbox series

Patch

diff --git a/doc/jargon.md b/doc/jargon.md
new file mode 100644
index 0000000000..3b78ffb61f
--- /dev/null
+++ b/doc/jargon.md
@@ -0,0 +1,96 @@ 
+# Jargon
+
+Terms used throughout the code that developers may need to know.
+
+@anchor context
+
+## Context
+
+A design pattern that stores the context (e.g. configuration) for a series
+of operations in a "context" structure, and moves other information elsewhere.
+
+Consider a trivial program to print uppercase text:
+
+```c
+/*
+ * Contextual information about where to print a series of messages
+ */
+struct UpperCasePrinterContext {
+    FILE* out;
+};
+
+/*
+ * Extra information about messages to print.
+ * This could be used multiple times in a single context,
+ * or reused several times across multiple contexts.
+ */
+struct PrintInfo {
+    char* str;
+};
+
+void print(
+    struct UpperCasePrinterContext * ctx,
+    struct PrintInfo * info
+) {
+    for ( char* c = info->str; *c; ++c ) {
+        char C = toupper(*c);
+        fwrite( &C, 1, 1, ctx->out );
+    }
+}
+
+int main()
+{
+    struct PrintInfo hello, world;
+    struct UpperCasePrinterContext ctx;
+
+    hello.str = "hello, ";
+    world.str = "world!\n";
+
+    ctx.out = stdout;
+
+    print( &ctx, &hello );
+    print( &ctx, &world );
+
+    return 0;
+}
+```
+
+The `UpperCasePrinterContext` object contains the information that's about
+the context of the current job (i.e. printing things to standard output).
+Information with a lifetime different than that of the context is moved
+to the `PrintInfo` object.
+
+FFmpeg's main context structures all happen to face some common problems:
+
+- querying, setting and getting options
+- handling "private" internal context, including options for
+  a particular instance of the generic context
+- configuring log message verbosity and content
+
+FFmpeg gradually converged on the AVClass struct to store this information,
+then converged on the @ref avoptions "AVOptions" system to manipulate it,
+so modern code often uses the terms "context", "AVClass context structure"
+and "AVOptions-enabled struct" interchangeably.  But it is occasionally
+necessary to distinguish between them - for example, AVMediaCodecContext
+is a context that does not use AVClass.
+
+To understand how this all works, consider some requirements for the
+`libx264` encoder:
+
+- it has to support common encoder options like "bitrate"
+- it has to support encoder-specific options like "profile"
+- it has to provide useful feedback about unsupported options
+
+Common encoder options like "bitrate" are stored in the AVCodecContext class,
+while encoder-specific options like "profile" are stored in an X264Context
+instance in AVCodecContext::priv_data.  These options are then exposed to
+users through a tree of AVOption objects, which include user-visible help
+text and machine-readable information about the memory location to read/write
+each option.  Common @ref avoptions "AVOptions" functionality lets you get
+and set those values, and provides readable feedback about errors.  Although
+X264Context can be set by users, it is not part of the public interface,
+so new releases can modify it without affecting the API version.
+
+FFmpeg itself uses context structures to handle FFmpeg-specific problems,
+but the design pattern, as well as the AVClass and @ref avoptions "AVOptions"
+implementations, are general solutions you can use for any purpose.