From: Nicolas George <george@nsup.org> To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH 2/8] lavu: new AVWriter API Date: Fri, 28 Apr 2023 11:55:02 +0200 Message-ID: <20230428095508.221826-2-george@nsup.org> (raw) In-Reply-To: <20230428095508.221826-1-george@nsup.org> 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. + +**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 */ -- 2.39.2 _______________________________________________ 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".
next prev parent reply other threads:[~2023-04-28 9:55 UTC|newest] Thread overview: 38+ messages / expand[flat|nested] mbox.gz Atom feed top 2023-04-28 9:55 [FFmpeg-devel] [PATCH 1/8] lavu: add macros to help making future-proof structures Nicolas George 2023-04-28 9:55 ` Nicolas George [this message] 2023-04-28 10:37 ` [FFmpeg-devel] [PATCH 2/8] lavu: new AVWriter API Rodney Baker 2023-04-28 11:20 ` Nicolas George 2023-05-02 15:53 ` Rémi Denis-Courmont 2023-05-02 16:53 ` Nicolas George 2023-05-02 18:29 ` Rémi Denis-Courmont 2023-05-02 18:36 ` Nicolas George 2023-05-02 18:46 ` Rémi Denis-Courmont 2023-05-02 18:47 ` Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 3/8] lavu/writer: add test Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 4/8] lavf/dump: use a writer Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 5/8] lavu: add a JSON writer API (WIP) Nicolas George 2023-04-29 9:11 ` Nicolas George 2023-04-29 9:41 ` Anton Khirnov 2023-04-29 14:06 ` James Almer 2023-04-29 17:17 ` Nicolas George 2023-04-29 15:06 ` Derek Buitenhuis 2023-04-30 0:29 ` Kieran Kunhya 2023-05-01 6:20 ` Vittorio Giovara 2023-04-29 17:11 ` Nicolas George 2023-04-29 18:27 ` Anton Khirnov 2023-04-29 18:33 ` Nicolas George 2023-05-01 6:57 ` Leo Izen 2023-05-01 9:51 ` Nicolas George 2023-05-01 10:18 ` Jean-Baptiste Kempf 2023-04-30 15:06 ` Michael Niedermayer 2023-04-30 21:51 ` Kieran Kunhya 2023-05-01 9:46 ` Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 6/8] lavu: add JSON writer test (WIP) Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 7/8] lavf/options: add av_disposition_write() Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 8/8] lavf/dump: use av_disposition_write() Nicolas George 2023-04-29 8:17 ` [FFmpeg-devel] [PATCH 1/8] lavu: add macros to help making future-proof structures Anton Khirnov 2023-04-29 15:11 ` Derek Buitenhuis 2023-05-02 15:36 ` Rémi Denis-Courmont 2023-05-02 16:42 ` Nicolas George 2023-05-02 18:31 ` Rémi Denis-Courmont 2023-05-02 18:38 ` Nicolas George
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20230428095508.221826-2-george@nsup.org \ --to=george@nsup.org \ --cc=ffmpeg-devel@ffmpeg.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel This inbox may be cloned and mirrored by anyone: git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \ ffmpegdev@gitmailbox.com public-inbox-index ffmpegdev Example config snippet for mirrors. AGPL code for this site: git clone https://public-inbox.org/public-inbox.git