From patchwork Fri Jun 21 10:43:16 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thilo Borgmann X-Patchwork-Id: 50033 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a59:ae71:0:b0:482:c625:d099 with SMTP id w17csp431871vqz; Fri, 21 Jun 2024 03:43:58 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCUbn0rhiowOR+VvYadKwkjpQLT1jTyvZFfQfFilAs8vAGkG3o86EeFv2BsTPO/l+4qP57HO5SviOpmrslt+LeSBV2HGJWobaLPM0g== X-Google-Smtp-Source: AGHT+IHPSdQR6a+Ek2RUHSAyl9LklzQ/4atts0NKxHiJO5XtvBLnt1h8pVrY1wlB3bSxImTxkqcr X-Received: by 2002:a50:d71c:0:b0:57c:d237:4fd with SMTP id 4fb4d7f45d1cf-57d20d49ee9mr2975281a12.4.1718966637891; Fri, 21 Jun 2024 03:43:57 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1718966637; cv=none; d=google.com; s=arc-20160816; b=AFN/P7xIxrEfIbRz3ch743Llzzc8hEK/Fm+KiUGaIyyK8uALLmHM92MXOtoENr+HSx MbwCMsSjLI1xwZXn3Ca/QTgdpJYdiy7eU1Xy6Bag1tPn/x9f2siXCpEwNGXuEZoozx40 rfRPXL2m7Kc//GGKrNqmbJCH19kE87eF3afVjZnjJjERkbs+Ov8cUmIPDMWxISmGEK4o LcP30fZHCUlpuwSNY5TtqP/rpg/8f94+KFxbvmzGLYrBJNFyUqtJh+8UkVL1aoOOi5w9 hZD+0E2JuZHiIVKfe7Rs6x/IIMGnoyTe02ov7zivhSKi11QCrE3W/w3ijm4ZSJy9rpn9 xZoQ== 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:from :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:delivered-to; bh=EF+RzKwjp/mQfsfUaWtHPmL7QlHn6DVOP5AjWdGDjss=; fh=uZWItAgYCSLFS5Zum7Ip+PilGyWYOeCwun+We0BTHAs=; b=Pgnfm1PJIeJUSMnn+Uh7YtXeqBhIaiomud7ccGkmKp2t6FRnQE3FehmTSCrmcOfAxN FcauBLB9D6EQ0FwZ13zaF4KnYOFbqitlpI71s+/hrGQ6ZFHW/ZF0VPCoDic5zWbfzcnG 8UNwBy6zLWnyOifYbonj+R13HNIQyk3nRXWjIuZGibq7oyfFEBU+hi5+bzzrJR2vqpGw DbOWN8smk8yaLhQMbiuJe8lCF/jMw83cYoyYQWNsIaK+Z7SI6yeUW2xi8d9mGxT82LS8 YUCDd8Bd4hnU/49QPfeHQhxM/lRdkXjQZkbrTkrVZbnpYEqhydeAiifHVFtTsskY9aHv m0Ng==; dara=google.com 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 4fb4d7f45d1cf-57d306b640esi614534a12.339.2024.06.21.03.43.57; Fri, 21 Jun 2024 03:43:57 -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 76E0268D841; Fri, 21 Jun 2024 13:43:37 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from shout02.mail.de (shout02.mail.de [62.201.172.25]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id E411168D7B9 for ; Fri, 21 Jun 2024 13:43:25 +0300 (EEST) Received: from postfix03.mail.de (postfix03.bt.mail.de [10.0.121.127]) by shout02.mail.de (Postfix) with ESMTP id 8B6DA240E31 for ; Fri, 21 Jun 2024 12:43:24 +0200 (CEST) Received: from smtp01.mail.de (smtp04.bt.mail.de [10.0.121.214]) by postfix03.mail.de (Postfix) with ESMTP id 7264480209 for ; Fri, 21 Jun 2024 12:43:24 +0200 (CEST) Received: from [127.0.0.1] (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp01.mail.de (Postfix) with ESMTPSA id 3AD13240AD8 for ; Fri, 21 Jun 2024 12:43:24 +0200 (CEST) To: ffmpeg-devel@ffmpeg.org Date: Fri, 21 Jun 2024 12:43:16 +0200 Message-Id: <20240621104323.92453-2-thilo.borgmann@mail.de> In-Reply-To: <20240621104323.92453-1-thilo.borgmann@mail.de> References: <20240621104323.92453-1-thilo.borgmann@mail.de> MIME-Version: 1.0 X-purgate: clean X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate-type: clean X-purgate-Ad: Categorized by eleven eXpurgate (R) http://www.eleven.de X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate: clean X-purgate-size: 646 X-purgate-ID: 154282::1718966604-E17EB338-FF1C6290/0/0 Subject: [FFmpeg-devel] [PATCH v13 1/8] avcodec/webp: remove unused definitions 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: , X-Patchwork-Original-From: Thilo Borgmann via ffmpeg-devel From: Thilo Borgmann Reply-To: FFmpeg development discussions and patches Cc: Thilo Borgmann Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: 49DSf9e3ATMy From: Thilo Borgmann via ffmpeg-devel --- libavcodec/webp.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/libavcodec/webp.c b/libavcodec/webp.c index 7c2a5f0111..af4ebcec27 100644 --- a/libavcodec/webp.c +++ b/libavcodec/webp.c @@ -60,8 +60,6 @@ #define VP8X_FLAG_ALPHA 0x10 #define VP8X_FLAG_ICC 0x20 -#define MAX_PALETTE_SIZE 256 -#define MAX_CACHE_BITS 11 #define NUM_CODE_LENGTH_CODES 19 #define HUFFMAN_CODES_PER_META_CODE 5 #define NUM_LITERAL_CODES 256 From patchwork Fri Jun 21 10:43:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thilo Borgmann X-Patchwork-Id: 50032 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a59:ae71:0:b0:482:c625:d099 with SMTP id w17csp431782vqz; Fri, 21 Jun 2024 03:43:48 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCUVJ/6fKiU5pQ/dJPN+qTVuwzj65PpE9J5OWc3u7w4fjc8LqIoRoHhipg/ikWvsCeiJtzBTSMrZiAv1SONHHGLXIiRx1B3NM/M2Vw== X-Google-Smtp-Source: AGHT+IGSHGEm4pJc0wYnVl1QUljZZy43Z31OyxN57GuaJoVVXsIAWX5Zky6qYerK30GO9xfd7Bak X-Received: by 2002:a05:6512:3c9a:b0:52c:8c4d:f8d6 with SMTP id 2adb3069b0e04-52ccaa53d09mr7036622e87.45.1718966628662; Fri, 21 Jun 2024 03:43:48 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1718966628; cv=none; d=google.com; s=arc-20160816; b=p0D7qfQ1dYJCssTZ0uwBYA8q75q2F3q5rgj6SoIXZuUQxBDQeKey/gBq3jj7xoBi7P TSHAnlhuoywpbaP92TQxEnUD8lqH4v9JeCxBnXdivzaga6jwgVrox46dhi5QZXRI6jQC qssgNB8hgwg6j1Unimpw4Tivf7s1/K5aVi9IUgVrJMFS91cnGjbljs8bxdi8EKEO1kJe YDujJSW+TiTegHjQOVt94BeEAGmRDUZg3ChoT9i6FYUDHBiYSZ13FgQx9nJ4rjovBXYx sxbtcKshyrWIQPJlzY9hskEekaQ7g98/S/2JaCggGdCNYAAMyoEWt7QysuploQwjdCsu jPYQ== 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:from :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:delivered-to; bh=wB8TX99lTpkvDd/Gm9GFsJhcBPmLboi7z3F8Wu0sBrc=; fh=uZWItAgYCSLFS5Zum7Ip+PilGyWYOeCwun+We0BTHAs=; b=VfpVf2h06pTIHuvveUacO7EXBkWsyLxu2uquu/VLi9aZc5ythiV167IQHzGfs6wYll fqxlnBsqd2G0TRV4+o1H5pefrnKDwe+IBW6Po6nR51yOwSMN+kYk2IonvbO6DzJpbFAc edGXxkx60uEea8+zQ5hEJFC+7l9I/RTfIO487O2uZDCE8mjNHOef1YkRUHLLddNrPvaS 2Xzaekw336A8yyfpKgauVDZdD3bc7IhkSFi4pbqRbg64SHmrC0WyY3h7HYb+OnFfv09f zTSoF600+nn/y55h/jjaoprwCD0iVH5TbTT/yFN0GHMIIiarI0dlkX9ZiEyYvwN0TUQa 77RA==; dara=google.com 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 2adb3069b0e04-52cd643bfb8si356585e87.431.2024.06.21.03.43.48; Fri, 21 Jun 2024 03:43: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 ABBD268D81C; Fri, 21 Jun 2024 13:43:35 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from shout02.mail.de (shout02.mail.de [62.201.172.25]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id AC62E68D7B9 for ; Fri, 21 Jun 2024 13:43:25 +0300 (EEST) Received: from postfix03.mail.de (postfix03.bt.mail.de [10.0.121.127]) by shout02.mail.de (Postfix) with ESMTP id 4D792240E47 for ; Fri, 21 Jun 2024 12:43:25 +0200 (CEST) Received: from smtp01.mail.de (smtp04.bt.mail.de [10.0.121.214]) by postfix03.mail.de (Postfix) with ESMTP id 3456B80209 for ; Fri, 21 Jun 2024 12:43:25 +0200 (CEST) Received: from [127.0.0.1] (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp01.mail.de (Postfix) with ESMTPSA id 80B21240A68 for ; Fri, 21 Jun 2024 12:43:24 +0200 (CEST) To: ffmpeg-devel@ffmpeg.org Date: Fri, 21 Jun 2024 12:43:17 +0200 Message-Id: <20240621104323.92453-3-thilo.borgmann@mail.de> In-Reply-To: <20240621104323.92453-1-thilo.borgmann@mail.de> References: <20240621104323.92453-1-thilo.borgmann@mail.de> MIME-Version: 1.0 X-purgate: clean X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate-type: clean X-purgate-Ad: Categorized by eleven eXpurgate (R) http://www.eleven.de X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate: clean X-purgate-size: 3847 X-purgate-ID: 154282::1718966604-E17EB338-08D6E00B/0/0 Subject: [FFmpeg-devel] [PATCH v13 2/8] avcodec/webp: separate VP8 decoding 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: , X-Patchwork-Original-From: Thilo Borgmann via ffmpeg-devel From: Thilo Borgmann Reply-To: FFmpeg development discussions and patches Cc: Thilo Borgmann Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: r/+sRnJkA2Ql From: Thilo Borgmann via ffmpeg-devel --- libavcodec/webp.c | 50 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/libavcodec/webp.c b/libavcodec/webp.c index af4ebcec27..c52b9732b4 100644 --- a/libavcodec/webp.c +++ b/libavcodec/webp.c @@ -195,6 +195,7 @@ typedef struct WebPContext { AVFrame *alpha_frame; /* AVFrame for alpha data decompressed from VP8L */ AVPacket *pkt; /* AVPacket to be passed to the underlying VP8 decoder */ AVCodecContext *avctx; /* parent AVCodecContext */ + AVCodecContext *avctx_vp8; /* wrapper context for VP8 decoder */ int initialized; /* set once the VP8 context is initialized */ int has_alpha; /* has a separate alpha chunk */ enum AlphaCompression alpha_compression; /* compression type for alpha chunk */ @@ -1299,12 +1300,13 @@ static int vp8_lossy_decode_frame(AVCodecContext *avctx, AVFrame *p, int ret; if (!s->initialized) { - ff_vp8_decode_init(avctx); + VP8Context *s_vp8 = s->avctx_vp8->priv_data; + s_vp8->actually_webp = 1; s->initialized = 1; - s->v.actually_webp = 1; } avctx->pix_fmt = s->has_alpha ? AV_PIX_FMT_YUVA420P : AV_PIX_FMT_YUV420P; s->lossless = 0; + s->avctx_vp8->pix_fmt = avctx->pix_fmt; if (data_size > INT_MAX) { av_log(avctx, AV_LOG_ERROR, "unsupported chunk size\n"); @@ -1315,14 +1317,32 @@ static int vp8_lossy_decode_frame(AVCodecContext *avctx, AVFrame *p, s->pkt->data = data_start; s->pkt->size = data_size; - ret = ff_vp8_decode_frame(avctx, p, got_frame, s->pkt); - if (ret < 0) + ret = avcodec_send_packet(s->avctx_vp8, s->pkt); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Error submitting a packet for decoding\n"); return ret; + } - if (!*got_frame) + ret = avcodec_receive_frame(s->avctx_vp8, p); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "VP8 decoding error: %s.\n", av_err2str(ret)); return AVERROR_INVALIDDATA; + } + + ret = ff_decode_frame_props(avctx, p); + if (ret < 0) { + return ret; + } + + if (!p->private_ref) { + ret = ff_attach_decode_data(p); + if (ret < 0) { + return ret; + } + } - update_canvas_size(avctx, avctx->width, avctx->height); + *got_frame = 1; + update_canvas_size(avctx, s->avctx_vp8->width, s->avctx_vp8->height); if (s->has_alpha) { ret = vp8_lossy_decode_alpha(avctx, p, s->alpha_data, @@ -1539,11 +1559,28 @@ exif_end: static av_cold int webp_decode_init(AVCodecContext *avctx) { WebPContext *s = avctx->priv_data; + int ret; + const AVCodec *codec; s->pkt = av_packet_alloc(); if (!s->pkt) return AVERROR(ENOMEM); + + /* Prepare everything needed for VP8 decoding */ + codec = avcodec_find_decoder(AV_CODEC_ID_VP8); + if (!codec) + return AVERROR_BUG; + s->avctx_vp8 = avcodec_alloc_context3(codec); + if (!s->avctx_vp8) + return AVERROR(ENOMEM); + s->avctx_vp8->flags = avctx->flags; + s->avctx_vp8->flags2 = avctx->flags2; + s->avctx_vp8->pix_fmt = avctx->pix_fmt; + ret = avcodec_open2(s->avctx_vp8, codec, NULL); + if (ret < 0) { + return ret; + } return 0; } @@ -1552,6 +1589,7 @@ static av_cold int webp_decode_close(AVCodecContext *avctx) WebPContext *s = avctx->priv_data; av_packet_free(&s->pkt); + avcodec_free_context(&s->avctx_vp8); if (s->initialized) return ff_vp8_decode_free(avctx); From patchwork Fri Jun 21 10:43:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thilo Borgmann X-Patchwork-Id: 50036 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a59:ae71:0:b0:482:c625:d099 with SMTP id w17csp432113vqz; Fri, 21 Jun 2024 03:44:28 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCW4Migk8Ma+G/hFZg2nVfSCFIJkf7r10sRApvLgalV4G7wVF91q+3m5+mRyYYJ/FGzcwEtAo6xzQ0K97XGc9D+gG0F1q+isXlDbQQ== X-Google-Smtp-Source: AGHT+IE6IElESc66Tpq2PioEzew6TnOVkBbbxsJCu4RuU+wMSW/wy+M2owBO8KQ249zcvJF1k+Jx X-Received: by 2002:ac2:4ecb:0:b0:52c:9421:2739 with SMTP id 2adb3069b0e04-52ccaa2a9c0mr4147752e87.9.1718966668079; Fri, 21 Jun 2024 03:44:28 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1718966668; cv=none; d=google.com; s=arc-20160816; b=XQr6j7iw8KF5VVen8ysAb0Co0xQem5MsfNzp+VGejfkQlbB2/kciavo7WC7Bwzf0Du 7dLCDk8c8VTXlFV2XuHYaTSPyE9FFJiHkEgXCEQ9g9XN5f9mVp7xEkFMsrGhZhm/PtDx xaSERVPJEMEe75U4XZAeNrzR/O8epnsm4QVCCYwiYX4SbFn8nXWRpZn6PyHiBnFjb3sm T/t/vd/i3vVn8hBrtdjnzCg57W9MJZBzMyVXBXNufQsyefIera6XXyA5jYOtkkkOBN/H INQlDif/bYYX85f7aKf6YojzwT6eakSV2tvLa9xtQEBSKYOMVBkfFJZZpwgIzsT9oK+o Unrw== 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:from :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:delivered-to; bh=PA2ozTsGrV9YIqumCVFirc7NaHIRr91l3xGkhyCpxT8=; fh=uZWItAgYCSLFS5Zum7Ip+PilGyWYOeCwun+We0BTHAs=; b=ZWUaNTigpxfPqQFEM2CVqsrhkNlcX57JTmQW3GMa8QFJdSuXqV/rr/wTeGqnCxtEG+ 3HOBKJeYazLP0QeuRsTbrEkvsOepQYnlJ7eTSvO9ilCTHkvtO3DrYq2toqEjKd6TlU3x M++Osi5DI3vAfkQj99ZMfNO0oY1efZSt4iW3Aiwm56TEciXq+XOV/tsSedpmtfGqjJET v8Yb/G1dlSlLjFNClS9WVZ0QxlR9doGPV2SBiRrh1RM1f1Tw2lzvZcewG27Xqh3yth3K /uZKQQZqIvgbtjhvC91r/CYLf1VHd/xUT8dQclfz123sdNzPGDA1reSp6uxtiSO9duIl VGOA==; dara=google.com 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 2adb3069b0e04-52cd6453ecfsi357726e87.579.2024.06.21.03.44.27; Fri, 21 Jun 2024 03:44: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; 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 A231268D892; Fri, 21 Jun 2024 13:43:42 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from shout02.mail.de (shout02.mail.de [62.201.172.25]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 1661B68D81A for ; Fri, 21 Jun 2024 13:43:31 +0300 (EEST) Received: from postfix03.mail.de (postfix03.bt.mail.de [10.0.121.127]) by shout02.mail.de (Postfix) with ESMTP id A80DE240E82 for ; Fri, 21 Jun 2024 12:43:25 +0200 (CEST) Received: from smtp01.mail.de (smtp04.bt.mail.de [10.0.121.214]) by postfix03.mail.de (Postfix) with ESMTP id 8FAA580209 for ; Fri, 21 Jun 2024 12:43:25 +0200 (CEST) Received: from [127.0.0.1] (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp01.mail.de (Postfix) with ESMTPSA id 43761240AD7 for ; Fri, 21 Jun 2024 12:43:25 +0200 (CEST) To: ffmpeg-devel@ffmpeg.org Date: Fri, 21 Jun 2024 12:43:18 +0200 Message-Id: <20240621104323.92453-4-thilo.borgmann@mail.de> In-Reply-To: <20240621104323.92453-1-thilo.borgmann@mail.de> References: <20240621104323.92453-1-thilo.borgmann@mail.de> MIME-Version: 1.0 X-purgate: clean X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate-type: clean X-purgate-Ad: Categorized by eleven eXpurgate (R) http://www.eleven.de X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate: clean X-purgate-size: 13444 X-purgate-ID: 154282::1718966605-E17EB338-BFA9AD90/0/0 Subject: [FFmpeg-devel] [PATCH v13 3/8] avcodec/bsf: Add awebp2webp bitstream filter 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: , X-Patchwork-Original-From: Thilo Borgmann via ffmpeg-devel From: Thilo Borgmann Reply-To: FFmpeg development discussions and patches Cc: Thilo Borgmann Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: /q06qs3BJARI From: Thilo Borgmann via ffmpeg-devel Splits a packet containing a webp animations into one non-compliant packet per frame of the animation. Skips RIFF and WEBP chunks for those packets except for the first. Copyies ICC, EXIF and XMP chunks first into each of the packets except for the first. --- configure | 1 + libavcodec/bitstream_filters.c | 1 + libavcodec/bsf/Makefile | 1 + libavcodec/bsf/awebp2webp.c | 353 +++++++++++++++++++++++++++++++++ 4 files changed, 356 insertions(+) create mode 100644 libavcodec/bsf/awebp2webp.c diff --git a/configure b/configure index 3bca638459..1b6e56496f 100755 --- a/configure +++ b/configure @@ -3437,6 +3437,7 @@ aac_adtstoasc_bsf_select="adts_header mpeg4audio" av1_frame_merge_bsf_select="cbs_av1" av1_frame_split_bsf_select="cbs_av1" av1_metadata_bsf_select="cbs_av1" +awebp2webp_bsf_select="" dts2pts_bsf_select="cbs_h264 h264parse" eac3_core_bsf_select="ac3_parser" evc_frame_merge_bsf_select="evcparse" diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c index 138246c50e..1f6471f4f3 100644 --- a/libavcodec/bitstream_filters.c +++ b/libavcodec/bitstream_filters.c @@ -28,6 +28,7 @@ extern const FFBitStreamFilter ff_aac_adtstoasc_bsf; extern const FFBitStreamFilter ff_av1_frame_merge_bsf; extern const FFBitStreamFilter ff_av1_frame_split_bsf; extern const FFBitStreamFilter ff_av1_metadata_bsf; +extern const FFBitStreamFilter ff_awebp2webp_bsf; extern const FFBitStreamFilter ff_chomp_bsf; extern const FFBitStreamFilter ff_dump_extradata_bsf; extern const FFBitStreamFilter ff_dca_core_bsf; diff --git a/libavcodec/bsf/Makefile b/libavcodec/bsf/Makefile index fb70ad0c21..48c67dd210 100644 --- a/libavcodec/bsf/Makefile +++ b/libavcodec/bsf/Makefile @@ -5,6 +5,7 @@ OBJS-$(CONFIG_AAC_ADTSTOASC_BSF) += bsf/aac_adtstoasc.o OBJS-$(CONFIG_AV1_FRAME_MERGE_BSF) += bsf/av1_frame_merge.o OBJS-$(CONFIG_AV1_FRAME_SPLIT_BSF) += bsf/av1_frame_split.o OBJS-$(CONFIG_AV1_METADATA_BSF) += bsf/av1_metadata.o +OBJS-$(CONFIG_AWEBP2WEBP_BSF) += bsf/awebp2webp.o OBJS-$(CONFIG_CHOMP_BSF) += bsf/chomp.o OBJS-$(CONFIG_DCA_CORE_BSF) += bsf/dca_core.o OBJS-$(CONFIG_DTS2PTS_BSF) += bsf/dts2pts.o diff --git a/libavcodec/bsf/awebp2webp.c b/libavcodec/bsf/awebp2webp.c new file mode 100644 index 0000000000..69a0156167 --- /dev/null +++ b/libavcodec/bsf/awebp2webp.c @@ -0,0 +1,353 @@ +/* + * Animated WebP into non-compliant WebP bitstream filter + * Copyright (c) 2024 Thilo Borgmann + * + * 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 + */ + +/** + * @file + * Animated WebP into non-compliant WebP bitstream filter + * Splits a packet containing a webp animations into + * one non-compliant packet per frame of the animation. + * Skips RIFF and WEBP chunks for those packets except + * for the first. Copyies ICC, EXIF and XMP chunks first + * into each of the packets except for the first. + * @author Thilo Borgmann + */ + +#include +#include + +#include "codec_id.h" +#include "bytestream.h" +#include "libavutil/error.h" +#include "libavutil/mem.h" + +#include "bsf.h" +#include "bsf_internal.h" +#include "packet.h" + +#define VP8X_FLAG_ANIMATION 0x02 +#define VP8X_FLAG_XMP_METADATA 0x04 +#define VP8X_FLAG_EXIF_METADATA 0x08 +#define VP8X_FLAG_ALPHA 0x10 +#define VP8X_FLAG_ICC 0x20 + +typedef struct WEBPBSFContext { + const AVClass *class; + GetByteContext gb; + + AVPacket *last_pkt; + uint8_t *last_iccp; + uint8_t *last_exif; + uint8_t *last_xmp; + + int iccp_size; + int exif_size; + int xmp_size; + + int add_iccp; + int add_exif; + int add_xmp; + + uint64_t last_pts; +} WEBPBSFContext; + +static int save_chunk(WEBPBSFContext *ctx, uint8_t **buf, int *buf_size, uint32_t chunk_size) +{ + if (*buf || !buf_size || !chunk_size) + return 0; + + *buf = av_malloc(chunk_size + 8); + if (!*buf) + return AVERROR(ENOMEM); + + *buf_size = chunk_size + 8; + + bytestream2_seek(&ctx->gb, -8, SEEK_CUR); + bytestream2_get_buffer(&ctx->gb, *buf, chunk_size + 8); + + return 0; +} + +static int awebp2webp_filter(AVBSFContext *ctx, AVPacket *out) +{ + WEBPBSFContext *s = ctx->priv_data; + AVPacket *in; + uint32_t chunk_type; + uint32_t chunk_size; + int64_t packet_start; + int64_t packet_end; + int64_t out_off; + int ret = 0; + int is_frame = 0; + int key_frame = 0; + int delay = 0; + int out_size = 0; + int has_anim = 0; + + // initialize for new packet + if (!bytestream2_size(&s->gb)) { + if (s->last_pkt) + av_packet_unref(s->last_pkt); + + ret = ff_bsf_get_packet(ctx, &s->last_pkt); + if (ret < 0) + goto fail; + + bytestream2_init(&s->gb, s->last_pkt->data, s->last_pkt->size); + + av_freep(&s->last_iccp); + av_freep(&s->last_exif); + av_freep(&s->last_xmp); + + // read packet scanning for metadata && animation + while (bytestream2_get_bytes_left(&s->gb) > 0) { + chunk_type = bytestream2_get_le32(&s->gb); + chunk_size = bytestream2_get_le32(&s->gb); + + if (chunk_size == UINT32_MAX) + return AVERROR_INVALIDDATA; + chunk_size += chunk_size & 1; + + if (!bytestream2_get_bytes_left(&s->gb) || + bytestream2_get_bytes_left(&s->gb) < chunk_size) + break; + + if (chunk_type == MKTAG('R', 'I', 'F', 'F') && chunk_size > 4) { + chunk_size = 4; + } + + switch (chunk_type) { + case MKTAG('I', 'C', 'C', 'P'): + if (!s->last_iccp) { + ret = save_chunk(s, &s->last_iccp, &s->iccp_size, chunk_size); + if (ret < 0) + goto fail; + } else { + bytestream2_skip(&s->gb, chunk_size); + } + break; + + case MKTAG('E', 'X', 'I', 'F'): + if (!s->last_exif) { + ret = save_chunk(s, &s->last_exif, &s->exif_size, chunk_size); + if (ret < 0) + goto fail; + } else { + bytestream2_skip(&s->gb, chunk_size); + } + break; + + case MKTAG('X', 'M', 'P', ' '): + if (!s->last_xmp) { + ret = save_chunk(s, &s->last_xmp, &s->xmp_size, chunk_size); + if (ret < 0) + goto fail; + } else { + bytestream2_skip(&s->gb, chunk_size); + } + break; + + case MKTAG('A', 'N', 'M', 'F'): + has_anim = 1; + bytestream2_skip(&s->gb, chunk_size); + break; + + default: + bytestream2_skip(&s->gb, chunk_size); + break; + } + } + + // if no animation is found, pass-through the packet + if (!has_anim) { + av_packet_move_ref(out, s->last_pkt); + return 0; + } + + // reset bytestream to beginning of packet + bytestream2_init(&s->gb, s->last_pkt->data, s->last_pkt->size); + } + + // packet read completely, reset and ask for next packet + if (!bytestream2_get_bytes_left(&s->gb)) { + if (s->last_pkt) + av_packet_free(&s->last_pkt); + // reset to empty buffer for reinit with next real packet + bytestream2_init(&s->gb, NULL, 0); + return AVERROR(EAGAIN); + } + + // start reading from packet until sub packet ready + packet_start = bytestream2_tell(&s->gb); + s->add_iccp = 1; + s->add_exif = 1; + s->add_xmp = 1; + + while (bytestream2_get_bytes_left(&s->gb) > 0) { + chunk_type = bytestream2_get_le32(&s->gb); + chunk_size = bytestream2_get_le32(&s->gb); + + if (chunk_size == UINT32_MAX) + return AVERROR_INVALIDDATA; + chunk_size += chunk_size & 1; + + if (!bytestream2_get_bytes_left(&s->gb) || + bytestream2_get_bytes_left(&s->gb) < chunk_size) + break; + + if (chunk_type == MKTAG('R', 'I', 'F', 'F') && chunk_size > 4) { + chunk_size = 4; + key_frame = 1; + } + + switch (chunk_type) { + case MKTAG('I', 'C', 'C', 'P'): + s->add_iccp = 0; + bytestream2_skip(&s->gb, chunk_size); + break; + + case MKTAG('E', 'X', 'I', 'F'): + s->add_exif = 0; + bytestream2_skip(&s->gb, chunk_size); + break; + + case MKTAG('X', 'M', 'P', ' '): + s->add_xmp = 0; + bytestream2_skip(&s->gb, chunk_size); + break; + + case MKTAG('V', 'P', '8', ' '): + if (is_frame) { + bytestream2_seek(&s->gb, -8, SEEK_CUR); + goto flush; + } + bytestream2_skip(&s->gb, chunk_size); + is_frame = 1; + break; + + case MKTAG('V', 'P', '8', 'L'): + if (is_frame) { + bytestream2_seek(&s->gb, -8, SEEK_CUR); + goto flush; + } + bytestream2_skip(&s->gb, chunk_size); + is_frame = 1; + break; + + case MKTAG('A', 'N', 'M', 'F'): + if (is_frame) { + bytestream2_seek(&s->gb, -8, SEEK_CUR); + goto flush; + } + bytestream2_skip(&s->gb, 12); + delay = bytestream2_get_le24(&s->gb); + if (!delay) + delay = s->last_pkt->duration; + bytestream2_skip(&s->gb, 1); + break; + + default: + bytestream2_skip(&s->gb, chunk_size); + break; + } + + packet_end = bytestream2_tell(&s->gb); + } + +flush: + // generate packet from data read so far + out_size = packet_end - packet_start; + out_off = 0; + + if (s->add_iccp && s->last_iccp) + out_size += s->iccp_size; + if (s->add_exif && s->last_exif) + out_size += s->exif_size; + if (s->add_xmp && s->last_xmp) + out_size += s->xmp_size; + + ret = av_new_packet(out, out_size); + if (ret < 0) + goto fail; + + // copy metadata + if (s->add_iccp && s->last_iccp) { + memcpy(out->data + out_off, s->last_iccp, s->iccp_size); + out_off += s->iccp_size; + } + if (s->add_exif && s->last_exif) { + memcpy(out->data + out_off, s->last_exif, s->exif_size); + out_off += s->exif_size; + } + if (s->add_xmp && s->last_xmp) { + memcpy(out->data + out_off, s->last_xmp, s->xmp_size); + out_off += s->xmp_size; + } + + // copy frame data + memcpy(out->data + out_off, s->last_pkt->data + packet_start, packet_end - packet_start); + + if (key_frame) + out->flags |= AV_PKT_FLAG_KEY; + else + out->flags &= ~AV_PKT_FLAG_KEY; + + out->pts = s->last_pts; + out->dts = out->pts; + out->pos = packet_start; + out->duration = delay; + out->stream_index = s->last_pkt->stream_index; + out->time_base = s->last_pkt->time_base; + + s->last_pts += (delay > 0) ? delay : 1; + + key_frame = 0; + + return 0; + +fail: + if (ret < 0) { + av_packet_unref(out); + return ret; + } + av_packet_free(&in); + + return ret; +} + +static void awebp2webp_close(AVBSFContext *ctx) +{ + WEBPBSFContext *s = ctx->priv_data; + av_freep(&s->last_iccp); + av_freep(&s->last_exif); + av_freep(&s->last_xmp); +} + +static const enum AVCodecID codec_ids[] = { + AV_CODEC_ID_WEBP, AV_CODEC_ID_NONE, +}; + +const FFBitStreamFilter ff_awebp2webp_bsf = { + .p.name = "awebp2webp", + .p.codec_ids = codec_ids, + .priv_data_size = sizeof(WEBPBSFContext), + .filter = awebp2webp_filter, + .close = awebp2webp_close, +}; From patchwork Fri Jun 21 10:43:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thilo Borgmann X-Patchwork-Id: 50034 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a59:ae71:0:b0:482:c625:d099 with SMTP id w17csp431963vqz; Fri, 21 Jun 2024 03:44:08 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCUVmPF0j0jRhgvfdZSvjqbplvZt69JXsB80eo97nsjWUNE3dqNLJhhEdC4zwFP8Tu6gctS/J2vKngExvTeYU71uHAHh50P9U5b9lQ== X-Google-Smtp-Source: AGHT+IGveKnjLRC8w2EYbotgLrF0o0FzvpZt7h4xtmN9p1HuG1rRkVbAZ7QQG/cuOUSHWdgyFBkH X-Received: by 2002:a05:651c:b26:b0:2ec:4086:ea6d with SMTP id 38308e7fff4ca-2ec4086ec1emr61100671fa.4.1718966647799; Fri, 21 Jun 2024 03:44:07 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1718966647; cv=none; d=google.com; s=arc-20160816; b=SQCJ059kb8a/3ORiY6LvpOnJqqb4oklRBw+HnDB+wWcXMX8Uoue86/zADUdMok6Pdi BvUOoMaC7jKuMSlEp1iGHAecp+lc2CfAsvc/5rbI9ZEI7Bo/XZo1tCUjl+1mazRMKVOW 7WItOZ7cf8d+U7el+ASU24FQzkkFRewdIAYVqgoflCrtVtC4H+bSjJ8cdIK7/ploN61g jsVLPADtVs63qGZsutWjbhnEIHAkgdjIZz+WRC1sUrYpqLsX0A8Me2eu0ap1tGVdEy59 sd6p7WqWEYWGlYiS1qDDQYbaP90wznK2/JpFt7coZCaZOFpRDFSRmTKl/FonvKXHbk7n ogyw== 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:from :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:delivered-to; bh=ZUFWaIpaVjA25Edfl7ZUshM04Go7YvZn7gsArhHdM3c=; fh=uZWItAgYCSLFS5Zum7Ip+PilGyWYOeCwun+We0BTHAs=; b=AeSxSE71tEA86Er2vmNOpPev4het+4NV4KM43ExZhceSyVdSf9duSeFVr2zAtxGKCp cmH+wWXoWGSsL6ujiMsuU1ipqASsVjW9tiaSTzzVgd2YUOGdKJhqYI2Jx5C+oLXWPoVL ybr4TAGF8btb2rQEgKBSIHPBQ/IcuJMFECyae4c2NxCJnTwhXPKJg1NN4+gMj9OFO59p lOBTW8hRLvaHfAeEeh/CzFj98GuqwCN0o89JEa+NFWLc9hb949uiVrMcgeyuSFxqmXns 18xm3ZYCmIsN9WU6QDwAZlWD7Q+bjpXciqhC/Ln9cogZk6f+KZVdEGMvlLfYJZ+CUUWD RvMg==; dara=google.com 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 4fb4d7f45d1cf-57d30af44dbsi600945a12.526.2024.06.21.03.44.07; Fri, 21 Jun 2024 03:44: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; 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 8FEAE68D834; Fri, 21 Jun 2024 13:43:39 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from shout01.mail.de (shout01.mail.de [62.201.172.24]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 64C1B68D7B9 for ; Fri, 21 Jun 2024 13:43:26 +0300 (EEST) Received: from postfix03.mail.de (postfix03.bt.mail.de [10.0.121.127]) by shout01.mail.de (Postfix) with ESMTP id 0B77C240E9D for ; Fri, 21 Jun 2024 12:43:26 +0200 (CEST) Received: from smtp01.mail.de (smtp04.bt.mail.de [10.0.121.214]) by postfix03.mail.de (Postfix) with ESMTP id E669D802E0 for ; Fri, 21 Jun 2024 12:43:25 +0200 (CEST) Received: from [127.0.0.1] (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp01.mail.de (Postfix) with ESMTPSA id A34EB240A68 for ; Fri, 21 Jun 2024 12:43:25 +0200 (CEST) To: ffmpeg-devel@ffmpeg.org Date: Fri, 21 Jun 2024 12:43:19 +0200 Message-Id: <20240621104323.92453-5-thilo.borgmann@mail.de> In-Reply-To: <20240621104323.92453-1-thilo.borgmann@mail.de> References: <20240621104323.92453-1-thilo.borgmann@mail.de> MIME-Version: 1.0 X-purgate: clean X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate-type: clean X-purgate-Ad: Categorized by eleven eXpurgate (R) http://www.eleven.de X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate: clean X-purgate-size: 49227 X-purgate-ID: 154282::1718966605-E17EB338-0B3E84B6/0/0 Subject: [FFmpeg-devel] [PATCH v13 4/8] libavcodec/webp: add support for animated WebP 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: , X-Patchwork-Original-From: Thilo Borgmann via ffmpeg-devel From: Thilo Borgmann Reply-To: FFmpeg development discussions and patches Cc: Thilo Borgmann Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: PsDPTbCQQiBJ From: Josef Zlomek Fixes: 4907 Adds support for decoding of animated WebP. The WebP decoder adds the animation related features according to the specs: https://developers.google.com/speed/webp/docs/riff_container#animation The frames of the animation may be smaller than the image canvas. Therefore, the frame is decoded to a temporary frame, then it is blended into the canvas, the canvas is copied to the output frame, and finally the frame is disposed from the canvas. The output to AV_PIX_FMT_YUVA420P/AV_PIX_FMT_YUV420P is still supported. The background color is specified only as BGRA in the WebP file so it is converted to YUVA if YUV formats are output. Signed-off-by: Josef Zlomek --- Changelog | 1 + libavcodec/codec_desc.c | 3 +- libavcodec/webp.c | 897 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 840 insertions(+), 61 deletions(-) diff --git a/Changelog b/Changelog index 06c00e981a..de6eedfd68 100644 --- a/Changelog +++ b/Changelog @@ -101,6 +101,7 @@ version 6.1: - ffprobe XML output schema changed to account for multiple variable-fields elements within the same parent element - ffprobe -output_format option added as an alias of -of +- animated WebP decoder version 6.0: diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c index a28ef68061..ec1dcc327a 100644 --- a/libavcodec/codec_desc.c +++ b/libavcodec/codec_desc.c @@ -1259,8 +1259,7 @@ static const AVCodecDescriptor codec_descriptors[] = { .type = AVMEDIA_TYPE_VIDEO, .name = "webp", .long_name = NULL_IF_CONFIG_SMALL("WebP"), - .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY | - AV_CODEC_PROP_LOSSLESS, + .props = AV_CODEC_PROP_LOSSY | AV_CODEC_PROP_LOSSLESS, .mime_types= MT("image/webp"), }, { diff --git a/libavcodec/webp.c b/libavcodec/webp.c index c52b9732b4..146d5cb393 100644 --- a/libavcodec/webp.c +++ b/libavcodec/webp.c @@ -35,13 +35,17 @@ * Exif metadata * ICC profile * + * @author Josef Zlomek, Pexeso Inc. + * Animation + * * Unimplemented: - * - Animation * - XMP metadata */ +#include "libavutil/common.h" #include "libavutil/imgutils.h" #include "libavutil/mem.h" +#include "libavutil/colorspace.h" #define BITSTREAM_READER_LE #include "avcodec.h" @@ -50,6 +54,7 @@ #include "decode.h" #include "exif.h" #include "get_bits.h" +#include "progressframe.h" #include "thread.h" #include "tiff_common.h" #include "vp8.h" @@ -68,6 +73,14 @@ #define NUM_SHORT_DISTANCES 120 #define MAX_HUFFMAN_CODE_LENGTH 15 +#define ANMF_DISPOSAL_METHOD 0x01 +#define ANMF_DISPOSAL_METHOD_UNCHANGED 0x00 +#define ANMF_DISPOSAL_METHOD_BACKGROUND 0x01 + +#define ANMF_BLENDING_METHOD 0x02 +#define ANMF_BLENDING_METHOD_ALPHA 0x00 +#define ANMF_BLENDING_METHOD_OVERWRITE 0x02 + static const uint16_t alphabet_sizes[HUFFMAN_CODES_PER_META_CODE] = { NUM_LITERAL_CODES + NUM_LENGTH_CODES, NUM_LITERAL_CODES, NUM_LITERAL_CODES, NUM_LITERAL_CODES, @@ -192,6 +205,8 @@ typedef struct ImageContext { typedef struct WebPContext { VP8Context v; /* VP8 Context used for lossy decoding */ GetBitContext gb; /* bitstream reader for main image chunk */ + ProgressFrame canvas_frame; /* ThreadFrame for canvas */ + AVFrame *frame; /* AVFrame for decoded frame */ AVFrame *alpha_frame; /* AVFrame for alpha data decompressed from VP8L */ AVPacket *pkt; /* AVPacket to be passed to the underlying VP8 decoder */ AVCodecContext *avctx; /* parent AVCodecContext */ @@ -204,9 +219,25 @@ typedef struct WebPContext { int alpha_data_size; /* alpha chunk data size */ int has_exif; /* set after an EXIF chunk has been processed */ int has_iccp; /* set after an ICCP chunk has been processed */ + int duration; /* frame duration in an animation */ int width; /* image width */ int height; /* image height */ - int lossless; /* indicates lossless or lossy */ + int vp8x_flags; /* global flags from VP8X chunk */ + int canvas_width; /* canvas width */ + int canvas_height; /* canvas height */ + int anmf_flags; /* frame flags from ANMF chunk */ + int pos_x; /* frame position X */ + int pos_y; /* frame position Y */ + int prev_anmf_flags; /* previous frame flags from ANMF chunk */ + int prev_width; /* previous frame width */ + int prev_height; /* previous frame height */ + int prev_pos_x; /* previous frame position X */ + int prev_pos_y; /* previous frame position Y */ + int await_progress; /* value of progress to wait for */ + uint8_t background_argb[4]; /* background color in ARGB format */ + uint8_t background_yuva[4]; /* background color in YUVA format */ + const uint8_t *background_data[4]; /* "planes" for background color in YUVA format */ + uint8_t transparent_yuva[4]; /* transparent black in YUVA format */ int nb_transforms; /* number of transforms */ enum TransformType transforms[4]; /* transformations used in the image, in order */ @@ -1091,7 +1122,6 @@ static int vp8_lossless_decode_frame(AVCodecContext *avctx, AVFrame *p, int w, h, ret, i, used; if (!is_alpha_chunk) { - s->lossless = 1; avctx->pix_fmt = AV_PIX_FMT_ARGB; } @@ -1305,9 +1335,10 @@ static int vp8_lossy_decode_frame(AVCodecContext *avctx, AVFrame *p, s->initialized = 1; } avctx->pix_fmt = s->has_alpha ? AV_PIX_FMT_YUVA420P : AV_PIX_FMT_YUV420P; - s->lossless = 0; s->avctx_vp8->pix_fmt = avctx->pix_fmt; + ff_thread_finish_setup(s->avctx); + if (data_size > INT_MAX) { av_log(avctx, AV_LOG_ERROR, "unsupported chunk size\n"); return AVERROR_PATCHWELCOME; @@ -1342,7 +1373,6 @@ static int vp8_lossy_decode_frame(AVCodecContext *avctx, AVFrame *p, } *got_frame = 1; - update_canvas_size(avctx, s->avctx_vp8->width, s->avctx_vp8->height); if (s->has_alpha) { ret = vp8_lossy_decode_alpha(avctx, p, s->alpha_data, @@ -1353,40 +1383,21 @@ static int vp8_lossy_decode_frame(AVCodecContext *avctx, AVFrame *p, return ret; } -static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, - int *got_frame, AVPacket *avpkt) +int init_canvas_frame(WebPContext *s, int format, int key_frame); + +static int webp_decode_frame_common(AVCodecContext *avctx, uint8_t *data, int size, + int *got_frame, int key_frame, AVFrame *p) { WebPContext *s = avctx->priv_data; GetByteContext gb; int ret; uint32_t chunk_type, chunk_size; - int vp8x_flags = 0; - - s->avctx = avctx; - s->width = 0; - s->height = 0; - *got_frame = 0; - s->has_alpha = 0; - s->has_exif = 0; - s->has_iccp = 0; - bytestream2_init(&gb, avpkt->data, avpkt->size); - - if (bytestream2_get_bytes_left(&gb) < 12) - return AVERROR_INVALIDDATA; - - if (bytestream2_get_le32(&gb) != MKTAG('R', 'I', 'F', 'F')) { - av_log(avctx, AV_LOG_ERROR, "missing RIFF tag\n"); - return AVERROR_INVALIDDATA; - } - chunk_size = bytestream2_get_le32(&gb); - if (bytestream2_get_bytes_left(&gb) < chunk_size) - return AVERROR_INVALIDDATA; + bytestream2_init(&gb, data, size); - if (bytestream2_get_le32(&gb) != MKTAG('W', 'E', 'B', 'P')) { - av_log(avctx, AV_LOG_ERROR, "missing WEBP tag\n"); - return AVERROR_INVALIDDATA; - } + // reset metadata bit for each packet + s->has_exif = 0; + s->has_iccp = 0; while (bytestream2_get_bytes_left(&gb) > 8) { char chunk_str[5] = { 0 }; @@ -1397,6 +1408,10 @@ static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, return AVERROR_INVALIDDATA; chunk_size += chunk_size & 1; + // we need to dive into RIFF chunk + if (chunk_type == MKTAG('R', 'I', 'F', 'F')) + chunk_size = 4; + if (bytestream2_get_bytes_left(&gb) < chunk_size) { /* we seem to be running out of data, but it could also be that the bitstream has trailing junk leading to bogus chunk_size. */ @@ -1404,10 +1419,26 @@ static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, } switch (chunk_type) { + case MKTAG('R', 'I', 'F', 'F'): + if (bytestream2_get_le32(&gb) != MKTAG('W', 'E', 'B', 'P')) { + av_log(avctx, AV_LOG_ERROR, "missing WEBP tag\n"); + return AVERROR_INVALIDDATA; + } + s->vp8x_flags = 0; + s->canvas_width = 0; + s->canvas_height = 0; + s->has_exif = 0; + s->has_iccp = 0; + ff_progress_frame_unref(&s->canvas_frame); + break; case MKTAG('V', 'P', '8', ' '): if (!*got_frame) { - ret = vp8_lossy_decode_frame(avctx, p, got_frame, - avpkt->data + bytestream2_tell(&gb), + ret = init_canvas_frame(s, AV_PIX_FMT_YUVA420P, key_frame); + if (ret < 0) + return ret; + + ret = vp8_lossy_decode_frame(avctx, s->frame, got_frame, + data + bytestream2_tell(&gb), chunk_size); if (ret < 0) return ret; @@ -1416,24 +1447,32 @@ static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, break; case MKTAG('V', 'P', '8', 'L'): if (!*got_frame) { - ret = vp8_lossless_decode_frame(avctx, p, got_frame, - avpkt->data + bytestream2_tell(&gb), + ret = init_canvas_frame(s, AV_PIX_FMT_ARGB, key_frame); + if (ret < 0) + return ret; + + ret = vp8_lossless_decode_frame(avctx, s->frame, got_frame, + data + bytestream2_tell(&gb), chunk_size, 0); if (ret < 0) return ret; + avctx->properties |= FF_CODEC_PROPERTY_LOSSLESS; + ff_thread_finish_setup(s->avctx); } bytestream2_skip(&gb, chunk_size); break; case MKTAG('V', 'P', '8', 'X'): - if (s->width || s->height || *got_frame) { + if (s->canvas_width || s->canvas_height || *got_frame) { av_log(avctx, AV_LOG_ERROR, "Canvas dimensions are already set\n"); return AVERROR_INVALIDDATA; } - vp8x_flags = bytestream2_get_byte(&gb); + s->vp8x_flags = bytestream2_get_byte(&gb); bytestream2_skip(&gb, 3); s->width = bytestream2_get_le24(&gb) + 1; s->height = bytestream2_get_le24(&gb) + 1; + s->canvas_width = s->width; + s->canvas_height = s->height; ret = av_image_check_size(s->width, s->height, 0, avctx); if (ret < 0) return ret; @@ -1441,7 +1480,7 @@ static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, case MKTAG('A', 'L', 'P', 'H'): { int alpha_header, filter_m, compression; - if (!(vp8x_flags & VP8X_FLAG_ALPHA)) { + if (!(s->vp8x_flags & VP8X_FLAG_ALPHA)) { av_log(avctx, AV_LOG_WARNING, "ALPHA chunk present, but alpha bit not set in the " "VP8X header\n"); @@ -1450,8 +1489,9 @@ static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, av_log(avctx, AV_LOG_ERROR, "invalid ALPHA chunk size\n"); return AVERROR_INVALIDDATA; } + alpha_header = bytestream2_get_byte(&gb); - s->alpha_data = avpkt->data + bytestream2_tell(&gb); + s->alpha_data = data + bytestream2_tell(&gb); s->alpha_data_size = chunk_size - 1; bytestream2_skip(&gb, s->alpha_data_size); @@ -1471,37 +1511,33 @@ static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, } case MKTAG('E', 'X', 'I', 'F'): { int le, ifd_offset, exif_offset = bytestream2_tell(&gb); - AVDictionary *exif_metadata = NULL; + AVDictionary **exif_metadata = NULL; GetByteContext exif_gb; if (s->has_exif) { av_log(avctx, AV_LOG_VERBOSE, "Ignoring extra EXIF chunk\n"); goto exif_end; } - if (!(vp8x_flags & VP8X_FLAG_EXIF_METADATA)) + if (!(s->vp8x_flags & VP8X_FLAG_EXIF_METADATA)) av_log(avctx, AV_LOG_WARNING, "EXIF chunk present, but Exif bit not set in the " "VP8X header\n"); s->has_exif = 1; - bytestream2_init(&exif_gb, avpkt->data + exif_offset, - avpkt->size - exif_offset); + bytestream2_init(&exif_gb, data + exif_offset, size - exif_offset); if (ff_tdecode_header(&exif_gb, &le, &ifd_offset) < 0) { av_log(avctx, AV_LOG_ERROR, "invalid TIFF header " "in Exif data\n"); goto exif_end; } + exif_metadata = (s->vp8x_flags & VP8X_FLAG_ANIMATION) ? &p->metadata : &s->frame->metadata; bytestream2_seek(&exif_gb, ifd_offset, SEEK_SET); - if (ff_exif_decode_ifd(avctx, &exif_gb, le, 0, &exif_metadata) < 0) { + if (ff_exif_decode_ifd(avctx, &exif_gb, le, 0, exif_metadata) < 0) { av_log(avctx, AV_LOG_ERROR, "error decoding Exif data\n"); goto exif_end; } - - av_dict_copy(&p->metadata, exif_metadata, 0); - exif_end: - av_dict_free(&exif_metadata); bytestream2_skip(&gb, chunk_size); break; } @@ -1513,13 +1549,12 @@ exif_end: bytestream2_skip(&gb, chunk_size); break; } - if (!(vp8x_flags & VP8X_FLAG_ICC)) + if (!(s->vp8x_flags & VP8X_FLAG_ICC)) av_log(avctx, AV_LOG_WARNING, "ICCP chunk present, but ICC Profile bit not set in the " "VP8X header\n"); s->has_iccp = 1; - ret = ff_frame_new_side_data(avctx, p, AV_FRAME_DATA_ICC_PROFILE, chunk_size, &sd); if (ret < 0) return ret; @@ -1531,8 +1566,51 @@ exif_end: } break; } - case MKTAG('A', 'N', 'I', 'M'): + case MKTAG('A', 'N', 'I', 'M'): { + const AVPixFmtDescriptor *desc; + int a, r, g, b; + if (!(s->vp8x_flags & VP8X_FLAG_ANIMATION)) { + av_log(avctx, AV_LOG_WARNING, + "ANIM chunk present, but animation bit not set in the " + "VP8X header\n"); + } + // background is stored as BGRA, we need ARGB + s->background_argb[3] = b = bytestream2_get_byte(&gb); + s->background_argb[2] = g = bytestream2_get_byte(&gb); + s->background_argb[1] = r = bytestream2_get_byte(&gb); + s->background_argb[0] = a = bytestream2_get_byte(&gb); + + // convert the background color to YUVA + desc = av_pix_fmt_desc_get(AV_PIX_FMT_YUVA420P); + s->background_yuva[desc->comp[0].plane] = RGB_TO_Y_CCIR(r, g, b); + s->background_yuva[desc->comp[1].plane] = RGB_TO_U_CCIR(r, g, b, 0); + s->background_yuva[desc->comp[2].plane] = RGB_TO_V_CCIR(r, g, b, 0); + s->background_yuva[desc->comp[3].plane] = a; + + bytestream2_skip(&gb, 2); // loop count is ignored + break; + } case MKTAG('A', 'N', 'M', 'F'): + if (!(s->vp8x_flags & VP8X_FLAG_ANIMATION)) { + av_log(avctx, AV_LOG_WARNING, + "ANMF chunk present, but animation bit not set in the " + "VP8X header\n"); + } + s->pos_x = bytestream2_get_le24(&gb) * 2; + s->pos_y = bytestream2_get_le24(&gb) * 2; + s->width = bytestream2_get_le24(&gb) + 1; + s->height = bytestream2_get_le24(&gb) + 1; + s->duration = bytestream2_get_le24(&gb); // duration + s->anmf_flags = bytestream2_get_byte(&gb); + + if (s->width + s->pos_x > s->canvas_width || + s->height + s->pos_y > s->canvas_height) { + av_log(avctx, AV_LOG_ERROR, + "frame does not fit into canvas\n"); + return AVERROR_INVALIDDATA; + } + s->vp8x_flags |= VP8X_FLAG_ANIMATION; + break; case MKTAG('X', 'M', 'P', ' '): AV_WL32(chunk_str, chunk_type); av_log(avctx, AV_LOG_WARNING, "skipping unsupported chunk: %s\n", @@ -1548,24 +1626,688 @@ exif_end: } } - if (!*got_frame) { - av_log(avctx, AV_LOG_ERROR, "image data not found\n"); - return AVERROR_INVALIDDATA; + return size; +} + +int init_canvas_frame(WebPContext *s, int format, int key_frame) +{ + AVFrame *canvas = s->canvas_frame.f; + int height; + int ret; + + // canvas is needed only for animation + if (!(s->vp8x_flags & VP8X_FLAG_ANIMATION)) + return 0; + + // avoid init for non-key frames whose format and size did not change + if (!key_frame && + canvas && + canvas->width == s->canvas_width && + canvas->height == s->canvas_height) + return 0; + + // canvas changes within IPPP sequences will lose thread sync + // because of the ThreadFrame reallocation and will wait forever + // so if frame-threading is used, forbid canvas changes and unlock + // previous frames + if (!key_frame && canvas) { + if (s->avctx->thread_count > 1) { + av_log(s->avctx, AV_LOG_WARNING, "Canvas change detected. The output will be damaged. Use -threads 1 to try decoding with best effort.\n"); + // unlock previous frames that have sent an _await() call + ff_progress_frame_report(&s->canvas_frame, INT_MAX); + return AVERROR_PATCHWELCOME; + } else { + // warn for damaged frames + av_log(s->avctx, AV_LOG_WARNING, "Canvas change detected. The output will be damaged.\n"); + } + } + + s->avctx->pix_fmt = format; + + // VP8 decoder changed the width and height in AVCodecContext. + // Change it back to the canvas size. + ret = ff_set_dimensions(s->avctx, s->canvas_width, s->canvas_height); + if (ret < 0) + return ret; + + ff_progress_frame_unref(&s->canvas_frame); + ret = ff_progress_frame_get_buffer(s->avctx, &s->canvas_frame, AV_GET_BUFFER_FLAG_REF); + if (ret < 0) + return ret; + + canvas = s->canvas_frame.f; + canvas->format = format; + canvas->duration = s->duration; + canvas->width = s->canvas_width; + canvas->height = s->canvas_height; + + if (canvas->format == AV_PIX_FMT_ARGB) { + height = canvas->height; + memset(canvas->data[0], 0, height * canvas->linesize[0]); + } else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(canvas->format); + for (int comp = 0; comp < desc->nb_components; comp++) { + int plane = desc->comp[comp].plane; + + if (comp == 1 || comp == 2) + height = AV_CEIL_RSHIFT(canvas->height, desc->log2_chroma_h); + else + height = FFALIGN(canvas->height, 1 << desc->log2_chroma_h); + + memset(canvas->data[plane], s->transparent_yuva[plane], + height * canvas->linesize[plane]); + } + } + + return 0; +} + +/* + * Blend src1 (foreground) and src2 (background) into dest, in ARGB format. + * width, height are the dimensions of src1 + * pos_x, pos_y is the position in src2 and in dest + */ +static void blend_alpha_argb(uint8_t *dest_data[4], int dest_linesize[4], + const uint8_t *src1_data[4], int src1_linesize[4], + const uint8_t *src2_data[4], int src2_linesize[4], + int src2_step[4], + int width, int height, int pos_x, int pos_y) +{ + for (int y = 0; y < height; y++) { + const uint8_t *src1 = src1_data[0] + y * src1_linesize[0]; + const uint8_t *src2 = src2_data[0] + (y + pos_y) * src2_linesize[0] + pos_x * src2_step[0]; + uint8_t *dest = dest_data[0] + (y + pos_y) * dest_linesize[0] + pos_x * sizeof(uint32_t); + for (int x = 0; x < width; x++) { + int src1_alpha = src1[0]; + int src2_alpha = src2[0]; + + if (src1_alpha == 255) { + memcpy(dest, src1, sizeof(uint32_t)); + } else if (src1_alpha + src2_alpha == 0) { + memset(dest, 0, sizeof(uint32_t)); + } else { + int tmp_alpha = src2_alpha - ROUNDED_DIV(src1_alpha * src2_alpha, 255); + int blend_alpha = src1_alpha + tmp_alpha; + + dest[0] = blend_alpha; + dest[1] = ROUNDED_DIV(src1[1] * src1_alpha + src2[1] * tmp_alpha, blend_alpha); + dest[2] = ROUNDED_DIV(src1[2] * src1_alpha + src2[2] * tmp_alpha, blend_alpha); + dest[3] = ROUNDED_DIV(src1[3] * src1_alpha + src2[3] * tmp_alpha, blend_alpha); + } + src1 += sizeof(uint32_t); + src2 += src2_step[0]; + dest += sizeof(uint32_t); + } + } +} + +/* + * Blend src1 (foreground) and src2 (background) into dest, in YUVA format. + * width, height are the dimensions of src1 + * pos_x, pos_y is the position in src2 and in dest + */ +static void blend_alpha_yuva(WebPContext *s, + uint8_t *dest_data[4], int dest_linesize[4], + const uint8_t *src1_data[4], int src1_linesize[4], + int src1_format, + const uint8_t *src2_data[4], int src2_linesize[4], + int src2_step[4], + int width, int height, int pos_x, int pos_y) +{ + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(src1_format); + + int plane_y = desc->comp[0].plane; + int plane_u = desc->comp[1].plane; + int plane_v = desc->comp[2].plane; + int plane_a = desc->comp[3].plane; + + // blend U & V planes first, because the later step may modify alpha plane + int w = AV_CEIL_RSHIFT(width, desc->log2_chroma_w); + int h = AV_CEIL_RSHIFT(height, desc->log2_chroma_h); + int px = AV_CEIL_RSHIFT(pos_x, desc->log2_chroma_w); + int py = AV_CEIL_RSHIFT(pos_y, desc->log2_chroma_h); + int tile_w = 1 << desc->log2_chroma_w; + int tile_h = 1 << desc->log2_chroma_h; + + for (int y = 0; y < h; y++) { + const uint8_t *src1_u = src1_data[plane_u] + y * src1_linesize[plane_u]; + const uint8_t *src1_v = src1_data[plane_v] + y * src1_linesize[plane_v]; + const uint8_t *src2_u = src2_data[plane_u] + (y + py) * src2_linesize[plane_u] + px * src2_step[plane_u]; + const uint8_t *src2_v = src2_data[plane_v] + (y + py) * src2_linesize[plane_v] + px * src2_step[plane_v]; + uint8_t *dest_u = dest_data[plane_u] + (y + py) * dest_linesize[plane_u] + px; + uint8_t *dest_v = dest_data[plane_v] + (y + py) * dest_linesize[plane_v] + px; + for (int x = 0; x < w; x++) { + // calculate the average alpha of the tile + int src1_alpha = 0; + int src2_alpha = 0; + for (int yy = 0; yy < tile_h; yy++) { + for (int xx = 0; xx < tile_w; xx++) { + src1_alpha += src1_data[plane_a][(y * tile_h + yy) * src1_linesize[plane_a] + + (x * tile_w + xx)]; + src2_alpha += src2_data[plane_a][((y + py) * tile_h + yy) * src2_linesize[plane_a] + + ((x + px) * tile_w + xx) * src2_step[plane_a]]; + } + } + src1_alpha = AV_CEIL_RSHIFT(src1_alpha, desc->log2_chroma_w + desc->log2_chroma_h); + src2_alpha = AV_CEIL_RSHIFT(src2_alpha, desc->log2_chroma_w + desc->log2_chroma_h); + + if (src1_alpha == 255) { + *dest_u = *src1_u; + *dest_v = *src1_v; + } else if (src1_alpha + src2_alpha == 0) { + *dest_u = s->transparent_yuva[plane_u]; + *dest_v = s->transparent_yuva[plane_v]; + } else { + int tmp_alpha = src2_alpha - ROUNDED_DIV(src1_alpha * src2_alpha, 255); + int blend_alpha = src1_alpha + tmp_alpha; + *dest_u = ROUNDED_DIV(*src1_u * src1_alpha + *src2_u * tmp_alpha, blend_alpha); + *dest_v = ROUNDED_DIV(*src1_v * src1_alpha + *src2_v * tmp_alpha, blend_alpha); + } + src1_u++; + src1_v++; + src2_u += src2_step[plane_u]; + src2_v += src2_step[plane_v]; + dest_u++; + dest_v++; + } + } + + // blend Y & A planes + for (int y = 0; y < height; y++) { + const uint8_t *src1_y = src1_data[plane_y] + y * src1_linesize[plane_y]; + const uint8_t *src1_a = src1_data[plane_a] + y * src1_linesize[plane_a]; + const uint8_t *src2_y = src2_data[plane_y] + (y + pos_y) * src2_linesize[plane_y] + pos_x * src2_step[plane_y]; + const uint8_t *src2_a = src2_data[plane_a] + (y + pos_y) * src2_linesize[plane_a] + pos_x * src2_step[plane_a]; + uint8_t *dest_y = dest_data[plane_y] + (y + pos_y) * dest_linesize[plane_y] + pos_x; + uint8_t *dest_a = dest_data[plane_a] + (y + pos_y) * dest_linesize[plane_a] + pos_x; + for (int x = 0; x < width; x++) { + int src1_alpha = *src1_a; + int src2_alpha = *src2_a; + + if (src1_alpha == 255) { + *dest_y = *src1_y; + *dest_a = 255; + } else if (src1_alpha + src2_alpha == 0) { + *dest_y = s->transparent_yuva[plane_y]; + *dest_a = 0; + } else { + int tmp_alpha = src2_alpha - ROUNDED_DIV(src1_alpha * src2_alpha, 255); + int blend_alpha = src1_alpha + tmp_alpha; + *dest_y = ROUNDED_DIV(*src1_y * src1_alpha + *src2_y * tmp_alpha, blend_alpha); + *dest_a = blend_alpha; + } + src1_y++; + src1_a++; + src2_y += src2_step[plane_y]; + src2_a += src2_step[plane_a]; + dest_y++; + dest_a++; + } + } +} + +static av_always_inline void webp_yuva2argb(uint8_t *out, int Y, int U, int V, int A) +{ + // variables used in macros + const uint8_t *cm = ff_crop_tab + MAX_NEG_CROP; + uint8_t r, g, b; + int y, cb, cr; + int r_add, g_add, b_add; + + YUV_TO_RGB1_CCIR(U, V); + YUV_TO_RGB2_CCIR(r, g, b, Y); + + out[0] = av_clip_uint8(A); + out[1] = av_clip_uint8(r); + out[2] = av_clip_uint8(g); + out[3] = av_clip_uint8(b); +} + +static av_always_inline void webp_argb2yuva(uint8_t *out_y, uint8_t *out_u, uint8_t *out_v, uint8_t *out_a, + uint8_t *in_argb, int A) +{ + int R = in_argb[1]; + int G = in_argb[2]; + int B = in_argb[3]; + + // convert yuv to rgb + *out_a = av_clip_uint8(A); + *out_y = RGB_TO_Y_CCIR(R, G, B); + *out_u = RGB_TO_U_CCIR(R, G, B, 0); + *out_v = RGB_TO_V_CCIR(R, G, B, 0); +} + +static void copy_yuva2argb(WebPContext *s, AVFrame *dst, AVFrame *src, + int pos_x, int pos_y) +{ + const AVPixFmtDescriptor *src_desc = av_pix_fmt_desc_get(src->format); + + // if src ARGB: copy + // else if src YUV(A) copy pixel per pixel: + int alpha = src_desc->nb_components > 3; + int plane_y = src_desc->comp[0].plane; + int plane_u = src_desc->comp[1].plane; + int plane_v = src_desc->comp[2].plane; + int plane_a = src_desc->comp[3].plane; + + // assert pos_y = src->height < dst_height + for (int y = 0; y < src->height; y++) { + int ys = (src_desc->log2_chroma_h) ? (y >> 1) : y; + uint8_t *dest = dst->data[0] + (y + pos_y) * dst->linesize[0] + pos_x * 4; + uint8_t *src_y = src->data[plane_y] + (y ) * src->linesize[plane_y]; + uint8_t *src_u = src->data[plane_u] + (ys) * src->linesize[plane_u]; + uint8_t *src_v = src->data[plane_v] + (ys) * src->linesize[plane_v]; + uint8_t *src_a = src->data[plane_a] + (y ) * src->linesize[plane_a]; + + for (int x = 0; x < src->width; x++) { + webp_yuva2argb(dest, *src_y, *src_u, *src_v, (alpha ? (*src_a): 255)); + src_y += 1; + src_u += (src_desc->log2_chroma_w) ? (x&1) : 1; + src_v += (src_desc->log2_chroma_w) ? (x&1) : 1; + src_a += 1; + dest += 4; + } + } +} + +static void blend_yuva2argb(WebPContext *s, AVFrame *dst, AVFrame *src, + int pos_x, int pos_y) +{ + const AVPixFmtDescriptor *src_desc = av_pix_fmt_desc_get(src->format); + + // if src ARGB: copy + // else if src YUV(A) copy pixel per pixel: + int alpha = src_desc->nb_components > 3; + int plane_y = src_desc->comp[0].plane; + int plane_u = src_desc->comp[1].plane; + int plane_v = src_desc->comp[2].plane; + int plane_a = src_desc->comp[3].plane; + + // assert pos_y = src->height < dst_height + for (int y = 0; y < src->height; y++) { + int ys = (src_desc->log2_chroma_h) ? (y >> 1) : y; + uint8_t *dest = dst->data[0] + (y + pos_y) * dst->linesize[0] + pos_x * 4; + uint8_t *src_y = src->data[plane_y] + (y ) * src->linesize[plane_y]; + uint8_t *src_u = src->data[plane_u] + (ys) * src->linesize[plane_u]; + uint8_t *src_v = src->data[plane_v] + (ys) * src->linesize[plane_v]; + uint8_t *src_a = src->data[plane_a] + (y ) * src->linesize[plane_a]; + + for (int x = 0; x < src->width; x++) { + int dst_alpha = dest[0]; + int src_alpha = *src_a; + + if (src_alpha == 255) { + webp_yuva2argb(dest, *src_y, *src_u, *src_v, src_alpha); + } else if (src_alpha + dst_alpha == 0) { + memset(dest, 0, 4 * sizeof(uint8_t)); + } else { + uint8_t tmp[4]; + int tmp_alpha = dst_alpha - ROUNDED_DIV(src_alpha * dst_alpha, 255); + int blend_alpha = src_alpha + tmp_alpha; + + webp_yuva2argb(tmp, *src_y, *src_u, *src_v, (alpha ? (*src_a): 255)); + + dest[0] = blend_alpha; + dest[1] = ROUNDED_DIV(tmp[1] * src_alpha + dest[1] * tmp_alpha, blend_alpha); + dest[2] = ROUNDED_DIV(tmp[2] * src_alpha + dest[2] * tmp_alpha, blend_alpha); + dest[3] = ROUNDED_DIV(tmp[3] * src_alpha + dest[3] * tmp_alpha, blend_alpha); + } + + src_y += 1; + src_u += (src_desc->log2_chroma_w) ? (x&1) : 1; + src_v += (src_desc->log2_chroma_w) ? (x&1) : 1; + src_a += 1; + dest += 4; + } + } +} + +static void blend_argb2yuva(WebPContext *s, AVFrame *dst, AVFrame *src, + int pos_x, int pos_y) +{ + const AVPixFmtDescriptor *dst_desc = av_pix_fmt_desc_get(dst->format); + + // if src ARGB: copy + // else if src YUV(A) copy pixel per pixel: + int alpha = dst_desc->nb_components > 3; + int plane_y = dst_desc->comp[0].plane; + int plane_u = dst_desc->comp[1].plane; + int plane_v = dst_desc->comp[2].plane; + int plane_a = dst_desc->comp[3].plane; + + // assert pos_y = src->height < dst_height + for (int y = 0; y < src->height; y++) { + int ys = (dst_desc->log2_chroma_h) ? (y >> 1) : y; + int pos_ys = (dst_desc->log2_chroma_h) ? (pos_y >> 1) : pos_y; + int pos_xs = (dst_desc->log2_chroma_w) ? (pos_x >> 1) : pos_x; + uint8_t *dst_y = dst->data[plane_y] + (y + pos_y) * dst->linesize[plane_y] + pos_x; + uint8_t *dst_u = dst->data[plane_u] + (ys + pos_ys) * dst->linesize[plane_u] + pos_xs; + uint8_t *dst_v = dst->data[plane_v] + (ys + pos_ys) * dst->linesize[plane_v] + pos_xs; + uint8_t *dst_a = dst->data[plane_a] + (y + pos_y) * dst->linesize[plane_a] + pos_x; + uint8_t *srcp = src->data[0] + (y) * src->linesize[0]; + + for (int x = 0; x < src->width; x++) { + int dst_alpha = *dst_a; //dest[0]; + int src_alpha = srcp[0]; //*src_a; + + if (src_alpha == 255) { + //webp_yuva2argb(dest, *src_y, *src_u, *src_v, src_alpha); + webp_argb2yuva(dst_y, dst_u, dst_v, dst_a, srcp, src_alpha); + } else if (src_alpha + dst_alpha == 0) { + //memset(dest, 0, 4 * sizeof(uint8_t)); + *dst_a = 0; + *dst_y = 0; + *dst_u = 0; + *dst_v = 0; + } else { + uint8_t tmp_y; + uint8_t tmp_u; + uint8_t tmp_v; + uint8_t tmp_a; + int tmp_alpha = dst_alpha - ROUNDED_DIV(src_alpha * dst_alpha, 255); + int blend_alpha = src_alpha + tmp_alpha; + + //webp_yuva2argb(tmp, *src_y, *src_u, *src_v, (alpha ? (*src_a): 255)); + webp_argb2yuva(&tmp_y, &tmp_u, &tmp_v, &tmp_a, srcp, (alpha ? (src_alpha): 255)); + + *dst_a = blend_alpha; + if (((dst_desc->log2_chroma_w) ? (x&1) : 1)) { + *dst_y = ROUNDED_DIV(tmp_y * src_alpha + (*dst_y) * tmp_alpha, blend_alpha); + *dst_u = ROUNDED_DIV(tmp_u * src_alpha + (*dst_u) * tmp_alpha, blend_alpha); + } + *dst_v = ROUNDED_DIV(tmp_v * src_alpha + (*dst_v) * tmp_alpha, blend_alpha); + } + + dst_y += 1; + dst_u += (dst_desc->log2_chroma_w) ? (x&1) : 1; + dst_v += (dst_desc->log2_chroma_w) ? (x&1) : 1; + dst_a += 1; + srcp += 4; + } + } +} + +static int blend_frame_into_canvas(WebPContext *s) +{ + AVFrame *canvas = s->canvas_frame.f; + AVFrame *frame = s->frame; + int width, height; + int pos_x, pos_y; + + if ((s->anmf_flags & ANMF_BLENDING_METHOD) == ANMF_BLENDING_METHOD_OVERWRITE + || frame->format == AV_PIX_FMT_YUV420P) { + // do not blend, overwrite + + if (canvas->format == AV_PIX_FMT_ARGB) { + width = s->width; + height = s->height; + pos_x = s->pos_x; + pos_y = s->pos_y; + + if (canvas->format == frame->format) { // COPY ARGB 2 ARGB + for (int y = 0; y < height; y++) { + const uint32_t *src = (uint32_t *) (frame->data[0] + y * frame->linesize[0]); + uint32_t *dst = (uint32_t *) (canvas->data[0] + (y + pos_y) * canvas->linesize[0]) + pos_x; + memcpy(dst, src, width * sizeof(uint32_t)); + } + } else { // COPY YUVA 2 ARGB + copy_yuva2argb(s, canvas, frame, pos_x, pos_y); + } + } else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format); + int plane; + + for (int comp = 0; comp < desc->nb_components; comp++) { + plane = desc->comp[comp].plane; + width = s->width; + height = s->height; + pos_x = s->pos_x; + pos_y = s->pos_y; + if (comp == 1 || comp == 2) { + width = AV_CEIL_RSHIFT(width, desc->log2_chroma_w); + height = AV_CEIL_RSHIFT(height, desc->log2_chroma_h); + pos_x = AV_CEIL_RSHIFT(pos_x, desc->log2_chroma_w); + pos_y = AV_CEIL_RSHIFT(pos_y, desc->log2_chroma_h); + } + + for (int y = 0; y < height; y++) { + const uint8_t *src = frame->data[plane] + y * frame->linesize[plane]; + uint8_t *dst = canvas->data[plane] + (y + pos_y) * canvas->linesize[plane] + pos_x; + memcpy(dst, src, width); + } + } + + if (desc->nb_components < 4) { + // frame does not have alpha, set alpha to 255 + desc = av_pix_fmt_desc_get(canvas->format); + plane = desc->comp[3].plane; + width = s->width; + height = s->height; + pos_x = s->pos_x; + pos_y = s->pos_y; + + for (int y = 0; y < height; y++) { + uint8_t *dst = canvas->data[plane] + (y + pos_y) * canvas->linesize[plane] + pos_x; + memset(dst, 255, width); + } + } + } + } else { + // alpha blending + + if (canvas->format == AV_PIX_FMT_ARGB) { + int src2_step[4] = { sizeof(uint32_t) }; + if (canvas->format == frame->format) { + blend_alpha_argb(canvas->data, canvas->linesize, + (const uint8_t **) frame->data, frame->linesize, + (const uint8_t **) canvas->data, canvas->linesize, + src2_step, s->width, s->height, s->pos_x, s->pos_y); + } else { + blend_yuva2argb(s, canvas, frame, s->pos_x, s->pos_y); + } + } else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ { + int src2_step[4] = { 1, 1, 1, 1 }; + if (canvas->format == frame->format) { + blend_alpha_yuva(s, canvas->data, canvas->linesize, + (const uint8_t **) frame->data, frame->linesize, + frame->format, + (const uint8_t **) canvas->data, canvas->linesize, + src2_step, s->width, s->height, s->pos_x, s->pos_y); + } else { + blend_argb2yuva(s, canvas, frame, s->pos_x, s->pos_y); + } + } + } + + return 0; +} + +static int copy_canvas_to_frame(WebPContext *s, AVFrame *frame, int key_frame) +{ + AVFrame *canvas = s->canvas_frame.f; + int src2_step[4] = { 0, 0, 0, 0 }; + int ret; + + frame->format = canvas->format; + frame->width = canvas->width; + frame->height = canvas->height; + + ret = av_frame_get_buffer(frame, 0); + if (ret < 0) + return ret; + + ret = av_frame_copy_props(frame, canvas); + if (ret < 0) + return ret; + + // blend the canvas with the background color into the output frame + if (canvas->format == AV_PIX_FMT_ARGB) { + const uint8_t *src2_data[4] = { &s->background_argb[0] }; + blend_alpha_argb(frame->data, frame->linesize, + (const uint8_t **) canvas->data, canvas->linesize, + (const uint8_t **) src2_data, src2_step, src2_step, + canvas->width, canvas->height, 0, 0); + } else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ { + blend_alpha_yuva(s, frame->data, frame->linesize, + (const uint8_t **) canvas->data, canvas->linesize, + canvas->format, + s->background_data, src2_step, src2_step, + canvas->width, canvas->height, 0, 0); + } + + if (key_frame) { + frame->pict_type = AV_PICTURE_TYPE_I; + } else { + frame->pict_type = AV_PICTURE_TYPE_P; } - return avpkt->size; + return 0; +} + +static int dispose_prev_frame_in_canvas(WebPContext *s) +{ + AVFrame *canvas = s->canvas_frame.f; + int width, height; + int pos_x, pos_y; + + if ((s->prev_anmf_flags & ANMF_DISPOSAL_METHOD) == ANMF_DISPOSAL_METHOD_BACKGROUND) { + // dispose to background + + if (canvas->format == AV_PIX_FMT_ARGB) { + width = s->prev_width; + height = s->prev_height; + pos_x = s->prev_pos_x; + pos_y = s->prev_pos_y; + + for (int y = 0; y < height; y++) { + uint32_t *dst = (uint32_t *) (canvas->data[0] + (y + pos_y) * canvas->linesize[0]) + pos_x; + memset(dst, 0, width * sizeof(uint32_t)); + } + } else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(canvas->format); + int plane; + + for (int comp = 0; comp < desc->nb_components; comp++) { + plane = desc->comp[comp].plane; + width = s->prev_width; + height = s->prev_height; + pos_x = s->prev_pos_x; + pos_y = s->prev_pos_y; + if (comp == 1 || comp == 2) { + width = AV_CEIL_RSHIFT(width, desc->log2_chroma_w); + height = AV_CEIL_RSHIFT(height, desc->log2_chroma_h); + pos_x = AV_CEIL_RSHIFT(pos_x, desc->log2_chroma_w); + pos_y = AV_CEIL_RSHIFT(pos_y, desc->log2_chroma_h); + } + + for (int y = 0; y < height; y++) { + uint8_t *dst = canvas->data[plane] + (y + pos_y) * canvas->linesize[plane] + pos_x; + memset(dst, s->transparent_yuva[plane], width); + } + } + } + } + + return 0; +} + +static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, + int *got_frame, AVPacket *avpkt) +{ + WebPContext *s = avctx->priv_data; + int ret; + int key_frame = avpkt->flags & AV_PKT_FLAG_KEY; + + *got_frame = 0; + + if (key_frame) { + // The canvas is passed from one thread to another in a sequence + // starting with a key frame followed by non-key frames. + // The key frame reports progress 1, + // the N-th non-key frame awaits progress N = s->await_progress + // and reports progress N + 1. + s->await_progress = 0; + } + + // reset the frame params + s->anmf_flags = 0; + s->width = 0; + s->height = 0; + s->pos_x = 0; + s->pos_y = 0; + s->has_alpha = 0; + + ret = webp_decode_frame_common(avctx, avpkt->data, avpkt->size, got_frame, key_frame, p); + if (ret < 0) + goto end; + + if (*got_frame) { + if (!(s->vp8x_flags & VP8X_FLAG_ANIMATION)) { + // no animation, output the decoded frame + av_frame_move_ref(p, s->frame); + } else { + if (!key_frame) { + ff_progress_frame_await(&s->canvas_frame, s->await_progress); + + ret = dispose_prev_frame_in_canvas(s); + if (ret < 0) + goto end; + } + + ret = blend_frame_into_canvas(s); + if (ret < 0) + goto end; + + ret = copy_canvas_to_frame(s, p, key_frame); + if (ret < 0) + goto end; + + p->pts = avpkt->pts; + p->duration = s->duration; + + ff_progress_frame_report(&s->canvas_frame, s->await_progress + 1); + } + + } + + ret = avpkt->size; + +end: + s->prev_anmf_flags = s->anmf_flags; + s->prev_width = s->width; + s->prev_height = s->height; + s->prev_pos_x = s->pos_x; + s->prev_pos_y = s->pos_y; + + av_frame_unref(s->frame); + return ret; } static av_cold int webp_decode_init(AVCodecContext *avctx) { WebPContext *s = avctx->priv_data; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(AV_PIX_FMT_YUVA420P); int ret; const AVCodec *codec; + s->avctx = avctx; s->pkt = av_packet_alloc(); - if (!s->pkt) + s->frame = av_frame_alloc(); + if (!s->pkt || !s->frame) { + av_packet_free(&s->pkt); + av_frame_free(&s->frame); return AVERROR(ENOMEM); + } + + // prepare data pointers for YUVA background + for (int i = 0; i < 4; i++) + s->background_data[i] = &s->background_yuva[i]; + // convert transparent black from RGBA to YUVA + s->transparent_yuva[desc->comp[0].plane] = RGB_TO_Y_CCIR(0, 0, 0); + s->transparent_yuva[desc->comp[1].plane] = RGB_TO_U_CCIR(0, 0, 0, 0); + s->transparent_yuva[desc->comp[2].plane] = RGB_TO_V_CCIR(0, 0, 0, 0); + s->transparent_yuva[desc->comp[3].plane] = 0; /* Prepare everything needed for VP8 decoding */ codec = avcodec_find_decoder(AV_CODEC_ID_VP8); @@ -1589,13 +2331,47 @@ static av_cold int webp_decode_close(AVCodecContext *avctx) WebPContext *s = avctx->priv_data; av_packet_free(&s->pkt); + ff_progress_frame_unref(&s->canvas_frame); + av_frame_free(&s->frame); avcodec_free_context(&s->avctx_vp8); - if (s->initialized) - return ff_vp8_decode_free(avctx); + return 0; +} + +static void webp_decode_flush(AVCodecContext *avctx) +{ + WebPContext *s = avctx->priv_data; + + ff_progress_frame_unref(&s->canvas_frame); +} + +#if HAVE_THREADS +static int webp_update_thread_context(AVCodecContext *dst, const AVCodecContext *src) +{ + WebPContext *wsrc = src->priv_data; + WebPContext *wdst = dst->priv_data; + + if (dst == src) + return 0; + + ff_progress_frame_replace(&wdst->canvas_frame, &wsrc->canvas_frame); + + wdst->vp8x_flags = wsrc->vp8x_flags; + wdst->canvas_width = wsrc->canvas_width; + wdst->canvas_height = wsrc->canvas_height; + wdst->prev_anmf_flags = wsrc->anmf_flags; + wdst->prev_width = wsrc->width; + wdst->prev_height = wsrc->height; + wdst->prev_pos_x = wsrc->pos_x; + wdst->prev_pos_y = wsrc->pos_y; + wdst->await_progress = wsrc->await_progress + 1; + + memcpy(wdst->background_argb, wsrc->background_argb, sizeof(wsrc->background_argb)); + memcpy(wdst->background_yuva, wsrc->background_yuva, sizeof(wsrc->background_yuva)); return 0; } +#endif const FFCodec ff_webp_decoder = { .p.name = "webp", @@ -1603,9 +2379,12 @@ const FFCodec ff_webp_decoder = { .p.type = AVMEDIA_TYPE_VIDEO, .p.id = AV_CODEC_ID_WEBP, .priv_data_size = sizeof(WebPContext), + UPDATE_THREAD_CONTEXT(webp_update_thread_context), .init = webp_decode_init, FF_CODEC_DECODE_CB(webp_decode_frame), .close = webp_decode_close, + .flush = webp_decode_flush, + .bsfs = "awebp2webp", .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_FRAME_THREADS, .caps_internal = FF_CODEC_CAP_ICC_PROFILES | FF_CODEC_CAP_USES_PROGRESSFRAMES, From patchwork Fri Jun 21 10:43:20 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thilo Borgmann X-Patchwork-Id: 50035 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a59:ae71:0:b0:482:c625:d099 with SMTP id w17csp432040vqz; Fri, 21 Jun 2024 03:44:18 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCXiaNn382exCpP4a80VjDFu9guMOnMhS9YeKjT+QGigfN90qKmLpX/X6U2uSpcyMns54kOoirudOV8p69KD4h0hQEo9s2j0Q7WnbQ== X-Google-Smtp-Source: AGHT+IHcJwBNDrOwKzNQvYtJDwQbXxocBHnVBjy2TfMjMfbQCsif4JoG6sogeM0anzRqP39VkjIy X-Received: by 2002:a17:906:4fd6:b0:a6f:b7e4:3ac5 with SMTP id a640c23a62f3a-a6fb7e43d0bmr452087866b.6.1718966658256; Fri, 21 Jun 2024 03:44:18 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1718966658; cv=none; d=google.com; s=arc-20160816; b=vOXrE0tVnpUS3dceh3PxTG1zZ1Ks4dpkAJH5IVyYMOG1ihxVj+/12PKGD38Bj43oA9 36rSyYaPS0Ln+sGzuGK+exwld2J9fDF+J25CbtgwIggb8b0aviUZhu2sTHOsC6YPveuR 7th3dJ8nAihc3O4PdcvytgF3sw1pQiFIuNlcXko2EWrEk4vW5Q1MKOz2Ha88h7s76iLh 4FkmkugSPX8xYCjoxfWwFsU8UTohhKt0Bd3xvLRaYrYfrMklpojUF7uJ7cKrd/whSMmF ly+w5CarjsYPOeXRCouUJAs7E2FATrPSHSMyGuBVtChF9oRom9829CQdug9B8Dozw8bs 3aTg== 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:from :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:delivered-to; bh=Hc9vP8CezwxkSv1+U4DMS5VQj+j+Kfu84j6u9bAOxcQ=; fh=uZWItAgYCSLFS5Zum7Ip+PilGyWYOeCwun+We0BTHAs=; b=FSUu1OCxMMTc2oTjiopVQDNhrZ7MfHTqc0EQyAGNPjKHmLnWJ5gCP7nNGJ5LyqTqSa /6aaEcWSqkO3yFst2C9gQO6MAdiKmvgnuN7vq8zAfGDJ9uWMaKocG72zi+8442lQgNK/ kT+sCPd6mKQMM2zLCeZfVQmYo0MiZsSSsMBYM3uN+urXIYb5iHGiIYwWh2RuJV5UzXex DkIgbXP5GzTAuVVn1UDq3NIdIjula9dH7mJIHi2tLQHy5yEpwfAS34i41GoqP37Z0Q6z M90Q2JQ+Vso+H0w+H/hfuPxbeOrwHhRyr5WlHa+s2tRN+Lw9bL0p+jQdyr8ExI0AH6Rw qoXA==; dara=google.com 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 a640c23a62f3a-a6fcf579698si70472766b.794.2024.06.21.03.44.17; Fri, 21 Jun 2024 03:44:18 -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 AFE6368D7EA; Fri, 21 Jun 2024 13:43:40 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from shout01.mail.de (shout01.mail.de [62.201.172.24]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id A8AA568D81A for ; Fri, 21 Jun 2024 13:43:30 +0300 (EEST) Received: from postfix03.mail.de (postfix03.bt.mail.de [10.0.121.127]) by shout01.mail.de (Postfix) with ESMTP id A4472240EA1 for ; Fri, 21 Jun 2024 12:43:26 +0200 (CEST) Received: from smtp01.mail.de (smtp04.bt.mail.de [10.0.121.214]) by postfix03.mail.de (Postfix) with ESMTP id 8BCC080209 for ; Fri, 21 Jun 2024 12:43:26 +0200 (CEST) Received: from [127.0.0.1] (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp01.mail.de (Postfix) with ESMTPSA id 0130D240AD7 for ; Fri, 21 Jun 2024 12:43:25 +0200 (CEST) To: ffmpeg-devel@ffmpeg.org Date: Fri, 21 Jun 2024 12:43:20 +0200 Message-Id: <20240621104323.92453-6-thilo.borgmann@mail.de> In-Reply-To: <20240621104323.92453-1-thilo.borgmann@mail.de> References: <20240621104323.92453-1-thilo.borgmann@mail.de> MIME-Version: 1.0 X-purgate: clean X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate-type: clean X-purgate-Ad: Categorized by eleven eXpurgate (R) http://www.eleven.de X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate: clean X-purgate-size: 6528 X-purgate-ID: 154282::1718966606-E17EB338-8081FF3F/0/0 Subject: [FFmpeg-devel] [PATCH v13 5/8] avcodec/webp: make init_canvas_frame static 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: , X-Patchwork-Original-From: Thilo Borgmann via ffmpeg-devel From: Thilo Borgmann Reply-To: FFmpeg development discussions and patches Cc: Thilo Borgmann Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: nfGNppFeOWQI From: Thilo Borgmann via ffmpeg-devel --- libavcodec/webp.c | 146 +++++++++++++++++++++++----------------------- 1 file changed, 72 insertions(+), 74 deletions(-) diff --git a/libavcodec/webp.c b/libavcodec/webp.c index 146d5cb393..bacf605ff2 100644 --- a/libavcodec/webp.c +++ b/libavcodec/webp.c @@ -1383,7 +1383,78 @@ static int vp8_lossy_decode_frame(AVCodecContext *avctx, AVFrame *p, return ret; } -int init_canvas_frame(WebPContext *s, int format, int key_frame); +static int init_canvas_frame(WebPContext *s, int format, int key_frame) +{ + AVFrame *canvas = s->canvas_frame.f; + int height; + int ret; + + // canvas is needed only for animation + if (!(s->vp8x_flags & VP8X_FLAG_ANIMATION)) + return 0; + + // avoid init for non-key frames whose format and size did not change + if (!key_frame && + canvas && + canvas->width == s->canvas_width && + canvas->height == s->canvas_height) + return 0; + + // canvas changes within IPPP sequences will lose thread sync + // because of the ThreadFrame reallocation and will wait forever + // so if frame-threading is used, forbid canvas changes and unlock + // previous frames + if (!key_frame && canvas) { + if (s->avctx->thread_count > 1) { + av_log(s->avctx, AV_LOG_WARNING, "Canvas change detected. The output will be damaged. Use -threads 1 to try decoding with best effort.\n"); + // unlock previous frames that have sent an _await() call + ff_progress_frame_report(&s->canvas_frame, INT_MAX); + return AVERROR_PATCHWELCOME; + } else { + // warn for damaged frames + av_log(s->avctx, AV_LOG_WARNING, "Canvas change detected. The output will be damaged.\n"); + } + } + + s->avctx->pix_fmt = format; + + // VP8 decoder changed the width and height in AVCodecContext. + // Change it back to the canvas size. + ret = ff_set_dimensions(s->avctx, s->canvas_width, s->canvas_height); + if (ret < 0) + return ret; + + ff_progress_frame_unref(&s->canvas_frame); + ret = ff_progress_frame_get_buffer(s->avctx, &s->canvas_frame, AV_GET_BUFFER_FLAG_REF); + if (ret < 0) + return ret; + + canvas = s->canvas_frame.f; + canvas->format = format; + canvas->duration = s->duration; + canvas->width = s->canvas_width; + canvas->height = s->canvas_height; + + if (canvas->format == AV_PIX_FMT_ARGB) { + height = canvas->height; + memset(canvas->data[0], 0, height * canvas->linesize[0]); + } else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(canvas->format); + for (int comp = 0; comp < desc->nb_components; comp++) { + int plane = desc->comp[comp].plane; + + if (comp == 1 || comp == 2) + height = AV_CEIL_RSHIFT(canvas->height, desc->log2_chroma_h); + else + height = FFALIGN(canvas->height, 1 << desc->log2_chroma_h); + + memset(canvas->data[plane], s->transparent_yuva[plane], + height * canvas->linesize[plane]); + } + } + + return 0; +} static int webp_decode_frame_common(AVCodecContext *avctx, uint8_t *data, int size, int *got_frame, int key_frame, AVFrame *p) @@ -1629,79 +1700,6 @@ exif_end: return size; } -int init_canvas_frame(WebPContext *s, int format, int key_frame) -{ - AVFrame *canvas = s->canvas_frame.f; - int height; - int ret; - - // canvas is needed only for animation - if (!(s->vp8x_flags & VP8X_FLAG_ANIMATION)) - return 0; - - // avoid init for non-key frames whose format and size did not change - if (!key_frame && - canvas && - canvas->width == s->canvas_width && - canvas->height == s->canvas_height) - return 0; - - // canvas changes within IPPP sequences will lose thread sync - // because of the ThreadFrame reallocation and will wait forever - // so if frame-threading is used, forbid canvas changes and unlock - // previous frames - if (!key_frame && canvas) { - if (s->avctx->thread_count > 1) { - av_log(s->avctx, AV_LOG_WARNING, "Canvas change detected. The output will be damaged. Use -threads 1 to try decoding with best effort.\n"); - // unlock previous frames that have sent an _await() call - ff_progress_frame_report(&s->canvas_frame, INT_MAX); - return AVERROR_PATCHWELCOME; - } else { - // warn for damaged frames - av_log(s->avctx, AV_LOG_WARNING, "Canvas change detected. The output will be damaged.\n"); - } - } - - s->avctx->pix_fmt = format; - - // VP8 decoder changed the width and height in AVCodecContext. - // Change it back to the canvas size. - ret = ff_set_dimensions(s->avctx, s->canvas_width, s->canvas_height); - if (ret < 0) - return ret; - - ff_progress_frame_unref(&s->canvas_frame); - ret = ff_progress_frame_get_buffer(s->avctx, &s->canvas_frame, AV_GET_BUFFER_FLAG_REF); - if (ret < 0) - return ret; - - canvas = s->canvas_frame.f; - canvas->format = format; - canvas->duration = s->duration; - canvas->width = s->canvas_width; - canvas->height = s->canvas_height; - - if (canvas->format == AV_PIX_FMT_ARGB) { - height = canvas->height; - memset(canvas->data[0], 0, height * canvas->linesize[0]); - } else /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ { - const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(canvas->format); - for (int comp = 0; comp < desc->nb_components; comp++) { - int plane = desc->comp[comp].plane; - - if (comp == 1 || comp == 2) - height = AV_CEIL_RSHIFT(canvas->height, desc->log2_chroma_h); - else - height = FFALIGN(canvas->height, 1 << desc->log2_chroma_h); - - memset(canvas->data[plane], s->transparent_yuva[plane], - height * canvas->linesize[plane]); - } - } - - return 0; -} - /* * Blend src1 (foreground) and src2 (background) into dest, in ARGB format. * width, height are the dimensions of src1 From patchwork Fri Jun 21 10:43:21 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thilo Borgmann X-Patchwork-Id: 50038 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a59:ae71:0:b0:482:c625:d099 with SMTP id w17csp432357vqz; Fri, 21 Jun 2024 03:44:56 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCVjgcooDobI5pCX8ph6rZ04OepmErYgGIl/xByV6jOIu02xjoerw+gL09doc5RTrcWSdV1/RydIIBbpKNVUnd9oc+EllW5Ws0Mtvw== X-Google-Smtp-Source: AGHT+IFpmIFneWU3+0Rz54YlhWzmVWuYCU+Gpfr7wB27el5wTpcjx+Ma1SSIvoEyG4hh07XZR/pn X-Received: by 2002:a17:906:52c3:b0:a6f:b23b:402c with SMTP id a640c23a62f3a-a6fb23b4231mr399799766b.1.1718966695809; Fri, 21 Jun 2024 03:44:55 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1718966695; cv=none; d=google.com; s=arc-20160816; b=bA0j3WXwO1up0VEJRcLqtmD5De0z53AvFAuagsPfsVw6T8u56yUD92VtUKPi9cDg76 SfURJfO3pmBR2ULNo0kF9yqzbGfn49ht+N4/zk+s/5CUwqRz/eRcZ3tHSNCFBFMPZ2f4 oVj6cIqRMmbtn7Hkk8M8Lfwf0fcJJ21nP7vLWQm6o0eGLkv54VGVeXjen4kpvMXReLa3 KB3wJcQyJUuFR7QeLkWr2F6nzb6guCIg4JNxW38Acf4aEFj02ExInTsTR72V4D1lonMH sXFcPyMTZIL94q77KcG9hewtW2ZqDOvdxLcyN0e3Tx9P03KOde56BcgOAlrFdEW4s3LR L3Lg== 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:from :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:delivered-to; bh=aLYl4AYdNNhnEtGU99xbLlxazEz4jETfBfCb+66waPc=; fh=uZWItAgYCSLFS5Zum7Ip+PilGyWYOeCwun+We0BTHAs=; b=eE0k+TO8hBIz1o7GRsNUZJ8HO1ZPnNl4+FNfDtD9XmR9irq2OVHUIHzMcreCJ429Gv Dl99JAo9RNYqpxiv49dZgQF01X0uBRsM9mBon7Ac/MIeUMae0Pz4YZ3jYsuPqRm1UOY1 e/vDGK7DjAbUwVQyNlDgA5hv8W7xys5TBg4JoYam1fUSLmzAL6WCKhhigWQ1nFnZCb7I MBaDMICIL51xE5MH7SBt7eHEZdpZBEsO91XlWdO8b/yT/haVm5YcgoKycE8sxUWoDMku log9Z3BHq9EDm5iVuTWXF1/HHhd5MrxV9pKhirst06TZgCXKvIFyjbbJ0088m3usSzDY xNgQ==; dara=google.com 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 a640c23a62f3a-a6fcf565c5dsi72918466b.614.2024.06.21.03.44.55; Fri, 21 Jun 2024 03:44:55 -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 7BAAA68D8AE; Fri, 21 Jun 2024 13:43:47 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from shout01.mail.de (shout01.mail.de [62.201.172.24]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id D2C4768D80F for ; Fri, 21 Jun 2024 13:43:31 +0300 (EEST) Received: from postfix03.mail.de (postfix03.bt.mail.de [10.0.121.127]) by shout01.mail.de (Postfix) with ESMTP id 01870240EA9 for ; Fri, 21 Jun 2024 12:43:27 +0200 (CEST) Received: from smtp01.mail.de (smtp04.bt.mail.de [10.0.121.214]) by postfix03.mail.de (Postfix) with ESMTP id DCB0580209 for ; Fri, 21 Jun 2024 12:43:26 +0200 (CEST) Received: from [127.0.0.1] (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp01.mail.de (Postfix) with ESMTPSA id 9D863240A68 for ; Fri, 21 Jun 2024 12:43:26 +0200 (CEST) To: ffmpeg-devel@ffmpeg.org Date: Fri, 21 Jun 2024 12:43:21 +0200 Message-Id: <20240621104323.92453-7-thilo.borgmann@mail.de> In-Reply-To: <20240621104323.92453-1-thilo.borgmann@mail.de> References: <20240621104323.92453-1-thilo.borgmann@mail.de> MIME-Version: 1.0 X-purgate: clean X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate-type: clean X-purgate-Ad: Categorized by eleven eXpurgate (R) http://www.eleven.de X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate: clean X-purgate-size: 21335 X-purgate-ID: 154282::1718966606-E17EB338-994F40D9/0/0 Subject: [FFmpeg-devel] [PATCH v13 6/8] libavformat/webp: add WebP demuxer 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: , X-Patchwork-Original-From: Thilo Borgmann via ffmpeg-devel From: Thilo Borgmann Reply-To: FFmpeg development discussions and patches Cc: Thilo Borgmann Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: YBUESrIeK1Kt From: Josef Zlomek Adds the demuxer of animated WebP files. It supports non-animated, animated, truncated, and concatenated files. Reading from a pipe (and other non-seekable inputs) is also supported. The WebP demuxer splits the input stream into packets containing one frame. It also marks the key frames properly. The loop count is ignored by default (same behaviour as animated PNG and GIF), it may be enabled by the option '-ignore_loop 0'. The frame rate is set according to the frame delay in the ANMF chunk. If the delay is too low, or the image is not animated, the default frame rate is set to 10 fps, similarly to other WebP libraries and browsers. The fate suite was updated accordingly. Signed-off-by: Josef Zlomek --- Changelog | 1 + doc/demuxers.texi | 28 ++ libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/webpdec.c | 384 ++++++++++++++++++ tests/ref/fate/exif-image-webp | 4 +- tests/ref/fate/webp-rgb-lena-lossless | 2 +- tests/ref/fate/webp-rgb-lena-lossless-rgb24 | 2 +- tests/ref/fate/webp-rgb-lossless | 2 +- .../fate/webp-rgb-lossless-palette-predictor | 2 +- tests/ref/fate/webp-rgb-lossy-q80 | 2 +- tests/ref/fate/webp-rgba-lossless | 2 +- tests/ref/fate/webp-rgba-lossy-q80 | 2 +- 13 files changed, 424 insertions(+), 9 deletions(-) create mode 100644 libavformat/webpdec.c diff --git a/Changelog b/Changelog index de6eedfd68..22a6d16824 100644 --- a/Changelog +++ b/Changelog @@ -102,6 +102,7 @@ version 6.1: variable-fields elements within the same parent element - ffprobe -output_format option added as an alias of -of - animated WebP decoder +- animated WebP demuxer version 6.0: diff --git a/doc/demuxers.texi b/doc/demuxers.texi index 04293c4813..9c9d0fee17 100644 --- a/doc/demuxers.texi +++ b/doc/demuxers.texi @@ -1158,4 +1158,32 @@ this is set to 0, which means that a sensible value is chosen based on the input format. @end table +@section webp + +Animated WebP demuxer. + +It accepts the following options: + +@table @option +@item -min_delay @var{int} +Set the minimum valid delay between frames in milliseconds. +Range is 0 to 60000. Default value is 10. + +@item -max_webp_delay @var{int} +Set the maximum valid delay between frames in milliseconds. +Range is 0 to 16777215. Default value is 16777215 (over four hours), +the maximum value allowed by the specification. + +@item -default_delay @var{int} +Set the default delay between frames in milliseconds. +Range is 0 to 60000. Default value is 100. + +@item -ignore_loop @var{bool} +WebP files can contain information to loop a certain number of times +(or infinitely). If @option{ignore_loop} is set to true, then the loop +setting from the input will be ignored and looping will not occur. +If set to false, then looping will occur and will cycle the number +of times according to the WebP. Default value is true. +@end table + @c man end DEMUXERS diff --git a/libavformat/Makefile b/libavformat/Makefile index 7ca68a7036..d86e98926d 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -628,6 +628,7 @@ OBJS-$(CONFIG_WEBM_MUXER) += matroskaenc.o matroska.o \ av1.o avlanguage.o OBJS-$(CONFIG_WEBM_DASH_MANIFEST_MUXER) += webmdashenc.o OBJS-$(CONFIG_WEBM_CHUNK_MUXER) += webm_chunk.o +OBJS-$(CONFIG_WEBP_DEMUXER) += webpdec.o OBJS-$(CONFIG_WEBP_MUXER) += webpenc.o OBJS-$(CONFIG_WEBVTT_DEMUXER) += webvttdec.o subtitles.o OBJS-$(CONFIG_WEBVTT_MUXER) += webvttenc.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 305fa46532..23f6ef7f7d 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -511,6 +511,7 @@ extern const FFInputFormat ff_webm_dash_manifest_demuxer; extern const FFOutputFormat ff_webm_dash_manifest_muxer; extern const FFOutputFormat ff_webm_chunk_muxer; extern const FFOutputFormat ff_webp_muxer; +extern const FFInputFormat ff_webp_demuxer; extern const FFInputFormat ff_webvtt_demuxer; extern const FFOutputFormat ff_webvtt_muxer; extern const FFInputFormat ff_wsaud_demuxer; diff --git a/libavformat/webpdec.c b/libavformat/webpdec.c new file mode 100644 index 0000000000..446e181156 --- /dev/null +++ b/libavformat/webpdec.c @@ -0,0 +1,384 @@ +/* + * WebP demuxer + * Copyright (c) 2020 Pexeso Inc. + * + * 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 + */ + +/** + * @file + * WebP demuxer. + */ + +#include "libavformat/demux.h" +#include "avformat.h" +#include "internal.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" + +typedef struct WebPDemuxContext { + const AVClass *class; + /** + * Time span in milliseconds before the next frame + * should be drawn on screen. + */ + int delay; + /** + * Minimum allowed delay between frames in milliseconds. + * Values below this threshold are considered to be invalid + * and set to value of default_delay. + */ + int min_delay; + int max_delay; + int default_delay; + + /* + * loop options + */ + int ignore_loop; ///< ignore loop setting + int num_loop; ///< number of times to loop the animation + int cur_loop; ///< current loop counter + int64_t file_start; ///< start position of the current animation file + uint32_t remaining_size; ///< remaining size of the current animation file + + /* + * variables for the key frame detection + */ + int nb_frames; ///< number of frames of the current animation file + int vp8x_flags; + int canvas_width; ///< width of the canvas + int canvas_height; ///< height of the canvas +} WebPDemuxContext; + +#define VP8X_FLAG_ANIMATION 0x02 +/** + * Major web browsers display WebPs at ~10-15fps when rate is not + * explicitly set or have too low values. We assume default rate to be 10. + * Default delay = 1000 microseconds / 10fps = 100 milliseconds per frame. + */ +#define WEBP_DEFAULT_DELAY 100 +/** + * By default delay values less than this threshold considered to be invalid. + */ +#define WEBP_MIN_DELAY 10 + +static int webp_probe(const AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if (AV_RB32(b) == MKBETAG('R', 'I', 'F', 'F') && + AV_RB32(b + 8) == MKBETAG('W', 'E', 'B', 'P')) + return AVPROBE_SCORE_MAX; + + return 0; +} + +static int webp_read_header(AVFormatContext *s) +{ + WebPDemuxContext *wdc = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st; + int ret, n; + uint32_t chunk_type, chunk_size; + int canvas_width = 0; + int canvas_height = 0; + int width = 0; + int height = 0; + + wdc->delay = wdc->default_delay; + wdc->num_loop = 1; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + wdc->file_start = avio_tell(pb); + wdc->remaining_size = avio_size(pb) - wdc->file_start; + + while (wdc->remaining_size > 8 && !avio_feof(pb)) { + chunk_type = avio_rl32(pb); + chunk_size = avio_rl32(pb); + if (chunk_size == UINT32_MAX) + return AVERROR_INVALIDDATA; + chunk_size += chunk_size & 1; + if (avio_feof(pb)) + break; + + if (wdc->remaining_size < 8 + chunk_size) + return AVERROR_INVALIDDATA; + + if (chunk_type == MKTAG('R', 'I', 'F', 'F')) { + wdc->remaining_size = 8 + chunk_size; + chunk_size = 4; + } + + wdc->remaining_size -= 8 + chunk_size; + + switch (chunk_type) { + case MKTAG('V', 'P', '8', 'X'): + if (chunk_size >= 10) { + wdc->vp8x_flags = avio_r8(pb); + avio_skip(pb, 3); + canvas_width = avio_rl24(pb) + 1; + canvas_height = avio_rl24(pb) + 1; + ret = avio_skip(pb, chunk_size - 10); + } else + ret = avio_skip(pb, chunk_size); + break; + case MKTAG('V', 'P', '8', ' '): + if (chunk_size >= 10) { + avio_skip(pb, 6); + width = avio_rl16(pb) & 0x3fff; + height = avio_rl16(pb) & 0x3fff; + ret = avio_skip(pb, chunk_size - 10); + } else + ret = avio_skip(pb, chunk_size); + break; + case MKTAG('V', 'P', '8', 'L'): + if (chunk_size >= 5) { + avio_skip(pb, 1); + n = avio_rl32(pb); + width = (n & 0x3fff) + 1; // first 14 bits + height = ((n >> 14) & 0x3fff) + 1; // next 14 bits + ret = avio_skip(pb, chunk_size - 5); + } else + ret = avio_skip(pb, chunk_size); + break; + case MKTAG('A', 'N', 'M', 'F'): + if (chunk_size >= 12) { + avio_skip(pb, 6); + width = avio_rl24(pb) + 1; + height = avio_rl24(pb) + 1; + ret = avio_skip(pb, chunk_size - 12); + } else + ret = avio_skip(pb, chunk_size); + break; + default: + ret = avio_skip(pb, chunk_size); + break; + } + + if (ret < 0) + return ret; + + // set canvas size if no VP8X chunk was present + if (!canvas_width && width > 0) + canvas_width = width; + if (!canvas_height && height > 0) + canvas_height = height; + } + + // WebP format operates with time in "milliseconds", therefore timebase is 1/1000 + avpriv_set_pts_info(st, 64, 1, 1000); + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + st->codecpar->codec_id = AV_CODEC_ID_WEBP; + st->codecpar->codec_tag = MKTAG('W', 'E', 'B', 'P'); + st->codecpar->width = canvas_width; + st->codecpar->height = canvas_height; + st->start_time = 0; + + // jump to start + if ((ret = avio_seek(pb, wdc->file_start, SEEK_SET)) < 0) + return ret; + wdc->remaining_size = 0; + + return 0; +} + +static int webp_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + WebPDemuxContext *wdc = s->priv_data; + AVIOContext *pb = s->pb; + int ret, n; + int64_t packet_start = avio_tell(pb); + int64_t packet_end; + uint32_t chunk_type; + uint32_t chunk_size; + int width = 0; + int height = 0; + int is_frame = 0; + int is_animation = wdc->vp8x_flags & VP8X_FLAG_ANIMATION; + int key_frame = 0; + + if (wdc->remaining_size == 0) { + wdc->remaining_size = avio_size(pb) - avio_tell(pb); + if (wdc->remaining_size == 0) { // EOF + int ret; + wdc->delay = wdc->default_delay; + if (wdc->ignore_loop || + (wdc->num_loop && wdc->cur_loop == wdc->num_loop - 1)) + return AVERROR_EOF; + + av_log(s, AV_LOG_DEBUG, "loop: %d\n", wdc->cur_loop); + + wdc->cur_loop++; + ret = avio_seek(pb, wdc->file_start, SEEK_SET); + if (ret < 0) + return AVERROR_INVALIDDATA; + wdc->remaining_size = avio_size(pb) - avio_tell(pb); + packet_start = avio_tell(pb); + } + } + + while (wdc->remaining_size > 0 && !avio_feof(pb)) { + chunk_type = avio_rl32(pb); + chunk_size = avio_rl32(pb); + + if (chunk_size == UINT32_MAX) + return AVERROR_INVALIDDATA; + chunk_size += chunk_size & 1; + + if (avio_feof(pb)) + break; + + // dive into RIFF chunk + if (chunk_type == MKTAG('R', 'I', 'F', 'F') && chunk_size > 4) { + wdc->file_start = avio_tell(pb) - 8; + wdc->remaining_size = 8 + chunk_size; + chunk_size = 4; + } + + switch (chunk_type) { + case MKTAG('A', 'N', 'I', 'M'): + if (chunk_size >= 6) { + avio_seek(pb, 4, SEEK_CUR); + wdc->num_loop = avio_rb16(pb); + avio_seek(pb, chunk_size - 6, SEEK_CUR); + } + break; + case MKTAG('V', 'P', '8', ' '): + if (is_frame && !is_animation) + goto flush; + is_frame = 1; + + if (chunk_size >= 10) { + avio_skip(pb, 6); + width = avio_rl16(pb) & 0x3fff; + height = avio_rl16(pb) & 0x3fff; + wdc->nb_frames++; + ret = avio_skip(pb, chunk_size - 10); + } else + ret = avio_skip(pb, chunk_size); + break; + case MKTAG('V', 'P', '8', 'L'): + if (is_frame && !is_animation) + goto flush; + is_frame = 1; + + if (chunk_size >= 5) { + avio_skip(pb, 1); + n = avio_rl32(pb); + width = (n & 0x3fff) + 1; // first 14 bits + height = ((n >> 14) & 0x3fff) + 1; // next 14 bits + wdc->nb_frames++; + ret = avio_skip(pb, chunk_size - 5); + } else + ret = avio_skip(pb, chunk_size); + break; + case MKTAG('A', 'N', 'M', 'F'): + if (chunk_size >= 16) { + avio_skip(pb, 6); + width = avio_rl24(pb) + 1; + height = avio_rl24(pb) + 1; + wdc->delay = avio_rl24(pb); + avio_skip(pb, 1); // anmf_flags + if (wdc->delay < wdc->min_delay) + wdc->delay = wdc->default_delay; + wdc->delay = FFMIN(wdc->delay, wdc->max_delay); + chunk_size = 16; + ret = 0; + } else + ret = avio_skip(pb, chunk_size); + break; + default: + ret = avio_skip(pb, chunk_size); + break; + } + if (ret == AVERROR_EOF) { + // EOF was reached but the position may still be in the middle + // of the buffer. Seek to the end of the buffer so that EOF is + // handled properly in the next invocation of webp_read_packet. + if ((ret = avio_seek(pb, pb->buf_end - pb->buf_ptr, SEEK_CUR) < 0)) + return ret; + wdc->remaining_size = 0; + return AVERROR_EOF; + } + if (ret < 0) + return ret; + + if (!wdc->canvas_width && width > 0) + wdc->canvas_width = width; + if (!wdc->canvas_height && height > 0) + wdc->canvas_height = height; + + if (wdc->remaining_size < 8 + chunk_size) + return AVERROR_INVALIDDATA; + wdc->remaining_size -= 8 + chunk_size; + + packet_end = avio_tell(pb); + } + +flush: + if ((ret = avio_seek(pb, packet_start, SEEK_SET)) < 0) + return ret; + + if ((ret = av_get_packet(pb, pkt, packet_end - packet_start)) < 0) + return ret; + + key_frame = is_frame && wdc->nb_frames == 1; + if (key_frame) + pkt->flags |= AV_PKT_FLAG_KEY; + else + pkt->flags &= ~AV_PKT_FLAG_KEY; + + pkt->stream_index = 0; + pkt->duration = is_frame ? wdc->delay : 0; + pkt->pts = pkt->dts = AV_NOPTS_VALUE; + pkt->time_base = (AVRational) {1, 1000}; + + if (is_frame && wdc->nb_frames == 1) + s->streams[0]->r_frame_rate = (AVRational) {1000, pkt->duration}; + + return ret; +} + +static const AVOption options[] = { + { "min_delay" , "minimum valid delay between frames (in milliseconds)", offsetof(WebPDemuxContext, min_delay) , AV_OPT_TYPE_INT, {.i64 = WEBP_MIN_DELAY} , 0, 1000 * 60, AV_OPT_FLAG_DECODING_PARAM }, + { "max_webp_delay", "maximum valid delay between frames (in milliseconds)", offsetof(WebPDemuxContext, max_delay) , AV_OPT_TYPE_INT, {.i64 = 0xffffff} , 0, 0xffffff , AV_OPT_FLAG_DECODING_PARAM }, + { "default_delay" , "default delay between frames (in milliseconds)" , offsetof(WebPDemuxContext, default_delay), AV_OPT_TYPE_INT, {.i64 = WEBP_DEFAULT_DELAY}, 0, 1000 * 60, AV_OPT_FLAG_DECODING_PARAM }, + { "ignore_loop" , "ignore loop setting" , offsetof(WebPDemuxContext, ignore_loop) , AV_OPT_TYPE_BOOL,{.i64 = 1} , 0, 1 , AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVClass demuxer_class = { + .class_name = "WebP demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, + .category = AV_CLASS_CATEGORY_DEMUXER, +}; + +const FFInputFormat ff_webp_demuxer = { + .p.name = "webp", + .p.long_name = NULL_IF_CONFIG_SMALL("WebP image"), + .p.flags = AVFMT_GENERIC_INDEX, + .p.priv_class = &demuxer_class, + .priv_data_size = sizeof(WebPDemuxContext), + .read_probe = webp_probe, + .read_header = webp_read_header, + .read_packet = webp_read_packet, +}; diff --git a/tests/ref/fate/exif-image-webp b/tests/ref/fate/exif-image-webp index 73560e8ba0..d0de863c3c 100644 --- a/tests/ref/fate/exif-image-webp +++ b/tests/ref/fate/exif-image-webp @@ -8,8 +8,8 @@ pkt_dts=0 pkt_dts_time=0.000000 best_effort_timestamp=0 best_effort_timestamp_time=0.000000 -duration=1 -duration_time=0.040000 +duration=100 +duration_time=0.100000 pkt_pos=0 pkt_size=39276 width=400 diff --git a/tests/ref/fate/webp-rgb-lena-lossless b/tests/ref/fate/webp-rgb-lena-lossless index c00715a5e7..e784c501eb 100644 --- a/tests/ref/fate/webp-rgb-lena-lossless +++ b/tests/ref/fate/webp-rgb-lena-lossless @@ -1,4 +1,4 @@ -#tb 0: 1/25 +#tb 0: 1/10 #media_type 0: video #codec_id 0: rawvideo #dimensions 0: 128x128 diff --git a/tests/ref/fate/webp-rgb-lena-lossless-rgb24 b/tests/ref/fate/webp-rgb-lena-lossless-rgb24 index 7f8f550afe..395a01fa1b 100644 --- a/tests/ref/fate/webp-rgb-lena-lossless-rgb24 +++ b/tests/ref/fate/webp-rgb-lena-lossless-rgb24 @@ -1,4 +1,4 @@ -#tb 0: 1/25 +#tb 0: 1/10 #media_type 0: video #codec_id 0: rawvideo #dimensions 0: 128x128 diff --git a/tests/ref/fate/webp-rgb-lossless b/tests/ref/fate/webp-rgb-lossless index 8dbdfd6887..1db3ce70f7 100644 --- a/tests/ref/fate/webp-rgb-lossless +++ b/tests/ref/fate/webp-rgb-lossless @@ -1,4 +1,4 @@ -#tb 0: 1/25 +#tb 0: 1/10 #media_type 0: video #codec_id 0: rawvideo #dimensions 0: 12x8 diff --git a/tests/ref/fate/webp-rgb-lossless-palette-predictor b/tests/ref/fate/webp-rgb-lossless-palette-predictor index 92a4ad9810..65537f7ed1 100644 --- a/tests/ref/fate/webp-rgb-lossless-palette-predictor +++ b/tests/ref/fate/webp-rgb-lossless-palette-predictor @@ -1,4 +1,4 @@ -#tb 0: 1/25 +#tb 0: 1/10 #media_type 0: video #codec_id 0: rawvideo #dimensions 0: 100x30 diff --git a/tests/ref/fate/webp-rgb-lossy-q80 b/tests/ref/fate/webp-rgb-lossy-q80 index f61d75ac13..cd43415b95 100644 --- a/tests/ref/fate/webp-rgb-lossy-q80 +++ b/tests/ref/fate/webp-rgb-lossy-q80 @@ -1,4 +1,4 @@ -#tb 0: 1/25 +#tb 0: 1/10 #media_type 0: video #codec_id 0: rawvideo #dimensions 0: 12x8 diff --git a/tests/ref/fate/webp-rgba-lossless b/tests/ref/fate/webp-rgba-lossless index bb654ae442..2f763c6c46 100644 --- a/tests/ref/fate/webp-rgba-lossless +++ b/tests/ref/fate/webp-rgba-lossless @@ -1,4 +1,4 @@ -#tb 0: 1/25 +#tb 0: 1/10 #media_type 0: video #codec_id 0: rawvideo #dimensions 0: 12x8 diff --git a/tests/ref/fate/webp-rgba-lossy-q80 b/tests/ref/fate/webp-rgba-lossy-q80 index d2c2aa3fce..6b114f772e 100644 --- a/tests/ref/fate/webp-rgba-lossy-q80 +++ b/tests/ref/fate/webp-rgba-lossy-q80 @@ -1,4 +1,4 @@ -#tb 0: 1/25 +#tb 0: 1/10 #media_type 0: video #codec_id 0: rawvideo #dimensions 0: 12x8 From patchwork Fri Jun 21 10:43:22 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thilo Borgmann X-Patchwork-Id: 50037 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a59:ae71:0:b0:482:c625:d099 with SMTP id w17csp432274vqz; Fri, 21 Jun 2024 03:44:46 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCUqDkc9jlbTQqQDTWYRfZ09ss3RG7I/U0S6eZft4kW8t0DiXG5PkvwihfKjZTBYmJRm17DDLGPV0+hXyVo+/vl6U/vz8PEDgrO5VQ== X-Google-Smtp-Source: AGHT+IGbaFppc1jYSq6Dnme/3T754/5o1VhTOI6qtlGjthzOa+v/0WBhufMcc/w0h7HVh9m90d8Q X-Received: by 2002:a50:d74f:0:b0:57c:5b7a:87e7 with SMTP id 4fb4d7f45d1cf-57d07e7ad6amr6055380a12.14.1718966686266; Fri, 21 Jun 2024 03:44:46 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1718966686; cv=none; d=google.com; s=arc-20160816; b=xom6JrW+XNhaY/OT7llNYKXvMYf3pNGM0luW9nCqf3NWH++PVZBH3l/7mhzCV013j3 8oL4jCjDwKtHJIDFBA2TF0M+twP8LO2biSF8L1klPd8m+R/YH7J8+La1koPibVFD1BOc 2ujRffo90rshklHsA44sEvUF/Syz0Dy8/tev/g7lgQ806n8jd5G0VbpEFjbaebOEDa8d Bc04ds/U3EyqJd7SLfMD666EofR6vDSxhb/ZPPKtx/WOnE7StUj2aLfQ5iVcLFMobCKt UeCQPdHNc09MjsPryWfox5zr0062p4QeNUvpYJY1BiVU5zYgXdHUpuUnYBn6A6aJxeAj QqjQ== 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:from :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:delivered-to; bh=wY+2Il+Xwl695shJg0RNN4IJB9ydZyYV8NFLsAHkMUk=; fh=uZWItAgYCSLFS5Zum7Ip+PilGyWYOeCwun+We0BTHAs=; b=MhaTcs/GX/dTKh1we0wDkSurARJGlQJ7qTjTOF+tCnDf4pkl5OazlTTuoQhE6xRfgw LaUyO4cnUf8YY9cNmHbSYWHITI0dnC+6WN/gFXBGVbpnffT+Vc2cyNOlIFVbcBki/1DY DZaVWovNKAP+ImHTh3vQC5LbspenTSj4GA0HR3CCp7s1T7PjGNxHcdKQL9TrAtq5rdst E48VeALbZSLgZLobFGOLnsqCNtJ66klST7p3nhbNjluDWroPBIz20AJwPERUTY41hKgn +DdINwi0LnxJdhmtk84rT/vlx+6mvDbICJGJIJqAfi7xHl7Ec5Agh2JrYlPZu9Qu+lvY qlKQ==; dara=google.com 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 4fb4d7f45d1cf-57d3064b5d5si617798a12.179.2024.06.21.03.44.45; Fri, 21 Jun 2024 03:44:46 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 3769B68D8A6; Fri, 21 Jun 2024 13:43:46 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from shout02.mail.de (shout02.mail.de [62.201.172.25]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 53A8A68D7FF for ; Fri, 21 Jun 2024 13:43:31 +0300 (EEST) Received: from postfix03.mail.de (postfix03.bt.mail.de [10.0.121.127]) by shout02.mail.de (Postfix) with ESMTP id 52A11240ED6 for ; Fri, 21 Jun 2024 12:43:27 +0200 (CEST) Received: from smtp01.mail.de (smtp04.bt.mail.de [10.0.121.214]) by postfix03.mail.de (Postfix) with ESMTP id 3A9BC80209 for ; Fri, 21 Jun 2024 12:43:27 +0200 (CEST) Received: from [127.0.0.1] (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp01.mail.de (Postfix) with ESMTPSA id EE19A240C92 for ; Fri, 21 Jun 2024 12:43:26 +0200 (CEST) To: ffmpeg-devel@ffmpeg.org Date: Fri, 21 Jun 2024 12:43:22 +0200 Message-Id: <20240621104323.92453-8-thilo.borgmann@mail.de> In-Reply-To: <20240621104323.92453-1-thilo.borgmann@mail.de> References: <20240621104323.92453-1-thilo.borgmann@mail.de> MIME-Version: 1.0 X-purgate: clean X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate-type: clean X-purgate-Ad: Categorized by eleven eXpurgate (R) http://www.eleven.de X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate: clean X-purgate-size: 10349 X-purgate-ID: 154282::1718966607-E17EB338-2D050F8E/0/0 Subject: [FFmpeg-devel] [PATCH v13 7/8] fate: add test for animated WebP 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: , X-Patchwork-Original-From: Thilo Borgmann via ffmpeg-devel From: Thilo Borgmann Reply-To: FFmpeg development discussions and patches Cc: Thilo Borgmann Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: WI2/E9gZvig+ From: Thilo Borgmann via ffmpeg-devel --- tests/fate/image.mak | 9 ++++ tests/ref/fate/webp-anim | 22 ++++++++ tests/ref/fate/webp-chfmt1 | 23 ++++++++ tests/ref/fate/webp-chfmt2 | 106 +++++++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+) create mode 100644 tests/ref/fate/webp-anim create mode 100644 tests/ref/fate/webp-chfmt1 create mode 100644 tests/ref/fate/webp-chfmt2 diff --git a/tests/fate/image.mak b/tests/fate/image.mak index 753936ec20..37dd0b83d9 100644 --- a/tests/fate/image.mak +++ b/tests/fate/image.mak @@ -566,6 +566,15 @@ fate-webp-rgb-lossy-q80: CMD = framecrc -i $(TARGET_SAMPLES)/webp/rgb_q80.webp FATE_WEBP += fate-webp-rgba-lossy-q80 fate-webp-rgba-lossy-q80: CMD = framecrc -i $(TARGET_SAMPLES)/webp/rgba_q80.webp +FATE_WEBP += fate-webp-anim +fate-webp-anim: CMD = framecrc -i $(TARGET_SAMPLES)/webp/anim.webp + +FATE_WEBP += fate-webp-chfmt1 +fate-webp-chfmt1: CMD = framecrc -i $(TARGET_SAMPLES)/webp/anim_rgb_yuv.webp + +FATE_WEBP += fate-webp-chfmt2 +fate-webp-chfmt2: CMD = framecrc -i $(TARGET_SAMPLES)/webp/anim_yuv_rgb.webp + FATE_WEBP-$(call DEMDEC, IMAGE2, WEBP) += $(FATE_WEBP) FATE_IMAGE_FRAMECRC += $(FATE_WEBP-yes) fate-webp: $(FATE_WEBP-yes) diff --git a/tests/ref/fate/webp-anim b/tests/ref/fate/webp-anim new file mode 100644 index 0000000000..f0d3f1a88f --- /dev/null +++ b/tests/ref/fate/webp-anim @@ -0,0 +1,22 @@ +#tb 0: 1/1000 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 100x70 +#sar 0: 0/1 +0, 0, 0, 80, 28000, 0x2023ba6e +0, 80, 80, 80, 28000, 0x4292b778 +0, 160, 160, 80, 28000, 0x1c972ef1 +0, 240, 240, 80, 28000, 0xa98d8d04 +0, 320, 320, 80, 28000, 0xd323b6af +0, 400, 400, 80, 28000, 0x508aba99 +0, 480, 480, 80, 28000, 0x5c672dda +0, 560, 560, 80, 28000, 0xc8961ebb +0, 640, 640, 1000, 28000, 0x82460e1b +0, 1640, 1640, 80, 28000, 0x3debbfc9 +0, 1720, 1720, 80, 28000, 0x427ab31f +0, 1800, 1800, 80, 28000, 0x6bbdec2e +0, 1880, 1880, 80, 28000, 0x5690b56b +0, 1960, 1960, 80, 28000, 0xb62963f3 +0, 2040, 2040, 80, 28000, 0x68dd37b2 +0, 2120, 2120, 80, 28000, 0x465c47d2 +0, 2200, 2200, 10000, 28000, 0xa92033df diff --git a/tests/ref/fate/webp-chfmt1 b/tests/ref/fate/webp-chfmt1 new file mode 100644 index 0000000000..bdb0616353 --- /dev/null +++ b/tests/ref/fate/webp-chfmt1 @@ -0,0 +1,23 @@ +#tb 0: 1/1000 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 488x488 +#sar 0: 0/1 +0, 0, 0, 80, 952576, 0x22e300c0 +0, 80, 80, 80, 952576, 0x4e7e9a01 +0, 160, 160, 80, 952576, 0x01b6a421 +0, 240, 240, 80, 952576, 0x26f09b88 +0, 320, 320, 80, 952576, 0xbb1404ac +0, 400, 400, 480, 952576, 0x14368b56 +0, 880, 880, 80, 952576, 0x1843fad6 +0, 960, 960, 80, 952576, 0xc3c4bb73 +0, 1040, 1040, 160, 952576, 0x9d662364 +0, 1200, 1200, 160, 952576, 0xf8218a9a +0, 1360, 1360, 160, 952576, 0x5828d888 +0, 1520, 1520, 560, 952576, 0x6a718e32 +0, 2080, 2080, 80, 952576, 0x95b7ff21 +0, 2160, 2160, 80, 952576, 0x84662ce1 +0, 2240, 2240, 720, 952576, 0x11974723 +0, 2960, 2960, 80, 952576, 0xd4a644ef +0, 3040, 3040, 80, 952576, 0x3d29c6a8 +0, 3120, 3120, 720, 952576, 0x3d3a2d40 diff --git a/tests/ref/fate/webp-chfmt2 b/tests/ref/fate/webp-chfmt2 new file mode 100644 index 0000000000..3d00544390 --- /dev/null +++ b/tests/ref/fate/webp-chfmt2 @@ -0,0 +1,106 @@ +#tb 0: 1/1000 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 320x240 +#sar 0: 0/1 +0, 0, 0, 30, 192000, 0x41a50269 +0, 30, 30, 30, 192000, 0xb54a0286 +0, 60, 60, 30, 192000, 0x842c01ab +0, 90, 90, 30, 192000, 0x19b0fd8f +0, 120, 120, 30, 192000, 0x9eb9fb71 +0, 150, 150, 30, 192000, 0x1e11fb1d +0, 180, 180, 30, 192000, 0x4e33fe49 +0, 210, 210, 30, 192000, 0x2e4fffa4 +0, 240, 240, 30, 192000, 0xfa74ff7f +0, 270, 270, 30, 192000, 0x695ff5dd +0, 300, 300, 30, 192000, 0xd263ff87 +0, 330, 330, 30, 192000, 0x8eb2f958 +0, 360, 360, 30, 192000, 0x2630f6dd +0, 390, 390, 30, 192000, 0xf84af899 +0, 420, 420, 30, 192000, 0xed44fcca +0, 450, 450, 30, 192000, 0x94e5f617 +0, 480, 480, 30, 192000, 0x2774f83c +0, 510, 510, 30, 192000, 0x081dfdeb +0, 540, 540, 30, 192000, 0xbca7050c +0, 570, 570, 30, 192000, 0x78211c16 +0, 600, 600, 30, 192000, 0x77de154e +0, 630, 630, 30, 192000, 0x553bfdfc +0, 660, 660, 30, 192000, 0x57e8f16e +0, 690, 690, 30, 192000, 0xccb0ef52 +0, 720, 720, 30, 192000, 0xee15e30e +0, 750, 750, 30, 192000, 0x9166de93 +0, 780, 780, 30, 192000, 0x60c5da31 +0, 810, 810, 30, 192000, 0x1944d84f +0, 840, 840, 30, 192000, 0xd826d9a5 +0, 870, 870, 30, 192000, 0x175fd3ca +0, 900, 900, 30, 192000, 0x993bdae7 +0, 930, 930, 30, 192000, 0x3a0fd8af +0, 960, 960, 30, 192000, 0xf23ad933 +0, 990, 990, 30, 192000, 0x7ad5e2e3 +0, 1020, 1020, 30, 192000, 0x4796f2ee +0, 1050, 1050, 30, 192000, 0x52a0f3ab +0, 1080, 1080, 30, 192000, 0x1b3ef6da +0, 1110, 1110, 30, 192000, 0x7478f41f +0, 1140, 1140, 30, 192000, 0x5e7d00bc +0, 1170, 1170, 30, 192000, 0x4436fc7a +0, 1200, 1200, 30, 192000, 0x4f82f88e +0, 1230, 1230, 30, 192000, 0x6bb7f834 +0, 1260, 1260, 30, 192000, 0xf611f604 +0, 1290, 1290, 30, 192000, 0xb8bff5b0 +0, 1320, 1320, 30, 192000, 0x40a7f673 +0, 1350, 1350, 30, 192000, 0xc922f8a5 +0, 1380, 1380, 30, 192000, 0xefe6f678 +0, 1410, 1410, 30, 192000, 0x33c9f434 +0, 1440, 1440, 30, 192000, 0xca85f2a3 +0, 1470, 1470, 30, 192000, 0x12a8f922 +0, 1500, 1500, 30, 192000, 0x7492f8ae +0, 1530, 1530, 30, 192000, 0x73c5f8bb +0, 1560, 1560, 30, 192000, 0x3f8bf999 +0, 1590, 1590, 30, 192000, 0x3a13f7bf +0, 1620, 1620, 30, 192000, 0x582bff4e +0, 1650, 1650, 30, 192000, 0xca85fed4 +0, 1680, 1680, 30, 192000, 0x51c5fbb3 +0, 1710, 1710, 30, 192000, 0x6326fd07 +0, 1740, 1740, 30, 192000, 0x3c2efc01 +0, 1770, 1770, 30, 192000, 0xc769ff48 +0, 1800, 1800, 30, 192000, 0x954e01d3 +0, 1830, 1830, 30, 192000, 0x3a2ffe57 +0, 1860, 1860, 30, 192000, 0xe453ff5c +0, 1890, 1890, 30, 192000, 0x9aeefc2a +0, 1920, 1920, 30, 192000, 0x437ef61f +0, 1950, 1950, 30, 192000, 0x4eb3fd83 +0, 1980, 1980, 30, 192000, 0x2650015d +0, 2010, 2010, 30, 192000, 0xd82afc78 +0, 2040, 2040, 30, 192000, 0x07d0fd48 +0, 2070, 2070, 30, 192000, 0xc799fcc4 +0, 2100, 2100, 30, 192000, 0xc95d04f3 +0, 2130, 2130, 30, 192000, 0x13eb06ab +0, 2160, 2160, 30, 192000, 0xa7600439 +0, 2190, 2190, 30, 192000, 0x888304fa +0, 2220, 2220, 30, 192000, 0xd8780597 +0, 2250, 2250, 30, 192000, 0x2549049c +0, 2280, 2280, 30, 192000, 0xc8c802a6 +0, 2310, 2310, 30, 192000, 0xcf9d02d0 +0, 2340, 2340, 30, 192000, 0x001001c2 +0, 2370, 2370, 30, 192000, 0xb9d308b0 +0, 2400, 2400, 30, 192000, 0x67a9086a +0, 2430, 2430, 30, 192000, 0x10d908d4 +0, 2460, 2460, 30, 192000, 0x24be0ac2 +0, 2490, 2490, 30, 192000, 0x1cb5099a +0, 2520, 2520, 30, 192000, 0x9c7a0a6a +0, 2550, 2550, 30, 192000, 0x11860d4b +0, 2580, 2580, 30, 192000, 0x1c230cbc +0, 2610, 2610, 30, 192000, 0xa3be0b89 +0, 2640, 2640, 30, 192000, 0x0485081d +0, 2670, 2670, 30, 192000, 0xbe9805fb +0, 2700, 2700, 30, 192000, 0x022701d8 +0, 2730, 2730, 30, 192000, 0xffbc0318 +0, 2760, 2760, 30, 192000, 0x3335028f +0, 2790, 2790, 30, 192000, 0xb5360198 +0, 2820, 2820, 30, 192000, 0x904f0405 +0, 2850, 2850, 30, 192000, 0x16fd0074 +0, 2880, 2880, 30, 192000, 0xb98c001f +0, 2910, 2910, 30, 192000, 0x527bfd4b +0, 2940, 2940, 30, 192000, 0x792bfb20 +0, 2970, 2970, 30, 192000, 0x34acfaea +0, 3000, 3000, 30, 192000, 0x7c6bfe5e From patchwork Fri Jun 21 10:43:23 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thilo Borgmann X-Patchwork-Id: 50040 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a59:ae71:0:b0:482:c625:d099 with SMTP id w17csp435957vqz; Fri, 21 Jun 2024 03:52:48 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCXqIdEtM2nY12NQI0if0FQMqm3eMndmZIdtSCx3SbbTTYlyxsyCoDuKsf1vrSK5zPlhQj9twwEEGVT7DYnZwkT+QcZGv6o6TQ2yGw== X-Google-Smtp-Source: AGHT+IH8apNTUaiQK8MbSStsmNwa8b1azIXbFuAIXu2loF1jrJk2Tkf9YE5G3BSnTfQrzPPpcKll X-Received: by 2002:a17:907:a587:b0:a6f:ac90:754d with SMTP id a640c23a62f3a-a6fac907737mr518672466b.29.1718967168403; Fri, 21 Jun 2024 03:52:48 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1718967168; cv=none; d=google.com; s=arc-20160816; b=xWvo1NOcqr6DQG2lniFT3tIj1D/k4j9iTQTWxmpeNkhO7YX6Jqz52eaEYQe6P3A1bo k266KFzIAwYK3ohbZk8udFOcKjkAZTGotSqgML8OatT62Mp3jrpvCHt2bpoKU1rS1j+H M3yWj7H51CMzn7ShnfJsws5xb/6kFtVSd051ULZ91JnACfMyf0scXr6fkqjTaP34eE5N IjudLNGbbuwOHJjhXCK3UBRpsFh02e0yrWQRdT6oeZ9340eeNZKHEDE0OqPEN6xvQXJB kLEVcFVNwdTJ1ezx8Ro1YMkVJE56LbtZXxzYynQTx2lIpzwtPEG+SET5qFNYeMKPHWAD W9NA== 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:from :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:delivered-to; bh=I/j2lyMxvD8r+Diw0mULo2XNuw1P79kmJez9LqHunyk=; fh=uZWItAgYCSLFS5Zum7Ip+PilGyWYOeCwun+We0BTHAs=; b=bNl86fsbB1FbIXr+Z5lZ8Alw0OzFruniS7TuDw3Sj08H+FEGzirtFMYMW43d1nW3gG SRR1eKZ9oWq8DqQeVlL0UJtFCQ/e3zi3KaOSpFyttL2/NvFPdrfAwxoehcDdQfQKSFJd XrmGCKcw+4QdFIFeBmXkPbgGFQ1CiUtWtTrknnspkigpGsMdkxey2b48RKn51Eg97u9r Q7R6J/cR1ePisRRUXKOznLM7xn7xL2k6+7NRYReaBz/4C8lMCgl9mt88nzyFyEWP73/l VmYUoz8i365lPNeFLuDUzaqeQRbOnBc+J5VQQPri5Dhjp3EJmzdQuL5OQ6scHa8C0bfc GTZg==; dara=google.com 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 a640c23a62f3a-a6fcf5aed44si74287766b.1000.2024.06.21.03.52.48; Fri, 21 Jun 2024 03:52: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 D7F7868D89C; Fri, 21 Jun 2024 13:43:43 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from shout01.mail.de (shout01.mail.de [62.201.172.24]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 4247E68D7EA for ; Fri, 21 Jun 2024 13:43:36 +0300 (EEST) Received: from postfix03.mail.de (postfix03.bt.mail.de [10.0.121.127]) by shout01.mail.de (Postfix) with ESMTP id 9A004240E99 for ; Fri, 21 Jun 2024 12:43:27 +0200 (CEST) Received: from smtp01.mail.de (smtp04.bt.mail.de [10.0.121.214]) by postfix03.mail.de (Postfix) with ESMTP id 8225280209 for ; Fri, 21 Jun 2024 12:43:27 +0200 (CEST) Received: from [127.0.0.1] (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp01.mail.de (Postfix) with ESMTPSA id 4D789240C94 for ; Fri, 21 Jun 2024 12:43:27 +0200 (CEST) To: ffmpeg-devel@ffmpeg.org Date: Fri, 21 Jun 2024 12:43:23 +0200 Message-Id: <20240621104323.92453-9-thilo.borgmann@mail.de> In-Reply-To: <20240621104323.92453-1-thilo.borgmann@mail.de> References: <20240621104323.92453-1-thilo.borgmann@mail.de> MIME-Version: 1.0 X-purgate: clean X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate-type: clean X-purgate-Ad: Categorized by eleven eXpurgate (R) http://www.eleven.de X-purgate: This mail is considered clean (visit http://www.eleven.de for further information) X-purgate: clean X-purgate-size: 3692 X-purgate-ID: 154282::1718966607-E17EB338-B98ADB1E/0/0 Subject: [FFmpeg-devel] [PATCH v13 8/8] avcodec/webp: export XMP metadata 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: , X-Patchwork-Original-From: Thilo Borgmann via ffmpeg-devel From: Thilo Borgmann Reply-To: FFmpeg development discussions and patches Cc: Thilo Borgmann Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: Bh5OQaOiacdv From: Thilo Borgmann via ffmpeg-devel --- libavcodec/webp.c | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/libavcodec/webp.c b/libavcodec/webp.c index bacf605ff2..c8be673060 100644 --- a/libavcodec/webp.c +++ b/libavcodec/webp.c @@ -38,8 +38,8 @@ * @author Josef Zlomek, Pexeso Inc. * Animation * - * Unimplemented: - * - XMP metadata + * @author Thilo Borgmann + * XMP metadata */ #include "libavutil/common.h" @@ -220,6 +220,7 @@ typedef struct WebPContext { int has_exif; /* set after an EXIF chunk has been processed */ int has_iccp; /* set after an ICCP chunk has been processed */ int duration; /* frame duration in an animation */ + int has_xmp; /* set after an XMP chunk has been processed */ int width; /* image width */ int height; /* image height */ int vp8x_flags; /* global flags from VP8X chunk */ @@ -1469,6 +1470,7 @@ static int webp_decode_frame_common(AVCodecContext *avctx, uint8_t *data, int si // reset metadata bit for each packet s->has_exif = 0; s->has_iccp = 0; + s->has_xmp = 0; while (bytestream2_get_bytes_left(&gb) > 8) { char chunk_str[5] = { 0 }; @@ -1500,6 +1502,7 @@ static int webp_decode_frame_common(AVCodecContext *avctx, uint8_t *data, int si s->canvas_height = 0; s->has_exif = 0; s->has_iccp = 0; + s->has_xmp = 0; ff_progress_frame_unref(&s->canvas_frame); break; case MKTAG('V', 'P', '8', ' '): @@ -1682,12 +1685,39 @@ exif_end: } s->vp8x_flags |= VP8X_FLAG_ANIMATION; break; - case MKTAG('X', 'M', 'P', ' '): - AV_WL32(chunk_str, chunk_type); - av_log(avctx, AV_LOG_WARNING, "skipping unsupported chunk: %s\n", - chunk_str); + case MKTAG('X', 'M', 'P', ' '): { + GetByteContext xmp_gb; + AVDictionary **xmp_metadata = NULL; + uint8_t *buffer; + int xmp_offset = bytestream2_tell(&gb); + + if (s->has_xmp) { + av_log(avctx, AV_LOG_VERBOSE, "Ignoring extra XMP chunk\n"); + goto xmp_end; + } + if (!(s->vp8x_flags & VP8X_FLAG_XMP_METADATA)) + av_log(avctx, AV_LOG_WARNING, + "XMP chunk present, but XMP bit not set in the " + "VP8X header\n"); + + // there are at least chunk_size bytes left to read + buffer = av_malloc(chunk_size + 1); + if (!buffer) { + return AVERROR(ENOMEM); + } + + s->has_xmp = 1; + bytestream2_init(&xmp_gb, data + xmp_offset, size - xmp_offset); + bytestream2_get_buffer(&xmp_gb, buffer, chunk_size); + buffer[chunk_size] = '\0'; + + xmp_metadata = (s->vp8x_flags & VP8X_FLAG_ANIMATION) ? &p->metadata : &s->frame->metadata; + av_dict_set(xmp_metadata, "xmp", buffer, AV_DICT_DONT_STRDUP_VAL); + +xmp_end: bytestream2_skip(&gb, chunk_size); break; + } default: AV_WL32(chunk_str, chunk_type); av_log(avctx, AV_LOG_VERBOSE, "skipping unknown chunk: %s\n",