diff mbox series

[FFmpeg-devel,2/8] lavu: new AVWriter API

Message ID 20230428095508.221826-2-george@nsup.org
State New
Headers show
Series [FFmpeg-devel,1/8] lavu: add macros to help making future-proof structures | expand

Checks

Context Check Description
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Nicolas George April 28, 2023, 9:55 a.m. UTC
Signed-off-by: Nicolas George <george@nsup.org>
---
 doc/avwriter_intro.md | 186 ++++++++++++++++
 libavutil/Makefile    |   2 +-
 libavutil/writer.c    | 458 +++++++++++++++++++++++++++++++++++++++
 libavutil/writer.h    | 488 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1133 insertions(+), 1 deletion(-)
 create mode 100644 doc/avwriter_intro.md
 create mode 100644 libavutil/writer.c
 create mode 100644 libavutil/writer.h

Comments

Rodney Baker April 28, 2023, 10:37 a.m. UTC | #1
I'm not normally a reviewer, but I noticed a few minor grammatical things that 
stood out - hope this is OK. 

Regards,
Rodney.

On Friday, 28 April 2023 7:25:02 PM ACST Nicolas George wrote:
> Signed-off-by: Nicolas George <george@nsup.org>
> ---
>  doc/avwriter_intro.md | 186 ++++++++++++++++
>  libavutil/Makefile    |   2 +-
>  libavutil/writer.c    | 458 +++++++++++++++++++++++++++++++++++++++
>  libavutil/writer.h    | 488 ++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 1133 insertions(+), 1 deletion(-)
>  create mode 100644 doc/avwriter_intro.md
>  create mode 100644 libavutil/writer.c
>  create mode 100644 libavutil/writer.h
> 
> diff --git a/doc/avwriter_intro.md b/doc/avwriter_intro.md
> new file mode 100644
> index 0000000000..0e092246a2
> --- /dev/null
> +++ b/doc/avwriter_intro.md
> @@ -0,0 +1,186 @@
> +# Quick start guide for AVWriter
> +
> +AVWriter is an API to unify functions returning strings and to make
> building +strings from parts easier. In this document, you will find an
> introduction +on how to *use* AVWriter, mostly in the form of code snippets
> compating +mainstream C solutions with their AVWriter counterpart.

Nit - s/compating/comparing/

[...]
> +
> +**Note:** AVWriter is 8-bit clean, the strings it manipulates can be

Use a hyphen or a semicolon rather than a comma after "clean". 

> buffers +of binary data. The documentation is mostly written uing the
> vocabulary of +strings for simplicity.
> +
> +In mainstream C, a function that needs to return a string usually have two
> +options: either they accept pointer to a buffer that they fill or they
> +allocate the buffer themselves and return it. Both these options have
> +drawbacks, which one is best depends on the circumstances of the caller.

Semicolon instead of comma after "drawbacks".

> +
> +AVWriter lets the caller choose the option best suited to the
> circumstances, +among a small variety of built-in options or custom

Drop comma after "circumstances". 

> implementations, +including on-the-fly compression or escaping and direct
> writing to a file. +The first built-in implementation, where the strings is
> stored in a +dynamically-allocated buffer, includes the optimization that
> small strings +are kept on the stack.
> +
> +AVWriter also makes the work of the called function easier by providing
> +convenient functions to append to the string that completely wrap error
> +checks. Note that it only works for strings created as streams; functions
> +that need random access to the string already built still need to manage
> +their own buffers; some AVWriter implementations can still help for that.

Full stop after "buffers" (instead of semicolon - you've already used one 
previously in the same sentence). 

[...]
Nicolas George April 28, 2023, 11:20 a.m. UTC | #2
Rodney Baker (12023-04-28):
> I'm not normally a reviewer, but I noticed a few minor grammatical things that 
> stood out - hope this is OK. 

Thanks, it is absolutely useful.

> Nit - s/compating/comparing/

Fixed.

> > +**Note:** AVWriter is 8-bit clean, the strings it manipulates can be
> Use a hyphen or a semicolon rather than a comma after "clean". 

After consideration, a semicolon would be too strong; a hyphen would be
strange, too literary.

> > +In mainstream C, a function that needs to return a string usually have two
> > +options: either they accept pointer to a buffer that they fill or they
> > +allocate the buffer themselves and return it. Both these options have
> > +drawbacks, which one is best depends on the circumstances of the caller.
> Semicolon instead of comma after "drawbacks".

Same here, I find a semicolon would be too strong.

> Drop comma after "circumstances". 

Done.

> > +AVWriter also makes the work of the called function easier by providing
> > +convenient functions to append to the string that completely wrap error
> > +checks. Note that it only works for strings created as streams; functions
> > +that need random access to the string already built still need to manage
> > +their own buffers; some AVWriter implementations can still help for that.
> Full stop after "buffers" (instead of semicolon - you've already used one 
> previously in the same sentence). 

Disagree on this one: the last part should still be in the sentence that
starts with “Note that”. There is no problem in having multiple
semicolons to separate parts on the same level.

Regards,
Rémi Denis-Courmont May 2, 2023, 3:53 p.m. UTC | #3
Le perjantaina 28. huhtikuuta 2023, 12.55.02 EEST Nicolas George a écrit :
> +/**************************************************************************
> * + * Generic API
> +
> ***************************************************************************
> / +
> +#define FIELDOK(st, f) ((char *)(&(st)->f + 1) <= (char *)(st) +
> (st)->self_size) +
> +#define methods_assert_abi(methods) av_assert0(FIELDOK(methods, flush))
> +
> +static void printf_unchecked(AVWriter wr, const char *fmt, ...)
> +{
> +    va_list va;
> +
> +    va_start(va, fmt);
> +    wr.methods->vprintf(wr, fmt, va);
> +    va_end(va);
> +}

Indirecting the printf function seems pretty pointless. The last thing you 
want are different implementations of printf() with different limitations. And 
it makes writing different backends needlessly complex, while you could just 
use vasprintf().

Typically, with this kind of abstraction, only the raw bytes writing is 
abstracted away. Examples include funopen() and fopencookie().

As for hypothetical use cases whence vasprintf() wouldb e "too slow", then 
should not use printf()-style or function pointers to begin with. Besides if 
you _really_ want to avoid the heap allocation, you can also use fopencookie() 
on systems that provide it or equivalent.

> +void av_writer_write(AVWriter wr, const char *data, size_t size)
> +{
> +    methods_assert_abi(wr.methods);
> +    if (wr.methods->write) {
> +        wr.methods->write(wr, data, size);
> +    } else if (wr.methods->get_buffer) {
> +        size_t buf_size;
> +        char *buf = wr.methods->get_buffer(wr, size, &buf_size);
> +        if (buf_size > 0)
> +            memcpy(buf, data, FFMIN(size, buf_size));
> +        write_or_discard(wr, buf_size, size);

That sounds like it belongs in whichever backend actually wants to heap-
allocate the output buffer, not the frontend.

> +    } else if (wr.methods->vprintf) {
> +        size_t i;
> +        for (i = 0; i < size; i++)
> +            printf_unchecked(wr, "%c", data[i]);

This is an abstraction inversion (and also highly inefficient) + what I noted 
above.

> +    } else {
> +        av_writer_impossible(wr, "av_writer_write()");
> +    }
> +}
> +
> +void av_writer_print(AVWriter wr, const char *str)
> +{

This is an analogue of puts/fputs, not "print".

> +    av_writer_write(wr, str, strlen(str));
> +}
> +
> +void av_writer_printf(AVWriter wr, const char *fmt, ...)
> +{
> +    va_list va;
> +
> +    va_start(va, fmt);
> +    av_writer_vprintf(wr, fmt, va);
> +    va_end(va);
> +}
> +
> +void av_writer_vprintf(AVWriter wr, const char *fmt, va_list va)
> +{
> +    methods_assert_abi(wr.methods);
> +    if (wr.methods->vprintf) {
> +        wr.methods->vprintf(wr, fmt, va);
> +    } else if (wr.methods->get_buffer) {
> +        size_t buf_size;
> +        char *buf = wr.methods->get_buffer(wr, 128, &buf_size);
> +        va_list va2;
> +        int ret;
> +        va_copy(va2, va);
> +        ret = vsnprintf(buf, buf_size, fmt, va2);
> +        va_end(va2);
> +        if (ret < 0)
> +            return;
> +        if ((size_t)ret + 1 > buf_size) {
> +            buf = wr.methods->get_buffer(wr, ret + 1, &buf_size);
> +            ret = vsnprintf(buf, buf_size, fmt, va);
> +            if (ret < 0)
> +                return;
> +        }
> +        write_or_discard(wr, buf_size, ret);
> +    } else if (wr.methods->write) {
> +        AVBPrint bp;
> +        av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
> +        av_vbprintf(&bp, fmt, va);
> +        if (av_bprint_is_complete(&bp)) {
> +            wr.methods->write(wr, bp.str, bp.len);
> +        } else {
> +            wr.methods->write(wr, bp.str, bp.size - 1);
> +            if (wr.methods->notify_discard)
> +                wr.methods->notify_discard(wr, bp.len - bp.size + 1);
> +        }
> +        av_bprint_finalize(&bp, NULL);
> +    } else {
> +        av_writer_impossible(wr, "av_writer_vprintf()");
> +    }
> +}

Same problems and overengineering as above.

> +
> +void av_writer_add_chars(AVWriter wr, char c, size_t n)
> +{
> +    char buf[512];
> +    size_t m;
> +
> +    m = FFMIN(n, sizeof(buf) - 1);
> +    memset(buf, c, m);
> +    while (n) {
> +        av_writer_write(wr, buf, m);
> +        n -= m;
> +        m = FFMIN(n, sizeof(buf) - 1);
> +    }
> +}
> +
> +void av_writer_flush(AVWriter wr)
> +{
> +    methods_assert_abi(wr.methods);
> +    if (wr.methods->flush)
> +        wr.methods->flush(wr);
> +}
> +
> +int av_writer_get_error(AVWriter wr, int self_only)
> +{
> +    methods_assert_abi(wr.methods);
> +    if (wr.methods->get_error)
> +        return wr.methods->get_error(wr, self_only);
> +    return 0;
> +}
> +
Nicolas George May 2, 2023, 4:53 p.m. UTC | #4
Rémi Denis-Courmont (12023-05-02):
> Indirecting the printf function seems pretty pointless. The last thing you 

Please re-read the code, there is no other way of obtaining a va_list
from an actual argument than making a call to a vararg function.

> want are different implementations of printf() with different limitations. And 
> it makes writing different backends needlessly complex, while you could just 
> use vasprintf().
> 
> Typically, with this kind of abstraction, only the raw bytes writing is 
> abstracted away. Examples include funopen() and fopencookie().
> 
> As for hypothetical use cases whence vasprintf() wouldb e "too slow", then 
> should not use printf()-style or function pointers to begin with. Besides if 
> you _really_ want to avoid the heap allocation, you can also use fopencookie() 
> on systems that provide it or equivalent.

Being portable and not doing dynamic allocation are two major points of
the design, so you will excuse me if I pass on that suggestion.

> That sounds like it belongs in whichever backend actually wants to heap-
> allocate the output buffer, not the frontend.

There is nothing about heap allocation in this code. And if code can be
in the framework common to all backends, then it is where it belongs,
not duplicated in each backend.

> This is an abstraction inversion (and also highly inefficient) + what I noted 
> above.

See above, it is necessary. (If you think it is not, try doing better.)

> This is an analogue of puts/fputs, not "print".

This is an analogue of puts, fputs and print, and I decided to call it
print.

> Same problems and overengineering as above.

I think you are missing something important about the purpose of this
code, but I cannot guess what exactly. Please be more detailed about why
you think it is overengineered and point how you would do it simpler.

Thanks for the comments.
Rémi Denis-Courmont May 2, 2023, 6:29 p.m. UTC | #5
Le tiistaina 2. toukokuuta 2023, 19.53.43 EEST Nicolas George a écrit :
> Rémi Denis-Courmont (12023-05-02):
> > Indirecting the printf function seems pretty pointless. The last thing you
> 
> Please re-read the code, there is no other way of obtaining a va_list
> from an actual argument than making a call to a vararg function.

Please re-read the comments. You are totally misses the point.

> > As for hypothetical use cases whence vasprintf() wouldb e "too slow", then
> > should not use printf()-style or function pointers to begin with. Besides
> > if you _really_ want to avoid the heap allocation, you can also use
> > fopencookie() on systems that provide it or equivalent.
> 
> Being portable and not doing dynamic allocation are two major points of
> the design, so you will excuse me if I pass on that suggestion.

Well, I'll add myself to the already long list of people publicly objecting to 
your patchset then.

> There is nothing about heap allocation in this code. And if code can be
> in the framework common to all backends, then it is where it belongs,
> not duplicated in each backend.

This makes zero sense. Why the hell would you want to have more than one heap-
allocation backend.

> > This is an abstraction inversion (and also highly inefficient) + what I
> > noted above.
> 
> See above, it is necessary. (If you think it is not, try doing better.)

Well duh, I wrote the VLC equivalent years ago. And glibc or FreeBSD libc did 
them more than a decade before I did.

> > This is an analogue of puts/fputs, not "print".
> 
> This is an analogue of puts, fputs and print, and I decided to call it
> print.

And I decided to object to that on the basis that it's dumb and confusing.

> I think you are missing something important about the purpose of this
> code, but I cannot guess what exactly. Please be more detailed about why
> you think it is overengineered and point how you would do it simpler.

I think I was pretty damn clear and you are just deliberately making up reason 
to ignore other people's comments (not just mine).

-1
Nicolas George May 2, 2023, 6:36 p.m. UTC | #6
Rémi Denis-Courmont (12023-05-02):
> Please re-read the comments. You are totally misses the point.

I confess so, indeed, I completely failed to understand your point after
reading your comment multiple times.

> Well, I'll add myself to the already long list of people publicly objecting to 
> your patchset then.

I will read your technical comments then.

> > There is nothing about heap allocation in this code. And if code can be
> > in the framework common to all backends, then it is where it belongs,
> > not duplicated in each backend.
> This makes zero sense. Why the hell would you want to have more than one heap-
> allocation backend.

FFmpeg only provides one. Applications can decide to provide their own
if they want. That is the point.

> Well duh, I wrote the VLC equivalent years ago. And glibc or FreeBSD libc did 
> them more than a decade before I did.

Pointer?

> And I decided to object to that on the basis that it's dumb and confusing.

The only thing dumb and confusing I see here is the mail I am answering
to.

> I think I was pretty damn clear and you are just deliberately making up reason 
> to ignore other people's comments (not just mine).

Think what you will about me.
Rémi Denis-Courmont May 2, 2023, 6:46 p.m. UTC | #7
Le tiistaina 2. toukokuuta 2023, 21.36.53 EEST Nicolas George a écrit :
> The only thing dumb and confusing I see here is the mail I am answering
> to.

> I must say, I am impressed by the rudeness and arrogance of such a
> comment without even looking at the code itself.

I think that that is pretty rich coming from you, just judging by your earlier 
responses to other people. That's as insulting as it is unsurprising.

I don't need to remind you that there is a TC here.
Nicolas George May 2, 2023, 6:47 p.m. UTC | #8
Rémi Denis-Courmont (12023-05-02):
> I think that that is pretty rich coming from you, just judging by your earlier 
> responses to other people.

Pointers?
diff mbox series

Patch

diff --git a/doc/avwriter_intro.md b/doc/avwriter_intro.md
new file mode 100644
index 0000000000..0e092246a2
--- /dev/null
+++ b/doc/avwriter_intro.md
@@ -0,0 +1,186 @@ 
+# Quick start guide for AVWriter
+
+AVWriter is an API to unify functions returning strings and to make building
+strings from parts easier. In this document, you will find an introduction
+on how to *use* AVWriter, mostly in the form of code snippets compating
+mainstream C solutions with their AVWriter counterpart.
+
+**Note:** AVWriter is 8-bit clean, the strings it manipulates can be buffers
+of binary data. The documentation is mostly written uing the vocabulary of
+strings for simplicity.
+
+In mainstream C, a function that needs to return a string usually have two
+options: either they accept pointer to a buffer that they fill or they
+allocate the buffer themselves and return it. Both these options have
+drawbacks, which one is best depends on the circumstances of the caller.
+
+AVWriter lets the caller choose the option best suited to the circumstances,
+among a small variety of built-in options or custom implementations,
+including on-the-fly compression or escaping and direct writing to a file.
+The first built-in implementation, where the strings is stored in a
+dynamically-allocated buffer, includes the optimization that small strings
+are kept on the stack.
+
+AVWriter also makes the work of the called function easier by providing
+convenient functions to append to the string that completely wrap error
+checks. Note that it only works for strings created as streams; functions
+that need random access to the string already built still need to manage
+their own buffers; some AVWriter implementations can still help for that.
+
+## I want a `char*` buffer, the function wants an AVWriter
+
+Old-style code:
+
+```
+        char *buf;
+        ret = av_something_to_string(&buf, something);
+        if (ret < 0)
+            die("Failed");
+        use_string(buf);
+        av_freep(&buf);
+```
+
+Equivalent code with AVWriter:
+
+```
+        AVWriter wr = av_dynbuf_writer();
+        av_something_write(wr, something, 0);
+        if (av_writer_get_error(wr, 0) < 0)
+            die("Failed");
+        use_string(av_dynbuf_writer_get_data(wr, NULL));
+        av_dynbuf_writer_finalize(wr, NULL, NULL);
+```
+
+If the string is small enough, no dynamic memory allocation happens.
+
+The NULL to `av_dynbuf_writer_get_data()` can be used to retrieve the size
+of the data in the buffer.
+
+Calling `av_writer_get_error()` is mandatory.
+
+## I want a *persistent* `char*` buffer, the function wants an AVWriter
+
+Old-style code:
+
+```
+        char *buf;
+        ret = av_something_to_string(&buf, something);
+        if (ret < 0)
+            die("Failed");
+        ctx->string = buf;
+```
+
+Equivalent code with AVWriter:
+
+```
+        AVWriter wr = av_dynbuf_writer();
+        av_something_write(wr, something, 0);
+        ret = av_dynbuf_writer_finalize(wr, &ctx->string, NULL);
+        if (ret < 0)
+            die("Failed");
+```
+
+## I have a `char[]` buffer, the function wants an AVWriter
+
+Old-style code:
+
+```
+        char buf[BUF_SIZE];
+        av_something_to_string(buf, sizeof(buf), something);
+        use_string(buf);
+```
+
+Equivalent code with AVWriter:
+
+```
+        char buf[BUF_SIZE];
+        av_something_write(av_buf_writer(buf, sizeof(buf)), something, 0);
+        use_string(buf);
+```
+
+## I need to build a string from parts
+
+Old-style code:
+
+```
+        char buf[1024];
+        int pos = 0;
+        pos += snprintf(buf + pos, sizeof(buf) - pos,
+                        "Stream %d: ", i);
+        av_get_channel_layout_string(buf + pos, sizeof(buf) - pos,
+                                     nb, layout);
+        pos += strlen(buf + pos);
+        pos += snprintf(buf + pos, sizeof(buf) - pos,
+                        ", %s", av_get_sample_fmt_name(fmt));
+```
+
+Note: this code could overflow the buffer.
+
+Equivalent code with AVWriter:
+
+```
+        AVWriter wr = av_dynbuf_writer();
+        av_writer_printf(wr, "Stream %d: ", i);
+        av_channel_layout_write(wr, nb, layout, 0);
+        av_writer_printf(wr, ", %s", av_get_sample_fmt_name(fmt));
+```
+
+See the first example on how to access the resulting string.
+
+Note: this is very similar to using AVBPrint; from this side, AVWriter
+replaces AVBPrint.
+
+## I am writing the function that returns a string
+
+Old-style code:
+
+```
+int myfunction(char **buf, something arguments)
+{
+    *buf = malloc(enough room);
+```
+
+or:
+
+```
+int myfunction(char *buf, size_t buf_size, something arguments)
+{
+    …
+    if (buf_size < pos + len)
+        return -1;
+```
+
+Equivalent with AVWriter:
+
+```
+void myfunction(AVWriter wr, something arguments)
+{
+```
+
+… and write on the AVWriter, see the previous section.
+
+## I want to write directly to a file
+
+If the file is a stdio `FILE*`:
+
+```
+        av_something_write(av_stdio_writer(file), something, 0);
+```
+
+(Checking for error using `ferror()` is still your responsibility.)
+
+If the file is a libavformat `AVIOContext*`, the implementation is yet to
+come.
+
+## I want to write into something specific to my case
+
+- … into a Java/Perl/whatever string.
+- … into a GUI text widget.
+- … into a data structure provided by another library.
+- … to compress on the fly.
+- … to escape special characters on the fly.
+- … to hash the data on the fly and get the digest in the end.
+
+All these are possible with AVWriter. When doing something on the fly, it
+will usually involve writing the result into another AVWriter, like a
+filter.
diff --git a/libavutil/Makefile b/libavutil/Makefile
index dc9012f9a8..eb8de1dfbc 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -182,7 +182,7 @@  OBJS = adler32.o                                                        \
        version.o                                                        \
        video_enc_params.o                                               \
        film_grain_params.o                                              \
-
+       writer.o                                                         \
 
 OBJS-$(CONFIG_CUDA)                     += hwcontext_cuda.o
 OBJS-$(CONFIG_D3D11VA)                  += hwcontext_d3d11va.o
diff --git a/libavutil/writer.c b/libavutil/writer.c
new file mode 100644
index 0000000000..1d1cbd6525
--- /dev/null
+++ b/libavutil/writer.c
@@ -0,0 +1,458 @@ 
+/*
+ * Copyright (c) 2023 Nicolas George
+ *
+ * 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
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <limits.h>
+#include "avassert.h"
+#include "error.h"
+#include "log.h"
+#include "writer.h"
+
+/***************************************************************************
+ * Generic API
+ ***************************************************************************/
+
+#define FIELDOK(st, f) ((char *)(&(st)->f + 1) <= (char *)(st) + (st)->self_size)
+
+#define methods_assert_abi(methods) av_assert0(FIELDOK(methods, flush))
+
+static void printf_unchecked(AVWriter wr, const char *fmt, ...)
+{
+    va_list va;
+
+    va_start(va, fmt);
+    wr.methods->vprintf(wr, fmt, va);
+    va_end(va);
+}
+
+static void write_or_discard(AVWriter wr, size_t buf_size, size_t write_size)
+{
+    av_assert0(wr.methods->advance_buffer);
+    wr.methods->advance_buffer(wr, FFMIN(buf_size, write_size));
+    if (write_size > buf_size && wr.methods->notify_discard)
+        wr.methods->notify_discard(wr, write_size - buf_size);
+}
+
+static void av_writer_impossible(AVWriter wr, const char *message)
+{
+    methods_assert_abi(wr.methods);
+    if (wr.methods->notify_impossible)
+        wr.methods->notify_impossible(wr, message);
+    else
+        av_log(NULL, AV_LOG_ERROR, "Operation impossible with %s: %s\n",
+               wr.methods->name, message);
+}
+
+void av_writer_write(AVWriter wr, const char *data, size_t size)
+{
+    methods_assert_abi(wr.methods);
+    if (wr.methods->write) {
+        wr.methods->write(wr, data, size);
+    } else if (wr.methods->get_buffer) {
+        size_t buf_size;
+        char *buf = wr.methods->get_buffer(wr, size, &buf_size);
+        if (buf_size > 0)
+            memcpy(buf, data, FFMIN(size, buf_size));
+        write_or_discard(wr, buf_size, size);
+    } else if (wr.methods->vprintf) {
+        size_t i;
+        for (i = 0; i < size; i++)
+            printf_unchecked(wr, "%c", data[i]);
+    } else {
+        av_writer_impossible(wr, "av_writer_write()");
+    }
+}
+
+void av_writer_print(AVWriter wr, const char *str)
+{
+    av_writer_write(wr, str, strlen(str));
+}
+
+void av_writer_printf(AVWriter wr, const char *fmt, ...)
+{
+    va_list va;
+
+    va_start(va, fmt);
+    av_writer_vprintf(wr, fmt, va);
+    va_end(va);
+}
+
+void av_writer_vprintf(AVWriter wr, const char *fmt, va_list va)
+{
+    methods_assert_abi(wr.methods);
+    if (wr.methods->vprintf) {
+        wr.methods->vprintf(wr, fmt, va);
+    } else if (wr.methods->get_buffer) {
+        size_t buf_size;
+        char *buf = wr.methods->get_buffer(wr, 128, &buf_size);
+        va_list va2;
+        int ret;
+        va_copy(va2, va);
+        ret = vsnprintf(buf, buf_size, fmt, va2);
+        va_end(va2);
+        if (ret < 0)
+            return;
+        if ((size_t)ret + 1 > buf_size) {
+            buf = wr.methods->get_buffer(wr, ret + 1, &buf_size);
+            ret = vsnprintf(buf, buf_size, fmt, va);
+            if (ret < 0)
+                return;
+        }
+        write_or_discard(wr, buf_size, ret);
+    } else if (wr.methods->write) {
+        AVBPrint bp;
+        av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+        av_vbprintf(&bp, fmt, va);
+        if (av_bprint_is_complete(&bp)) {
+            wr.methods->write(wr, bp.str, bp.len);
+        } else {
+            wr.methods->write(wr, bp.str, bp.size - 1);
+            if (wr.methods->notify_discard)
+                wr.methods->notify_discard(wr, bp.len - bp.size + 1);
+        }
+        av_bprint_finalize(&bp, NULL);
+    } else {
+        av_writer_impossible(wr, "av_writer_vprintf()");
+    }
+}
+
+void av_writer_add_chars(AVWriter wr, char c, size_t n)
+{
+    char buf[512];
+    size_t m;
+
+    m = FFMIN(n, sizeof(buf) - 1);
+    memset(buf, c, m);
+    while (n) {
+        av_writer_write(wr, buf, m);
+        n -= m;
+        m = FFMIN(n, sizeof(buf) - 1);
+    }
+}
+
+void av_writer_flush(AVWriter wr)
+{
+    methods_assert_abi(wr.methods);
+    if (wr.methods->flush)
+        wr.methods->flush(wr);
+}
+
+int av_writer_get_error(AVWriter wr, int self_only)
+{
+    methods_assert_abi(wr.methods);
+    if (wr.methods->get_error)
+        return wr.methods->get_error(wr, self_only);
+    return 0;
+}
+
+/***************************************************************************
+ * AVBufWriter - write to pre-allocated memory
+ ***************************************************************************/
+
+#define buf_writer_assert_abi(bwr) av_assert0(FIELDOK(bwr, pos))
+
+static size_t buf_writer_room(AVBufWriter *bwr)
+{
+    return bwr->pos < bwr->size ? bwr->size - bwr->pos - 1 : 0;
+}
+
+static void buf_writer_write(AVWriter wr, const char *data, size_t size)
+{
+    AVBufWriter *bwr = wr.obj;
+
+    av_assert0(av_buf_writer_check(wr));
+    buf_writer_assert_abi(bwr);
+    size = FFMIN(buf_writer_room(bwr), size);
+    memcpy(bwr->buf + bwr->pos, data, size);
+    bwr->pos += size;
+    bwr->buf[bwr->pos] = 0;
+}
+
+static void buf_writer_vprintf(AVWriter wr, const char *fmt, va_list va)
+{
+    AVBufWriter *bwr = wr.obj;
+    int ret;
+
+    av_assert0(av_buf_writer_check(wr));
+    buf_writer_assert_abi(bwr);
+    ret = vsnprintf(bwr->buf + bwr->pos, buf_writer_room(bwr) + 1, fmt, va);
+    if (ret > 0)
+        bwr->pos += ret;
+}
+
+static char *buf_writer_get_buffer(AVWriter wr, size_t min_size, size_t *size)
+{
+    AVBufWriter *bwr = wr.obj;
+
+    av_assert0(av_buf_writer_check(wr));
+    buf_writer_assert_abi(bwr);
+    *size = bwr->size - 1 - bwr->pos;
+    return bwr->buf + bwr->pos;
+}
+
+static void buf_writer_advance_buffer(AVWriter wr, size_t size)
+{
+    AVBufWriter *bwr = wr.obj;
+
+    av_assert0(av_buf_writer_check(wr));
+    buf_writer_assert_abi(bwr);
+    bwr->pos += size;
+    bwr->buf[bwr->pos] = 0;
+}
+
+AV_WRITER_DEFINE_METHODS(/*public*/, AVBufWriter, av_buf_writer) {
+    .self_size        = sizeof(AVWriterMethods),
+    .name             = "AVBufWriter",
+    .write            = buf_writer_write,
+    .vprintf          = buf_writer_vprintf,
+    .get_buffer       = buf_writer_get_buffer,
+    .advance_buffer   = buf_writer_advance_buffer,
+};
+
+AVBufWriter *av_buf_writer_init(AVBufWriter *bwr, char *buf, size_t size)
+{
+    buf_writer_assert_abi(bwr);
+    bwr->buf       = buf;
+    bwr->size      = size;
+    bwr->pos       = 0;
+    buf[0] = 0;
+    return bwr;
+}
+
+AVWriter av_buf_writer_wrap(AVBufWriter *bwr)
+{
+    AVWriter r = { av_buf_writer_get_methods(), bwr };
+    buf_writer_assert_abi(bwr);
+    return r;
+}
+
+/***************************************************************************
+ * AVDynbufWriter - write to a dynamic buffer
+ ***************************************************************************/
+
+#define dynbuf_writer_assert_abi(dwr) av_assert0(FIELDOK(dwr, bp))
+
+#define DWR_DIRTY 1
+
+static void dynbuf_writer_write(AVWriter wr, const char *data, size_t size)
+{
+    AVDynbufWriter *dwr = wr.obj;
+    unsigned char *buf;
+    unsigned buf_size;
+    size_t copy;
+
+    av_assert0(av_dynbuf_writer_check(wr));
+    dynbuf_writer_assert_abi(dwr);
+    av_bprint_get_buffer(&dwr->bp, FFMIN(size, UINT_MAX - 5), &buf, &buf_size);
+    if (buf_size >= 1) {
+        copy = FFMIN(buf_size - 1, size);
+        memcpy(buf, data, copy);
+        buf[copy] = 0;
+    }
+    dwr->bp.len += size;
+    dwr->flags |= DWR_DIRTY;
+}
+
+static void dynbuf_writer_vprintf(AVWriter wr, const char *fmt, va_list va)
+{
+    AVDynbufWriter *dwr = wr.obj;
+
+    av_assert0(av_dynbuf_writer_check(wr));
+    dynbuf_writer_assert_abi(dwr);
+    av_vbprintf(&dwr->bp, fmt, va);
+    dwr->flags |= DWR_DIRTY;
+}
+
+char *av_dynbuf_writer_get_buffer(AVWriter wr, size_t size, size_t *rsize)
+{
+    AVDynbufWriter *dwr = wr.obj;
+    unsigned char *buf;
+    unsigned isize;
+
+    av_assert0(av_dynbuf_writer_check(wr));
+    dynbuf_writer_assert_abi(dwr);
+    av_bprint_get_buffer(&dwr->bp, FFMIN(size, UINT_MAX - 5), &buf, &isize);
+    *rsize = isize ? isize - 1 : 0;
+    return buf;
+}
+
+void av_dynbuf_writer_advance_buffer(AVWriter wr, size_t size)
+{
+    AVDynbufWriter *dwr = wr.obj;
+
+    av_assert0(av_dynbuf_writer_check(wr));
+    dynbuf_writer_assert_abi(dwr);
+    dwr->bp.len += size;
+    dwr->bp.str[FFMIN(dwr->bp.len, dwr->bp.size - 1)] = 0;
+    dwr->flags |= DWR_DIRTY;
+}
+
+static int dynbuf_writer_get_error(AVWriter wr, int self_only)
+{
+    AVDynbufWriter *dwr = wr.obj;
+
+    av_assert0(av_dynbuf_writer_check(wr));
+    dynbuf_writer_assert_abi(dwr);
+    dwr->flags &= ~DWR_DIRTY;
+    return av_bprint_is_complete(&dwr->bp) ? 0 : AVERROR(ENOMEM);
+}
+
+AV_WRITER_DEFINE_METHODS(/*public*/, AVDynbufWriter, av_dynbuf_writer) {
+    .self_size        = sizeof(AVWriterMethods),
+    .name             = "AVDynbufWriter",
+    .write            = dynbuf_writer_write,
+    .vprintf          = dynbuf_writer_vprintf,
+    .get_buffer       = av_dynbuf_writer_get_buffer,
+    .advance_buffer   = av_dynbuf_writer_advance_buffer,
+    .get_error        = dynbuf_writer_get_error,
+};
+
+AVDynbufWriter *av_dynbuf_writer_init(AVDynbufWriter *dwr)
+{
+    dynbuf_writer_assert_abi(dwr);
+    av_bprint_init(&dwr->bp, 0, AV_BPRINT_SIZE_UNLIMITED);
+    dwr->flags = 0;
+    return dwr;
+}
+
+AVWriter av_dynbuf_writer_wrap(AVDynbufWriter *dwr)
+{
+    AVWriter r = { av_dynbuf_writer_get_methods(), dwr };
+    dynbuf_writer_assert_abi(dwr);
+    return r;
+}
+
+char *av_dynbuf_writer_get_data(AVWriter wr, size_t *size)
+{
+    AVDynbufWriter *dwr = wr.obj;
+
+    dynbuf_writer_assert_abi(dwr);
+    av_assert0(!(dwr->flags & DWR_DIRTY));
+    av_assert0(!dynbuf_writer_get_error(wr, 0));
+    return av_dynbuf_writer_get_data_unsafe(wr, size);
+}
+
+char *av_dynbuf_writer_get_data_unsafe(AVWriter wr, size_t *size)
+{
+    AVDynbufWriter *dwr = wr.obj;
+
+    av_assert0(av_dynbuf_writer_check(wr));
+    dynbuf_writer_assert_abi(dwr);
+    if (size)
+        *size = dwr->bp.len;
+    return dwr->bp.str;
+}
+
+int av_dynbuf_writer_finalize(AVWriter wr, char **buf, size_t *size)
+{
+    int ret;
+
+    ret = dynbuf_writer_get_error(wr, 0);
+    if (ret < 0) {
+        *buf = NULL;
+        *size = 0;
+        av_dynbuf_writer_finalize_unsafe(wr, NULL, NULL);
+        return ret;
+    } else {
+        return av_dynbuf_writer_finalize_unsafe(wr, buf, size);
+    }
+}
+
+int av_dynbuf_writer_finalize_unsafe(AVWriter wr, char **buf, size_t *size)
+{
+    AVDynbufWriter *dwr = wr.obj;
+
+    av_assert0(av_dynbuf_writer_check(wr));
+    dynbuf_writer_assert_abi(dwr);
+    if (size)
+        *size = dwr->bp.len;
+    return av_bprint_finalize(&dwr->bp, buf);
+}
+
+/***************************************************************************
+ * AVLogWriter - write to av_log()
+ ***************************************************************************/
+
+static void log_writer_vprintf(AVWriter wr, const char *fmt, va_list va)
+{
+    av_assert0(av_log_writer_check(wr));
+    av_vlog(wr.obj, AV_LOG_INFO, fmt, va);
+}
+
+AV_WRITER_DEFINE_METHODS(/*public*/, AVLogWriter, av_log_writer) {
+    .self_size        = sizeof(AVWriterMethods),
+    .name             = "AVLogWriter",
+    .vprintf          = log_writer_vprintf,
+};
+
+AVWriter av_log_writer(void *obj)
+{
+    AVWriter wr = {
+        .methods = av_log_writer_get_methods(),
+        .obj     = obj,
+    };
+    return wr;
+}
+
+void av_log_writer_log(AVWriter wr, int level, const char *fmt, ...)
+{
+    va_list va;
+
+    va_start(va, fmt);
+    if (av_log_writer_check(wr)) {
+        av_vlog(wr.obj, level, fmt, va);
+    } else {
+        av_writer_vprintf(wr, fmt, va);
+    }
+    va_end(va);
+}
+
+/***************************************************************************
+ * AVStdioWriter - write to stdio
+ ***************************************************************************/
+
+static void stdio_writer_vprintf(AVWriter wr, const char *fmt, va_list va)
+{
+    av_assert0(av_stdio_writer_check(wr));
+    vfprintf(wr.obj, fmt, va);
+}
+
+static int stdio_writer_get_error(AVWriter wr, int self_only)
+{
+    av_assert0(av_stdio_writer_check(wr));
+    return AVERROR(ferror(wr.obj));
+}
+
+AV_WRITER_DEFINE_METHODS(/*public*/, AVStdioWriter, av_stdio_writer) {
+    .self_size        = sizeof(AVWriterMethods),
+    .name             = "AVStdioWriter",
+    .vprintf          = stdio_writer_vprintf,
+    .get_error        = stdio_writer_get_error,
+};
+
+AVWriter av_stdio_writer(FILE *out)
+{
+    AVWriter wr = {
+        .methods = av_stdio_writer_get_methods(),
+        .obj     = out,
+    };
+    return wr;
+}
diff --git a/libavutil/writer.h b/libavutil/writer.h
new file mode 100644
index 0000000000..d11fa2f01e
--- /dev/null
+++ b/libavutil/writer.h
@@ -0,0 +1,488 @@ 
+/*
+ * Copyright (c) 2023 The FFmpeg project
+ *
+ * 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 AVUTIL_WRITER_H
+#define AVUTIL_WRITER_H
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdarg.h>
+
+#include "extendable.h"
+#include "bprint.h"
+
+/**
+ * @defgroup av_writer AVWriter
+ *
+ * Object-oriented API to write strings and binary data.
+ *
+ * @{
+ */
+
+typedef struct AVWriterMethods AVWriterMethods;
+
+/**
+ * Opaque object to write strings and binary data.
+ *
+ * AVWriter is meant to allow to build and return strings (and blocks of
+ * binary data) efficiently between functions.
+ * For example, a function that serializes something into a string will
+ * actually write into an AVWriter, and the caller will choose the way the
+ * data will be stored or processed on the fly.
+ *
+ * For a quick introduction on how to use AVWriter in simple cases, see
+ * doc/avwriter_intro.md.
+ *
+ * There are various pre-defined types of AVWriter, see below:
+ *
+ * - av_dynbuf_writer() writes the data into a buffer that is grown with
+ *   av_realloc() if it does not fit in the initial buffer; it is the
+ *   recommended choice.
+ *
+ * - av_buf_writer() writes the data into a pre-existing finite buffer.
+ *
+ * - av_stdio_writer() writes the data into a FILE*.
+ *
+ * - av_log_writer() writes the data to av_log().
+ *
+ * AVWriter objects are passed by value. It allows creating a writer on the
+ * fly, without extra variable or error checking. The structure can never
+ * change.
+ */
+typedef struct AVWriter {
+    const AVWriterMethods *methods;
+    void *obj;
+} AVWriter;
+
+/**
+ * Write a data buffer to an AVWriter.
+ */
+void av_writer_write(AVWriter wr, const char *buf, size_t size);
+
+/**
+ * Write a 0-terminated string to an AVWriter.
+ */
+void av_writer_print(AVWriter wr, const char *str);
+
+/**
+ * Write a formatted string to an AVWriter.
+ * Note: do not use libc-specific extensions to the format string.
+ */
+void av_writer_printf(AVWriter wr, const char *fmt, ...);
+
+/**
+ * Write a formatted to string an AVWriter using a va_list.
+ */
+void av_writer_vprintf(AVWriter wr, const char *fmt, va_list va);
+
+/**
+ * Write a sequence of identical chars to an AVWriter.
+ */
+void av_writer_add_chars(AVWriter wr, char c, size_t n);
+
+/**
+ * Flush data that may be buffered in the writer.
+ */
+void av_writer_flush(AVWriter wr);
+
+/**
+ * Return the error status of the writer, an AVERROR code.
+ *
+ * If self_only is non-zero, return errors only on this writer and not on
+ * possible other writers that it got from the caller. If in doubt, use 0.
+ */
+int av_writer_get_error(AVWriter wr, int self_only);
+
+/**
+ * Print a sane value for invalid input.
+ *
+ * This is a flag for all av_something_write() functions: when presented
+ * with an invalid "something", if the flag is not present they should leave
+ * the writer unchanged; if the flag is present they should print a sane
+ * fallback string.
+ */
+#define AV_WRITER_FALLBACK 0x10000
+
+/***************************************************************************/
+
+/**
+ * @defgroup av_dynbuf_writer AVDynbufWriter
+ *
+ * An implementation of AVWriter that writes to a dynamic buffer.
+ *
+ * The buffer is kept 0-terminated.
+ *
+ * The buffer is initially kept in the AVWriter data itself, it switches to
+ * heap allocation if it grows too large. In that case,
+ * av_writer_get_error() can return AVERROR(ENOMEM) if the allocation fails.
+ *
+ * @{
+ */
+
+/**
+ * An AVWriter object for pre-allocated memory buffers.
+ *
+ * Can be allocated on the stack.
+ *
+ * Should be inited with one of the utility functions.
+ */
+typedef struct AVDynbufWriter {
+    size_t self_size; /**< Size of the structure itself */
+    unsigned flags;
+    AVBPrint bp;
+} AVDynbufWriter;
+
+const AVWriterMethods *av_dynbuf_writer_get_methods(void);
+int av_dynbuf_writer_check(AVWriter wr);
+
+/**
+ * Initialize an AVDynbufWriter.
+ *
+ * @return  dwr itself
+ * dwr->self_size must be set.
+ */
+AVDynbufWriter *av_dynbuf_writer_init(AVDynbufWriter *dwr);
+
+/**
+ * Create an AVWriter from an AVDynbufWriter structure.
+ */
+AVWriter av_dynbuf_writer_wrap(AVDynbufWriter *dwr);
+
+/**
+ * Create an AVWriter to a dynamic buffer.
+ *
+ * Note: as it relies on a compound statement, the AVDynbufWriter object has
+ * a scope limited to the block where this macro is called.
+ */
+#define av_dynbuf_writer() \
+    av_dynbuf_writer_wrap(av_dynbuf_writer_init(&FF_NEW_SZ(AVDynbufWriter)))
+
+/**
+ * Get a pointer to the buffer data of an AVWriter to a dynamic buffer.
+ *
+ * If not null, size will be set to the size of the data in the buffer.
+ *
+ * Undefined behavior if called with another type of AVWriter.
+ * Undefined behavior if called without checking for error first.
+ */
+char *av_dynbuf_writer_get_data(AVWriter wr, size_t *size);
+
+/**
+ * Get a pointer to the buffer data of an AVWriter to a dynamic buffer
+ * (unsafe version).
+ *
+ * If not null, size will be set to the size of the data in the buffer.
+ *
+ * If the writer is in error, the data may be truncated.
+ *
+ * Undefined behavior if called with another type of AVWriter.
+ */
+char *av_dynbuf_writer_get_data_unsafe(AVWriter wr, size_t *size);
+
+/**
+ * Finalize an AVWriter to a dynamic buffer.
+ *
+ * @arg[out] buf   if not NULL, used to return the buffer contents
+ * @arg[out] size  if not NULL, used to return the size of the data
+ * @return  0 for success or error code (probably AVERROR(ENOMEM))
+ *
+ * In case of error, the buffer will not be duplicated but still freed.
+ * Same if the writer is already in error.
+ *
+ * Undefined behavior if called with another type of AVWriter.
+ */
+int av_dynbuf_writer_finalize(AVWriter wr, char **buf, size_t *size);
+
+/**
+ * Finalize an AVWriter to a dynamic buffer (unsafe version).
+ *
+ * @arg[out] buf   if not NULL, used to return the buffer contents
+ * @arg[out] size  if not NULL, used to return the size of the data
+ * @return  0 for success or error code (probably AVERROR(ENOMEM))
+ *
+ * If the writer is in error, the returned data may be truncated.
+ *
+ * In case of error, the buffer will not be duplicated but still freed.
+ *
+ * Undefined behavior if called with another type of AVWriter.
+ */
+int av_dynbuf_writer_finalize_unsafe(AVWriter wr, char **buf, size_t *size);
+
+/**
+ * Allocate chars in the buffer for external use.
+ *
+ * Undefined behavior if called with another type of AVWriter.
+ */
+char *av_dynbuf_writer_get_buffer(AVWriter wr, size_t size, size_t *rsize);
+
+/**
+ * Advance the position in the buffer.
+ *
+ * size must be <= *rsize on a previous call to
+ * av_dynbuf_writer_get_buffer().
+ *
+ * Undefined behavior if called with another type of AVWriter.
+ * Undefined behavior if called not directly after
+ * av_dynbuf_writer_get_buffer().
+ */
+void av_dynbuf_writer_advance_buffer(AVWriter wr, size_t size);
+
+/**
+ * @}
+ */
+
+/***************************************************************************/
+
+/**
+ * @defgroup av_buf_writer AVBufWriter
+ *
+ * An implementtion of AVWriter that writes into an already-allocated buffer
+ * of memory.
+ *
+ * The buffer is kept 0-terminated. If the buffer is too small, the data is
+ * discarded, but the total size is computed.
+ *
+ * @{
+ */
+
+/**
+ * An AVWriter object for pre-allocated memory buffers.
+ *
+ * Can be allocated on the stack.
+ *
+ * Should be inited with one of the utility functions.
+ */
+typedef struct AVBufWriter {
+    size_t self_size; /**< Size of the structure itself */
+    char *buf; /**< Memory buffer. Must not be NULL nor empty. */
+    size_t size; /**< Size of the memory buffer. Must not be 0. */
+    size_t pos; /**< Position in the memory buffer. */
+} AVBufWriter;
+
+const AVWriterMethods *av_buf_writer_get_methods(void);
+int av_buf_writer_check(AVWriter wr);
+
+/**
+ * Initialize an AVBufWriter to an already-allocated memory buffer.
+ *
+ * @return  bwr itself
+ * bwr->self_size must be set.
+ */
+AVBufWriter *av_buf_writer_init(AVBufWriter *bwr, char *buf, size_t size);
+
+/**
+ * Create an AVWriter from an AVBufWriter structure.
+ */
+AVWriter av_buf_writer_wrap(AVBufWriter *bwr);
+
+/**
+ * Create an AVWriter to a buffer.
+ *
+ * Note: as it relies on a compound statement, the AVBufWriter object has
+ * a scope limited to the block where this macro is called.
+ */
+#define av_buf_writer(buf, size) \
+    av_buf_writer_wrap(av_buf_writer_init(&FF_NEW_SZ(AVBufWriter), (buf), (size)))
+
+/**
+ * Create an AVWriter to a char[] or equivalent.
+ *
+ * Note: as it relies on a compound statement, the AVBufWriter object has
+ * a scope limited to the block where this macro is called.
+ */
+#define av_buf_writer_array(array) \
+    av_buf_writer(array, sizeof (array))
+
+/**
+ * @}
+ */
+
+/***************************************************************************/
+
+/**
+ * @defgroup av_stdio_writer AVStdioWriter
+ *
+ * An implementation of AVWriter that writes to stdio.
+ *
+ * @{
+ */
+
+const AVWriterMethods *av_stdio_writer_get_methods(void);
+int av_stdio_writer_check(AVWriter wr);
+
+/**
+ * Create an AVWriter that goes to a stdio FILE.
+ */
+AVWriter av_stdio_writer(FILE *out);
+
+/**
+ * @}
+ */
+
+/***************************************************************************/
+
+/**
+ * @defgroup av_log_writer AVLogWriter
+ *
+ * An implementation of AVWriter that writes to av_log().
+ *
+ * @{
+ */
+
+const AVWriterMethods *av_log_writer_get_methods(void);
+int av_log_writer_check(AVWriter wr);
+
+/**
+ * Create an AVWriter that goes to av_log(obj).
+ */
+AVWriter av_log_writer(void *obj);
+
+/**
+ * Log to a writer.
+ * If wr does not go to av_log(), level is ignored.
+ */
+void av_log_writer_log(AVWriter wr, int level, const char *fmt, ...);
+
+/**
+ * @}
+ */
+
+/***************************************************************************/
+
+/**
+ * @defgroup av_writer_methods AVWriterMethods
+ *
+ * Structures and utility needed to define new types of AVWriter.
+ *
+ * @{
+ */
+
+/**
+ * Set of methods for implementing an AVWriter.
+ *
+ * Applications that want to implement other kinds of AVWriter
+ * need to create a structure with some of these methods implemented.
+ *
+ * These methods should only be called directly by the implementation of
+ * AVWriter. Their documentation is written from the point of view of
+ * implementing them.
+ *
+ * Every type of AVWriter is supposed to have a pair of functions:
+ * av_something_writer_get_methods() returns the methods for that type.
+ * av_something_writer_checks() returns 1 if the writer is of that type.
+ *
+ * A macro to define the structure and functions is provided below.
+ *
+ * When a type of AVWriter defines specific functions, it is usually the
+ * responsibility of the caller to ensure that they are called only with a
+ * compatible AVWriter; otherwise the behavior is undefined.
+ */
+struct AVWriterMethods {
+
+    /**
+     * Size of the structure itself.
+     * Must normally be set to sizeof(AVWriterMethods).
+     */
+    size_t self_size;
+
+    /**
+     * Name of the object type.
+     */
+    const char *name;
+
+    /**
+     * Warn that an operation was impossible even with fallbacks.
+     */
+    void (*notify_impossible)(AVWriter wr, const char *message);
+
+    /**
+     * Notify that size chars have been discarded
+     */
+    void (*notify_discard)(AVWriter wr, size_t size);
+
+    /**
+     * Get the error status of the writer
+     * If self_only is non-zero, return errors only on this writer and not on
+     * possible other writers that it got from the caller. Errors from
+     * writers created internally should always be checked.
+     */
+    int (*get_error)(AVWriter wr, int self_only);
+
+    /**
+     * Write chars directly.
+     */
+    void (*write)(AVWriter wr, const char *buf, size_t size);
+
+    /**
+     * Write a formatted string.
+     */
+    void (*vprintf)(AVWriter wr, const char *fmt, va_list ap);
+
+    /**
+     * Get a buffer to write data.
+     * If the returned *size is < min_size, data may get discarded.
+     * A writer that implements this method must implement advance_buffer too.
+     * If vprintf is not implemented, av_write_printf() will use
+     * get_buffer() and vsnprintf(): it will need an extra char in the
+     * buffer for the 0 that vsnprintf() adds.
+     */
+    char *(*get_buffer)(AVWriter wr, size_t min_size, size_t *size);
+
+    /**
+     * Acknowledge chars written in a buffer obtained with get_buffer().
+     * size is guaranteed <= the size value returned by get_buffer().
+     */
+    void (*advance_buffer)(AVWriter wr, size_t size);
+
+    /**
+     * Flush immediately data that may be buffered in the writer.
+     */
+    void (*flush)(AVWriter wr);
+
+};
+
+/**
+ * Convenience macro for the boilerplate necessary to define a writer class.
+ *
+ * @arg qual    type qualifier for for the symbols, for example "static"
+ * @arg type    class name for the type of writer,
+ *              for example "AVBufWriter" or "MyWriter"
+ * @arg prefix  prefix for the symbols,
+ *              for example "av_buf_writer" or "my_writer"
+ */
+#define AV_WRITER_DEFINE_METHODS(qual, type, prefix) \
+static const AVWriterMethods prefix##_methods; \
+qual const AVWriterMethods *prefix##_get_methods(void) { \
+    return &prefix##_methods; \
+} \
+qual int prefix##_check(AVWriter wr) { \
+    return wr.methods == prefix##_get_methods(); \
+} \
+static const AVWriterMethods prefix##_methods =
+
+/**
+ * @}
+ */
+
+/**
+ * @}
+ */
+
+#endif /* AVUTIL_WRITER_H */