From patchwork Wed Apr 21 12:27:00 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 27200 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a6b:5014:0:0:0:0:0 with SMTP id e20csp349548iob; Wed, 21 Apr 2021 05:27:35 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyyUEy8heOG1qIJoSAn8RyvYfs/WWMSyHtYJMif9fTr0c/hXKjUlPqXI8PvCOwjRY6LeXBD X-Received: by 2002:a17:907:c0b:: with SMTP id ga11mr32103958ejc.545.1619008055517; Wed, 21 Apr 2021 05:27:35 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1619008055; cv=none; d=google.com; s=arc-20160816; b=PjZMO7diGp7LhcxGVOdwXpbPvyHuA9xexoKPMOG6Lewuu3TahYQw20p+Vm3ZWXkeaL V+4WrIdDsJqBrJtXFXOjGi+kpB2BPUKdUx2IkZjZkgo0uXLVMyZDL3BPk68ATKDZiw9q v+5j17xMSCcIOBFn/v/Eifpd0aktzUt+cscUbkbeGir0+uRgMyDMzZjlHzbANsY+8S+a L+X1243lnOWCNYbxJD8FNilE45J8ZAdJdACtXd5RcpOXPfvSz3TxuDHdobGF2Tw1MyIj CKQYc7FVZOCVbehN/HpkF1HrZEJ3HQGjAKR2HJ7NUxsWjjp/9lb2GXGQxDL0YjkKxFaf q1sA== 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=V/IFK248cKxIbrsSv0UMooSGADYe5TUBBv6Gcj2zzCc=; b=h9RNjJRWS+T3hNnpg6c3D4p3oaX9yCD+VkwSi/IwI2e8zmYvuqi6vSYq1WIL/fFVNV x6Z8zpcsXhyoC3hTtSWX3gz1FcZTC/yTNNcThS6Nfb17iWSmb5/wnR2zM7H8xRZf7qey N0Mbvym7nfpM2UYJmnzIfR1Qa5H5kFGurM181ATFLHiJMaR4RlBTb6/cNm9JRK6gvM+E EVOcjOYH+xw+N8VedRWdnZIx9AyLiEkQ3YlY4OjOS2BjjyKkhVIU9fny8rsWudr2TBLK NZpGQK7XaRS8hEzimTEaoTsfZTCjzobxlhFsCO2KUz6RyASFxv1cHr7U7Z09r7YWE9/e sodA== 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 q14si1641444eju.317.2021.04.21.05.27.35; Wed, 21 Apr 2021 05:27: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 62C9C6899A8; Wed, 21 Apr 2021 15:27:17 +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 EE7286897C2 for ; Wed, 21 Apr 2021 15:27:09 +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 13LCR83t006268 for ; Wed, 21 Apr 2021 14:27:09 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id B5476EB5BD; Wed, 21 Apr 2021 14:27:08 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Wed, 21 Apr 2021 14:27:00 +0200 Message-Id: <20210421122706.9002-2-george@nsup.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210421122706.9002-1-george@nsup.org> References: <20210421122706.9002-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]); Wed, 21 Apr 2021 14:27:09 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 1/7] 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: ZJHXWrcFSM8m Signed-off-by: Nicolas George --- libavutil/extendable.h | 59 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 libavutil/extendable.h 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 Wed Apr 21 12:27:01 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 27202 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a6b:5014:0:0:0:0:0 with SMTP id e20csp349691iob; Wed, 21 Apr 2021 05:27:48 -0700 (PDT) X-Google-Smtp-Source: ABdhPJz3+fpYUwF6PQfCW27d+kWG5nIg3tCz0dKaXPGB1OrqGFjhiJHeSmTkNmBMxv1ejvkdOOCB X-Received: by 2002:a05:6402:a4a:: with SMTP id bt10mr37907769edb.39.1619008068397; Wed, 21 Apr 2021 05:27:48 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1619008068; cv=none; d=google.com; s=arc-20160816; b=DyLe1iUfOuOGEhzoVbBwtl3ZXqBJxqFRk/bHr3uAzDBRl+hTlYzPS5JhoHfcBo5cRm CGAX81xFxo1An3HR4shMfnNU6yG/zLhc694DAAWsuII4xnMKMhveCvrCYnPVWKZ32AZi ju7juvtTnan9u3ZoJPx5UX0pKpGvggz4ASMCEhw/7H7JHMrYEiKBMqMhFIXoE40Wr2a0 FbyJQ+fKoUX5A+8KlDMGuGNirW4f2Io/zMkhj3euWRQkKZATLKaGJZgRCY+avJSa7Jl7 Wl+Rln2VW+LTj+SBZrjVy+7AJchJJAT5xWxJmf7gwbHhI8biJTGaXziSxqw3bLeZRGJD +30w== 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=p+QrA2/RrC32XRxxLbzPWNFlxBQcTizsF9FIAGuRoMw=; b=TvVzWX8SJz2S5dWyzgruZLuvu23FEm/EOKVVqS8uVZGQR/lareccBpBwo89sOuFX9w MxHpRpewzwuU4ktueHCzhTG134gA47jIRLYl0Hvtha5O8y4lKr16WQ5o3fwgaG2KKLLP N4Gxl+O4D0UpqJb3hY0w8aIhVKbbI/aI/wwTQ3fthZeFUEhcgCgt29iolIQE4pSM3x50 54T7cOR/YV1MaA4xWbm50FyW3NMYl4A4fCTlPRsLZRj4Jh6GaFte7T0S9mNH3qiQyP0q 7tyipbGicOuMT6d0vGKyUsZR2eD3DRwEP9wJ4s1hWOiFFwlSrgxB84kZaokcnaBVxFrp KnIw== 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 f5si1815144edr.186.2021.04.21.05.27.47; Wed, 21 Apr 2021 05:27:48 -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 6B44368996A; Wed, 21 Apr 2021 15:27: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 1BA8A6899A8 for ; Wed, 21 Apr 2021 15:27: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 13LCR93U006279 for ; Wed, 21 Apr 2021 14:27:10 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id 96E3CEB5BC; Wed, 21 Apr 2021 14:27:09 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Wed, 21 Apr 2021 14:27:01 +0200 Message-Id: <20210421122706.9002-3-george@nsup.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210421122706.9002-1-george@nsup.org> References: <20210421122706.9002-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]); Wed, 21 Apr 2021 14:27:11 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 2/7] 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: RunRb0xSOqmx Signed-off-by: Nicolas George --- libavutil/Makefile | 2 +- libavutil/writer.c | 443 +++++++++++++++++++++++++++++++++++++++++ libavutil/writer.h | 487 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 931 insertions(+), 1 deletion(-) create mode 100644 libavutil/writer.c create mode 100644 libavutil/writer.h diff --git a/libavutil/Makefile b/libavutil/Makefile index 27bafe9e12..d92872e296 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -172,7 +172,7 @@ OBJS = adler32.o \ tx_int32.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..89159ceaa2 --- /dev/null +++ b/libavutil/writer.c @@ -0,0 +1,443 @@ +/* + * 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 "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_assert1(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_assert1(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->impossible) + wr.methods->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_assert1(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_assert1(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_assert1(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_assert1(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_assert1(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_assert1(FIELDOK(dwr, bp)) + +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_assert1(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; +} + +static void dynbuf_writer_vprintf(AVWriter wr, const char *fmt, va_list va) +{ + AVDynbufWriter *dwr = wr.obj; + + av_assert1(av_dynbuf_writer_check(wr)); + dynbuf_writer_assert_abi(dwr); + av_vbprintf(&dwr->bp, fmt, va); +} + +char *av_dynbuf_writer_get_buffer(AVWriter wr, size_t size, size_t *rsize) +{ + AVDynbufWriter *dwr = wr.obj; + unsigned char *buf; + unsigned isize; + + av_assert1(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_assert1(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; +} + +static int dynbuf_writer_get_error(AVWriter wr, int self_only) +{ + AVDynbufWriter *dwr = wr.obj; + + av_assert1(av_dynbuf_writer_check(wr)); + dynbuf_writer_assert_abi(dwr); + 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); + 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) +{ + av_assert1(!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_assert1(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_assert1(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_assert1(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_assert1(av_stdio_writer_check(wr)); + vfprintf(wr.obj, fmt, va); +} + +static int stdio_writer_get_error(AVWriter wr, int self_only) +{ + av_assert1(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..661da381c2 --- /dev/null +++ b/libavutil/writer.h @@ -0,0 +1,487 @@ +/* + * 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_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. + * + * An AVWriter is meant to allow to build and return string (and blocks of + * binary data) efficiently between functions. + * For example, a function that serialize something into a string will + * actually write into an AVWriter. + * + * To emulate idiomatic practices of writing into a buffer, you can write: + * char buf[4096]; + * AVWriter wr = av_buf_writer_array(buf); + * which is a macro equivalent (using modern C magic) to: + * AVWriterBuf bwr; + * av_buf_writer_init(&bwr, buf, sizeof(buf)); + * AVWriter wr = av_buf_writer_wrap(&bwr); + * so you can write into any already allocated memory. + * The string will be 0-terminated, so you can omit len. + * + * To avoid the problems of size limits, you should use a dynamic buffer. + * AVDynbufWriter provides an implementation that uses the stack for small + * strings and the heap for longer ones. + * + * AVWriter wr = av_dynbuf_writer(); + * write_something(wr); + * if (!av_writer_get_error(wr)) { + * output(av_dynbuf_writer_get_data(wr, &size)); + * av_dynbuf_finish(wr, NULL, NULL); + * + * It is possible to create an AVWriter to av_log() and to implement new + * kinds of AVWriter. + * + * AVWriter objects are passed by value. It allows creating a writer on the + * fly, without extra variable or error checking. The structure can never + * change. + * + * Every type of AVWriter is supposed to have a pair of functions: + * av_something_writer_get_methods() returns the methods for that type. + * av_something_writer_checks() returns 1 if the writer is of that type. + * When a type of AVWriter defines specific functions, it is usually the + * responsibility of the caller to ensure that they are called only with a + * compatible AVWriter; otherwise the behavior is undefined. + */ +typedef struct AVWriter { + const AVWriterMethods *methods; + void *obj; +} AVWriter; + +/** + * Write a data buffer to an AVWriter. + */ +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. + */ +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. In both case they should return an error code. + */ +#define AV_WRITER_FALLBACK 0x10000 + +/***************************************************************************/ + +/** + * @defgroup av_buf_writer AVBufWriter + * + * An implementtion of AVWriter that writes into an already-allocated buffer + * of memory. + * + * The buffer is kept 0-terminated. If the buffer is too small, the data is + * discarded, but the total size is computed. + * + * @{ + */ + +/** + * An AVWriter object for pre-allocated memory buffers. + * + * Can be allocated on the stack. + * + * Should be inited with one of the utility functions. + */ +typedef struct AVBufWriter { + size_t self_size; /**< Size of the structure itself */ + char *buf; /**< Memory buffer. Must not be NULL nor empty. */ + size_t size; /**< Size of the memory buffer. Must not be 0. */ + size_t pos; /**< Position in the memory buffer. */ +} AVBufWriter; + +const AVWriterMethods *av_buf_writer_get_methods(void); +int av_buf_writer_check(AVWriter wr); + +/** + * Initialize an AVBufWriter to an already-allocated memory buffer. + * + * @return bwr itself + * bwr->self_size must be set. + */ +AVBufWriter *av_buf_writer_init(AVBufWriter *bwr, char *buf, size_t size); + +/** + * Create an AVWriter from an AVBufWriter structure. + */ +AVWriter av_buf_writer_wrap(AVBufWriter *bwr); + +/** + * Create an AVWriter to a buffer. + * + * Note: as it relies on a compound statement, the AVBufWriter object has + * a scope limited to the block where this macro is called. + */ +#define av_buf_writer(buf, size) \ + av_buf_writer_wrap(av_buf_writer_init(&FF_NEW_SZ(AVBufWriter), (buf), (size))) + +/** + * Create an AVWriter to a char[] or equivalent. + * + * Note: as it relies on a compound statement, the AVBufWriter object has + * a scope limited to the block where this macro is called. + */ +#define av_buf_writer_array(array) \ + av_buf_writer(array, sizeof (array)) + +/** + * @} + */ + +/***************************************************************************/ + +/** + * @defgroup av_dynbuf_writer AVDynbufWriter + * + * An implementation of AVWriter that writes to a dynamic buffer. + * + * The buffer is kept 0-terminated. + * + * The buffer is initially kept in the variable 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 */ + 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. + */ +/* XXX do we need to expose size_init and max_size from AVBPrint? */ +AVDynbufWriter *av_dynbuf_writer_init(AVDynbufWriter *dwr); + +/** + * Create an AVWriter from an AVDynbufWriter structure. + */ +AVWriter av_dynbuf_writer_wrap(AVDynbufWriter *dwr); + +/** + * Create an AVWriter to a dynamic buffer. + * + * Note: as it relies on a compound statement, the AVDynbufWriter object has + * a scope limited to the block where this macro is called. + */ +#define av_dynbuf_writer() \ + av_dynbuf_writer_wrap(av_dynbuf_writer_init(&FF_NEW_SZ(AVDynbufWriter))) + +/** + * Get 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 with a writer in error. + */ +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. + * + * 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. + * + * @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. + */ +void av_dynbuf_writer_advance_buffer(AVWriter wr, size_t size); + +/** + * @} + */ + +/***************************************************************************/ + +/** + * @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. + * + * There is no error report. + * Implementations must provide their own way of reporting errors. + */ +struct AVWriterMethods { + + /** + * Size of the structure itself. + * Must normally be set to sizeof(AVWriterMethods). + */ + size_t self_size; + + /** + * Name of the object type. + */ + const char *name; + + /** + * Warn that an operation was impossible even with fallbacks. + */ + void (*impossible)(AVWriter wr, const char *message); + + /** + * Notify that size chars have been discarded + */ + void (*notify_discard)(AVWriter wr, size_t size); + + /** + * Get 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 *size is returned < 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 Wed Apr 21 12:27:02 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 27203 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a6b:5014:0:0:0:0:0 with SMTP id e20csp349830iob; Wed, 21 Apr 2021 05:28:01 -0700 (PDT) X-Google-Smtp-Source: ABdhPJx0zkU8YKUfjhb9dlXlu79/dyClzHsz0OJMuhotTaVlGsZQ6oK98fbzmimH625ajX8FQTp2 X-Received: by 2002:aa7:ca04:: with SMTP id y4mr37837033eds.72.1619008080924; Wed, 21 Apr 2021 05:28:00 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1619008080; cv=none; d=google.com; s=arc-20160816; b=NGSqlzJeH8ZOP4ib9QL8XR4AdGBYlqeTWqYR0o5KPLA0b6uS5Djs/LulBJ3/PNKEw9 euugyCnUKM8QUOv8HWPQx7tdPngEs8RWVWsahu0ZxIOuuNF4SWAjfNhjQ5VcHDikdILz 7iCOcHirUaRMOWV0llPJHIIbS1p4SQJCiiEhFl5rPvWTJxOWvZttooBnbEosG6Ga8Eea AlBKzYBj86DDnKAzNADpGisYTwiPsejMwPLGHPh0SWYp5W35zC7nG4LrMdzRRbOMxOtA x+FaoSjb6DuWdQtFvymiIry2tjWZw0i9jgwUTiNUHAy2SuZQvnmKIymR7cld0L6OLZgn eiTg== 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=N2YJ2bZNuaPOjRCcQKJR87JZZE3QW7Ndt+EvwdeWnlI=; b=0Gke2J0LM1pSX+ewc5/6H5wVIa58ba5CXWgY2d9iZ9CKZvYH2apYkUDpDw1wlmX8Y9 oF3q0I9nfuAwbZPpV8fuycNxJnELuFx83VP5nP7PCXWKDkcYWjtFLhjTPbZfGpV7oIGC w3TuQVqoF5QlZ5C6oECTdyfcSkFff8W1llYfehD0eBIvYyz9CsHEX+cLVKSEPb+7/xUC +THX5zCSLrYNj4JOyNNS7vAgThjbTZacrzn9FeU4gjpZMOwo9q7XffjUPi6qN4ysO7Yr 6eppgSq3CZRox6Z0I6rXO4LQnNmb/SorWssm8O9b//S2cXNXOffCl4ii2SytW/3D/yV2 JRgA== 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 h29si1864121edj.494.2021.04.21.05.28.00; Wed, 21 Apr 2021 05:28:00 -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 954596899EC; Wed, 21 Apr 2021 15:27: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 7F0C46899CF for ; Wed, 21 Apr 2021 15:27: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 13LCRAej006287 for ; Wed, 21 Apr 2021 14:27:11 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id 70ED5EB5BD; Wed, 21 Apr 2021 14:27:10 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Wed, 21 Apr 2021 14:27:02 +0200 Message-Id: <20210421122706.9002-4-george@nsup.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210421122706.9002-1-george@nsup.org> References: <20210421122706.9002-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]); Wed, 21 Apr 2021 14:27:12 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 3/7] 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: saB2PvN1vH5r Signed-off-by: Nicolas George --- libavutil/Makefile | 1 + libavutil/tests/.gitignore | 1 + libavutil/tests/writer.c | 190 +++++++++++++++++++++++++++++++++++++ tests/fate/libavutil.mak | 4 + tests/ref/fate/writer | 36 +++++++ 5 files changed, 232 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 d92872e296..80d0580289 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -252,6 +252,7 @@ TESTPROGS = adler32 \ utf8 \ 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 9d90827954..0fd8026bd2 100644 --- a/libavutil/tests/.gitignore +++ b/libavutil/tests/.gitignore @@ -49,3 +49,4 @@ /twofish /utf8 /xtea +/writer diff --git a/libavutil/tests/writer.c b/libavutil/tests/writer.c new file mode 100644 index 0000000000..d414adadc7 --- /dev/null +++ b/libavutil/tests/writer.c @@ -0,0 +1,190 @@ +/* + * 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 "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_assert1(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_assert1(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_assert1(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 1ec9ed00ad..651f065d10 100644 --- a/tests/fate/libavutil.mak +++ b/tests/fate/libavutil.mak @@ -166,6 +166,10 @@ FATE_LIBAVUTIL += fate-opt fate-opt: libavutil/tests/opt$(EXESUF) fate-opt: CMD = run libavutil/tests/opt$(EXESUF) +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 Wed Apr 21 12:27:03 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 27201 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a6b:5014:0:0:0:0:0 with SMTP id e20csp350132iob; Wed, 21 Apr 2021 05:28:25 -0700 (PDT) X-Google-Smtp-Source: ABdhPJwZSd7Nu7XlcgVg8msnAqTa9mgUK7JuUaiMdSS1UhmRT8C+apbu6Q69XzzMFeUAObghu7Ab X-Received: by 2002:a05:6402:5111:: with SMTP id m17mr18095171edd.175.1619008105793; Wed, 21 Apr 2021 05:28:25 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1619008105; cv=none; d=google.com; s=arc-20160816; b=qvF2a4NUU4cAN1vAVlBsMEIcQnDJg+KK4Ilpe/h7TqYmq14sGoWzLDOaNPFXy9ka6P Gx24rBD1MjglUB45yVMb+8a1GbdFKr5gt2/qE9XI+I4YML47dWEHglEE6rWbpCrweeow 6lx+3+2UnFDAh3htUMxD9q0hvxiqcEVbYBPdnMmTEZw6pB1iJEVqwcBk37WlnS7iZ3ic haQy+ypMRXqTkOXNoV9mAMtbqZ+so8i5itURrcc4HTBVz+nP35TvD4YWHYuWnqUpVfqk gK2g1wIRj89XFalpST5dV96Q/z+Z5HrOArPgLfcpowYAvJOJhk6sSbBVYhZ+R45HftUz nvqA== 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=74eGPAL2ljmRVp3m2PF8wXpVxqUCPsvYD+t2O7lgOo8=; b=K/lj1i0EcQedawAAfBNq5OZ9NnzoDyBEsL6lAqI3SVYqt7rMme5YXjLm7+k3jtdlzW xh53nrtIlnMefBASizJpcPVeYamdNE+2XKvvVLP2JJSl4QJyQbIbIR+Uy5eeQX2UowAa +IPT0NnFYwzJ4HiqOuuXmHdSuPb+OvaOfpg/pdVfzUbA2W3AU6uEaTRxoO3KrMF2VHuQ aBdLI9X5umoh1/FuDLP7LfUKTqNVzyMRPIKFWHA4+eMyrVBHChHh+Cu+0QRYyBrM+YI6 MxMqxS71MFZMmvDzdjnCb62PMRRMtSurHUX3q7WHbPeY5+n4VpbiFDI77Hhar9oHMLDK LKAw== 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 d26si1629444ejt.459.2021.04.21.05.28.25; Wed, 21 Apr 2021 05:28:25 -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 EC3A6689BBC; Wed, 21 Apr 2021 15:27:23 +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 0441D6899E4 for ; Wed, 21 Apr 2021 15:27: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 13LCRBED006290 for ; Wed, 21 Apr 2021 14:27:12 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id 60D58EB5BF; Wed, 21 Apr 2021 14:27:11 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Wed, 21 Apr 2021 14:27:03 +0200 Message-Id: <20210421122706.9002-5-george@nsup.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210421122706.9002-1-george@nsup.org> References: <20210421122706.9002-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]); Wed, 21 Apr 2021 14:27:13 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 4/7] 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: qfzVVKHjE04W Signed-off-by: Nicolas George --- libavformat/avformat.h | 21 +++ libavformat/dump.c | 317 +++++++++++++++++++++-------------------- 2 files changed, 185 insertions(+), 153 deletions(-) diff --git a/libavformat/avformat.h b/libavformat/avformat.h index 41482328f6..13e07ee114 100644 --- a/libavformat/avformat.h +++ b/libavformat/avformat.h @@ -312,6 +312,7 @@ #include "libavcodec/avcodec.h" #include "libavutil/dict.h" #include "libavutil/log.h" +#include "libavutil/writer.h" #include "avio.h" #include "libavformat/version.h" @@ -2787,6 +2788,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 62ef5e9852..245d712504 100644 --- a/libavformat/dump.c +++ b/libavformat/dump.c @@ -35,6 +35,7 @@ #include "libavutil/spherical.h" #include "libavutil/stereo3d.h" #include "libavutil/timecode.h" +#include "libavutil/writer.h" #include "avformat.h" @@ -119,47 +120,47 @@ 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_get(m, "", tag, AV_DICT_IGNORE_SUFFIX))) 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) { char tmp[256]; size_t len = strcspn(p, "\x8\xa\xb\xc\xd"); av_strlcpy(tmp, p, FFMIN(sizeof(tmp), len+1)); - av_log(ctx, AV_LOG_INFO, "%s", tmp); + av_writer_printf(wr, "%s", tmp); 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; @@ -179,7 +180,7 @@ static void dump_paramchange(void *ctx, const AVPacketSideData *sd) 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) @@ -187,7 +188,7 @@ static void dump_paramchange(void *ctx, const AVPacketSideData *sd) 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)); } if (flags & AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE) { @@ -196,7 +197,7 @@ static void dump_paramchange(void *ctx, const AVPacketSideData *sd) 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) @@ -207,121 +208,121 @@ static void dump_paramchange(void *ctx, const AVPacketSideData *sd) 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, #if FF_API_UNSANITIZED_BITRATES "bitrate max/min/avg: %d/%d/%d buffer size: %d ", #else @@ -330,16 +331,16 @@ static void dump_cpb(void *ctx, const AVPacketSideData *sd) 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", @@ -354,51 +355,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, @@ -408,104 +409,105 @@ 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, + av_writer_printf(wr, "unknown side data type %d (%d 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 +545,17 @@ FF_ENABLE_DEPRECATION_WARNINGS 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", st->codec_info_nb_frames, + av_writer_printf(wr, "(%s)", lang->value); + av_log_writer_log(wr, AV_LOG_DEBUG, ", %d, %d/%d", st->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 +564,7 @@ FF_ENABLE_DEPRECATION_WARNINGS 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); } @@ -580,80 +582,82 @@ FF_ENABLE_DEPRECATION_WARNINGS #endif if (fps || tbr || tbn || tbc) - 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 || tbc ? "fps, " : "fps"); + print_fps(wr, av_q2d(st->avg_frame_rate), tbr || tbn || tbc ? "fps, " : "fps"); if (tbr) - print_fps(av_q2d(st->r_frame_rate), tbn || tbc ? "tbr, " : "tbr"); + print_fps(wr, av_q2d(st->r_frame_rate), tbn || tbc ? "tbr, " : "tbr"); if (tbn) - print_fps(1 / av_q2d(st->time_base), tbc ? "tbn, " : "tbn"); + print_fps(wr, 1 / av_q2d(st->time_base), tbc ? "tbn, " : "tbn"); #if FF_API_LAVF_AVCTX FF_DISABLE_DEPRECATION_WARNINGS if (tbc) - print_fps(1 / av_q2d(st->codec->time_base), "tbc"); + print_fps(wr, 1 / av_q2d(st->codec->time_base), "tbc"); FF_ENABLE_DEPRECATION_WARNINGS #endif } 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_log(NULL, AV_LOG_INFO, "\n"); + av_writer_printf(wr, " (still image)"); + 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); @@ -663,40 +667,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) { @@ -705,23 +709,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 Wed Apr 21 12:27:04 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 27198 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a6b:5014:0:0:0:0:0 with SMTP id e20csp349996iob; Wed, 21 Apr 2021 05:28:13 -0700 (PDT) X-Google-Smtp-Source: ABdhPJz3Z33W1ZcVk36M5/6kgWTRIvQtRrexi+BlFwS9+tGMMaWLQDqr9csHq1TpDCopLK4coRrx X-Received: by 2002:a17:906:6896:: with SMTP id n22mr33088926ejr.316.1619008092877; Wed, 21 Apr 2021 05:28:12 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1619008092; cv=none; d=google.com; s=arc-20160816; b=ILwnJpk6jVvGeRZXwOCjUDkYB1tUgeCbEkMHQM9bOAH9TSsNpoX3Lz3WQ2j3Kw1XkJ /wP5Gx0wq4J/qoaXVZ/y16BeIUnfWmVgB+20mlRHFkFUY+I+Mnw4QuKqIDGIzP9JQ5wP NzMCMvU5ixjcFWhwYXAGaSyvIoJKpGEJYBP4yfWp2kna8lIV0w4HiTivh49/qb3tRDYP jvSC58vPS6JR8O7pcuAxMvp0zyHgJgA5BWjs5vZQg3WqNt9fdAuhUsIXG8F4L1Mtkaom 4tLYcL+TRfjFtKs9+YcLzZp8KJdxgV303PTMigSsYg0l4FstrsywQ9RdqShX8EHHUeOr r6YQ== 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=by/puLi1KazIx4JK7XX9BQNAonW+3oEn9zP7Kg/QSX8=; b=JHXMazacLC9G4ozUpbOvuS0yzHRdVmA/p4tKzEA6+JZ5ao6pJf+9/3UWlEu9eoSEzW +OtjoTV68o6wvCi/NzSCZ3UURKkU/M5xv6nScxk5oNniiJcbC0BBs4KinLAdm9cvtP/k IKE2iESyuLIf84JzJJRKhlzmq/+6H/9gSV8zAe9SfH+S/EmfUFWDMPlAG2MxUUy5FFox h4syVzeGIe4yjydG/IKFu/KtCR0k9jFflA2OHRabSWqx11oco9JZ0abRGIppQwodC7oz SODIOwJMcIXdmKVGkbgwPTbDRdRt8+wEw+biiJUQQFSHkjAvTgCMDY4HRdvFf15Xf29J oX7A== 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 k14si1686906ejc.229.2021.04.21.05.28.12; Wed, 21 Apr 2021 05:28:12 -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 026DF689B83; Wed, 21 Apr 2021 15:27:22 +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 E78406899CF for ; Wed, 21 Apr 2021 15:27: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 13LCRClv006300 for ; Wed, 21 Apr 2021 14:27:12 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id 23E95EB5BC; Wed, 21 Apr 2021 14:27:12 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Wed, 21 Apr 2021 14:27:04 +0200 Message-Id: <20210421122706.9002-6-george@nsup.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210421122706.9002-1-george@nsup.org> References: <20210421122706.9002-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]); Wed, 21 Apr 2021 14:27:13 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 5/7] WIP: add an intro to AVWriter 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: FzZvV32l9ZUU Signed-off-by: Nicolas George --- avwriter_intro.txt | 261 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 avwriter_intro.txt diff --git a/avwriter_intro.txt b/avwriter_intro.txt new file mode 100644 index 0000000000..b1b54711c9 --- /dev/null +++ b/avwriter_intro.txt @@ -0,0 +1,261 @@ +XXX make error return more strict +XXX? make buf size return more foolproof + + +# An introduction to the AVWriter API. + +Do not look at the length headers. Do not look at the convoluted API. Just +imagine these few use cases. + + +## Scenario 1: You need to call a function that takes an AVWriter. + +What are AVWriter anyway? They are for when a function returns a string. +For example, take the very common: + + int av_foobar_to_string(char *buf, size_t buf_size, const AVFoobar *foobar); + +It uses buf[] to return the string. In the simplest cases, you call it with +a buffer on the stack: + + char buf[1024]; + ret = av_foobar_to_string(buf, sizeof(buf)); + +Now, with AVWriter. The function you need to call is: + + void av_foobar_write(AVWriter wr, const AVFoobar *foobar); + +And you use the av_buf_writer_array() macro to encode buf as an AVWriter: + + char buf[1024]; + ret = av_foobar_write(av_buf_writer_array(buf), foobar); + +For all you know, av_buf_writer_array() could just be a fancy name for a +macro that just expands to "buf, sizeof(buf)". And if you want to stop here +and just use AVWriter the way you do in good old C, you can. But if you read +on, you will see that it is much fancier than that, while being almost as +simple to use. + + +## Scenario 2: Concatenating the string into a message. + +Let us say you have two foobars to present to the user. Good old C: + + char msg[2500], foo1_buf[1000], foo2_buf[1000]; + av_foobar_to_string(foo1_buf, sizeof(foo1_buf), foo1); + av_foobar_to_string(foo2_buf, sizeof(foo2_buf), foo2); + snprintf(msg, sizeof(msg), "F%d = [ %s, %s ]", num, foo1_buf, foo2_buf); + +But it's ugly. Less ugly, but more complicated: + + char msg[2500]; + size_t pos = 0; + pos += snprintf (msg + pos, sizeof(msg) - pos, "F%d = [ ", num); + av_foobar_to_string(msg + pos, sizeof(msg) - pos, foo1); + pos += strlen(msg + pos); + pos += snprintf (msg + pos, sizeof(msg) - pos, ", "); + av_foobar_to_string(msg + pos, sizeof(msg) - pos, foo2); + pos += strlen(msg + pos); + pos += snprintf (msg + pos, sizeof(msg) - pos, "]"); + +(Note: that's buggy, the snprintf() in the middle can overflow.) + +Well, that was the first thing AVWriter was meant to do: allow to build +strings by concatenating them together. So, the AVWraper version of this +code: + + char msg[2500]; + AVWriter wr = av_buf_writer_array(msg); + av_writer_printf(wr, "F%d = [ ", num); + av_foobar_write(wr, foo1); + av_writer_print(wr, ", "); + av_foobar_write(wr, foo2); + av_writer_print(wr, " ]"); + +(I am confident I can teach av_writer_printf() to call av_foobar_write().) + +If I have my way, we will have "av_writer_fmt(AVWriter wr, const char *fmt, +...);" where the arguments can be any type for which somebody have bothered +to implement a _write() function. But that is for a bit later. + +And that's it. av_buf_writer_array() is just fancy macro work to keep track +with a "size_t pos". + +But don't use it, it's rubbish. Use av_dynbuf_writer(), it's fancier. + + +## Scenario 3: The strings are long and precious. + +Let us say that the string you are building can be arbitrarily long, and it +is important to have it in its entirety. With good old C, you have to rely +on some kind of realloc(). You know the kind of code, it is very boring, I +will not write it here. + +That's exactly what av_buf_writer_array() is for. As a special optimization, +it starts with a buffer on the stack, which means that for small strings, it +will be as fast as the char[] version. + +XXX+ the commit says av_dynbuf_get_buffer in the doxy and other inconsistencies + + AVWriter wr = av_dynbuf_writer(); + /* same code as above */ + char *msg = av_dynbuf_writer_get_data(wr, NULL); + use_the_message(msg); + av_dynbuf_writer_finalize(wr, NULL, NULL); + +WARNING: I have not covered error checking. Without error checking, this +code will not cause an invalid memory access, and in particular will not be +a security issue by itself, but it will cause the string to be truncated. +That means silent data corruption, which is worse than a non-exploitable +crash. + + +## Scenario 4: So let's do error checks. + +The example code above will not crash. At all point, +av_dynbuf_writer_get_data() will point to a valid 0-terminated buffer. If +you know your data is can be truncated later, then maybe you can tolerate to +have it truncated here, especially since that kind of memory allocation +failure is very very unlikely. + +But you are not lazy, and it's better to diaplay "Out Of Cheese Error" than +to store corrupted data without noticing. + + ret = av_dynbuf_writer_get_error(wr, &size); + if (ret < 0) { + fprintf("Could not display foobar, would have been %zu long", size); + return ret; + } + +XXX ça c'est bien, mais av_dynbuf_writer_get_data() devrait retourner une valeur sûre + + +## Scenario 5: We want to keep the string. + +Maybe what we will do with the message is something like: + + av_dict_set(metadata, "foo_pair", msg); + +But if av_dynbuf_writer() did use use dynamic allocation, we could use +AV_DICT_DONT_STRDUP_VAL. + +That's what the pointers arguments to av_dynbuf_writer_finalize() are for. + + ret = av_dynbuf_writer_finalize(wr, &msg, NULL); + if (ret < 0) + return ret; + av_dict_set(metadata, "foo_pair", msg, AV_DICT_DONT_STRDUP_VAL); + + +XXX+ av_dynbuf_writer_finish error if truncated, av_dynbuf_writer_finish_anyway. +XXX+ error check is all wrong + + +## Scenario 6: You work with a library that already has string functions. +## Scenario 7: You work with an API that does its own buffering. + +Let us say you are writing a Gtk+ application and you already use GString +all over the place. (Note: GString is more or less like av_dynbuf_writer(), +but with the Gtk+ taste instead of the FFmpeg taste; the API example should +be obvious.) You could always: + + av_foobar_write(wr, foo); + char *msg = av_dynbuf_writer_get_data(wr, NULL); + g_string_append(str, msg); + +But that's clumsy, you have to copy the string. If this comes frequently in +the code, it would be better to have AVWriter collaborate with GString: + + av_foobar_write(gstring_writer(str), foo); + +To be able to do that you need to implement a few callbacks and adapt some +boilerplate code. It is not difficult, but too long for this introduction. + +There is no central repository, neither static nor dynamic. As soon as a +part of a program has implemented the proper callbacks and set the proper +pointers, it makes a valid AVWriter that can be used anywhere. + +Note that it applies to any kind of library or API: as the name says, +AVWriter is made to write strings. Any API that looks like writing can be +desguised as AVWriter. + +For example, there is another built-in AVWriter: av_log_writer(obj) it goes +directly to av_log(obj, AV_LOG_INFO); + + +## Scenario 8: This time, you are the one who returns a string. + +This time, you are the one who was about to write: + + int av_bazqux_to_string(char *buf, size_t size, const Bazqux *baz); + +and you decide to write it: + + void av_bazqux_write(AVWriter wr, const Bazqux *baz); + +There is nothing new to say, you already know how to add things to an +AVWriter, it is in the second example: + + av_writer_printf(wr, ...); + +Just use this, or any of the similar functions, to add whatever you want to +the AVWriter you got as an argument. There is no error checking to do: it is +not your responsibility. + +At this point, you could implement av_bazqux_to_string() as a trivial +wrapper: + + int av_bazqux_to_string(char *buf, size_t size, const Bazqux *baz) { + av_bazqux_write(av_buf_writer(buf, size), baz); + if (strlen(baz) == size - 1) + return AVERROR_BUFFER_TOO_SMALL; + } + +(Since av_buf_writer() does not keep track of error, we consider that +reaching the end is already overflowing. We are wasting one char in a very +rare situation.) But is it really useful? + + +## Scenario 9: What with the types? + +If you are observant, you will wonder what happens if we write: + + AVWriter wr = av_log_writer(buf); + char *msg = av_dynbuf_writer_get_data(wr, NULL); + +The short answer is: it will crash, badly. And that's ok: you knew you were +doing something stupid. You got what you deserved. Same as if you had written: + + char *msg = NULL; + snprintf(msg, strlen(msg), "Good Bye"); + +The C language is based on the assumption that you know things about the +code that cannot be expressed in the language itself, in particular with the +type system. There is no type for non-NULL-pointer, yet there are places in +the code you know for certain that a pointer is not NULL. + +AVWriter is designed with the same philosophy. When you write + + AVWriter wr = av_dynbuf_writer(); + +you know that wr is actually an AVDynbufWriter, and therefore you can call +av_dynbuf_writer_get_data() on it. + +On the other hand, if you have an AVWriter of unknown origin, you cannot +call a specific function. But you can call all the av_writer_whatever() +functions. + +If you want to make a special case, to check for a specific kind of +AVWriter, you can use: + + if (av_dynbuf_writer_check(wr)) + +In fact, all functions that accept only a specific kind of AVWriter in +FFmpeg start with some kind of: + + av_assert1(av_dynbuf_writer_check(wr)); + +That means that if you are working with a libavutil built for development, +if you make a mistake, you will get a clear error message. But really, there +is no reason to make a mistake if you are not trying to tie your brain in a +knot. From patchwork Wed Apr 21 12:27:05 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 27199 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a6b:5014:0:0:0:0:0 with SMTP id e20csp350319iob; Wed, 21 Apr 2021 05:28:40 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyqXZXfdx8igmHnJkwTJuBqwpR25ueU4GTr+O/4vAMlYbWxG2GvgQuFdVMXCx81Hyeva7Wr X-Received: by 2002:a17:907:9485:: with SMTP id dm5mr32579460ejc.194.1619008120599; Wed, 21 Apr 2021 05:28:40 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1619008120; cv=none; d=google.com; s=arc-20160816; b=Y29v8uUvMy1j1svQyeAidcGLyHUcSq8JgY5chSm1dRF6ofhlc3xs2MSbIpcQ7RfEig I2qgrKh2oVhFxipKy0ztVjAXX7rDQTy6Ss8JJM1Whq3v166sm32vWC+TTE0PG6y8CVso cKxgWLV9OE+oz5gwf9RUw0P8vjYnwYKWLwZxj2iFppztaPEhQ0MbrSbUyfYYhpuN9A2N I1abcbiFigFiK/tj8Z0JgMLY6JgSiMrlF96J3J2M9s1u4aIUDsADHb6FZlM5MoKFFu+H VKU1WbV+szQkkFopJh2hJPaK64NDW8hMynyJnl1/bR+otT3Bng2M/13q+tbKDqJJ6vwH s4PA== 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=L0lJ3ax6aJc5mOGkUD9OoRlhFa2BSsNOjiS9XCbY58s=; b=j4+Gjf1W0Cc0oxqhmJoBistEXTzFuSAsTEBx1yrs6h6RjmyIz26aNH/ZRgFgGoLoXH cTL4FKJny5uIT5FEHrDD1NZr37NELeIzQdh1E9mpFT9uX5egFwspIho06iW1CfJNRr4W LXQxagvIzACHjBl0UNX6mwW2O8K/rgrvgxq8ar1w7eiDZ3v/WqqU7oZRdlfFmnk8qRlX gwY7UCX2y3jHERjcC4R9B0Ro4Qg7yjCj35y8ISqNXsNsDROiDU9lK9duQXwMDpKTKAlP omYyuDGgGXz/TeT4mMvNhAsxqQzE4Vq2NKFgQsPgg+ZawOPymgt5xlWtRxSJnYIFk8CO CybA== 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 f9si1752069ejk.446.2021.04.21.05.28.40; Wed, 21 Apr 2021 05:28:40 -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 D5F52689A19; Wed, 21 Apr 2021 15:27: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 BFC9F6899E8 for ; Wed, 21 Apr 2021 15:27: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 13LCRCr6006310 for ; Wed, 21 Apr 2021 14:27:13 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id EF9F9EB5BD; Wed, 21 Apr 2021 14:27:12 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Wed, 21 Apr 2021 14:27:05 +0200 Message-Id: <20210421122706.9002-7-george@nsup.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210421122706.9002-1-george@nsup.org> References: <20210421122706.9002-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]); Wed, 21 Apr 2021 14:27:14 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 6/7] lavu: add a JSON writer 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: iGpVNhg5RpkF Signed-off-by: Nicolas George --- libavutil/Makefile | 1 + libavutil/json.c | 368 +++++++++++++++++++++++++++++++++++ libavutil/json.h | 467 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 836 insertions(+) create mode 100644 libavutil/json.c create mode 100644 libavutil/json.h diff --git a/libavutil/Makefile b/libavutil/Makefile index 80d0580289..6ad31426e5 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -133,6 +133,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..8048305c11 --- /dev/null +++ b/libavutil/json.h @@ -0,0 +1,467 @@ +/* + * 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 +#define AVUTIL_JSON + +#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 */