From patchwork Fri Apr 28 09:55:01 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 41393 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:dca6:b0:f3:34fa:f187 with SMTP id ky38csp1188197pzb; Fri, 28 Apr 2023 02:55:24 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ6bZo7UAQcGNZ7rp6+WZJuoUwlJEa6s6QselX5onoz0vOBEHc/n7aa0dWWxUgipZzQDLzIr X-Received: by 2002:aa7:dbd2:0:b0:50a:16ab:340e with SMTP id v18-20020aa7dbd2000000b0050a16ab340emr4110551edt.29.1682675723867; Fri, 28 Apr 2023 02:55:23 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1682675723; cv=none; d=google.com; s=arc-20160816; b=UT6SaYIYE5shtkZEeBwLa7pYlYpZBeBSSGfP+UqyLsp0yq3sfcBPstAxl++oCTeDfa FA7eWYxSw+0E+4lacFuWTJ3cTGuuvK2Q9PzJ57I6GgPoKiUKBDqoyimqMPprHAh9Hkp4 OobpfM8aEiWOTPsRMR7pSiKIIT4xsgLuZsmZNNmv/Hep2CUek0QhvqUwgYvRDs65bT2/ RNYgFnIcB5glu9cG7ItVm6Rv7iDZvsl7wJ6O8cHghFEzomgF1CGR/5UHPV9Zd8hqBBkP tg1sgeBa4rW2doWygpZstK+wWDLX8kfWA5d+g9+ityPP0J0snrRgQ7DUrmRBQAE0tmzb XfBg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:message-id:date:to:from :delivered-to; bh=os2DogV3HtKPrSBl7u48lZwMlHtN8lnwWvjlgCRMHI8=; b=BpYoRogs38iF6GN9Ybt9UV1WSR70pGRXe13MgiwaoIkS+Ptbmo84JPvH+V04+IxeP8 e8jQnhtP3Z/OVzJayDkV75k2oCh4UmVn7SszlnTUQgahB0MmAYEDVl2QVBK/dnYIdsdr 79P7cYxhd3OGtZftSbshefMt5GipdYZsXfDrKgKOwtkHZHAfApRu2SQi6Gh7W5q/+hA2 Nw7kr7aJS58j16djXCjddv1IZNZvRBAAlwEDPG168DF1/23LrmBjzTqz8U7cY3xRNhAG 3MehiopLVWY0v7OF8KGEQDoMLFon0HMNqZ+zdlsCbntHGb0ShXnINFPtKnl2KYbEk7nG 0TDQ== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id i25-20020a056402055900b00506bdb0c581si15854380edx.152.2023.04.28.02.55.23; Fri, 28 Apr 2023 02:55:23 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 5211968BF83; Fri, 28 Apr 2023 12:55:18 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from nef.ens.fr (nef2.ens.fr [129.199.96.40]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 78CA468BF06 for ; Fri, 28 Apr 2023 12:55:11 +0300 (EEST) X-ENS-nef-client: 129.199.129.80 ( name = phare.normalesup.org ) Received: from phare.normalesup.org (phare.normalesup.org [129.199.129.80]) by nef.ens.fr (8.14.4/1.01.28121999) with ESMTP id 33S9tAKi014762 for ; Fri, 28 Apr 2023 11:55:10 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id A7337EB5BF; Fri, 28 Apr 2023 11:55:10 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Fri, 28 Apr 2023 11:55:01 +0200 Message-Id: <20230428095508.221826-1-george@nsup.org> X-Mailer: git-send-email 2.39.2 MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Fri, 28 Apr 2023 11:55:10 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 1/8] lavu: add macros to help making future-proof structures X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: FjEs9hAFaDn1 Signed-off-by: Nicolas George --- libavutil/extendable.h | 59 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 libavutil/extendable.h FFReservedPadding is used by the WIP JSON writer. diff --git a/libavutil/extendable.h b/libavutil/extendable.h new file mode 100644 index 0000000000..79980fa202 --- /dev/null +++ b/libavutil/extendable.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 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_EXTENDABLE_H +#define AVUTIL_EXTENDABLE_H + +/** + * @defgroup ff_extendable FFExtendable + * + * Types and macros to help designing structures that can be allocated by + * the application, including on the stack, but will not break ABI when + * extendded. + * + * This should not be used outside FFmpeg. + * + * @{ + */ + +/** + * Define a value of type as a compound literal (hidden local variable) + * with the field self_size filled. + */ +#define FF_NEW_SZ(type) ((type){ .self_size = sizeof(type) }) + +/** + * Type suitable for paddign at the end of a structure, with maximum + * alignment. + */ +typedef union FFReservedPadding { + union { + double d; + void *p; + void (*f)(void); + intmax_t i; + } dummy; +} FFReservedPadding; + +/** + * @} + */ + +#endif /* AVUTIL_EXTENDABLE_H */ From patchwork Fri Apr 28 09:55:02 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 41394 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:dca6:b0:f3:34fa:f187 with SMTP id ky38csp1188265pzb; Fri, 28 Apr 2023 02:55:36 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ7XeoyMf4ML1dFlMFQNSFH27LKXtqVhKKPgEuu5FgsUTrHISzWv2cK7aQg9bTA1BidATeKl X-Received: by 2002:aa7:d3ca:0:b0:4fe:9555:3a57 with SMTP id o10-20020aa7d3ca000000b004fe95553a57mr4366250edr.16.1682675735868; Fri, 28 Apr 2023 02:55:35 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1682675735; cv=none; d=google.com; s=arc-20160816; b=NKl+DlmWgpsb/XD3+aI1wHgxTncrFgF/g1I+bBV81QOTEX8TvhTz1FZ3WXNW6wInYP KFqrHMOtbiU4Me2uLVTB6qM1fIEipsNQk7czcG4SrrKAxwdKXTmVj7qKI0cdmoHn5WOy e6yh6fnuqttP1nXB8YeqvE78dOZTe5Uh+NY/ZdmqnFlkHJyqT9NhjkkNG9hnpUdYEeQi pRjd4y3pGhBfR4v9Nrt9WRd7wlCsDY61vx4VDXNs1hAqEKYOWee3/ZiJaS0UDJ+6EJR6 TBn+xPW3eQnGwZdTnYbTlDSqDi9Yhx2PyO8TfPvbHGbzzc4fqmQHp/40GaT+cWnP1b2D A1VA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:delivered-to; bh=/TnbgzEKk9kth/OEM/sWz+ajKMh1kmSlnzWxDRTTgqU=; b=hRDqLb5dMvs4LQ2IpBXPgayYpzGU6Z5ExUCacwVkw3Odv1Q/YHvn+SUNrtYCvoN6Ea komyicSm7QL5lGRZBUsdmFiTmPzkMOOFnii+Xp/j30mOMCACXV7DIJVuEE41aMBPG3cG EbGxow5CQe88r7C51NwZkOsTINweJGSnBQmwmjYEPaGGL1xL2+iPo2jfAQTMBVbeWcsl jaYL0NhYqx9dp6jP/U8qYKJMCItN1/y91sFnHgxxkjpKBn7ECDkt1JTytDTtt7+5v+BF rhrtb59HbIikDHV1DMQ62R4D2PiTquYeFoEQLSm29LnHGpuDaACBYODORzQcWdckyh29 wosg== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id r18-20020a056402019200b0050684da86d8si13186742edv.645.2023.04.28.02.55.35; Fri, 28 Apr 2023 02:55:35 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id A08D668BF8F; Fri, 28 Apr 2023 12:55:19 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from nef.ens.fr (nef2.ens.fr [129.199.96.40]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 699A068BE11 for ; Fri, 28 Apr 2023 12:55:12 +0300 (EEST) X-ENS-nef-client: 129.199.129.80 ( name = phare.normalesup.org ) Received: from phare.normalesup.org (phare.normalesup.org [129.199.129.80]) by nef.ens.fr (8.14.4/1.01.28121999) with ESMTP id 33S9tBuH014773 for ; Fri, 28 Apr 2023 11:55:11 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id AB670EB5BF; Fri, 28 Apr 2023 11:55:11 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Fri, 28 Apr 2023 11:55:02 +0200 Message-Id: <20230428095508.221826-2-george@nsup.org> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230428095508.221826-1-george@nsup.org> References: <20230428095508.221826-1-george@nsup.org> MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Fri, 28 Apr 2023 11:55:11 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 2/8] lavu: new AVWriter API X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: EHfhHLwzhoAS Signed-off-by: Nicolas George --- 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 +#include +#include +#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 +#include +#include + +#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 */ From patchwork Fri Apr 28 09:55:03 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 41395 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:dca6:b0:f3:34fa:f187 with SMTP id ky38csp1188320pzb; Fri, 28 Apr 2023 02:55:46 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ4v+hrXwbR1pLEH+Gcwl12TlAelTrxv2J8ycd76emY6lMFe0AifQp8OcAqYnIY7iaJdYVpd X-Received: by 2002:aa7:c586:0:b0:504:9349:7901 with SMTP id g6-20020aa7c586000000b0050493497901mr3488566edq.38.1682675746299; Fri, 28 Apr 2023 02:55:46 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1682675746; cv=none; d=google.com; s=arc-20160816; b=Pp47yGBuTaPidUaIhKDw+usWPOzYBVIpgEBqOcOfZbwiR+rmKKQ+or84FKTaZzxHpK EN/PLsk/ZobBj32BEYtegPgmxEm69JoucG8WmH3PK1m1DUV6a9xmEqgXeVUAQAvroVtI vei9oH9GFPoQaBsRynE31eGml2Zaw4+lj+lsu1Ga8UrKXZcpOnX55KO/caQt5tk6NfJe 5ZhwF5mbe+jW4+q/xEuNqK3jtfOGpw+0OFg49Gpr6VjdGzkB3tEZPFhPef3hdIAX7XIs b3niHut7qp1gVpPW+2Zxrlf2kTWAlYyl+07hhR1uNA65c9VqJOeEYRdBL0ChnC+M/9CW eZRg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:delivered-to; bh=hwyuloSkaPMvIiWvmt4YKCrs86ANeYMXTx3trw/RBb8=; b=D5WNIcWs5nsnn0TcANBO3NIcYDVICiR47pLIFtnnrlNKw3uuHZzlckijXwBNv3dECZ EsiuGpz1I3WEcwnF0NG5EblTrAf855/CbWoZF2TO9Hq4iMokj1QNH0tbS67BbvxBza3k sN2rubJqG2klantrYUH0ix2H4P4sIkkct0K4RJh8Q6iq5q9a4Azzt3B4ET2ovFjg6fRQ oIKw7myUphem6O1vP3AULwYDTI9wWTPqBzW7AKFxwSWeUI1yg03Dm1sD0e/MxLX+R67G 13kbf52FHbw54sItrMqD+o8IQbE2XMEc1kPxi1PWd+inOt6qG+gLz2426ALbgKryvGbR h3QA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id bc13-20020a056402204d00b004bbffb82fbcsi15920848edb.359.2023.04.28.02.55.45; Fri, 28 Apr 2023 02:55:46 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id A96DE68BF9F; Fri, 28 Apr 2023 12:55:20 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from nef.ens.fr (nef2.ens.fr [129.199.96.40]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 43CEC68BF7A for ; Fri, 28 Apr 2023 12:55:13 +0300 (EEST) X-ENS-nef-client: 129.199.129.80 ( name = phare.normalesup.org ) Received: from phare.normalesup.org (phare.normalesup.org [129.199.129.80]) by nef.ens.fr (8.14.4/1.01.28121999) with ESMTP id 33S9tCEw014781 for ; Fri, 28 Apr 2023 11:55:12 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id 8AF98EB5BF; Fri, 28 Apr 2023 11:55:12 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Fri, 28 Apr 2023 11:55:03 +0200 Message-Id: <20230428095508.221826-3-george@nsup.org> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230428095508.221826-1-george@nsup.org> References: <20230428095508.221826-1-george@nsup.org> MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Fri, 28 Apr 2023 11:55:12 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 3/8] lavu/writer: add test X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: E3NHUpIjop/W Signed-off-by: Nicolas George --- libavutil/Makefile | 1 + libavutil/tests/.gitignore | 1 + libavutil/tests/writer.c | 192 +++++++++++++++++++++++++++++++++++++ tests/fate/libavutil.mak | 4 + tests/ref/fate/writer | 36 +++++++ 5 files changed, 234 insertions(+) create mode 100644 libavutil/tests/writer.c create mode 100644 tests/ref/fate/writer +Answer: 42 +Question: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 * 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009 +************************************************************************ +] diff --git a/libavutil/Makefile b/libavutil/Makefile index eb8de1dfbc..4526ec80ca 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -269,6 +269,7 @@ TESTPROGS = adler32 \ uuid \ xtea \ tea \ + writer \ TESTPROGS-$(HAVE_THREADS) += cpu_init TESTPROGS-$(HAVE_LZO1X_999_COMPRESS) += lzo diff --git a/libavutil/tests/.gitignore b/libavutil/tests/.gitignore index 87895912f5..7559e7a24c 100644 --- a/libavutil/tests/.gitignore +++ b/libavutil/tests/.gitignore @@ -51,3 +51,4 @@ /utf8 /uuid /xtea +/writer diff --git a/libavutil/tests/writer.c b/libavutil/tests/writer.c new file mode 100644 index 0000000000..fdb8c4d703 --- /dev/null +++ b/libavutil/tests/writer.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2019 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 +#include +#include + +#include "libavutil/avassert.h" +#include "libavutil/error.h" +#include "libavutil/log.h" +#include "libavutil/writer.h" + +/* see 69bd73b3ff873abb43de9db062b04425de153643 */ + +/* AVWriter with only get_buffer/advance_buffer */ + +static char *getbuf_get_buffer(AVWriter wr, size_t min_size, size_t *size); +static void getbuf_advance_buffer(AVWriter wr, size_t size); + +AV_WRITER_DEFINE_METHODS(static, Getbuf, getbuf) { + .self_size = sizeof(AVWriterMethods), + .name = "Getbuf", + .get_buffer = getbuf_get_buffer, + .advance_buffer = getbuf_advance_buffer, +}; + +static char *getbuf_get_buffer(AVWriter wr, size_t min_size, size_t *size) +{ + AVBufWriter *bwr = wr.obj; + + av_assert0(getbuf_check(wr)); + *size = bwr->size - 1 - bwr->pos; + return bwr->buf + bwr->pos; +} + +static void getbuf_advance_buffer(AVWriter wr, size_t size) +{ + AVBufWriter *bwr = wr.obj; + + bwr->pos += size; + bwr->buf[bwr->pos] = 0; +} + +/* AVWriter with only write */ + +static void write_write(AVWriter wr, const char *data, size_t size); + +AV_WRITER_DEFINE_METHODS(static, Write, write) { + .self_size = sizeof(AVWriterMethods), + .name = "Write", + .write = write_write, +}; + +static void write_write(AVWriter wr, const char *data, size_t size) +{ + AVBufWriter *bwr = wr.obj; + + av_assert0(write_check(wr)); + size = FFMIN(bwr->size - 1 - bwr->pos, size); + memcpy(bwr->buf + bwr->pos, data, size); + bwr->pos += size; + bwr->buf[bwr->pos] = 0; +} + +/* AVWriter with only vprintf */ + +static void vprintf_vprintf(AVWriter wr, const char *fmt, va_list va); + +AV_WRITER_DEFINE_METHODS(static, Vprintf, vprintf) { + .self_size = sizeof(AVWriterMethods), + .name = "Vprintf", + .vprintf = vprintf_vprintf, +}; + +static void vprintf_vprintf(AVWriter wr, const char *fmt, va_list va) +{ + AVBufWriter *bwr = wr.obj; + int ret; + + av_assert0(vprintf_check(wr)); + ret = vsnprintf(bwr->buf + bwr->pos, bwr->size - bwr->pos, fmt, va); + if (ret > 0) + bwr->pos += ret; +} + +/* Tests */ + +static void stress_writer(AVWriter wr) +{ + av_writer_add_chars(wr, '*', 72); + av_writer_add_chars(wr, '\n', 1); + av_writer_print(wr, "Stressing the writer\n"); + av_writer_printf(wr, "Answer: %d\n", 42); + av_writer_printf(wr, "Question: %0600d * %0600d\n", 6, 9); /* > sizeof(AVBPrint) */ + av_writer_add_chars(wr, '*', 72); + av_writer_add_chars(wr, '\n', 1); + av_writer_write(wr, "\0Bonus track!\n", 14); +} + +static void test_buf_writer_small(void) +{ + char buf[1024]; + AVWriter wr = av_buf_writer_array(buf); + stress_writer(wr); + printf("AVBufWriter/1024: [%s]\n", buf); +} + +static void test_buf_writer_large(void) +{ + char buf[8192]; + AVWriter wr = av_buf_writer_array(buf); + stress_writer(wr); + printf("AVDynbufWriter/8192: [%s]\n", buf); +} + +static void test_dynbuf_writer(void) +{ + AVWriter wr = av_dynbuf_writer(); + char *data; + size_t size, i; + int ret; + + stress_writer(wr); + data = av_dynbuf_writer_get_buffer(wr, 100, &size); + ret = snprintf(data, size, "Some more?\n"); + av_dynbuf_writer_advance_buffer(wr, ret); + ret = av_writer_get_error(wr, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "DynbufWriter (%zd chars): %s\n", size, av_err2str(ret)); + return; + } + data = av_dynbuf_writer_get_data(wr, &size); + for (i = 0; i < size; i++) + if (data[i] == 0) + data[i] = '@'; /* avoid confusing diff */ + printf("AVDynbufWriter (%zd chars): [", size); + fwrite(data, 1, size, stdout); + printf("]\n"); + av_dynbuf_writer_finalize(wr, NULL, NULL); +} + +static void test_writer_limited(const char *name, const AVWriterMethods *methods) +{ + char buf[8192]; + AVBufWriter bwr = { sizeof(AVBufWriter), buf, sizeof(buf), 0 }; + AVWriter wr = { methods, &bwr }; + stress_writer(wr); + printf("%s: [%s]\n", name, buf); +} + +static void test_writer_get_buffer(void) +{ + test_writer_limited("get_buffer", getbuf_get_methods()); +} + +static void test_writer_write(void) +{ + test_writer_limited("write", write_get_methods()); +} + +static void test_writer_vprintf(void) +{ + test_writer_limited("vprintf", vprintf_get_methods()); +} + +int main(int argc, char **argv) +{ + if (1) test_buf_writer_small(); + if (1) test_buf_writer_large(); + if (1) test_dynbuf_writer(); + if (1) test_writer_get_buffer(); + if (1) test_writer_write(); + if (1) test_writer_vprintf(); + return 0; +} diff --git a/tests/fate/libavutil.mak b/tests/fate/libavutil.mak index 80153f4395..a9dcc9ec87 100644 --- a/tests/fate/libavutil.mak +++ b/tests/fate/libavutil.mak @@ -175,6 +175,10 @@ fate-uuid: libavutil/tests/uuid$(EXESUF) fate-uuid: CMD = run libavutil/tests/uuid$(EXESUF) fate-uuid: CMP = null +FATE_LIBAVUTIL += fate-writer +fate-writer: libavutil/tests/writer$(EXESUF) +fate-writer: CMD = run libavutil/tests/writer$(EXESUF) + FATE_LIBAVUTIL += $(FATE_LIBAVUTIL-yes) FATE-$(CONFIG_AVUTIL) += $(FATE_LIBAVUTIL) fate-libavutil: $(FATE_LIBAVUTIL) diff --git a/tests/ref/fate/writer b/tests/ref/fate/writer new file mode 100644 index 0000000000..3068d83a4a --- /dev/null +++ b/tests/ref/fate/writer @@ -0,0 +1,36 @@ +AVBufWriter/1024: [************************************************************************ +Stressing the writer +Answer: 42 +Question: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 * 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000] +AVDynbufWriter/8192: [************************************************************************ +Stressing the writer +Answer: 42 +Question: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 * 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009 +************************************************************************ +] +AVDynbufWriter (1417 chars): [************************************************************************ +Stressing the writer +Answer: 42 +Question: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 * 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009 +************************************************************************ +@Bonus track! +Some more? +] +get_buffer: [************************************************************************ +Stressing the writer +Answer: 42 +Question: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 * 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009 +************************************************************************ +] +write: [************************************************************************ +Stressing the writer +Answer: 42 +Question: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 * 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009 +************************************************************************ +] +vprintf: [************************************************************************ +Stressing the writer From patchwork Fri Apr 28 09:55:04 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 41396 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:dca6:b0:f3:34fa:f187 with SMTP id ky38csp1188379pzb; Fri, 28 Apr 2023 02:55:57 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ6AnTjE83gSBT/d7bhWfXHjErfNfZW3lV16GDR7e/fYxxovF4bYhLrpNoirRrPxH9Vpbr9M X-Received: by 2002:a17:907:16a9:b0:94a:845c:3528 with SMTP id hc41-20020a17090716a900b0094a845c3528mr5386513ejc.45.1682675756720; Fri, 28 Apr 2023 02:55:56 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1682675756; cv=none; d=google.com; s=arc-20160816; b=jilRdF7yqixv3skbhhs1vaQyNHZFQkNXNFb5+b8UxXO6wlCsZA0NfI1k8AfHxwNfZT IDlx0Y/4ZU8H05tO487fE8zONEjIqzrK7rYTZ9rr019n7N4m/i4zqjmW9RdBgVG895ic Wow7XvlDItyFw+siAvyuQ85rhmq+TrR7jW68o2iIom4NPOd9rbKCy6h8Q4cNPCWrmiGB N4GDX3NwkTd6E/kFny2h/qoefCE/cz2PGGkw/Lr5VSIn/94bjGfk1L+kNsO0VQeOASeO D0KglWH/pYTMrFGMeGaigTldDQnkP41A9nmM3HfRXrYAFpR/YuvXgSfsCCAMKAPVn539 FgxA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:delivered-to; bh=WjmKCb7LurKNSOZjz+3ELgEgdHwIFAu8j2H94opsiV4=; b=UF5Gt6xw3Z3rR3tAkQY92FmqjfzAf+6HFQmADdVTh0B28IpeehihYOMJy615R91CNE RNAG3cYUo6bfAGXUrOSz+CKWRuZq2yh/NzWPTSM6ExCWYamVNlE2SOJxSgj3TBi/jNXw noc/AIf0cjtOwKw7QjuEhLhIpHhudoXd36QIVGB56/GdIC/p/FZuPvymfneX4hqQBDQ9 YXbL0vHTH9iBFGq3qHzd1L1mI0ovbdU3kSousHENyODxxlk01ZRLMpc+b2/eXGdHm60X eNFqtHZ2jscnXHmGzPk14nFUasMqmO1rUlNYFpBSiwUVD5j+B5Z+l4lZ6eilDVOnT+Hx 0XLg== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id ck26-20020a170906c45a00b0094fa9ec9bf8si14723587ejb.1023.2023.04.28.02.55.56; Fri, 28 Apr 2023 02:55:56 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id E311268BF80; Fri, 28 Apr 2023 12:55:21 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from nef.ens.fr (nef2.ens.fr [129.199.96.40]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 3DE5868BF8F for ; Fri, 28 Apr 2023 12:55:14 +0300 (EEST) X-ENS-nef-client: 129.199.129.80 ( name = phare.normalesup.org ) Received: from phare.normalesup.org (phare.normalesup.org [129.199.129.80]) by nef.ens.fr (8.14.4/1.01.28121999) with ESMTP id 33S9tDmQ014788 for ; Fri, 28 Apr 2023 11:55:13 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id 861BCEB5BF; Fri, 28 Apr 2023 11:55:13 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Fri, 28 Apr 2023 11:55:04 +0200 Message-Id: <20230428095508.221826-4-george@nsup.org> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230428095508.221826-1-george@nsup.org> References: <20230428095508.221826-1-george@nsup.org> MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Fri, 28 Apr 2023 11:55:13 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 4/8] lavf/dump: use a writer X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: XfHtSwGdi4N1 Signed-off-by: Nicolas George --- libavformat/avformat.h | 21 +++ libavformat/dump.c | 318 +++++++++++++++++++++-------------------- 2 files changed, 185 insertions(+), 154 deletions(-) This is meant to be an example, I chose a piece of code that does a lot of strings but does not change much (to avoid rebase conflicts). diff --git a/libavformat/avformat.h b/libavformat/avformat.h index 1916aa2dc5..5302a34c0d 100644 --- a/libavformat/avformat.h +++ b/libavformat/avformat.h @@ -317,6 +317,7 @@ #include "libavutil/dict.h" #include "libavutil/log.h" +#include "libavutil/writer.h" #include "avio.h" #include "libavformat/version_major.h" @@ -2638,6 +2639,26 @@ void av_dump_format(AVFormatContext *ic, const char *url, int is_output); +/** + * Write detailed information about the input or output format, such as + * duration, bitrate, streams, container, programs, metadata, side data, + * codec and time base, to an AVWriter. + * + * @param wr the writer where the format should be written + * @param ic the context to analyze + * @param index index of the stream to dump information about + * @param url the URL to print, such as source or destination file + * @param flags formatting flags, see below + */ +void av_dump_format_to_writer(struct AVWriter wr, + AVFormatContext *ic, int index, + const char *url, unsigned flags); + +/** + * Flag to av_dump_format_to_writer(): + * if set, the cotext is output; if unset, the context is input. + */ +#define AV_DUMP_FORMAT_IS_OUTPUT 1 #define AV_FRAME_FILENAME_FLAGS_MULTIPLE 1 ///< Allow multiple %d diff --git a/libavformat/dump.c b/libavformat/dump.c index d31e4c2ec6..96b364e337 100644 --- a/libavformat/dump.c +++ b/libavformat/dump.c @@ -34,6 +34,7 @@ #include "libavutil/spherical.h" #include "libavutil/stereo3d.h" #include "libavutil/timecode.h" +#include "libavutil/writer.h" #include "libavcodec/avcodec.h" @@ -121,45 +122,45 @@ void av_pkt_dump_log2(void *avcl, int level, const AVPacket *pkt, int dump_paylo } -static void print_fps(double d, const char *postfix) +static void print_fps(AVWriter wr, double d, const char *postfix) { uint64_t v = lrintf(d * 100); if (!v) - av_log(NULL, AV_LOG_INFO, "%1.4f %s", d, postfix); + av_writer_printf(wr, "%1.4f %s", d, postfix); else if (v % 100) - av_log(NULL, AV_LOG_INFO, "%3.2f %s", d, postfix); + av_writer_printf(wr, "%3.2f %s", d, postfix); else if (v % (100 * 1000)) - av_log(NULL, AV_LOG_INFO, "%1.0f %s", d, postfix); + av_writer_printf(wr, "%1.0f %s", d, postfix); else - av_log(NULL, AV_LOG_INFO, "%1.0fk %s", d / 1000, postfix); + av_writer_printf(wr, "%1.0fk %s", d / 1000, postfix); } -static void dump_metadata(void *ctx, const AVDictionary *m, const char *indent) +static void dump_metadata(AVWriter wr, const AVDictionary *m, const char *indent) { if (m && !(av_dict_count(m) == 1 && av_dict_get(m, "language", NULL, 0))) { const AVDictionaryEntry *tag = NULL; - av_log(ctx, AV_LOG_INFO, "%sMetadata:\n", indent); + av_writer_printf(wr, "%sMetadata:\n", indent); while ((tag = av_dict_iterate(m, tag))) if (strcmp("language", tag->key)) { const char *p = tag->value; - av_log(ctx, AV_LOG_INFO, + av_writer_printf(wr, "%s %-16s: ", indent, tag->key); while (*p) { size_t len = strcspn(p, "\x8\xa\xb\xc\xd"); - av_log(ctx, AV_LOG_INFO, "%.*s", (int)(FFMIN(255, len)), p); + av_writer_printf(wr, "%.*s", (int)(FFMIN(255, len)), p); p += len; - if (*p == 0xd) av_log(ctx, AV_LOG_INFO, " "); - if (*p == 0xa) av_log(ctx, AV_LOG_INFO, "\n%s %-16s: ", indent, ""); + if (*p == 0xd) av_writer_printf(wr, " "); + if (*p == 0xa) av_writer_printf(wr, "\n%s %-16s: ", indent, ""); if (*p) p++; } - av_log(ctx, AV_LOG_INFO, "\n"); + av_writer_printf(wr, "\n"); } } } /* param change side data*/ -static void dump_paramchange(void *ctx, const AVPacketSideData *sd) +static void dump_paramchange(AVWriter wr, const AVPacketSideData *sd) { int size = sd->size; const uint8_t *data = sd->data; @@ -184,7 +185,7 @@ FF_DISABLE_DEPRECATION_WARNINGS channels = AV_RL32(data); data += 4; size -= 4; - av_log(ctx, AV_LOG_INFO, "channel count %"PRIu32", ", channels); + av_writer_printf(wr, "channel count %"PRIu32", ", channels); } if (flags & AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_LAYOUT) { if (size < 8) @@ -192,7 +193,7 @@ FF_DISABLE_DEPRECATION_WARNINGS layout = AV_RL64(data); data += 8; size -= 8; - av_log(ctx, AV_LOG_INFO, + av_writer_printf(wr, "channel layout: %s, ", av_get_channel_name(layout)); } FF_ENABLE_DEPRECATION_WARNINGS @@ -203,7 +204,7 @@ FF_ENABLE_DEPRECATION_WARNINGS sample_rate = AV_RL32(data); data += 4; size -= 4; - av_log(ctx, AV_LOG_INFO, "sample_rate %"PRIu32", ", sample_rate); + av_writer_printf(wr, "sample_rate %"PRIu32", ", sample_rate); } if (flags & AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS) { if (size < 8) @@ -214,135 +215,135 @@ FF_ENABLE_DEPRECATION_WARNINGS height = AV_RL32(data); data += 4; size -= 4; - av_log(ctx, AV_LOG_INFO, "width %"PRIu32" height %"PRIu32, width, height); + av_writer_printf(wr, "width %"PRIu32" height %"PRIu32, width, height); } return; fail: - av_log(ctx, AV_LOG_ERROR, "unknown param\n"); + av_log_writer_log(wr, AV_LOG_ERROR, "unknown param\n"); } /* replaygain side data*/ -static void print_gain(void *ctx, const char *str, int32_t gain) +static void print_gain(AVWriter wr, const char *str, int32_t gain) { - av_log(ctx, AV_LOG_INFO, "%s - ", str); + av_writer_printf(wr, "%s - ", str); if (gain == INT32_MIN) - av_log(ctx, AV_LOG_INFO, "unknown"); + av_writer_printf(wr, "unknown"); else - av_log(ctx, AV_LOG_INFO, "%f", gain / 100000.0f); - av_log(ctx, AV_LOG_INFO, ", "); + av_writer_printf(wr, "%f", gain / 100000.0f); + av_writer_printf(wr, ", "); } -static void print_peak(void *ctx, const char *str, uint32_t peak) +static void print_peak(AVWriter wr, const char *str, uint32_t peak) { - av_log(ctx, AV_LOG_INFO, "%s - ", str); + av_writer_printf(wr, "%s - ", str); if (!peak) - av_log(ctx, AV_LOG_INFO, "unknown"); + av_writer_printf(wr, "unknown"); else - av_log(ctx, AV_LOG_INFO, "%f", (float) peak / UINT32_MAX); - av_log(ctx, AV_LOG_INFO, ", "); + av_writer_printf(wr, "%f", (float) peak / UINT32_MAX); + av_writer_printf(wr, ", "); } -static void dump_replaygain(void *ctx, const AVPacketSideData *sd) +static void dump_replaygain(AVWriter wr, const AVPacketSideData *sd) { const AVReplayGain *rg; if (sd->size < sizeof(*rg)) { - av_log(ctx, AV_LOG_ERROR, "invalid data\n"); + av_log_writer_log(wr, AV_LOG_ERROR, "invalid data\n"); return; } rg = (const AVReplayGain *)sd->data; - print_gain(ctx, "track gain", rg->track_gain); - print_peak(ctx, "track peak", rg->track_peak); - print_gain(ctx, "album gain", rg->album_gain); - print_peak(ctx, "album peak", rg->album_peak); + print_gain(wr, "track gain", rg->track_gain); + print_peak(wr, "track peak", rg->track_peak); + print_gain(wr, "album gain", rg->album_gain); + print_peak(wr, "album peak", rg->album_peak); } -static void dump_stereo3d(void *ctx, const AVPacketSideData *sd) +static void dump_stereo3d(AVWriter wr, const AVPacketSideData *sd) { const AVStereo3D *stereo; if (sd->size < sizeof(*stereo)) { - av_log(ctx, AV_LOG_ERROR, "invalid data\n"); + av_log_writer_log(wr, AV_LOG_ERROR, "invalid data\n"); return; } stereo = (const AVStereo3D *)sd->data; - av_log(ctx, AV_LOG_INFO, "%s", av_stereo3d_type_name(stereo->type)); + av_writer_printf(wr, "%s", av_stereo3d_type_name(stereo->type)); if (stereo->flags & AV_STEREO3D_FLAG_INVERT) - av_log(ctx, AV_LOG_INFO, " (inverted)"); + av_writer_printf(wr, " (inverted)"); } -static void dump_audioservicetype(void *ctx, const AVPacketSideData *sd) +static void dump_audioservicetype(AVWriter wr, const AVPacketSideData *sd) { const enum AVAudioServiceType *ast = (const enum AVAudioServiceType *)sd->data; if (sd->size < sizeof(*ast)) { - av_log(ctx, AV_LOG_ERROR, "invalid data\n"); + av_log_writer_log(wr, AV_LOG_ERROR, "invalid data\n"); return; } switch (*ast) { case AV_AUDIO_SERVICE_TYPE_MAIN: - av_log(ctx, AV_LOG_INFO, "main"); + av_writer_printf(wr, "main"); break; case AV_AUDIO_SERVICE_TYPE_EFFECTS: - av_log(ctx, AV_LOG_INFO, "effects"); + av_writer_printf(wr, "effects"); break; case AV_AUDIO_SERVICE_TYPE_VISUALLY_IMPAIRED: - av_log(ctx, AV_LOG_INFO, "visually impaired"); + av_writer_printf(wr, "visually impaired"); break; case AV_AUDIO_SERVICE_TYPE_HEARING_IMPAIRED: - av_log(ctx, AV_LOG_INFO, "hearing impaired"); + av_writer_printf(wr, "hearing impaired"); break; case AV_AUDIO_SERVICE_TYPE_DIALOGUE: - av_log(ctx, AV_LOG_INFO, "dialogue"); + av_writer_printf(wr, "dialogue"); break; case AV_AUDIO_SERVICE_TYPE_COMMENTARY: - av_log(ctx, AV_LOG_INFO, "commentary"); + av_writer_printf(wr, "commentary"); break; case AV_AUDIO_SERVICE_TYPE_EMERGENCY: - av_log(ctx, AV_LOG_INFO, "emergency"); + av_writer_printf(wr, "emergency"); break; case AV_AUDIO_SERVICE_TYPE_VOICE_OVER: - av_log(ctx, AV_LOG_INFO, "voice over"); + av_writer_printf(wr, "voice over"); break; case AV_AUDIO_SERVICE_TYPE_KARAOKE: - av_log(ctx, AV_LOG_INFO, "karaoke"); + av_writer_printf(wr, "karaoke"); break; default: - av_log(ctx, AV_LOG_WARNING, "unknown"); + av_log_writer_log(wr, AV_LOG_WARNING, "unknown"); break; } } -static void dump_cpb(void *ctx, const AVPacketSideData *sd) +static void dump_cpb(AVWriter wr, const AVPacketSideData *sd) { const AVCPBProperties *cpb = (const AVCPBProperties *)sd->data; if (sd->size < sizeof(*cpb)) { - av_log(ctx, AV_LOG_ERROR, "invalid data\n"); + av_log_writer_log(wr, AV_LOG_ERROR, "invalid data\n"); return; } - av_log(ctx, AV_LOG_INFO, + av_writer_printf(wr, "bitrate max/min/avg: %"PRId64"/%"PRId64"/%"PRId64" buffer size: %"PRId64" ", cpb->max_bitrate, cpb->min_bitrate, cpb->avg_bitrate, cpb->buffer_size); if (cpb->vbv_delay == UINT64_MAX) - av_log(ctx, AV_LOG_INFO, "vbv_delay: N/A"); + av_writer_printf(wr, "vbv_delay: N/A"); else - av_log(ctx, AV_LOG_INFO, "vbv_delay: %"PRIu64"", cpb->vbv_delay); + av_writer_printf(wr, "vbv_delay: %"PRIu64"", cpb->vbv_delay); } -static void dump_mastering_display_metadata(void *ctx, const AVPacketSideData *sd) +static void dump_mastering_display_metadata(AVWriter wr, const AVPacketSideData* sd) { const AVMasteringDisplayMetadata *metadata = (const AVMasteringDisplayMetadata *)sd->data; - av_log(ctx, AV_LOG_INFO, "Mastering Display Metadata, " + av_writer_printf(wr, "Mastering Display Metadata, " "has_primaries:%d has_luminance:%d " "r(%5.4f,%5.4f) g(%5.4f,%5.4f) b(%5.4f %5.4f) wp(%5.4f, %5.4f) " "min_luminance=%f, max_luminance=%f", @@ -357,51 +358,51 @@ static void dump_mastering_display_metadata(void *ctx, const AVPacketSideData *s av_q2d(metadata->min_luminance), av_q2d(metadata->max_luminance)); } -static void dump_content_light_metadata(void *ctx, const AVPacketSideData *sd) +static void dump_content_light_metadata(AVWriter wr, const AVPacketSideData* sd) { const AVContentLightMetadata *metadata = (const AVContentLightMetadata *)sd->data; - av_log(ctx, AV_LOG_INFO, "Content Light Level Metadata, " + av_writer_printf(wr, "Content Light Level Metadata, " "MaxCLL=%d, MaxFALL=%d", metadata->MaxCLL, metadata->MaxFALL); } -static void dump_spherical(void *ctx, const AVCodecParameters *par, +static void dump_spherical(AVWriter wr, const AVCodecParameters *par, const AVPacketSideData *sd) { const AVSphericalMapping *spherical = (const AVSphericalMapping *)sd->data; double yaw, pitch, roll; if (sd->size < sizeof(*spherical)) { - av_log(ctx, AV_LOG_ERROR, "invalid data\n"); + av_log_writer_log(wr, AV_LOG_ERROR, "invalid data\n"); return; } - av_log(ctx, AV_LOG_INFO, "%s ", av_spherical_projection_name(spherical->projection)); + av_writer_printf(wr, "%s ", av_spherical_projection_name(spherical->projection)); yaw = ((double)spherical->yaw) / (1 << 16); pitch = ((double)spherical->pitch) / (1 << 16); roll = ((double)spherical->roll) / (1 << 16); - av_log(ctx, AV_LOG_INFO, "(%f/%f/%f) ", yaw, pitch, roll); + av_writer_printf(wr, "(%f/%f/%f) ", yaw, pitch, roll); if (spherical->projection == AV_SPHERICAL_EQUIRECTANGULAR_TILE) { size_t l, t, r, b; av_spherical_tile_bounds(spherical, par->width, par->height, &l, &t, &r, &b); - av_log(ctx, AV_LOG_INFO, + av_writer_printf(wr, "[%"SIZE_SPECIFIER", %"SIZE_SPECIFIER", %"SIZE_SPECIFIER", %"SIZE_SPECIFIER"] ", l, t, r, b); } else if (spherical->projection == AV_SPHERICAL_CUBEMAP) { - av_log(ctx, AV_LOG_INFO, "[pad %"PRIu32"] ", spherical->padding); + av_writer_printf(wr, "[pad %"PRIu32"] ", spherical->padding); } } -static void dump_dovi_conf(void *ctx, const AVPacketSideData *sd) +static void dump_dovi_conf(AVWriter wr, const AVPacketSideData *sd) { const AVDOVIDecoderConfigurationRecord *dovi = (const AVDOVIDecoderConfigurationRecord *)sd->data; - av_log(ctx, AV_LOG_INFO, "version: %d.%d, profile: %d, level: %d, " + av_writer_printf(wr, "version: %d.%d, profile: %d, level: %d, " "rpu flag: %d, el flag: %d, bl flag: %d, compatibility id: %d", dovi->dv_version_major, dovi->dv_version_minor, dovi->dv_profile, dovi->dv_level, @@ -411,104 +412,104 @@ static void dump_dovi_conf(void *ctx, const AVPacketSideData *sd) dovi->dv_bl_signal_compatibility_id); } -static void dump_s12m_timecode(void *ctx, const AVStream *st, const AVPacketSideData *sd) +static void dump_s12m_timecode(AVWriter wr, const AVStream *st, const AVPacketSideData *sd) { const uint32_t *tc = (const uint32_t *)sd->data; if ((sd->size != sizeof(uint32_t) * 4) || (tc[0] > 3)) { - av_log(ctx, AV_LOG_ERROR, "invalid data\n"); + av_log_writer_log(wr, AV_LOG_ERROR, "invalid data\n"); return; } for (int j = 1; j <= tc[0]; j++) { char tcbuf[AV_TIMECODE_STR_SIZE]; av_timecode_make_smpte_tc_string2(tcbuf, st->avg_frame_rate, tc[j], 0, 0); - av_log(ctx, AV_LOG_INFO, "timecode - %s%s", tcbuf, j != tc[0] ? ", " : ""); + av_writer_printf(wr, "timecode - %s%s", tcbuf, j != tc[0] ? ", " : ""); } } -static void dump_sidedata(void *ctx, const AVStream *st, const char *indent) +static void dump_sidedata(AVWriter wr, const AVStream *st, const char *indent) { int i; if (st->nb_side_data) - av_log(ctx, AV_LOG_INFO, "%sSide data:\n", indent); + av_writer_printf(wr, "%sSide data:\n", indent); for (i = 0; i < st->nb_side_data; i++) { const AVPacketSideData *sd = &st->side_data[i]; - av_log(ctx, AV_LOG_INFO, "%s ", indent); + av_writer_printf(wr, "%s ", indent); switch (sd->type) { case AV_PKT_DATA_PALETTE: - av_log(ctx, AV_LOG_INFO, "palette"); + av_writer_printf(wr, "palette"); break; case AV_PKT_DATA_NEW_EXTRADATA: - av_log(ctx, AV_LOG_INFO, "new extradata"); + av_writer_printf(wr, "new extradata"); break; case AV_PKT_DATA_PARAM_CHANGE: - av_log(ctx, AV_LOG_INFO, "paramchange: "); - dump_paramchange(ctx, sd); + av_writer_printf(wr, "paramchange: "); + dump_paramchange(wr, sd); break; case AV_PKT_DATA_H263_MB_INFO: - av_log(ctx, AV_LOG_INFO, "H.263 macroblock info"); + av_writer_printf(wr, "H.263 macroblock info"); break; case AV_PKT_DATA_REPLAYGAIN: - av_log(ctx, AV_LOG_INFO, "replaygain: "); - dump_replaygain(ctx, sd); + av_writer_printf(wr, "replaygain: "); + dump_replaygain(wr, sd); break; case AV_PKT_DATA_DISPLAYMATRIX: - av_log(ctx, AV_LOG_INFO, "displaymatrix: rotation of %.2f degrees", + av_writer_printf(wr, "displaymatrix: rotation of %.2f degrees", av_display_rotation_get((const int32_t *)sd->data)); break; case AV_PKT_DATA_STEREO3D: - av_log(ctx, AV_LOG_INFO, "stereo3d: "); - dump_stereo3d(ctx, sd); + av_writer_printf(wr, "stereo3d: "); + dump_stereo3d(wr, sd); break; case AV_PKT_DATA_AUDIO_SERVICE_TYPE: - av_log(ctx, AV_LOG_INFO, "audio service type: "); - dump_audioservicetype(ctx, sd); + av_writer_printf(wr, "audio service type: "); + dump_audioservicetype(wr, sd); break; case AV_PKT_DATA_QUALITY_STATS: - av_log(ctx, AV_LOG_INFO, "quality factor: %"PRId32", pict_type: %c", + av_writer_printf(wr, "quality factor: %"PRId32", pict_type: %c", AV_RL32(sd->data), av_get_picture_type_char(sd->data[4])); break; case AV_PKT_DATA_CPB_PROPERTIES: - av_log(ctx, AV_LOG_INFO, "cpb: "); - dump_cpb(ctx, sd); + av_writer_printf(wr, "cpb: "); + dump_cpb(wr, sd); break; case AV_PKT_DATA_MASTERING_DISPLAY_METADATA: - dump_mastering_display_metadata(ctx, sd); + dump_mastering_display_metadata(wr, sd); break; case AV_PKT_DATA_SPHERICAL: - av_log(ctx, AV_LOG_INFO, "spherical: "); - dump_spherical(ctx, st->codecpar, sd); + av_writer_printf(wr, "spherical: "); + dump_spherical(wr, st->codecpar, sd); break; case AV_PKT_DATA_CONTENT_LIGHT_LEVEL: - dump_content_light_metadata(ctx, sd); + dump_content_light_metadata(wr, sd); break; case AV_PKT_DATA_ICC_PROFILE: - av_log(ctx, AV_LOG_INFO, "ICC Profile"); + av_writer_printf(wr, "ICC Profile"); break; case AV_PKT_DATA_DOVI_CONF: - av_log(ctx, AV_LOG_INFO, "DOVI configuration record: "); - dump_dovi_conf(ctx, sd); + av_writer_printf(wr, "DOVI configuration record: "); + dump_dovi_conf(wr, sd); break; case AV_PKT_DATA_S12M_TIMECODE: - av_log(ctx, AV_LOG_INFO, "SMPTE ST 12-1:2014: "); - dump_s12m_timecode(ctx, st, sd); + av_writer_printf(wr, "SMPTE ST 12-1:2014: "); + dump_s12m_timecode(wr, st, sd); break; default: - av_log(ctx, AV_LOG_INFO, "unknown side data type %d " - "(%"SIZE_SPECIFIER" bytes)", sd->type, sd->size); + av_writer_printf(wr, "unknown side data type %d (%"SIZE_SPECIFIER" bytes)", sd->type, sd->size); break; } - av_log(ctx, AV_LOG_INFO, "\n"); + av_writer_printf(wr, "\n"); } } /* "user interface" functions */ -static void dump_stream_format(const AVFormatContext *ic, int i, +static void dump_stream_format(AVWriter wr, + const AVFormatContext *ic, int i, int index, int is_output) { char buf[256]; @@ -543,17 +544,17 @@ static void dump_stream_format(const AVFormatContext *ic, int i, avcodec_string(buf, sizeof(buf), avctx, is_output); avcodec_free_context(&avctx); - av_log(NULL, AV_LOG_INFO, " Stream #%d:%d", index, i); + av_writer_printf(wr, " Stream #%d:%d", index, i); /* the pid is an important information, so we display it */ /* XXX: add a generic system */ if (flags & AVFMT_SHOW_IDS) - av_log(NULL, AV_LOG_INFO, "[0x%x]", st->id); + av_writer_printf(wr, "[0x%x]", st->id); if (lang) - av_log(NULL, AV_LOG_INFO, "(%s)", lang->value); - av_log(NULL, AV_LOG_DEBUG, ", %d, %d/%d", sti->codec_info_nb_frames, + av_writer_printf(wr, "(%s)", lang->value); + av_log_writer_log(wr, AV_LOG_DEBUG, ", %d, %d/%d", sti->codec_info_nb_frames, st->time_base.num, st->time_base.den); - av_log(NULL, AV_LOG_INFO, ": %s", buf); + av_writer_printf(wr, ": %s", buf); if (st->sample_aspect_ratio.num && av_cmp_q(st->sample_aspect_ratio, st->codecpar->sample_aspect_ratio)) { @@ -562,7 +563,7 @@ static void dump_stream_format(const AVFormatContext *ic, int i, st->codecpar->width * (int64_t)st->sample_aspect_ratio.num, st->codecpar->height * (int64_t)st->sample_aspect_ratio.den, 1024 * 1024); - av_log(NULL, AV_LOG_INFO, ", SAR %d:%d DAR %d:%d", + av_writer_printf(wr, ", SAR %d:%d DAR %d:%d", st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, display_aspect_ratio.num, display_aspect_ratio.den); } @@ -573,76 +574,78 @@ static void dump_stream_format(const AVFormatContext *ic, int i, int tbn = st->time_base.den && st->time_base.num; if (fps || tbr || tbn) - av_log(NULL, AV_LOG_INFO, "%s", separator); + av_writer_printf(wr, "%s", separator); if (fps) - print_fps(av_q2d(st->avg_frame_rate), tbr || tbn ? "fps, " : "fps"); + print_fps(wr, av_q2d(st->avg_frame_rate), tbr || tbn ? "fps, " : "fps"); if (tbr) - print_fps(av_q2d(st->r_frame_rate), tbn ? "tbr, " : "tbr"); + print_fps(wr, av_q2d(st->r_frame_rate), tbn ? "tbr, " : "tbr"); if (tbn) - print_fps(1 / av_q2d(st->time_base), "tbn"); + print_fps(wr, 1 / av_q2d(st->time_base), "tbn"); } if (st->disposition & AV_DISPOSITION_DEFAULT) - av_log(NULL, AV_LOG_INFO, " (default)"); + av_writer_printf(wr, " (default)"); if (st->disposition & AV_DISPOSITION_DUB) - av_log(NULL, AV_LOG_INFO, " (dub)"); + av_writer_printf(wr, " (dub)"); if (st->disposition & AV_DISPOSITION_ORIGINAL) - av_log(NULL, AV_LOG_INFO, " (original)"); + av_writer_printf(wr, " (original)"); if (st->disposition & AV_DISPOSITION_COMMENT) - av_log(NULL, AV_LOG_INFO, " (comment)"); + av_writer_printf(wr, " (comment)"); if (st->disposition & AV_DISPOSITION_LYRICS) - av_log(NULL, AV_LOG_INFO, " (lyrics)"); + av_writer_printf(wr, " (lyrics)"); if (st->disposition & AV_DISPOSITION_KARAOKE) - av_log(NULL, AV_LOG_INFO, " (karaoke)"); + av_writer_printf(wr, " (karaoke)"); if (st->disposition & AV_DISPOSITION_FORCED) - av_log(NULL, AV_LOG_INFO, " (forced)"); + av_writer_printf(wr, " (forced)"); if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED) - av_log(NULL, AV_LOG_INFO, " (hearing impaired)"); + av_writer_printf(wr, " (hearing impaired)"); if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED) - av_log(NULL, AV_LOG_INFO, " (visual impaired)"); + av_writer_printf(wr, " (visual impaired)"); if (st->disposition & AV_DISPOSITION_CLEAN_EFFECTS) - av_log(NULL, AV_LOG_INFO, " (clean effects)"); + av_writer_printf(wr, " (clean effects)"); if (st->disposition & AV_DISPOSITION_ATTACHED_PIC) - av_log(NULL, AV_LOG_INFO, " (attached pic)"); + av_writer_printf(wr, " (attached pic)"); if (st->disposition & AV_DISPOSITION_TIMED_THUMBNAILS) - av_log(NULL, AV_LOG_INFO, " (timed thumbnails)"); + av_writer_printf(wr, " (timed thumbnails)"); if (st->disposition & AV_DISPOSITION_CAPTIONS) - av_log(NULL, AV_LOG_INFO, " (captions)"); + av_writer_printf(wr, " (captions)"); if (st->disposition & AV_DISPOSITION_DESCRIPTIONS) - av_log(NULL, AV_LOG_INFO, " (descriptions)"); + av_writer_printf(wr, " (descriptions)"); if (st->disposition & AV_DISPOSITION_METADATA) - av_log(NULL, AV_LOG_INFO, " (metadata)"); + av_writer_printf(wr, " (metadata)"); if (st->disposition & AV_DISPOSITION_DEPENDENT) - av_log(NULL, AV_LOG_INFO, " (dependent)"); + av_writer_printf(wr, " (dependent)"); if (st->disposition & AV_DISPOSITION_STILL_IMAGE) - av_log(NULL, AV_LOG_INFO, " (still image)"); + av_writer_printf(wr, " (still image)"); if (st->disposition & AV_DISPOSITION_NON_DIEGETIC) - av_log(NULL, AV_LOG_INFO, " (non-diegetic)"); - av_log(NULL, AV_LOG_INFO, "\n"); + av_writer_printf(wr, " (non-diegetic)"); + av_writer_printf(wr, "\n"); - dump_metadata(NULL, st->metadata, " "); + dump_metadata(wr, st->metadata, " "); - dump_sidedata(NULL, st, " "); + dump_sidedata(wr, st, " "); } -void av_dump_format(AVFormatContext *ic, int index, - const char *url, int is_output) +void av_dump_format_to_writer(AVWriter wr, + AVFormatContext *ic, int index, + const char *url, unsigned flags) { + int is_output = flags & AV_DUMP_FORMAT_IS_OUTPUT; int i; uint8_t *printed = ic->nb_streams ? av_mallocz(ic->nb_streams) : NULL; if (ic->nb_streams && !printed) return; - av_log(NULL, AV_LOG_INFO, "%s #%d, %s, %s '%s':\n", + av_writer_printf(wr, "%s #%d, %s, %s '%s':\n", is_output ? "Output" : "Input", index, is_output ? ic->oformat->name : ic->iformat->name, is_output ? "to" : "from", url); - dump_metadata(NULL, ic->metadata, " "); + dump_metadata(wr, ic->metadata, " "); if (!is_output) { - av_log(NULL, AV_LOG_INFO, " Duration: "); + av_writer_printf(wr, " Duration: "); if (ic->duration != AV_NOPTS_VALUE) { int64_t hours, mins, secs, us; int64_t duration = ic->duration + (ic->duration <= INT64_MAX - 5000 ? 5000 : 0); @@ -652,40 +655,40 @@ void av_dump_format(AVFormatContext *ic, int index, secs %= 60; hours = mins / 60; mins %= 60; - av_log(NULL, AV_LOG_INFO, "%02"PRId64":%02"PRId64":%02"PRId64".%02"PRId64"", hours, mins, secs, + av_writer_printf(wr, "%02"PRId64":%02"PRId64":%02"PRId64".%02"PRId64"", hours, mins, secs, (100 * us) / AV_TIME_BASE); } else { - av_log(NULL, AV_LOG_INFO, "N/A"); + av_writer_printf(wr, "N/A"); } if (ic->start_time != AV_NOPTS_VALUE) { int secs, us; - av_log(NULL, AV_LOG_INFO, ", start: "); + av_writer_printf(wr, ", start: "); secs = llabs(ic->start_time / AV_TIME_BASE); us = llabs(ic->start_time % AV_TIME_BASE); - av_log(NULL, AV_LOG_INFO, "%s%d.%06d", + av_writer_printf(wr, "%s%d.%06d", ic->start_time >= 0 ? "" : "-", secs, (int) av_rescale(us, 1000000, AV_TIME_BASE)); } - av_log(NULL, AV_LOG_INFO, ", bitrate: "); + av_writer_printf(wr, ", bitrate: "); if (ic->bit_rate) - av_log(NULL, AV_LOG_INFO, "%"PRId64" kb/s", ic->bit_rate / 1000); + av_writer_printf(wr, "%"PRId64" kb/s", ic->bit_rate / 1000); else - av_log(NULL, AV_LOG_INFO, "N/A"); - av_log(NULL, AV_LOG_INFO, "\n"); + av_writer_printf(wr, "N/A"); + av_writer_printf(wr, "\n"); } if (ic->nb_chapters) av_log(NULL, AV_LOG_INFO, " Chapters:\n"); for (i = 0; i < ic->nb_chapters; i++) { const AVChapter *ch = ic->chapters[i]; - av_log(NULL, AV_LOG_INFO, " Chapter #%d:%d: ", index, i); - av_log(NULL, AV_LOG_INFO, + av_writer_printf(wr, " Chapter #%d:%d: ", index, i); + av_writer_printf(wr, "start %f, ", ch->start * av_q2d(ch->time_base)); - av_log(NULL, AV_LOG_INFO, + av_writer_printf(wr, "end %f\n", ch->end * av_q2d(ch->time_base)); - dump_metadata(NULL, ch->metadata, " "); + dump_metadata(wr, ch->metadata, " "); } if (ic->nb_programs) { @@ -694,23 +697,30 @@ void av_dump_format(AVFormatContext *ic, int index, const AVProgram *program = ic->programs[j]; const AVDictionaryEntry *name = av_dict_get(program->metadata, "name", NULL, 0); - av_log(NULL, AV_LOG_INFO, " Program %d %s\n", program->id, + av_writer_printf(wr, " Program %d %s\n", program->id, name ? name->value : ""); - dump_metadata(NULL, program->metadata, " "); + dump_metadata(wr, program->metadata, " "); for (k = 0; k < program->nb_stream_indexes; k++) { - dump_stream_format(ic, program->stream_index[k], + dump_stream_format(wr, ic, program->stream_index[k], index, is_output); printed[program->stream_index[k]] = 1; } total += program->nb_stream_indexes; } if (total < ic->nb_streams) - av_log(NULL, AV_LOG_INFO, " No Program\n"); + av_writer_printf(wr, " No Program\n"); } for (i = 0; i < ic->nb_streams; i++) if (!printed[i]) - dump_stream_format(ic, i, index, is_output); + dump_stream_format(wr, ic, i, index, is_output); av_free(printed); } + +void av_dump_format(AVFormatContext *ic, int index, + const char *url, int is_output) +{ + av_dump_format_to_writer(av_log_writer(NULL), ic, index, url, + is_output ? AV_DUMP_FORMAT_IS_OUTPUT : 0); +} From patchwork Fri Apr 28 09:55:05 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 41397 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:dca6:b0:f3:34fa:f187 with SMTP id ky38csp1188444pzb; Fri, 28 Apr 2023 02:56:07 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ4bpU5cXS5B5hOr0OufCknwUZiUqIfpgb/cPZdO6z/xuxzJSVHk+jc8B7TMbzdcFSZNKyoK X-Received: by 2002:a17:907:7207:b0:960:d9d:ffb5 with SMTP id dr7-20020a170907720700b009600d9dffb5mr5070443ejc.41.1682675766965; Fri, 28 Apr 2023 02:56:06 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1682675766; cv=none; d=google.com; s=arc-20160816; b=HN3KJy8YcQ3vMGkVAj/QkeQ/s6wJ8v/vJiUA/rRDXXk4ulMgmq4zxqluQezGaM3Oh1 f6iyxsTouifxLu/CT9tzqECUEKD+yiuXwEmRku//OfHsFGIzFqlV7gGFy3gVPgaMy2G1 Ulgc19tNYuUogSNmH87BQzbZV54NVHi94fTzLomK1tpZaN+BNjOzEBWzGYHQNUz3dUI4 s99PSZ1HSdyJH2trmcRwuM4g14ShzK0tAKQS+5FzrLKb3orrR6jxZdmGoz788KMAAkvt ruA5U+B4xfzXUdPWI+WCQ9mH4p4CH8FWSV2cIaTPa2IIvNdCDNbD5q+7HVhdS3FEHAsr mXDA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:delivered-to; bh=4D12w9cNjJiVDPzXkzVBEoP+/lbMQYRADlFeuizdGYk=; b=tf8QSENKvcRAWj+7S3xgpdKnO/AaI5sste36HA6EdH2nr+5P/0ndULNLQ5RKus9u2h 7UVA2a2DWKNTJjQvOu8dKoiGg8albMhFmbTiYYAPZGfGcUJsZtm9ipWvBI5NYFHPwl3M IpUtR71QuS4/ecQgQYSsGFVLtRKcJKWRStHJTMWFwTNQhdseRpfy7GgLBDBH1tHKo6tf GG6Cnrpy3cIqhbrropY9Zwz6OvmDCXb7qloHDVX5a+3TlbRm5OXJJoiifanFQX35hgG3 xk/X0ZEOUZjv9bX5DH7B0XuJsuSNTEf2Z7uiMpy36EGrF7UuGKQd6rnUY6qIh3gckDHG klcA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id dr20-20020a170907721400b00957ee6a8ab0si11645616ejc.653.2023.04.28.02.56.06; Fri, 28 Apr 2023 02:56:06 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 1363368BFDC; Fri, 28 Apr 2023 12:55:24 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from nef.ens.fr (nef2.ens.fr [129.199.96.40]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 25DB468BF7D for ; Fri, 28 Apr 2023 12:55:15 +0300 (EEST) X-ENS-nef-client: 129.199.129.80 ( name = phare.normalesup.org ) Received: from phare.normalesup.org (phare.normalesup.org [129.199.129.80]) by nef.ens.fr (8.14.4/1.01.28121999) with ESMTP id 33S9tEsm014799 for ; Fri, 28 Apr 2023 11:55:14 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id 70B11EB5BF; Fri, 28 Apr 2023 11:55:14 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Fri, 28 Apr 2023 11:55:05 +0200 Message-Id: <20230428095508.221826-5-george@nsup.org> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230428095508.221826-1-george@nsup.org> References: <20230428095508.221826-1-george@nsup.org> MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Fri, 28 Apr 2023 11:55:14 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 5/8] lavu: add a JSON writer API (WIP) X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: sbB/k4eeBsHT Signed-off-by: Nicolas George --- libavutil/Makefile | 1 + libavutil/json.c | 368 +++++++++++++++++++++++++++++++++++ libavutil/json.h | 470 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 839 insertions(+) create mode 100644 libavutil/json.c create mode 100644 libavutil/json.h diff --git a/libavutil/Makefile b/libavutil/Makefile index 4526ec80ca..a8a9700778 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -140,6 +140,7 @@ OBJS = adler32.o \ imgutils.o \ integer.o \ intmath.o \ + json.o \ lfg.o \ lls.o \ log.o \ diff --git a/libavutil/json.c b/libavutil/json.c new file mode 100644 index 0000000000..7ee68aaa4a --- /dev/null +++ b/libavutil/json.c @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2021 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 "avassert.h" +#include "common.h" +#include "json.h" +#include "opt.h" + +#define FIELDOK(st, f) ((char *)(&(st)->f + 1) <= (char *)(st) + (st)->self_size) + +#define json_assert_abi(jwr) av_assert1(FIELDOK(jwr, padding)) +#define json_escape_writer_assert_abi(jwr) av_assert1(FIELDOK(jwr, padding)) + +AVClass *av_json_get_class(void) +{ + return NULL; +} + +int av_json_alloc(AVJson **jc, unsigned max_depth) +{ + return AVERROR_BUG; +} + +void av_json_free(AVJson **jc) +{ +} + +AVJson *av_json_preinit(AVJson *jc, AVJsonEscapeWriter *jwr) +{ + json_assert_abi(jc); + json_escape_writer_assert_abi(jwr); + jc->av_class = av_json_get_class(); + jc->escape_writer = jwr; + av_opt_set_defaults(jc); + return jc; +} + +void av_json_init(AVJson *jc, AVWriter wr, unsigned flags, AVDictionary *options) +{ + jc->out = wr; + jc->flags = flags; + jc->in_string = 0; + jc->in_object = 0; + jc->first_element = 1; + jc->depth = 0; + jc->stack = NULL; +} + +static inline int check_value_must_be_string(AVJson *jc, int string) +{ + return !jc->object_key || string; +} + +static inline int check_stack_clean(AVJsonStack *stack) +{ + return !stack->prev; +} + +static inline int check_begin_end_balanced(AVJson *jc, int obj) +{ + return jc->in_object == obj && jc->stack; +} + +static inline int flag_pretty_print(AVJson *jc) +{ + return !!(jc->flags & AV_JSON_FLAG_PRETTY_PRINT); +} + +static void end_value(AVJson *jc) +{ + if (!jc->stack && flag_pretty_print(jc)) + av_writer_print(jc->out, "\n"); +} + +static void auto_end_string(AVJson *jc) +{ + if (jc->in_string) { + av_writer_print(jc->out, "\""); + jc->in_string = 0; + end_value(jc); + } +} + +static void auto_add_separator(AVJson *jc) +{ + int indent = flag_pretty_print(jc); + + auto_end_string(jc); + if (!jc->first_element) { + if (jc->object_key) { + av_writer_print(jc->out, flag_pretty_print(jc) ? " : " : ":"); + indent = jc->object_key = 0; + } else { + av_writer_print(jc->out, ","); + jc->object_key = jc->in_object; + } + } + if (indent) { + if (jc->stack) + av_writer_print(jc->out, "\n"); + av_writer_add_chars(jc->out, ' ', 3 * jc->depth); + } + jc->first_element = 0; +} + +static void begin_value(AVJson *jc, int string) +{ + auto_add_separator(jc); + av_assert1(check_value_must_be_string(jc, string)); +} + +static void begin_compound(AVJson *jc, AVJsonStack *stack, unsigned obj) +{ + av_assert1(FIELDOK(stack, in_object)); + av_assert1(check_stack_clean(stack)); + stack->prev = jc->stack; + stack->in_object = jc->in_object; + jc->stack = stack; + jc->first_element = 1; + jc->in_object = jc->object_key = obj; + jc->depth++; +} + +static void end_compound(AVJson *jc, unsigned obj) +{ + AVJsonStack *stack = jc->stack; + + av_assert1(check_begin_end_balanced(jc, obj)); + auto_end_string(jc); + jc->depth--; + if (!jc->first_element && flag_pretty_print(jc)) { + av_writer_print(jc->out, "\n"); + av_writer_add_chars(jc->out, ' ', 3 * jc->depth); + } + jc->in_object = stack->in_object; + jc->object_key = 0; + jc->first_element = 0; + jc->stack = stack->prev; + stack->prev = NULL; +} + +void av_json_begin_object_with_stack(AVJson *jc, AVJsonStack *stack) +{ + begin_value(jc, 0); + begin_compound(jc, stack, 1); + av_writer_print(jc->out, "{"); +} + +void av_json_end_object(AVJson *jc) +{ + end_compound(jc, 1); + av_writer_print(jc->out, "}"); + end_value(jc); +} + +void av_json_begin_array_with_stack(AVJson *jc, AVJsonStack *stack) +{ + begin_value(jc, 0); + begin_compound(jc, stack, 0); + av_writer_print(jc->out, "["); +} + +void av_json_end_array(AVJson *jc) +{ + end_compound(jc, 0); + av_writer_print(jc->out, "]"); + end_value(jc); +} + +AVWriter av_json_begin_string(AVJson *jc) +{ + begin_value(jc, 1); + jc->in_string = 1; + av_writer_print(jc->out, "\""); + av_json_escape_writer_init(jc->escape_writer, jc->out, jc->flags); + return av_json_escape_writer_wrap(jc->escape_writer); +} + +void av_json_end_string(AVJson *jc) +{ + av_assert1(jc->in_string); + auto_end_string(jc); +} + +void av_json_add_string(AVJson *jc, const char *str) +{ + AVWriter wr = av_json_begin_string(jc); + av_writer_print(wr, str); +} + +void av_json_add_string_vprintf(AVJson *jc, const char *fmt, va_list va) +{ + AVWriter wr = av_json_begin_string(jc); + av_writer_vprintf(wr, fmt, va); + av_json_end_string(jc); +} + +void av_json_add_string_printf(AVJson *jc, const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + av_json_add_string_vprintf(jc, fmt, va); + va_end(va); +} + +void av_json_add_int(AVJson *jc, intmax_t val) +{ + begin_value(jc, 0); + av_writer_printf(jc->out, "%jd", val); + end_value(jc); +} + +void av_json_add_double(AVJson *jc, double val) +{ + begin_value(jc, 0); + av_writer_printf(jc->out, "%.15g", val); + end_value(jc); +} + +void av_json_add_bool(AVJson *jc, int val) +{ + begin_value(jc, 0); + av_writer_print(jc->out, val ? "true" : "false"); + end_value(jc); +} + +void av_json_add_null(AVJson *jc) +{ + begin_value(jc, 0); + av_writer_print(jc->out, "null"); + end_value(jc); +} + +void av_json_add_raw_vprintf(AVJson *jc, const char *fmt, va_list va) +{ + begin_value(jc, 1); + av_writer_vprintf(jc->out, fmt, va); + end_value(jc); +} + +void av_json_add_raw_printf(AVJson *jc, const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + av_json_add_raw_vprintf(jc, fmt, va); + va_end(va); +} + +/*************************************************************************** + * AVJsonEscapeWriter - escpae JSON strings + ***************************************************************************/ + +static void json_escape_writer_write(AVWriter wr, const char *data, size_t size) +{ + AVJsonEscapeWriter *jwr = wr.obj; + const uint8_t *end = data + size; + const uint8_t *cur = data; + const uint8_t *written = data; + const uint8_t *raw = data; + unsigned char buf[13]; + const char *escape; + int32_t c; + int ret; + + av_assert1(av_json_escape_writer_check(wr)); + json_escape_writer_assert_abi(jwr); + if (jwr->error) + return; + while (cur < end) { + raw = cur; + ret = av_utf8_decode(&c, &cur, end, 0); + if (ret < 0) { + if ((jwr->flags & AV_JSON_FLAG_BAD_ENCODING_EXPLODE)) { + av_log(NULL, AV_LOG_PANIC, "Bad UTF-8 in JSON\n"); + abort(); + } + if ((jwr->flags & AV_JSON_FLAG_BAD_ENCODING_REPLACE)) { + c = 0xFFFD; + } else { + jwr->error = ret; + return; + } + } + av_assert1(c >= 0 && c <= 0x10FFFF); + if ((unsigned)(c - ' ') < 127 - ' ' && c != '"' && c != '\\') + continue; + if ((unsigned)(c - 0x0080) <= (0xFFFF - 0x0080)) /* TODO flag */ + continue; + if (c == '"') { + escape = "\\\""; + } else if (c == '\\') { + escape = "\\\\"; + } else if (c == '\n') { + escape = "\\n"; + } else if (c == '\r') { + escape = "\\r"; + } else if (c == '\t') { + escape = "\\t"; + } else if (c == '\f') { + escape = "\\f"; + } else if (c == '\b') { + escape = "\\b"; + } else { + if (c < 0x10000) + snprintf(buf, sizeof(buf), "\\u%04X", c); + else /* JSON sucks: UTF-16 */ + snprintf(buf, sizeof(buf), "\\u%04X\\u%04X", + 0xD800 + (((c - 0x10000) >> 10) & 0x3FF), + 0xDC00 + (((c - 0x10000) >> 0) & 0x3FF)); + escape = buf; + } + if (raw > written) + av_writer_write(jwr->owr, written, raw - written); + av_writer_print(jwr->owr, escape); + written = raw = cur; + } + if (cur > written) + av_writer_write(jwr->owr, written, cur - written); +} + +static int json_escape_writer_get_error(AVWriter wr, int self_only) +{ + AVJsonEscapeWriter *jwr = wr.obj; + + av_assert1(av_json_escape_writer_check(wr)); + return jwr->error ? jwr->error : + self_only ? 0 : av_writer_get_error(jwr->owr, 0); +} + +AV_WRITER_DEFINE_METHODS(/*public*/, AVJsonEscapeWriter, av_json_escape_writer) { + .self_size = sizeof(AVWriterMethods), + .name = "AVJsonEscapeWriter", + .write = json_escape_writer_write, + .get_error = json_escape_writer_get_error, +}; + +AVJsonEscapeWriter *av_json_escape_writer_init(AVJsonEscapeWriter *jwr, AVWriter owr, unsigned flags) +{ + json_escape_writer_assert_abi(jwr); + jwr->owr = owr; + jwr->flags = flags; + return jwr; +} + +AVWriter av_json_escape_writer_wrap(AVJsonEscapeWriter *jwr) +{ + AVWriter r = { av_json_escape_writer_get_methods(), jwr }; + json_escape_writer_assert_abi(jwr); + return r; +} + diff --git a/libavutil/json.h b/libavutil/json.h new file mode 100644 index 0000000000..6cdcdc1348 --- /dev/null +++ b/libavutil/json.h @@ -0,0 +1,470 @@ +/* + * 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_JSON_H +#define AVUTIL_JSON_H + +#include +#include +#include "dict.h" +#include "extendable.h" +#include "log.h" +#include "writer.h" + +/** + * @defgroup av_json AVJson + * + * API to serialize data to JSON. + * + * API summary and quick HOWTO: + * + * AVWriter out = av_dynbuf_writer(); + * AVJson *jc = AV_JSON_DEFINE(); + * av_json_init(jc, out, AV_JSON_FLAG_PRETTY_PRINT, NULL); + * av_json_begin_object(jc); + * av_json_add_string("user"); + * av_json_add_string(user_name); + * av_json_add_string("score"); + * av_json_add_int("%d", score); + * av_json_end_object(jc); + * av_json_finish(jc); + * ret = av_writer_get_error(out, 0); + * if (ret < 0) ... + * data = av_dynbuf_writer_get_data(wr, &size); + * + * @{ + */ + +typedef struct AVJson AVJson; +typedef struct AVJsonStack AVJsonStack; +typedef struct AVJsonEscapeWriter AVJsonEscapeWriter; +typedef struct AVJsonValue AVJsonValue; +typedef enum AVJsonValueType AVJsonValueType; + +/** + * Produce ASCII output by escaping non-ASCII characters + */ +#define AV_JSON_FLAG_ASCII 0x0001 + +/** + * Abort if the input is not valid UTF-8. + */ +#define AV_JSON_FLAG_BAD_ENCODING_EXPLODE 0x0002 + +/** + * If the input is not valid UTF-8, replace the offending partis with a + * replacement character. + */ +#define AV_JSON_FLAG_BAD_ENCODING_REPLACE 0x0004 + +/** + * Pretty-print the output with spaces and indentation. + * XXX + */ +#define AV_JSON_FLAG_PRETTY_PRINT 0x0008 + +/** + * Consider strings to be pseudo-binary instead of UTF-8. + * + * Warning: without AV_JSON_FLAG_ASCII, it will probably produce + * non-standard JSON. + * XXX + */ +#define AV_JSON_FLAG_PSEUDO_BINARY 0x0010 + +/** + * Define a JSON context by allocating it as compound literals + * (hidden local variables). + * The context will be valid in the current scope and no further. + */ +#define AV_JSON_DEFINE(max_depth) \ + av_json_preinit(&FF_NEW_SZ(AVJson), &FF_NEW_SZ(AVJsonEscapeWriter)) + +/** + * Get the AVClass for a JSON context. + */ +AVClass *av_json_get_class(void); + +/** + * Pre-initialize a JSON context with its escape writer. + * @return jc itself + */ +AVJson *av_json_preinit(AVJson *jc, AVJsonEscapeWriter *jwr); + +/** + * Allocate a new JSON context. + * Only use this if AV_JSON_DEFINE() is not suitable. + * @return 0 or an AVERROR code, including AVERROR(ENOMEM). + */ +int av_json_alloc(AVJson **jc, unsigned max_depth); + +/** + * Free a JSON context allocated with av_json_alloc(). + */ +void av_json_free(AVJson **jc); + +/** + * Initialize a JSON context with output, flags an options. + * This function can be called several times on the same context to reuse + * it. + */ +void av_json_init(AVJson *jc, AVWriter wr, unsigned flags, AVDictionary *options); + +/** + * Begin an object, i.e. a { key : value... } dictionary. + * After this, every other value must be a string. + * The behavior is undefined if a key is not a string. + * The stack object must be allocated and inited, and valid until the + * corresponding av_json_end_object(). + * See av_json_begin_object() for a more convenient version. + */ +void av_json_begin_object_with_stack(AVJson *jc, AVJsonStack *stack); + +/** + * End an object. + */ +void av_json_end_object(AVJson *jc); + +/** + * Begin an array, i.e. a [ value, value... ] list. + * The stack object must be allocated and inited, and valid until the + * corresponding av_json_end_object(). + * See av_json_begin_array() for a more convenient version. + */ +void av_json_begin_array_with_stack(AVJson *jc, AVJsonStack *stack); + +/** + * End an array. + */ +void av_json_end_array(AVJson *jc); + +/** + * Begin a string and a return an AVWriter to write its contents. + */ +AVWriter av_json_begin_string(AVJson *jc); + +/** + * End a string. Optional. + */ +void av_json_end_string(AVJson *jc); + +/** + * Add a string all at once. + */ +void av_json_add_string(AVJson *jc, const char *str); + +/** + * Add a string all at once from a format string and a va_list. + */ +void av_json_add_string_vprintf(AVJson *jc, const char *fmt, va_list va); + +/** + * Add a sring all at once from a format string and arguments. + */ +void av_json_add_string_printf(AVJson *jc, const char *fmt, ...) av_printf_format(2, 3); + +/** + * Add an integer number. + */ +void av_json_add_int(AVJson *jc, intmax_t val); + +/** + * Add a floating-point number. + */ +void av_json_add_double(AVJson *jc, double val); + +/** + * Add a boolean value (true/false). + */ +void av_json_add_bool(AVJson *jc, int val); + +/** + * Add a null value. + */ +void av_json_add_null(AVJson *jc); + +/** + * Add an arbitrary value from a format string and a va_list. + * Useful for adding a floating point number and controlling the format. + * Warning: the validity of the output cannot be guaranteed. + */ +void av_json_add_raw_vprintf(AVJson *jc, const char *fmt, va_list va); + +/** + * Add an arbitrary value from a format string and arguments. + * Useful for adding a floating point number and controlling the format. + * Warning: the validity of the output cannot be guaranteed. + */ +void av_json_add_raw_printf(AVJson *jc, const char *fmt, ...) av_printf_format(2, 3); + +/** + * Define and init a stack element as a compound literal + * (hidden local variable). + * Using this directly is not recommended. + */ +#define AV_JSON_STACK() (&FF_NEW_SZ(AVJsonStack)) + +/** + * Allocate a stack element. + * Only use this if av_json_begin_object() / av_json_begin_array() cannot be + * used. + * @return 0 or an AVERROR code, including AVERROR(ENOMEM). + */ +int av_json_stack_alloc(AVJsonStack **stack); + +/** + * Free a stack element allocated with av_json_stack_alloc(). + */ +void av_json_stack_free(AVJsonStack **stack); + +/** + * Begin an object with a stack element as a compound literal + * (hidden local variable). + * The corresponding av_json_end_object() must be in the same scope. + * After this, every other value must be a string. + * The behavior is undefined if a key is not a string. + */ +#define av_json_begin_object(jc) av_json_begin_object_with_stack((jc), AV_JSON_STACK()) + +/** + * Begin an array with a stack element as a compound literal + * (hidden local variable). + * The corresponding av_json_end_array() must be in the same scope. + */ +#define av_json_begin_array(jc) av_json_begin_array_with_stack((jc), AV_JSON_STACK()) + +/** + * An AVWriter object to escape JSON strings. + * + * Can be allocated on the stack. + * + * Should be inited with one of the utility functions. + */ +struct AVJsonEscapeWriter { + size_t self_size; /**< Size of the structure itself */ + AVWriter owr; /**< AVWriter to send the output */ + unsigned flags; /**< Escaping flags, see AV_JSON_FLAG_* */ + int error; /**< Error status */ + unsigned replacement_char; /**< Replacement character for bad UTF-8 */ + FFReservedPadding padding[2]; +}; + +/** + * Get the methods for a JSON escape writer. + * Probably not useful to use directly. + */ +const AVWriterMethods *av_json_escape_writer_get_methods(void); + +/** + * Check if a writer is a JSON escape writer. + */ +int av_json_escape_writer_check(AVWriter wr); + +/** + * Initialize an AVJsonEscapeWriter to an already-allocated memory buffer. + * + * @return jwr itself + * jwr->self_size must be set. + */ +AVJsonEscapeWriter *av_json_escape_writer_init(AVJsonEscapeWriter *jwr, AVWriter owr, unsigned flags); + +/** + * Create an AVWriter from an AVJsonEscapeWriter structure. + */ +AVWriter av_json_escape_writer_wrap(AVJsonEscapeWriter *jwr); + +/** + * Create an AVWriter to escape JSON strings. + * + * Note: as it relies on a compound statement, the AVJsonEscapeWriter object has + * a scope limited to the block where this macro is called. + */ +#define av_json_escape_writer(owr, flags) \ + av_json_escape_writer_wrap(av_json_escape_writer_init(&FF_NEW_SZ(AVJsonEscapeWriter), (owr), (flags))) + +/** + * JSON encoding context. + * + * This structure must be allocated with AV_JSON_DEFINE() or equivalent + * code that will set av_class, self_size and escape_writer. + * + * It then must be initialized using av_json_init(). + */ +struct AVJson { + + /** + * Class; must be initialized to av_json_get_class(). + */ + const AVClass *av_class; + + /** + * Size of the structure, must be initialized at allocation. + */ + size_t self_size; + + /** + * Encoding flags, see AV_JSON_FLAG_*. + */ + unsigned flags; + + /** + * Indentation shift. + */ + unsigned indentation; + + /** + * Output writer. + */ + AVWriter out; + + /**************************************************************** + * The fields below this limit are private. + ****************************************************************/ + + /** + * Stack of states (object/array) + */ + AVJsonStack *stack; + + /** + * Pre-allocated writer for escaping strings. + * + * Must be allocated before init. + */ + AVJsonEscapeWriter *escape_writer; + + /** + * Depth of nested structures, for indentation. + */ + unsigned depth; + + /** + * True if a string is being constructed. + */ + unsigned in_string : 1; + + /** + * True if we currently are directly in an object. + */ + unsigned in_object : 1; + + /** + * True if we are about to write the first element of a structure. + */ + unsigned first_element : 1; + + /** + * True if we are about to write the key in an object. + */ + unsigned object_key : 1; + + FFReservedPadding padding[8]; +}; + +/** + * Stack element for the JSON context. + */ +struct AVJsonStack { + size_t self_size; + AVJsonStack *prev; + unsigned short in_object; + FFReservedPadding padding; +}; + +/** + * Type of a JSON value. + * + * Note that JSON does not distinguish int and float values. + */ +enum AVJsonValueType { + AV_JSON_TYPE_NULL, + AV_JSON_TYPE_BOOL, + AV_JSON_TYPE_STRING, + AV_JSON_TYPE_INT, + AV_JSON_TYPE_DOUBLE, + AV_JSON_TYPE_OBJECT, + AV_JSON_TYPE_ARRAY, +}; + +/** + * Typed atomic JSON values. + * Objects and arrays cannot be represented. + * This structure is meant to be passed by value. + */ +struct AVJsonValue { + AVJsonValueType type; + union AVJsonValueValue { + intmax_t i; + double d; + const char *s; + } val; +}; + +/** + * Build an AVJsonValue for null. + */ +static inline AVJsonValue av_json_value_null(void) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_NULL }; + return ret; +} + +/** + * Build an AVJsonValue for a boolean. + */ +static inline AVJsonValue av_json_value_bool(int val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_BOOL, .val.i = val }; + return ret; +} + +/** + * Build an AVJsonValue for a string. + * The pointer must stay valid while the value is used. + */ +static inline AVJsonValue av_json_value_string(const char *val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_STRING, .val.s = val }; + return ret; +} + +/** + * Build an AVJsonValue for an integer. + */ +static inline AVJsonValue av_json_value_int(intmax_t val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_INT, .val.i = val }; + return ret; +} + +/** + * Build an AVJsonValue for an integer. + */ +static inline AVJsonValue av_json_value_double(double val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_INT, .val.d = val }; + return ret; +} + +/** + * @} + */ + +#endif /* AVUTIL_JSON_H */ From patchwork Fri Apr 28 09:55:07 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 41398 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:dca6:b0:f3:34fa:f187 with SMTP id ky38csp1188596pzb; Fri, 28 Apr 2023 02:56:25 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ7/OvpV2gmouSOl0sg8RRYUwvQQYCldydaQuxCXQyg8v1iZHn3vL8CMdqtF2gU0s47JVvuc X-Received: by 2002:aa7:de92:0:b0:504:8929:71ca with SMTP id j18-20020aa7de92000000b00504892971camr3831353edv.6.1682675784912; Fri, 28 Apr 2023 02:56:24 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1682675784; cv=none; d=google.com; s=arc-20160816; b=VZnHJkr1r07g+U8Sn48rOX4xW9gBzskTVi6gZ2dbqixpfsz3O26EKMlv9jQDx4gZN9 W0TAlQIjSk/FFOC0qToFOCTIy5GWDwz82o1hGN3a5Bwj830YFS2uNMbbBFGJli9TYWEL M2Km4d0mf2acYutut3bbAyk5RKr7d7r2i382vI1SFSTVCJb8/tStVVUwP0YVrzPvtkZM kwDnufSkjsIW4N1l11G3m1IrNd5Zfeb/tUkDTwbfZSYVRoUFPoOg0nfSU2TsGyPMil6P V8w5V4gCmH4jpfQCbUjeiLZjeE7+z+NGinTitSiBQmx8yUoMUkh93VdgmJS+TLMEKuRn hhXA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:delivered-to; bh=X/wn6IFA2KxDeiEn8Hu7fT2TE4+IJtMaC+/hyDRHEwA=; b=FiF+nxePdf31ziMVu3WXi7cJWbqEpO7f23tYLHlh/n9VMT01f4UrhACAcDn+9cB9kP H5WgYBiU79fPebRVOcogo3MViaM6Xz0j2AFSoi8dBQbv803eFyk1lr/9W3tFB2mWN5Hl 7uPv2w8tu9QkJiiFea6b1O6P4rwjrQHuq/TF9QjZXDlnVqszeNvtxNnA0PYgdZAjZscm hSggFBS5p35BjKViVG6WV4tzVgfGuo6jAnHt5y0CIBYI5ZXBAN8t0qy1YrshihhN0I7R eqyvILZAY20Ws+iaa6lhbWkke204RLjx2ZHVh32EaRcmS7fQzdVlVbj/nGGfRjfz02ZD l94w== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id r23-20020a056402035700b00506addaaabasi15561835edw.64.2023.04.28.02.56.24; Fri, 28 Apr 2023 02:56:24 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id E100A68BFE8; Fri, 28 Apr 2023 12:55:25 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from nef.ens.fr (nef2.ens.fr [129.199.96.40]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id A50FF68BFA9 for ; Fri, 28 Apr 2023 12:55:16 +0300 (EEST) X-ENS-nef-client: 129.199.129.80 ( name = phare.normalesup.org ) Received: from phare.normalesup.org (phare.normalesup.org [129.199.129.80]) by nef.ens.fr (8.14.4/1.01.28121999) with ESMTP id 33S9tFMR014813 for ; Fri, 28 Apr 2023 11:55:16 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id EEF11EB5BF; Fri, 28 Apr 2023 11:55:15 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Fri, 28 Apr 2023 11:55:07 +0200 Message-Id: <20230428095508.221826-7-george@nsup.org> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230428095508.221826-1-george@nsup.org> References: <20230428095508.221826-1-george@nsup.org> MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Fri, 28 Apr 2023 11:55:16 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 7/8] lavf/options: add av_disposition_write() X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: 3ewcjGvqP57F TODO APIchanges entry Signed-off-by: Nicolas George --- libavformat/avformat.h | 14 ++++++++++++++ libavformat/options.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) This and the next patch show the kind of fruits become low-hanging thanks to the platform of AVWriter. Eventually, I want that every public structure and enumeration to have a _write() function with a standardized API, and later we will be able to use a custom %something format string in printf directly. Another project of mine (that has AVWriter as a prerequisite) would invert the logic of this function: instead of having the to-string and from-string function explore the options table, we would have the options system quety the to-string and from-string functions. diff --git a/libavformat/avformat.h b/libavformat/avformat.h index 5302a34c0d..51fd78b71c 100644 --- a/libavformat/avformat.h +++ b/libavformat/avformat.h @@ -824,6 +824,20 @@ int av_disposition_from_string(const char *disp); */ const char *av_disposition_to_string(int disposition); +/** + * Write a disposition as a string. + * The before and after string are written around each element of the + * disposition and the between string is written between them, if they are + * not NULL. + * Unknown disposition bits are written as an extra hexadecimal value. + * No flags are defined. + */ +void av_disposition_write(AVWriter wr, unsigned disposition, + const char *before, + const char *after, + const char *between, + unsigned flags); + /** * Options for behavior on timestamp wrap detection. */ diff --git a/libavformat/options.c b/libavformat/options.c index e4a3aceed0..a813e11060 100644 --- a/libavformat/options.c +++ b/libavformat/options.c @@ -29,6 +29,7 @@ #include "libavutil/internal.h" #include "libavutil/intmath.h" #include "libavutil/opt.h" +#include "libavutil/writer.h" /** * @file @@ -348,3 +349,36 @@ const char *av_disposition_to_string(int disposition) return NULL; } + +void av_disposition_write(AVWriter wr, unsigned disposition, + const char *before, + const char *after, + const char *between, + unsigned flags) +{ + int sep = 0; + + for (const AVOption *opt = stream_options; opt->name; opt++) { + if (option_is_disposition(opt) && (disposition & opt->default_val.i64)) { + if (sep && between) + av_writer_print(wr, between); + if (before) + av_writer_print(wr, before); + av_writer_print(wr, opt->name); + if (after) + av_writer_print(wr, after); + disposition &= ~opt->default_val.i64; + sep = 1; + } + } + if (disposition) { + if (sep && between) + av_writer_print(wr, between); + if (before) + av_writer_print(wr, before); + av_writer_printf(wr, "%x?", disposition); + if (after) + av_writer_print(wr, after); + sep = 1; + } +} From patchwork Fri Apr 28 09:55:08 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 41399 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:dca6:b0:f3:34fa:f187 with SMTP id ky38csp1188672pzb; Fri, 28 Apr 2023 02:56:33 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ7d1vfHQ7rl3pi4Nso1MPJ60ue6DT0MOAIS9oq6mI1cX142eRA9pDauOW0qvMEMx8DDit/R X-Received: by 2002:a17:907:7f1c:b0:953:4775:baa7 with SMTP id qf28-20020a1709077f1c00b009534775baa7mr4929020ejc.52.1682675793549; Fri, 28 Apr 2023 02:56:33 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1682675793; cv=none; d=google.com; s=arc-20160816; b=zUFNkfQtKIvwQJxdg8l7Qensq2S8r96fMROHXpUZUPz5mCkxo4dyN2DwwOl5vVYESy DSAPPquAIwr8hRsp/XFjmHR6uAqGjy/dXbFJ1Up2tszd3sXleaZLpCurua4mF5Je3oG1 rXRO3mR7KeyemLwNpsIyZ1rA3ygEmXevdorUy+HYboG6fe9uqNYU+rGVhfQH3u76HJRl KRUrIoR4Nia5xBr6f2xVQJhYKoUkc0+t7o19BEw+y49hEmItcwmbWO1PN90+TdLCg5bs x2ZjmnNyRcSCJbrXPPIShETZ4RyzP6zLo6xn4GXGzKRHa5zCphp0B/BPGSKnlXVbycgX vTRQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:delivered-to; bh=3SxkUahW3cUE9bfklNsyoN0PLQO1uyKS87pM3/Zd/4E=; b=mju+xVRRxZkWIAMUBMs0mZWFPyQiRoReuYAY4UcGBgDXbaxEaI3nDasF4JoqERDfoX FmMiZ7y7eAvLrILVUyURjIMzXnQ0v9k9NHRpb9C8o0GHLcDMNaUk5UtxT5CLgynbMK2b LVzq571M71gtkrE91gPoy+XlUUS3fheiW1X8Tlj5kQQ7YGmk9toLmArUjqHDlBFOhLxn SLZm43yjbOb1Wi1Vdmlf0Q531zGA/Bc7dhOPujLJWjG0REKjO+i38XaIadPVrSPX7KVR wfEYmkYorT6U/ZNnn6vAeY9q/q2hIA57hkc99vdkAyClyLuMpjV8xI6idz9IO5MfdsFF KE0w== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id vw13-20020a170907a70d00b009600ce55675si2739691ejc.44.2023.04.28.02.56.33; Fri, 28 Apr 2023 02:56:33 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id E41E568BFED; Fri, 28 Apr 2023 12:55:26 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from nef.ens.fr (nef2.ens.fr [129.199.96.40]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 5FBDB68BFAE for ; Fri, 28 Apr 2023 12:55:17 +0300 (EEST) X-ENS-nef-client: 129.199.129.80 ( name = phare.normalesup.org ) Received: from phare.normalesup.org (phare.normalesup.org [129.199.129.80]) by nef.ens.fr (8.14.4/1.01.28121999) with ESMTP id 33S9tGPS014824 for ; Fri, 28 Apr 2023 11:55:16 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id AFD43EB5BF; Fri, 28 Apr 2023 11:55:16 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Fri, 28 Apr 2023 11:55:08 +0200 Message-Id: <20230428095508.221826-8-george@nsup.org> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230428095508.221826-1-george@nsup.org> References: <20230428095508.221826-1-george@nsup.org> MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Fri, 28 Apr 2023 11:55:16 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 8/8] lavf/dump: use av_disposition_write() X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: 8WtKUCdBDgz9 It changes the output to have underscores instead of spaces. Signed-off-by: Nicolas George --- libavformat/dump.c | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) Note: I consider the change to backspaces good: now we can copy-paste from dump directly into a command-line. diff --git a/libavformat/dump.c b/libavformat/dump.c index 96b364e337..8b55e60b6a 100644 --- a/libavformat/dump.c +++ b/libavformat/dump.c @@ -584,42 +584,7 @@ static void dump_stream_format(AVWriter wr, print_fps(wr, 1 / av_q2d(st->time_base), "tbn"); } - if (st->disposition & AV_DISPOSITION_DEFAULT) - av_writer_printf(wr, " (default)"); - if (st->disposition & AV_DISPOSITION_DUB) - av_writer_printf(wr, " (dub)"); - if (st->disposition & AV_DISPOSITION_ORIGINAL) - av_writer_printf(wr, " (original)"); - if (st->disposition & AV_DISPOSITION_COMMENT) - av_writer_printf(wr, " (comment)"); - if (st->disposition & AV_DISPOSITION_LYRICS) - av_writer_printf(wr, " (lyrics)"); - if (st->disposition & AV_DISPOSITION_KARAOKE) - av_writer_printf(wr, " (karaoke)"); - if (st->disposition & AV_DISPOSITION_FORCED) - av_writer_printf(wr, " (forced)"); - if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED) - av_writer_printf(wr, " (hearing impaired)"); - if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED) - av_writer_printf(wr, " (visual impaired)"); - if (st->disposition & AV_DISPOSITION_CLEAN_EFFECTS) - av_writer_printf(wr, " (clean effects)"); - if (st->disposition & AV_DISPOSITION_ATTACHED_PIC) - av_writer_printf(wr, " (attached pic)"); - if (st->disposition & AV_DISPOSITION_TIMED_THUMBNAILS) - av_writer_printf(wr, " (timed thumbnails)"); - if (st->disposition & AV_DISPOSITION_CAPTIONS) - av_writer_printf(wr, " (captions)"); - if (st->disposition & AV_DISPOSITION_DESCRIPTIONS) - av_writer_printf(wr, " (descriptions)"); - if (st->disposition & AV_DISPOSITION_METADATA) - av_writer_printf(wr, " (metadata)"); - if (st->disposition & AV_DISPOSITION_DEPENDENT) - av_writer_printf(wr, " (dependent)"); - if (st->disposition & AV_DISPOSITION_STILL_IMAGE) - av_writer_printf(wr, " (still image)"); - if (st->disposition & AV_DISPOSITION_NON_DIEGETIC) - av_writer_printf(wr, " (non-diegetic)"); + av_disposition_write(wr, st->disposition, " (", ")", NULL, 0); av_writer_printf(wr, "\n"); dump_metadata(wr, st->metadata, " ");