From patchwork Mon Mar 28 20:47:52 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vignesh Venkat X-Patchwork-Id: 35025 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c05:b0:7a:e998:b410 with SMTP id bw5csp526979pzb; Mon, 28 Mar 2022 13:48:07 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyK8hdYpHUZwIUSVuWdKs5JMyBg/0gwpssbFIKLQG+8h14/wbVZyR3e8oBIsfpan9H14c2s X-Received: by 2002:a17:906:d789:b0:6e0:bdd5:a884 with SMTP id pj9-20020a170906d78900b006e0bdd5a884mr18171942ejb.201.1648500487323; Mon, 28 Mar 2022 13:48:07 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1648500487; cv=none; d=google.com; s=arc-20160816; b=kbxLQ534VLFEA3Oe19ImgYO0ZSNsM9m7ZYrOS356q8NMQ9fpannznw0AJvjzcPXo33 /orDCEo00RI8Gv3HjwW1Y/PhO8A1/W0huh5kG8XsX7wZpzyYjcbiI9hoQa7WiWxwq7mS +lB0mYgai2OyTUm5ZiiaAfd/EiL6My29XJNCZs7EB9p4osV0tVIkkcyfxw7kd27VZoPV BiJ58I/e7j/LQ6/YOK3fz6M/0U5/fH2rO3mrUOgzX0oIr5cpYQWzpMwxcZGPIJof5Nax pTo28SGYszxGkQoqgj1pduitX8T1L8fg1GLZk8AufnCuGO2Sieuvx2/2QRxEvwMzUbZE prtQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:to:from:references:mime-version :message-id:in-reply-to:date:dkim-signature:delivered-to; bh=QT3O26n+gSlQUG9b3xR3qxK3/GH3KFjrJZfclq1s36Q=; b=j9EEM2ulC0g5AkOcGclnE7O7kxYA6SYmPm6kYZRI07K622NOqUU9+IrPhaowTLXFje C8oGqTb1tbgZH89QcENdFlutBYdO2UiN9T/7pl3OeNiQ3Ov9EUsDnoUmYP8vo1+zo8mC kWgfyslf1ouySkEjOEhwSX7fXZ5fxypSZNfkgn3BdinCPfwZhzJh/idJzqzf3qZhcpN3 SPK7bfHihIkfmsp3bzK3SRxqBQvw5liUlfg3gKY6/m7x+fsvYktyl+JeZG2DCpXVR+Lh kpS6OUwq/Qp4OxR8Ykfi453u+stlEBN5q9qzgsnrvS6SWbJ+z8mkM7gIck8xAJVxmNdL 6b/Q== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@google.com header.s=20210112 header.b=Qoak2vGd; 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 a27-20020a17090640db00b006df7cded054si14703842ejk.698.2022.03.28.13.48.06; Mon, 28 Mar 2022 13:48:07 -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; dkim=neutral (body hash did not verify) header.i=@google.com header.s=20210112 header.b=Qoak2vGd; 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 C6C0A68B2B0; Mon, 28 Mar 2022 23:48:04 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id DC43668B12C for ; Mon, 28 Mar 2022 23:47:57 +0300 (EEST) Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-2e6aaf57a0eso121438327b3.11 for ; Mon, 28 Mar 2022 13:47:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=1vAAlgZlX8s8PPkF4mAcINGxifXRwtxAmQA7oyOh9bU=; b=Qoak2vGdF2D0pG77J7FS6WtrHrXJHxUjsFSiJXR6ERRB6lWGsXuwaImSRiGykdgyiN fT/zuLO5h9hOv4KarYwmibIpqELWSgrt4MyZXFykjWA/vIPyJ6YRA3nNW6XSwSZaj/yf mBqUmEFgeaIIjGYpe/7tyGGuI7S1yaDpTBNLCnSPXHemf8DD0/02UwDW/jFFvgcLXsLs LfPED6izkYSoobfS0Vbc4eBnqjI4buoKNUi8y1f+EXB7fJFgymJ9s7Wn5lCdn4MVulj9 joZaeqT2zRVcbaK945FpbnkleslTuNF1VjOs0q5o2ht5aHKCpUW9zW0jaxsM+3DkxbZ8 M6/Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=1vAAlgZlX8s8PPkF4mAcINGxifXRwtxAmQA7oyOh9bU=; b=SowZMiJORchuz3hKTCcg+xybjVb6xVbly9En36f+Lz/U7UlwJvFpYlWNAvGqWDpi/T IaiSXp34bE1fgojcfw7dIR7o3LBPYBSNXW/UvCXRbvS2LxM8RuAassrwTIg2ChmKGV7S 0d4Rjz6VTqiqj+EMRHiHi69wtVCMJ4SBbGcVkPbw6PLRBcNYONjnXOpv5kgJ7qW4h0KL KSjB3KTp9zL5Z1mlsW+1iJb2wlDfxnBgrpNoxHanierODZJGqQPKiLyCI87Qe1jynvr0 qvoXt7SPttOgC2ouDFvZbunPjMzbtKwhtPNa6QhcscprDkB2KVyw/WjPL9ssIw3rVnQW ET8Q== X-Gm-Message-State: AOAM532auL3hL22JQZw6bAZnecXXT8Y+mytw+RlsL65TQ1ML8DfJ6jXC UT3/UinF1FpbuXUA9oERkvvzDm8VBe5gCixqEwCulTATG6dCVoXNfRtG4EnW73bQAcV8I09Hm+X H0pmW4xODV953aIi3tQPIclccV9g0zCG255ZRYK8BobwLylvlzrCfFJRF/bf0x1kPvuzJ X-Received: from vigneshv3.mtv.corp.google.com ([2620:0:1000:2511:228a:277d:bb24:94ea]) (user=vigneshv job=sendgmr) by 2002:a25:b9cb:0:b0:61d:a7a5:6005 with SMTP id y11-20020a25b9cb000000b0061da7a56005mr24058197ybj.360.1648500476557; Mon, 28 Mar 2022 13:47:56 -0700 (PDT) Date: Mon, 28 Mar 2022 13:47:52 -0700 In-Reply-To: <20220222213655.3049471-1-vigneshv@google.com> Message-Id: <20220328204752.3339971-1-vigneshv@google.com> Mime-Version: 1.0 References: <20220222213655.3049471-1-vigneshv@google.com> X-Mailer: git-send-email 2.35.1.1021.g381101b075-goog From: Vignesh Venkatasubramanian To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding 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 Cc: Vignesh Venkatasubramanian Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: Qj0maOAq8XQK Add a parameter to libaom-av1 encoder to enforce some of the single image constraints in the AV1 encoder. Setting this flag will limit the encoder to producing exactly one frame and the sequence header that is produced by the encoder will be conformant to the AVIF specification [1]. Part of Fixing Trac ticket #7621 [1] https://aomediacodec.github.io/av1-avif Signed-off-by:: Vignesh Venkatasubramanian --- libavcodec/libaomenc.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavcodec/libaomenc.c b/libavcodec/libaomenc.c index 7dbb6f6f39..ccd0823b97 100644 --- a/libavcodec/libaomenc.c +++ b/libavcodec/libaomenc.c @@ -100,6 +100,7 @@ typedef struct AOMEncoderContext { int enable_restoration; int usage; int tune; + int still_picture; int enable_rect_partitions; int enable_1to4_partitions; int enable_ab_partitions; @@ -747,6 +748,18 @@ static av_cold int aom_init(AVCodecContext *avctx, if (res < 0) return res; + if (ctx->still_picture) { + // Set the maximum number of frames to 1. This will let libaom set + // still_picture and reduced_still_picture_header to 1 in the Sequence + // Header as required by AVIF still images. + enccfg.g_limit = 1; + // Reduce memory usage for still images. + enccfg.g_lag_in_frames = 0; + // All frames will be key frames. + enccfg.kf_max_dist = 0; + enccfg.kf_mode = AOM_KF_DISABLED; + } + /* Construct Encoder Context */ res = aom_codec_enc_init(&ctx->encoder, iface, &enccfg, flags); if (res != AOM_CODEC_OK) { @@ -1291,6 +1304,7 @@ static const AVOption options[] = { { "psnr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_PSNR}, 0, 0, VE, "tune"}, { "ssim", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_SSIM}, 0, 0, VE, "tune"}, FF_AV1_PROFILE_OPTS + { "still-picture", "Encode in single frame mode (typically used for still AVIF images).", OFFSET(still_picture), AV_OPT_TYPE_BOOL, {.i64 = 0}, -1, 1, VE }, { "enable-rect-partitions", "Enable rectangular partitions", OFFSET(enable_rect_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-1to4-partitions", "Enable 1:4/4:1 partitions", OFFSET(enable_1to4_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-ab-partitions", "Enable ab shape partitions", OFFSET(enable_ab_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, From patchwork Mon Mar 28 20:48:33 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vignesh Venkat X-Patchwork-Id: 35032 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c05:b0:7a:e998:b410 with SMTP id bw5csp527406pzb; Mon, 28 Mar 2022 13:48:47 -0700 (PDT) X-Google-Smtp-Source: ABdhPJxOV4PUbU+EL1PX9AlHmN69TPdJwP5oDF+fc4a9rd8jIx0ZLHeia8g9OkC4lou95KPU5FQZ X-Received: by 2002:a17:906:5804:b0:6ce:3f17:bf35 with SMTP id m4-20020a170906580400b006ce3f17bf35mr29810079ejq.346.1648500527217; Mon, 28 Mar 2022 13:48:47 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1648500527; cv=none; d=google.com; s=arc-20160816; b=oiW0cLYFFLcOysvHX7Rej6tl0Vfkbf7xrr4KSWvnRbxsYBLqvoBYUGEN0mrg/wKuYt u4hjyYoWXHtSbq+KUpeMFhMjUkjqaweqN8sIOvO6O/BImy/72pgi9ynZrHgKqy33cwqX SxHRNfewyOWxH8xFr+CPsG8/7s1MMjr7JZSHV2Qeuv3O83sKWtv7uNEAoQc7LAiigeio ZY1W+IPyHMKTpP5FywW7oQPWPBRpTHrxMM1a+HQigIPGz0wtnY16Zy4kzIZUperAVE6C B08mroh56tDIfXhH9a5iOqvuyhvMvUGv2ZTKDBRtTrWaemBHcAydVYG10OM07URzl3XX bxSQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:to:from:references:mime-version :message-id:in-reply-to:date:dkim-signature:delivered-to; bh=Q/6NrmrAfn1yJWqmO0rtohWNVZ9cTJ6+Ie7jYaboHWE=; b=EwwlyMDCVIu1kL9IR19DBndjz99bx48U7y/2Luo2+A42Lo5D1yPUUdCHh/mudfUEDG lnvvKbqyFsbgsRmfZVO9sumhmWv+VQHEEKjg8c4MHNOsivviRlOBOLLigy9stnLjPI7F yXUAIDGVgCyTX6tawG8URkpgUoxuKyReXiIm73kWIdSQYh6P/zTBKsr5Yfl8gksond7z 6yogtMPQBl3UhKJXAgzbrxDp+qXmVc6dSl1sQ5SCebSdVUomDBFssPNDYIMKXcz2i7Hy smd22HKBS5gW0lzsKb9Z/Obmo1IWa0wFaBW3CBLNY7HR5qEhVKzA2aO11Z25mR6App16 9kKA== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@google.com header.s=20210112 header.b=OKmdaAot; 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 my22-20020a1709065a5600b006e1124be7c4si4277075ejc.160.2022.03.28.13.48.46; Mon, 28 Mar 2022 13:48:47 -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; dkim=neutral (body hash did not verify) header.i=@google.com header.s=20210112 header.b=OKmdaAot; 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 E89CB68B2DD; Mon, 28 Mar 2022 23:48:44 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-yw1-f202.google.com (mail-yw1-f202.google.com [209.85.128.202]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id B811268B214 for ; Mon, 28 Mar 2022 23:48:37 +0300 (EEST) Received: by mail-yw1-f202.google.com with SMTP id 00721157ae682-2e689dfe112so127814697b3.20 for ; Mon, 28 Mar 2022 13:48:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=LbtkaM1GSBcAVsLj5pMb8pLjI3PZPMVSFPy/pup2Of4=; b=OKmdaAot51jjvuycR/A3NHOGhrTpli3IvPlJh21aGEtJPZa5ILyzABwmgkz3hgc9FI NFwuDe8Tey02kFhPsibNh+Ie/CxECPPGxfrW3X68Kj4r/uGik5D1fd293OqBorrWUXGB Qx6WtkEbmXhMQLvFZQpzyUekC/Nec5XKL6F6jeGpjnagubOFt9JJqbdbl95MJfqPUgJu XkhKMMmom3JvVS6XMkPjAusDcYRMV3YuYXUKIknzkqNaft7ZZ1Olec3d4+tfbzg2Zgmk H2zD/2AFpaAFSlTzlF6WsN+rNoODq1u9MxTvRkU/NheOwnMH8r7vbMAIXAEMInlxTM+D ygqA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=LbtkaM1GSBcAVsLj5pMb8pLjI3PZPMVSFPy/pup2Of4=; b=E4CAd2VU/S17m82lV1asBztKt6Uwbagy4ovSvIzTn3ouvH/AuTt5kmNfN1XFQptQgp Bf7Cht5vP+2g2EA2Dk3UqOvv77LAIk7o8TFXKUBR7yR5UIfHj3weHWzjiFCmRE09uGrm 5VCD0SonTExC3cpo3soDfHojbSFU9eeWH3lYaj5Mm3lSJtsbYIwMBLGJ4ZxVlM5V1nOp Gnqs2tPlp2lavpfpRV41l3oT6gy6kJm/Mywlp2oNeVAcBy/QJGvagz3Ezo2X/zQTs9oM dFYzU+rmrC38QNrdaK++NwqiGrmdeOQHTf39Zp5ifZOq2HAGHGhFNzCrPengVXDk2hJi wbXw== X-Gm-Message-State: AOAM530BGnICDIYok0sz1g/94Rm2PgWdAKO0pQ40jGzg8fNWZSoBZVvt Dhq+IOU4iWIqVdircu97GoknXMCPcV/4uASxihCioeaMucehEWRhoLbodbh+P8C7k/l0j8N1rQA u+kb7csdeRg9jz8dSSKAypzUvPd0j0689+wy5hZA/G2EV+la+cW0mVRGKoWon6OHwzaoo X-Received: from vigneshv3.mtv.corp.google.com ([2620:0:1000:2511:228a:277d:bb24:94ea]) (user=vigneshv job=sendgmr) by 2002:a0d:ca0a:0:b0:2e5:ad11:1fff with SMTP id m10-20020a0dca0a000000b002e5ad111fffmr26932127ywd.395.1648500516469; Mon, 28 Mar 2022 13:48:36 -0700 (PDT) Date: Mon, 28 Mar 2022 13:48:33 -0700 In-Reply-To: <431afea2-ce1e-4c94-8ed2-2ee8ff33cd50@gmail.com> Message-Id: <20220328204833.3342663-1-vigneshv@google.com> Mime-Version: 1.0 References: <431afea2-ce1e-4c94-8ed2-2ee8ff33cd50@gmail.com> X-Mailer: git-send-email 2.35.1.1021.g381101b075-goog From: Vignesh Venkatasubramanian To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header 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 Cc: Vignesh Venkatasubramanian Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: wCZTTnbllmMe Add a parameter to omit seq header when generating the av1C atom. For now, this does not change any behavior. This will be used by a follow-up patch to add AVIF support. Signed-off-by: Vignesh Venkatasubramanian --- libavformat/av1.c | 7 +++++-- libavformat/av1.h | 4 +++- libavformat/matroskaenc.c | 4 ++-- libavformat/movenc.c | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/libavformat/av1.c b/libavformat/av1.c index 79065d0c9f..b6eaf50627 100644 --- a/libavformat/av1.c +++ b/libavformat/av1.c @@ -395,7 +395,8 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int return is_av1c ? 0 : AVERROR_INVALIDDATA; } -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, + int write_seq_header) { AVIOContext *meta_pb; AV1SequenceParameters seq_params; @@ -485,7 +486,9 @@ int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) flush_put_bits(&pbc); avio_write(pb, header, sizeof(header)); - avio_write(pb, seq, seq_size); + if (write_seq_header) { + avio_write(pb, seq, seq_size); + } meta_size = avio_get_dyn_buf(meta_pb, &meta); if (meta_size) diff --git a/libavformat/av1.h b/libavformat/av1.h index f57dabe986..a393fbb78f 100644 --- a/libavformat/av1.h +++ b/libavformat/av1.h @@ -96,9 +96,11 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int * @param pb pointer to the AVIOContext where the av1C box shall be written * @param buf input data buffer * @param size size in bytes of the input data buffer + * @param write_seq_header If 1, Sequence Header OBU will be written inside the + * av1C box. Otherwise, Sequence Header OBU will be omitted. * * @return >= 0 in case of success, a negative AVERROR code in case of failure */ -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size); +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, int write_seq_header); #endif /* AVFORMAT_AV1_H */ diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 3b8ca11f28..d789a618a4 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1089,7 +1089,7 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, case AV_CODEC_ID_AV1: if (par->extradata_size) return ff_isom_write_av1c(dyn_cp, par->extradata, - par->extradata_size); + par->extradata_size, 1); else put_ebml_void(pb, 4 + 3); break; @@ -2665,7 +2665,7 @@ static int mkv_check_new_extra_data(AVFormatContext *s, const AVPacket *pkt) ret = avio_open_dyn_buf(&dyn_cp); if (ret < 0) return ret; - ff_isom_write_av1c(dyn_cp, side_data, side_data_size); + ff_isom_write_av1c(dyn_cp, side_data, side_data_size, 1); codecpriv_size = avio_get_dyn_buf(dyn_cp, &codecpriv); if ((ret = dyn_cp->error) < 0 || !codecpriv_size && (ret = AVERROR_INVALIDDATA)) { diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 46d66c29c2..70ceb0dea4 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1306,7 +1306,7 @@ static int mov_write_av1c_tag(AVIOContext *pb, MOVTrack *track) avio_wb32(pb, 0); ffio_wfourcc(pb, "av1C"); - ff_isom_write_av1c(pb, track->vos_data, track->vos_len); + ff_isom_write_av1c(pb, track->vos_data, track->vos_len, 1); return update_size(pb, pos); } From patchwork Mon Mar 28 20:49:13 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vignesh Venkat X-Patchwork-Id: 35033 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c05:b0:7a:e998:b410 with SMTP id bw5csp527799pzb; Mon, 28 Mar 2022 13:49:28 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzXNNYrdnyjuiYPfSOlS+RtQ2+1XXNOBLZQesTZRvV765uH79XlT2tq6QS4GjiHXbQR1ub/ X-Received: by 2002:a05:6402:2342:b0:419:e86:c7d5 with SMTP id r2-20020a056402234200b004190e86c7d5mr18859692eda.92.1648500568771; Mon, 28 Mar 2022 13:49:28 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1648500568; cv=none; d=google.com; s=arc-20160816; b=CQJVS9SqPyyddlDvvhPozQVJ70yQhQMS55uyMKPlX8gXszOpluf+Lo0rXGp2dc9Zt1 6vOc6zITrnac61CF2x2nHz1iUEqXjrGk15xrFi10wDTTmlDb63QVz9nq4OcC7p3gNS4U VU10pLTygw1uN9DdEomU3azN1nD7AFeFaYxXKgnyaPeiwDx5a6CAeAWeE8smoDueK1/8 2ztOB9EXk37PJNMEYyDvLiU1MFv8wnGC00FhYsB0CLKbUozThppWCtgl2XyUCM245uZ9 g/JDYldHjMxALJVobx/eBkyFLKcqoaZM2SCIY9RWdKayWYGgzTJ/eQo4/fFWae+L9NAh Repw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:to:from:references:mime-version :message-id:in-reply-to:date:dkim-signature:delivered-to; bh=qPpo4P2EUAAqEHoV5V79XLXxrIYdZFgYGdWtwKxrebE=; b=sCvINGajcmjs3wQQNoP2v3cq9d4EuGNBCTXbe2JVQQinRrWK5AUiUyQPdeemKMUdO0 F5WoOpDgE9BAHYWwvmijUBRgKPGSmxJzPTYEA0J2zJbKGjjM3z94CNltwtneTHeugc2l FX7YpxOL+xFCxE1C8SzbXjP59koYatbtAVVIc5GQhrhPlunpNw7oBIrCIk6LsznDaS9m douc1FGM6fMMY21mnrKQ9kEkTPfzZRdXHH3Zls1vqrY05S8JI3Mzz+lUMvHCtBVJ4Ote KS5JYyvm7nWciFshHi3kvplnMzOKamGbuC1mwXntGYrznS1QCz81Mynz8jXO0GJKhe2N jqcg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@google.com header.s=20210112 header.b=CphYcgIy; 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 y29-20020a50ce1d000000b00419075664c3si13132459edi.273.2022.03.28.13.49.28; Mon, 28 Mar 2022 13:49:28 -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; dkim=neutral (body hash did not verify) header.i=@google.com header.s=20210112 header.b=CphYcgIy; 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 1B6CE68B2E4; Mon, 28 Mar 2022 23:49:26 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-yw1-f202.google.com (mail-yw1-f202.google.com [209.85.128.202]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id A56E668B214 for ; Mon, 28 Mar 2022 23:49:18 +0300 (EEST) Received: by mail-yw1-f202.google.com with SMTP id 00721157ae682-2e6d04210b3so97471627b3.10 for ; Mon, 28 Mar 2022 13:49:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; h=date:in-reply-to:message-id:mime-version:references:subject:from:to :cc; bh=kUB587PUfIi0bwUqtRJYIb8Adn3/fKwGhCiYhQ4RWrA=; b=CphYcgIy4XwI7ojT98wj0irhsrudekydIsGbFXfCmFYyR1NmC9FRO0pLDiaj90banL regVFoI0IDHijN+B0ix4ByeCOrjwx41MRTGH740MsSpAMbz1mZITGhsi8r4U5zPDua+9 /vIsDnBgkFtrF7KNBabfZkRwmz7hBJ+WuFrujuC5EwEhyBl5qyO/puBw0fPGUvGTrkLE tnERBO5+Iulf2Lr0LrhL50X39ASs5OnGA0eo33WpJ+yGyM7pQ+SMj2e3zRpd93ivEaMW xXyvFcIrbvFxAtVx7XNK8iFB7zxgr+i5ZZ5O+cnVDEEWD75o727zOWcLmlhu5cfr7egU XQ8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=kUB587PUfIi0bwUqtRJYIb8Adn3/fKwGhCiYhQ4RWrA=; b=nFfpzaCW0IAA/7rB4GImR9feZTrx6v4BYcIbyGM12BFuAI/SRwb+NV5RR6/a1Gyohl mJGz87c7PmrAfTh5v5YrP1963MfiWfZ0PYLQCD+UujBMuiL30wdIrNgT7SoCguNaZby3 YbT526j4s6ydbUK5oBNEWzJqtqjoVEecoV3aE4GBWJZRO1iQ52FW9ef6AWhLD+WZoHKE bYJWRpmk+ecsZLnoJdJq9/Sc4GXtG4qvA8C/aEQ2rcQezZexAgS4h6bZ/nG6koDtfe1u JwJsu40aQTQCX/A7GajofArRMagM9Zyh8N/RN/ALHfHNl4suQuXJ29284JuvASyUsM9h QxBA== X-Gm-Message-State: AOAM531W7W3l88+DFw1xeOslHStf3XKGQnC1Rk40iz1+KSOgFTCpD4d8 INtAvnuvlEvooRxIM7JjXZURlhkvpRsEW0EFvuZ/gm1EI6beOCrBti3xjYM0Xo6QWYoN06Ba1hY xu2gk+gwSqFYZA/YllK7wO06H9uiPWPyAdoLsw3OvFCKix2TE7Am7Nm3zxuZcpzPt+Gf0 X-Received: from vigneshv3.mtv.corp.google.com ([2620:0:1000:2511:228a:277d:bb24:94ea]) (user=vigneshv job=sendgmr) by 2002:a25:40cb:0:b0:633:be3c:d9a1 with SMTP id n194-20020a2540cb000000b00633be3cd9a1mr24229296yba.648.1648500557338; Mon, 28 Mar 2022 13:49:17 -0700 (PDT) Date: Mon, 28 Mar 2022 13:49:13 -0700 In-Reply-To: Message-Id: <20220328204913.3343629-1-vigneshv@google.com> Mime-Version: 1.0 References: X-Mailer: git-send-email 2.35.1.1021.g381101b075-goog From: Vignesh Venkatasubramanian To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 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 Cc: Vignesh Venkatasubramanian Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: gwRBeJk9SnWh Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specifiation: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verfied to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- libavformat/movenc.h | 5 + 4 files changed, 319 insertions(+), 25 deletions(-) diff --git a/configure b/configure index e4d36aa639..b9a79d8982 100755 --- a/configure +++ b/configure @@ -3396,6 +3396,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 587ad59b3c..29e58353ee 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 70ceb0dea4..920f3dcf25 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1306,7 +1306,7 @@ static int mov_write_av1c_tag(AVIOContext *pb, MOVTrack *track) avio_wb32(pb, 0); ffio_wfourcc(pb, "av1C"); - ff_isom_write_av1c(pb, track->vos_data, track->vos_len, 1); + ff_isom_write_av1c(pb, track->vos_data, track->vos_len, track->mode != MODE_AVIF); return update_size(pb, pos); } @@ -2007,12 +2007,13 @@ static int mov_write_colr_tag(AVIOContext *pb, MOVTrack *track, int prefer_icc) } } - /* We should only ever be called by MOV or MP4. */ - av_assert0(track->mode == MODE_MOV || track->mode == MODE_MP4); + /* We should only ever be called for MOV, MP4 and AVIF. */ + av_assert0(track->mode == MODE_MOV || track->mode == MODE_MP4 || + track->mode == MODE_AVIF); avio_wb32(pb, 0); /* size */ ffio_wfourcc(pb, "colr"); - if (track->mode == MODE_MP4) + if (track->mode == MODE_MP4 || track->mode == MODE_AVIF) ffio_wfourcc(pb, "nclx"); else ffio_wfourcc(pb, "nclc"); @@ -2022,7 +2023,7 @@ static int mov_write_colr_tag(AVIOContext *pb, MOVTrack *track, int prefer_icc) avio_wb16(pb, track->par->color_primaries); avio_wb16(pb, track->par->color_trc); avio_wb16(pb, track->par->color_space); - if (track->mode == MODE_MP4) { + if (track->mode == MODE_MP4 || track->mode == MODE_AVIF) { int full_range = track->par->color_range == AVCOL_RANGE_JPEG; avio_w8(pb, full_range << 7); } @@ -2088,7 +2089,7 @@ static void find_compressor(char * compressor_name, int len, MOVTrack *track) || (track->par->width == 1440 && track->par->height == 1080) || (track->par->width == 1920 && track->par->height == 1080); - if (track->mode == MODE_MOV && + if ((track->mode == MODE_AVIF || track->mode == MODE_MOV) && (encoder = av_dict_get(track->st->metadata, "encoder", NULL, 0))) { av_strlcpy(compressor_name, encoder->value, 32); } else if (track->par->codec_id == AV_CODEC_ID_MPEG2VIDEO && xdcam_res) { @@ -2109,6 +2110,25 @@ static void find_compressor(char * compressor_name, int len, MOVTrack *track) } } +static int mov_write_ccst_tag(AVIOContext *pb) +{ + int64_t pos = avio_tell(pb); + // Write sane defaults: + // all_ref_pics_intra = 0 : all samples can use any type of reference. + // intra_pred_used = 1 : intra prediction may or may not be used. + // max_ref_per_pic = 15 : reserved value to indicate that any number of + // reference images can be used. + uint8_t ccstValue = (0 << 7) | /* all_ref_pics_intra */ + (1 << 6) | /* intra_pred_used */ + (15 << 2); /* max_ref_per_pic */ + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "ccst"); + avio_wb32(pb, 0); /* Version & flags */ + avio_w8(pb, ccstValue); + avio_wb24(pb, 0); /* reserved */ + return update_size(pb, pos); +} + static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) { int ret = AVERROR_BUG; @@ -2126,6 +2146,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2242,7 +2264,7 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex else av_log(mov->fc, AV_LOG_WARNING, "Not writing 'gama' atom. Format is not MOV.\n"); } - if (track->mode == MODE_MOV || track->mode == MODE_MP4) { + if (track->mode == MODE_MOV || track->mode == MODE_MP4 || track->mode == MODE_AVIF) { int has_color_info = track->par->color_primaries != AVCOL_PRI_UNSPECIFIED && track->par->color_trc != AVCOL_TRC_UNSPECIFIED && track->par->color_space != AVCOL_SPC_UNSPECIFIED; @@ -2294,6 +2316,9 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex if (avid) avio_wb32(pb, 0); + if (track->mode == MODE_AVIF) + mov_write_ccst_tag(pb); + return update_size(pb, pos); } @@ -2795,7 +2820,10 @@ static int mov_write_hdlr_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra if (track) { hdlr = (track->mode == MODE_MOV) ? "mhlr" : "\0\0\0\0"; - if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "ffmpeg"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2862,6 +2890,129 @@ static int mov_write_hdlr_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra return update_size(pb, pos); } +static int mov_write_pitm_tag(AVIOContext *pb, int item_id) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "pitm"); + avio_wb32(pb, 0); /* Version & flags */ + avio_wb16(pb, item_id); /* item_id */ + return update_size(pb, pos); +} + +static int mov_write_iloc_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "iloc"); + avio_wb32(pb, 0); /* Version & flags */ + avio_w8(pb, (4 << 4) + 4); /* offset_size(4) and length_size(4) */ + avio_w8(pb, 0); /* base_offset_size(4) and reserved(4) */ + avio_wb16(pb, 1); /* item_count */ + + avio_wb16(pb, 1); /* item_id */ + avio_wb16(pb, 0); /* data_reference_index */ + avio_wb16(pb, 1); /* extent_count */ + mov->avif_extent_pos = avio_tell(pb); + avio_wb32(pb, 0); /* extent_offset (written later) */ + // For animated AVIF, we simply write the first packet's size. + avio_wb32(pb, mov->avif_extent_length); /* extent_length */ + + return update_size(pb, pos); +} + +static int mov_write_iinf_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) +{ + int64_t infe_pos; + int64_t iinf_pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "iinf"); + avio_wb32(pb, 0); /* Version & flags */ + avio_wb16(pb, 1); /* entry_count */ + + infe_pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "infe"); + avio_w8(pb, 0x2); /* Version */ + avio_wb24(pb, 0); /* flags */ + avio_wb16(pb, 1); /* item_id */ + avio_wb16(pb, 0); /* item_protection_index */ + avio_write(pb, "av01", 4); /* item_type */ + avio_write(pb, "Color\0", 6); /* item_name */ + update_size(pb, infe_pos); + + return update_size(pb, iinf_pos); +} + +static int mov_write_ispe_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "ispe"); + avio_wb32(pb, 0); /* Version & flags */ + avio_wb32(pb, s->streams[0]->codecpar->width); /* image_width */ + avio_wb32(pb, s->streams[0]->codecpar->height); /* image_height */ + return update_size(pb, pos); +} + + +static int mov_write_pixi_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) +{ + int64_t pos = avio_tell(pb); + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "pixi"); + avio_wb32(pb, 0); /* Version & flags */ + avio_w8(pb, pixdesc->nb_components); /* num_channels */ + for (int i = 0; i < pixdesc->nb_components; ++i) { + avio_w8(pb, pixdesc->comp[i].depth); /* bits_per_channel */ + } + return update_size(pb, pos); +} + +static int mov_write_ipco_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "ipco"); + mov_write_ispe_tag(pb, mov, s); + mov_write_pixi_tag(pb, mov, s); + mov_write_av1c_tag(pb, &mov->tracks[0]); + mov_write_colr_tag(pb, &mov->tracks[0], 0); + return update_size(pb, pos); +} + +static int mov_write_ipma_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "ipma"); + avio_wb32(pb, 0); /* Version & flags */ + avio_wb32(pb, 1); /* entry_count */ + avio_wb16(pb, 1); /* item_ID */ + avio_w8(pb, 4); /* association_count */ + + // ispe association. + avio_w8(pb, 1); /* essential and property_index */ + // pixi association. + avio_w8(pb, 2); /* essential and property_index */ + // av1C association. + avio_w8(pb, 0x80 | 3); /* essential and property_index */ + // colr association. + avio_w8(pb, 4); /* essential and property_index */ + return update_size(pb, pos); +} + +static int mov_write_iprp_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s) +{ + int64_t pos = avio_tell(pb); + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "iprp"); + mov_write_ipco_tag(pb, mov, s); + mov_write_ipma_tag(pb, mov, s); + return update_size(pb, pos); +} + static int mov_write_hmhd_tag(AVIOContext *pb) { /* This atom must be present, but leaving the values at zero @@ -3059,7 +3210,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3107,7 +3258,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, if (st && (track->par->codec_type == AVMEDIA_TYPE_VIDEO || track->par->codec_type == AVMEDIA_TYPE_SUBTITLE)) { int64_t track_width_1616; - if (track->mode == MODE_MOV) { + if (track->mode == MODE_MOV || track->mode == MODE_AVIF) { track_width_1616 = track->par->width * 0x10000ULL; } else { track_width_1616 = av_rescale(st->sample_aspect_ratio.num, @@ -3442,7 +3593,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3917,8 +4069,15 @@ static int mov_write_meta_tag(AVIOContext *pb, MOVMuxContext *mov, mov_write_mdta_hdlr_tag(pb, mov, s); mov_write_mdta_keys_tag(pb, mov, s); mov_write_mdta_ilst_tag(pb, mov, s); - } - else { + } else if (mov->mode == MODE_AVIF) { + mov_write_hdlr_tag(s, pb, &mov->tracks[0]); + // We always write the primary item id as 1 since only one track is + // supported for AVIF. + mov_write_pitm_tag(pb, 1); + mov_write_iloc_tag(pb, mov, s); + mov_write_iinf_tag(pb, mov, s); + mov_write_iprp_tag(pb, mov, s); + } else { /* iTunes metadata tag */ mov_write_itunes_hdlr_tag(pb, mov, s); mov_write_ilst_tag(pb, mov, s); @@ -4248,10 +4407,11 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, } mov_write_mvhd_tag(pb, mov); - if (mov->mode != MODE_MOV && !mov->iods_skip) + if (mov->mode != MODE_MOV && mov->mode != MODE_AVIF && !mov->iods_skip) mov_write_iods_tag(pb, mov); for (i = 0; i < mov->nb_streams; i++) { - if (mov->tracks[i].entry > 0 || mov->flags & FF_MOV_FLAG_FRAGMENT) { + if (mov->tracks[i].entry > 0 || mov->flags & FF_MOV_FLAG_FRAGMENT || + mov->mode == MODE_AVIF) { int ret = mov_write_trak_tag(s, pb, mov, &(mov->tracks[i]), i < s->nb_streams ? s->streams[i] : NULL); if (ret < 0) return ret; @@ -4262,7 +4422,7 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, if (mov->mode == MODE_PSP) mov_write_uuidusmt_tag(pb, s); - else + else if (mov->mode != MODE_AVIF) mov_write_udta_tag(pb, mov, s); return update_size(pb, pos); @@ -5005,6 +5165,9 @@ static void mov_write_ftyp_tag_internal(AVIOContext *pb, AVFormatContext *s, else if (mov->mode == MODE_3GP) { ffio_wfourcc(pb, has_h264 ? "3gp6" : "3gp4"); minor = has_h264 ? 0x100 : 0x200; + } else if (mov->mode == MODE_AVIF) { + ffio_wfourcc(pb, mov->is_animated_avif ? "avis" : "avif"); + minor = 0; } else if (mov->mode & MODE_3G2) { ffio_wfourcc(pb, has_h264 ? "3g2b" : "3g2a"); minor = has_h264 ? 0x20000 : 0x10000; @@ -5068,6 +5231,31 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) // compatible brand a second time. if (mov->mode == MODE_ISM) { ffio_wfourcc(pb, "piff"); + } else if (mov->mode == MODE_AVIF) { + const AVPixFmtDescriptor *pix_fmt_desc = + av_pix_fmt_desc_get(s->streams[0]->codecpar->format); + const int depth = pix_fmt_desc->comp[0].depth; + if (mov->is_animated_avif) { + // For animated AVIF, major brand is "avis". Add "avif" as a + // compatible brand. + ffio_wfourcc(pb, "avif"); + ffio_wfourcc(pb, "msf1"); + ffio_wfourcc(pb, "iso8"); + } + ffio_wfourcc(pb, "mif1"); + ffio_wfourcc(pb, "miaf"); + if (depth == 8 || depth == 10) { + // MA1B and MA1A brands are based on AV1 profile. Short hand for + // computing that is based on chroma subsampling type. 420 chroma + // subsampling is MA1B. 444 chroma subsampling is MA1A. + if (pix_fmt_desc->log2_chroma_w == 0 && pix_fmt_desc->log2_chroma_h == 0) { + // 444 chroma subsampling. + ffio_wfourcc(pb, "MA1A"); + } else { + // 420 chroma subsampling. + ffio_wfourcc(pb, "MA1B"); + } + } } else if (mov->mode != MODE_MOV) { // We add tfdt atoms when fragmenting, signal this with the iso6 compatible // brand, if not already the major brand. This is compatible with users that @@ -5671,7 +5859,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) if (ret < 0) return ret; - if (mov->flags & FF_MOV_FLAG_FRAGMENT) { + if (mov->flags & FF_MOV_FLAG_FRAGMENT || mov->mode == MODE_AVIF) { int ret; if (mov->moov_written || mov->flags & FF_MOV_FLAG_EMPTY_MOOV) { if (mov->frag_interleave && mov->fragments > 0) { @@ -5812,7 +6000,11 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) avio_write(pb, reformatted_data, size); } else { size = ff_av1_filter_obus(pb, pkt->data, pkt->size); + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { + mov->avif_extent_length = size; + } } + #if CONFIG_AC3_PARSER } else if (par->codec_id == AV_CODEC_ID_EAC3) { size = handle_eac3(mov, pkt, trk); @@ -6545,11 +6737,15 @@ static int mov_init(AVFormatContext *s) else if (IS_MODE(ipod, IPOD)) mov->mode = MODE_IPOD; else if (IS_MODE(ismv, ISMV)) mov->mode = MODE_ISM; else if (IS_MODE(f4v, F4V)) mov->mode = MODE_F4V; + else if (IS_MODE(avif, AVIF)) mov->mode = MODE_AVIF; #undef IS_MODE if (mov->flags & FF_MOV_FLAG_DELAY_MOOV) mov->flags |= FF_MOV_FLAG_EMPTY_MOOV; + if (mov->mode == MODE_AVIF) + mov->flags |= FF_MOV_FLAG_DELAY_MOOV; + /* Set the FRAGMENT flag if any of the fragmentation methods are * enabled. */ if (mov->max_fragment_duration || mov->max_fragment_size || @@ -6630,11 +6826,25 @@ static int mov_init(AVFormatContext *s) /* Non-seekable output is ok if using fragmentation. If ism_lookahead * is enabled, we don't support non-seekable output at all. */ if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL) && - (!(mov->flags & FF_MOV_FLAG_FRAGMENT) || mov->ism_lookahead)) { + (!(mov->flags & FF_MOV_FLAG_FRAGMENT) || mov->ism_lookahead || + mov->mode == MODE_AVIF)) { av_log(s, AV_LOG_ERROR, "muxer does not support non seekable output\n"); return AVERROR(EINVAL); } + /* AVIF output must have exactly one video stream */ + if (mov->mode == MODE_AVIF) { + if (s->nb_streams > 1) { + av_log(s, AV_LOG_ERROR, "AVIF output requires exactly one stream\n"); + return AVERROR(EINVAL); + } + if (s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) { + av_log(s, AV_LOG_ERROR, "AVIF output requires one video stream\n"); + return AVERROR(EINVAL); + } + } + + mov->nb_streams = s->nb_streams; if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) mov->chapter_track = mov->nb_streams++; @@ -6773,12 +6983,13 @@ static int mov_init(AVFormatContext *s) pix_fmt == AV_PIX_FMT_MONOWHITE || pix_fmt == AV_PIX_FMT_MONOBLACK; } - if (track->par->codec_id == AV_CODEC_ID_VP9 || - track->par->codec_id == AV_CODEC_ID_AV1) { - if (track->mode != MODE_MP4) { - av_log(s, AV_LOG_ERROR, "%s only supported in MP4.\n", avcodec_get_name(track->par->codec_id)); - return AVERROR(EINVAL); - } + if (track->par->codec_id == AV_CODEC_ID_VP9 && track->mode != MODE_MP4) { + av_log(s, AV_LOG_ERROR, "%s only supported in MP4.\n", avcodec_get_name(track->par->codec_id)); + return AVERROR(EINVAL); + } else if (track->par->codec_id == AV_CODEC_ID_AV1 && + track->mode != MODE_MP4 && track->mode != MODE_AVIF) { + av_log(s, AV_LOG_ERROR, "%s only supported in MP4 and AVIF.\n", avcodec_get_name(track->par->codec_id)); + return AVERROR(EINVAL); } else if (track->par->codec_id == AV_CODEC_ID_VP8) { /* altref frames handling is not defined in the spec as of version v1.0, * so just forbid muxing VP8 streams altogether until a new version does */ @@ -6982,7 +7193,7 @@ static int mov_write_header(AVFormatContext *s) FF_MOV_FLAG_FRAG_EVERY_FRAME)) && !mov->max_fragment_duration && !mov->max_fragment_size) mov->flags |= FF_MOV_FLAG_FRAG_KEYFRAME; - } else { + } else if (mov->mode != MODE_AVIF) { if (mov->flags & FF_MOV_FLAG_FASTSTART) mov->reserved_header_pos = avio_tell(pb); mov_write_mdat_tag(pb, mov); @@ -7270,6 +7481,50 @@ static int mov_check_bitstream(AVFormatContext *s, AVStream *st, return ret; } +static int avif_write_trailer(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + MOVMuxContext *mov = s->priv_data; + int64_t pos_backup, mdat_pos; + uint8_t *buf; + int buf_size, moov_size; + + if (mov->moov_written) return 0; + + mov->is_animated_avif = s->streams[0]->nb_frames > 1; + mov_write_identification(pb, s); + mov_write_meta_tag(pb, mov, s); + + moov_size = get_moov_size(s); + mov->tracks[0].data_offset = avio_tell(pb) + moov_size + 8; + + if (mov->is_animated_avif) { + int ret; + if ((ret = mov_write_moov_tag(pb, mov, s)) < 0) + return ret; + } + + buf_size = avio_get_dyn_buf(mov->mdat_buf, &buf); + avio_wb32(pb, buf_size + 8); + ffio_wfourcc(pb, "mdat"); + mdat_pos = avio_tell(pb); + + if (mdat_pos != (uint32_t)mdat_pos) { + av_log(s, AV_LOG_ERROR, "mdat offset does not fit in 32 bits\n"); + return AVERROR_INVALIDDATA; + } + + avio_write(pb, buf, buf_size); + + // write extent offset. + pos_backup = avio_tell(pb); + avio_seek(pb, mov->avif_extent_pos, SEEK_SET); + avio_wb32(pb, mdat_pos); /* rewrite offset */ + avio_seek(pb, pos_backup, SEEK_SET); + + return 0; +} + #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER static const AVCodecTag codec_3gp_tags[] = { { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, @@ -7352,6 +7607,20 @@ static const AVCodecTag codec_f4v_tags[] = { { AV_CODEC_ID_NONE, 0 }, }; +#if CONFIG_AVIF_MUXER +static const AVCodecTag codec_avif_tags[] = { + { AV_CODEC_ID_AV1, MKTAG('a','v','0','1') }, + { AV_CODEC_ID_NONE, 0 }, +}; +static const AVCodecTag *const codec_avif_tags_list[] = { codec_avif_tags, NULL }; + +static const AVClass mov_avif_muxer_class = { + .class_name = "avif muxer", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, +}; +#endif + #if CONFIG_MOV_MUXER const AVOutputFormat ff_mov_muxer = { .name = "mov", @@ -7514,3 +7783,21 @@ const AVOutputFormat ff_f4v_muxer = { .priv_class = &mov_isobmff_muxer_class, }; #endif +#if CONFIG_AVIF_MUXER +const AVOutputFormat ff_avif_muxer = { + .name = "avif", + .long_name = NULL_IF_CONFIG_SMALL("AVIF"), + .mime_type = "image/avif", + .extensions = "avif", + .priv_data_size = sizeof(MOVMuxContext), + .video_codec = AV_CODEC_ID_AV1, + .init = mov_init, + .write_header = mov_write_header, + .write_packet = mov_write_packet, + .write_trailer = avif_write_trailer, + .deinit = mov_free, + .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH, + .codec_tag = codec_avif_tags_list, + .priv_class = &mov_avif_muxer_class, +}; +#endif diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 2ac84ed070..55b8469f68 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -43,6 +43,7 @@ #define MODE_IPOD 0x20 #define MODE_ISM 0x40 #define MODE_F4V 0x80 +#define MODE_AVIF 0x100 typedef struct MOVIentry { uint64_t pos; @@ -242,6 +243,10 @@ typedef struct MOVMuxContext { MOVPrftBox write_prft; int empty_hdlr_name; int movie_timescale; + + int64_t avif_extent_pos; + int avif_extent_length; + int is_animated_avif; } MOVMuxContext; #define FF_MOV_FLAG_RTP_HINT (1 << 0)