Hi. [ TL;DR: a lightweight oo API for functions that need to return a string, designed to be as convenient as possible. ]
Yesterday, in my comments about the new channel layout API: https://ffmpeg.org/pipermail/ffmpeg-devel/2019-December/254020.html I evoked the following issue: We have many functions that return strings to the caller: char *av_get_sample_fmt_string(char *buf, int buf_size, enum AVSampleFormat sample_fmt); const char *av_get_media_type_string(enum AVMediaType media_type); char *av_base64_encode(char *out, int out_size, const uint8_t *in, int in_size); int av_dict_get_string(const AVDictionary *m, char **buffer, const char key_val_sep, const char pairs_sep); int av_strerror(int errnum, char *errbuf, size_t errbuf_size); void av_hash_final_hex(struct AVHashContext *ctx, uint8_t *dst, int size); const char *av_get_known_color_name(int color_idx, const uint8_t **rgb); const char *av_color_range_name(enum AVColorRange range); char *av_timecode_make_mpeg_tc_string(char *buf, uint32_t tc25bit); static inline char *av_ts_make_string(char *buf, int64_t ts) void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode); The simplest of them are "enum → static string" functions, but what do they do with an invalid value: return NULL or a meaningful string like "unknown"? A lot of them demand a pair of "char *buf, size_t buf_size" arguments to return the string. What is a reasonable size for the buffer? How do we handle the cases where the buffer is too small? When the string is likely to be long, they dynamically allocate the buffer. This is an inconsistent mess. And unsatisfactory on top of it: most of the time, we will just copy the buffer to another buffer, write it to a file or dump it to a log. The string function may need a big buffer to build the string while it is at no point necessary to have it in full. I have been wanting to find a clean solution for this issue for a long time. And now I have. (The AVBPrint was a preliminary attempt at that.) But before polishing it, I wanted to know the opinions of my fellow developers on this issue. The design is a bit unconventional (which is what enables it to be convenient), I suspect some people may object because of that. I have attached the header file so people can have a look at it. The idea is to implement the bare minimum of a very simple object system. To return a string (or a binary blob, that changes nothing), the functions take an AVWriter object and just write to it using a fprintf()-like function or other useful functions. The AVWriter object is abstract and can actually write in several different ways. av_buf_writer_array(buf) will just wrap "char buf[42]" and write in it. av_dynbuf_writer() prepares a dynamic buffer. av_log_writer(obj) writes to av_log(). User application can implement their own AVWriter types: write directly in a GUI text buffer or to syslog. I wanted to avoid as much as possible adding new necessary error checks. I think I have succeeded. av_foobar_to_string(buf, sizeof(buf), foobar); becomes av_foobar_write(av_buf_writer_array(buf), foobar); which has a little more letters but is actually simpler. But it is really more interesting than that because if we keep the same AVWriter for several call, it will just concatenate, allowing to build a message. Another thing that is not yet in the header: have all the av_foobar_write() function take an extra "unsigned flags" argument. And have a few transverse flags, especially: #define AV_WRITER_FALLBACK (1U << 16) When this flag is set, the function writes a fall-back string for unknown values ("unknown", "?", "invalid", depends on the type). Otherwise, it prints nothing. Before working more on integrating this, I would like a little feedback, please. Regards, -- Nicolas George
/* * Copyright (c) 2019 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 */ #include <stddef.h> #include <stdarg.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. * * An AVWriter is meant to allow to build and return string (and blocks of * binary data) efficiently between functions. * For example, a function that serialize something into a string will * actually write into an AVWriter. * * To emulate idiomatic practices of writing into a buffer, you can write: * char buf[4096]; * AVWriter wr = av_buf_writer_array(buf); * which is a macro equivalent (using modern C magic) to: * AVWriterBuf bwr; * av_buf_writer_init(&bwr, buf, sizeof(buf)); * AVWriter wr = av_buf_writer_wrap(&bwr); * so you can write into any already allocated memory. * The string will be 0-terminated, so you can omit len. * * To avoid the problems of size limits, you should use a dynamic buffer. * AVDynbufWriter provides an implementation that uses the stack for small * strings and the heap for longer ones. * * AVWriter wr = av_dynbuf_writer(XXX); * write_something(wr); * if (!av_dynbuf_get_error(wr)) { * output(av_dynbuf_get_buffer(wr, &size)); * av_dynbuf_finish(wr, NULL, NULL); * * It is possible to create an AVWriter to av_log() and to implement new * kinds of AVWriter. * * 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. * * 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. * 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. */ typedef struct AVWriter { const AVWriterMethods *methods; void *obj; } AVWriter; /** * Write a data buffer to an AVWriter. */ /* TODO */ void av_writer_write(AVWriter wr, const char *buf, size_t size); /** * Write a 0-terminated string to an AVWriter. */ /* TODO */ void av_writer_print(AVWriter wr, const char *str); /** * Write a sequence of identical chars to an AVWriter. */ /* TODO */ void av_writer_print(AVWriter wr, char c, size_t n); /** * Write a formatted string to an AVWriter. */ 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); /***************************************************************************/ /** * @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_dynbuf_writer AVDynbufWriter * * An implementation of AVWriter that writes to a dynamic buffer. * * The buffer is kept 0-terminated. * * @{ */ /** * 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 */ 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. */ /* FIXME do we need to expose size_init and max_size from AVBPrint? */ 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 the error status of the writer. * * AVERROR(ENOMEM) means that allocation was impossible and caused data to * be discarded. If full_size is not NULL, it will be set to the size of the * full_data, including the discarded part. * * Undefined behavior if called with another type of AVWriter. */ /* TODO */ int av_dynbuf_writer_get_error(AVWriter wr, size_t *full_size); /** * 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. */ char *av_dynbuf_writer_get_data(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. * * Undefined behavior if called with another type of AVWriter. */ /* TODO */ int av_dynbuf_writer_finalize(AVWriter wr, char **buf, size_t *size); /** * Allocate chars in the buffer for external use. * * Undefined behavior if called with another type of AVWriter. */ /* TODO */ void av_dynbuf_writer_get_buffer(AVWriter wr, size_t size char **buf, 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. */ /* TODO */ void av_dynbuf_writer_advance_buffer(AVWriter wr, size_t size); /** * @} */ /***************************************************************************/ /** * @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. * * There is no error report. * Implementations must provide their own way of reporting errors. */ 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 (*impossible)(AVWriter wr, const char *message); /** * Notify that size chars have been discarded */ void (*notify_discard)(AVWriter wr, size_t size); /** * Get a buffer to write data. * If *size is returned < min_size, data may get discarded. */ 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 (*write_buffer)(AVWriter wr, size_t size); /** * 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); }; /** * Convenience macro for the boilerplate necessary to define a writer class. */ #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 = #define FF_NEW_SZ(type) ((type){ .self_size = sizeof(type) }) /** * @} */ /** * @} */
signature.asc
Description: PGP signature
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".