From patchwork Mon Feb 5 19:41:38 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Diego Felix de Souza via ffmpeg-devel X-Patchwork-Id: 46052 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:7b08:b0:19e:8a94:b663 with SMTP id s8csp971732pzh; Mon, 5 Feb 2024 11:42:06 -0800 (PST) X-Forwarded-Encrypted: i=2; AJvYcCVAWVy940iI4cGb1dzJLyrNhj5nfroeKGHfWR4027Wd572Mshk3rBhIEv5slowR+zut7BrEvfkdvSUAuR/ZhTCrXyY+LCphkRzSJw== X-Google-Smtp-Source: AGHT+IEbeuCs/sDgMwn0qwozXxuqm6w9e7attbC0pvkzycG+GQchpdmv/aRhKYzYWgQOTKORJV5y X-Received: by 2002:a50:bb62:0:b0:560:1f1c:cccc with SMTP id y89-20020a50bb62000000b005601f1cccccmr165968ede.4.1707162126189; Mon, 05 Feb 2024 11:42:06 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1707162126; cv=none; d=google.com; s=arc-20160816; b=QzjdZFYDBjri9I+mgO1phSBznE8yh7RTW3OQ6kYx3+E6IW7lFGJ1nbVJOP1E4X2h+I U9BXdxjcjalxyp8LDOdTgLf2JV/Y3VMW5HWiT5bQ7x8pYzKOkOjGFgk648b8UOZCgWdV 48ek7yydlHF3+75anXbHubJNfvfpMdQKDzJWMrab6usKoQ7BIXprci9E+fOJJg2Nv02T M+PQkyavWwB6s+fKhzeY/LAkQxDalsxydDZOG+8PeTF5K2TeVfwqI7LR7tcibK9XfD5Z oX9HQJfllJOlpLbk8bZL1TF04qMV355ROB4f/q6oe4H3nRYe4MCnaJg/vJXBzoPW4MTd fYEA== 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=MQcu0OzW4XHpAzlhR51ig0m60jGjIK/6Ve80qKEZ9CY=; fh=cw7ejNPFF+lVUIHh8FtQKnQVB982++abkYG+9qxPJVM=; b=l0ZPOLf8KDb0lR3rhV7CAPFZ8aA7LeVsesMz6Ny+qkcba3K6aTNTxLHbpUw4whxqSo 1LIjoMyBgK35J7lZeTQOOCPFRsWoKL3NDWjWgcyP4/iHdSD1bGMb4nYDPQoFre6kvyBq bDF/2uFkwzuhXamu6TZopmoDApeh3l46UdeR4qEtHF/0LRzGfeuhHSuEI+3IkzgeL19A 0MYV5JGI6nG6YtTKkrugJ7KMSs4/X5BHgGyoZa4Sb7U3SZSpQSmWsv7kKhIljT/ajzvZ V3IDAE+JJ2eOp8E7zQpK8jjzKkHUbysVMvQyGDa9rcLLlhuEEC3uRoNb4ZabjBDpJVjp rmnQ==; 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 X-Forwarded-Encrypted: i=0; AJvYcCV+Yw0RYfdlD78nBcIUfBBnv2JrPUiwjqCaq8Mg+8yQ3Yfw5yFYT2JabnnVm2uGohIRTqG06dgoY7rVer7Oq6CWWAbKnxbWMgrY/4W+3h7S/x6+18bfnuiqg3EylHcxF60+GjWHOynHixa2jLrk12CCdMd7nPT6CzF23JpYhvFw9kZf4GUSyabkG4CI3jNpGSZpS8QtZulWuF/vQCvB3io0JppgRG5gN4nRm3NC/T2GKoZO7yWm+rIeua+WQtJKrvD8c5phQEJzsf5z/vvgRnHf9LS8A8Oti+2QYtNtmjzyUUOoOSkSGdka2gS096VoexB8R2hNLXJ1nsGcqH1pAD2puEoCHuMgDmnGeG7NoJQSWOtWjXXV/OYZQIMcRZrDInrT1NiegAU37NT4wXn48ihdY8u7fW6HZ43yvp/Tz0hjxT0Cc5KigKhz4m1sPtrqPumbpquSyimNOjyx1asYmMQKIgViJuaK3/LFMfHiIq92emFCfPmreeMt+NQNE8fgWGs7elMBMl67e5PICk3bVuMEq1JstPcWLl6TsfJzQ9U3o27BOL/yZT2jRGER/zKwgF5jA/D2kxvbqxXWCR0Ipc3M4o5/umJZnce6mDGiSmpaPYXFbDzAa6rM/V/wcIB/zD25hdnXaF2ubX6LehuTytok7bat6M2ZZa2IKDv+62intojQCeb3AHPsOFDKc7syuuq1CJP7tO6F6WyLkE/LSp4OMg/0KLz2QzHScblRylaDQty0+6XA17EsahdrcAhDlzywheRXoA354XL7mepXCsgVoVjVoJvlaZobrF5jQKR47eVnAZjlqItCPXxewxDxO6MhJoBEElA1hrlMdaJ+WYO+hQnsEjH6BaI0S3lEdVdFCY0qwsDGrQN65xnERU+Oib+V26M3NxoRwDECcTBzynNFeddF3KoBkGsbg+SxiQfKn4HwHfMIYW3z4/7iGA8xmlb15D bAf8q0zILth4+0JoUNesxfwg2F3MS27YSS0CpgfuP6GKZbNEQsjpIkeZ4SDfFmb2ChHR9LLf2ILxa27p+gVZEjCQ/6435WFemidHtTuknALmmFzqig1mmbZ584482gmeOmw7pGcJwsRWXOLrLkIPBiV47UAZfa96bv3cZ32riBVNl2lQbpTGTeczMe+LMHbKrsCyRgzsBJtrou/EtYOgXMW5udf8zMcoPggNvRvKg7uB9O/gT2Fh8C+bxG3iif9fnple+KxaUBg1r5u8nkgc0HiDVCx4WSl8AhZuQ9gdgE4SOWdKGWIm3wdB10s5mLEPTf9A4DabHk+HtS6r4vsN6siuBpi6DVGD/uisrTm6aDAPpUMA0hIGgSp9uobNlYQuVUpmuMGoxa3xkmP0KsqDIoaqCXXzWew0oPrIvt8SXPhe2SyNEd8sQXk5CmhH2ReUmXcLAgYrokhHMsUjmGxRaLXgdPyiAJ/LWoiLLa/ljCU0ZIeTLLGt3KRFiIiOfdLdXiMdv45btu9cxL5ucupbmIODCVrbkqh25T0uUthoGd69DgMl+us7iO3Dw4sap3Mo8TKdgZNAIAZiN8eGeE/6IzvnM0dV0= Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id n17-20020a056402061100b0055f8936f3c2si208394edv.682.2024.02.05.11.42.05; Mon, 05 Feb 2024 11:42:06 -0800 (PST) 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 984CA68BED3; Mon, 5 Feb 2024 21:41:53 +0200 (EET) 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 497B168D0EF for ; Mon, 5 Feb 2024 21:41:45 +0200 (EET) Received: from postfix01.mail.de (postfix01.bt.mail.de [10.0.121.125]) by shout01.mail.de (Postfix) with ESMTP id D2881240D02 for ; Mon, 5 Feb 2024 20:41:44 +0100 (CET) Received: from smtp01.mail.de (smtp03.bt.mail.de [10.0.121.213]) by postfix01.mail.de (Postfix) with ESMTP id B9C72801CB for ; Mon, 5 Feb 2024 20:41:44 +0100 (CET) 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 56F8C240A85 for ; Mon, 5 Feb 2024 20:41:44 +0100 (CET) To: ffmpeg-devel@ffmpeg.org Date: Mon, 5 Feb 2024 20:41:38 +0100 Message-Id: <20240205194142.37049-2-thilo.borgmann@mail.de> In-Reply-To: <20240205194142.37049-1-thilo.borgmann@mail.de> References: <20240205194142.37049-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: 628 X-purgate-ID: 154282::1707162104-30E261F9-D725A561/0/0 Subject: [FFmpeg-devel] [PATCH v10 1/5] 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: Diego Felix de Souza via ffmpeg-devel Reply-To: FFmpeg development discussions and patches Cc: thilo.borgmann@mail.de Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: UDPlWqnS+pnR From: Thilo Borgmann --- libavcodec/webp.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/libavcodec/webp.c b/libavcodec/webp.c index 54b3fde6dc..4fd107aa0c 100644 --- a/libavcodec/webp.c +++ b/libavcodec/webp.c @@ -59,8 +59,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 Mon Feb 5 19:41:39 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Diego Felix de Souza via ffmpeg-devel X-Patchwork-Id: 46053 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:7b08:b0:19e:8a94:b663 with SMTP id s8csp971819pzh; Mon, 5 Feb 2024 11:42:14 -0800 (PST) X-Google-Smtp-Source: AGHT+IHBGtej69zhbelXyr53G/iaY0ABDP6fzjX302dXIyj4x0HXUB8u5bexFFQ5KTuS/9BN4pk5 X-Received: by 2002:a17:906:4815:b0:a37:3abb:99c2 with SMTP id w21-20020a170906481500b00a373abb99c2mr284946ejq.74.1707162134573; Mon, 05 Feb 2024 11:42:14 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1707162134; cv=none; d=google.com; s=arc-20160816; b=xuS8cfNvwlj6qYj2Pazg5TklV0TXTmkX4ZVDmwhAbJi4ta1N15uN/jPDsSEbNHRY0c ZHxCVB03I32yLWjRtHKWW0n+qR/H92XHEE6k9nmO724fMMcZwSiO4PfEtRKvldKzZxGF DmEG1uxYW/v2yEavxCXe3eXNERJJfJhDQ2aHauibMvROtVLnY7XHdb5NDi/Xj1da0KMR p9r7aG9KumeITLT9lUvgZvnHMm6eDi5j57JBZR0L3Ur0CsWhdTVT8+tdJBwJw2CvVt/Q DVYHojU+nOe2XwvCfy+/1XGq6MH7GYDU8dVLAbhjA6/60SjovHcMG0ypCJwty/Ow6Fsm vi4A== 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=vrqIFQs7D7CjgYwgfkuB7lsQeeKPCIPvvuF+Rnv21yU=; fh=QsWc6vpCLHVjnBL2Dgw1iJFpGobXLLCY5AU3q8u3Z1k=; b=ts7y+vrsgbVM2mCvtrjbJHwCA9+YrWs9brRzmWfdGeisxzsK30BJTrhQCsDp7yTBtp NvPzAI4s68qQqDTeTN0x37OexmZJKdVdbwmewTau4Awh/fBZklq9VbIteGbQQcwIMP34 1tcJUqhvMJ1YIx0BnysodMV0UzXmn0N21S/MaGlET6Ey+qE7EQYKsve3bCBvmuc973c4 b2mlfsrW8vT7GyrMEJN7mbXsSy6xrfrjWIxsmlj2gvKInYz/VLhF10genx2DCX1bVk+x bkibazMW65di67YDpJ9UEJxB043vIJFYr4FncvD+l8vwV6LiTnjGi6c+TxcmUlTuX1r2 T0OQ==; 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 X-Forwarded-Encrypted: i=0; AJvYcCUAqAA8IYvrU8GXnWNHeuwB8Fy+F5cNcLipH0Y67UoMtOB+m5E3uXmdB1/vVUm7fJnB7a0O0uCYmXzbv3q0t9Vw0od97HOWjZmHMATMSgEcjiEWomXWb5LDluFe/UaDyY4i7JnreafqJG5gFN9xyADsun7WJgO8nZik4E6UMhvxYqJR3vHmzZpPDsxOvn4PodBasvMUH5aVmNM0nAPTPFeKWtQ5OPdCwhDKxrblucROAfUcRnIcDGVcLLyyed1uBOO96ocXjRPkGwNiFQPMZmbP1Bx4DBFRNYSjqo04ddN2qBgAAOJxxrAIyBmPzuq73OdQJAs35SBmV8fMVfrpbHYKlJ0Gv7bh60pSMbYBU9hCos0kA0fK59T2T0oPC/IpqHNZXIkpS9jyIfLY1qtVPO6uQWcMrbU0HwhMiWjiwTwXrjlqLoT+RHNh5EKQb2idfd2e8zCo5RrmhKJOt+VsncIvtqpLz4Mj5rA5EB9kuffW/1D2ZC1DycO02E6sQO9FWMHyrd0PNeLVsMtwSPKrN4pKyj9ymm61EQ/95LdFRGB+BFY8AgH9Qu9mmVRATyGbkGTGgQZ3f003GL/E3FbgKSe8RWJdTSo7DGYhlKSxxMblrTWl8W9HrFij37MawRGnTn76gLxfKJk8UK8EsuI0gxZnWMOEUwLEOsJH/gxvF1PKjZLzkTDjrzVtfN+8PMbslDmYpOJbOKanYbO2Jrs5/x400R8TGHwuxwXlT1ThXLzSbO43GMSGvENRB2KxSeByhqGynC5fZ5TforQ3fDZY2fLOirpFxqGtDt6L0yb9Wc28gATIAcMe4oJm6wNySpB9XmtwzPwdA0qMqd6OAQh9HaieSRaVv+2GULndmCdHeynnUsbBteJMOFkdiwkGfluv8cVt7riDs4FwMdLFankUdePggr0h5hchOtNiie/5KR9EhQCDfYXubp5p3tXaQg5fgteLyf OkyPGbmsKvImTd3YKZyAoQjJ2HM5QfqfnYC/hFsBqx4z8Tl3f5g4K1zSGOYQJTtWPaU60CmjXuIr3D5waf6fa6orCfTPR4i+evbkhgocqGOAQ1mOtX7x31hN2dGhy18leYXDOhGmXeE03OY4HEA3PMBGHCvQYGJEkr9atxk5TI9E5nmna+w4SnIdO7gJ7iGZDhFQZbN9eYbvA21xC+94rb/5gHC7HzEkVd+1Pw5zX+cQ2C+olt9LCHip1kpnn7+sx5X2084VVT0e7LdIpJYV5qx/yfycywl0MLgy9jmFM54tmtiHswJm22ZULZsuRv7Z5JOA1mqru0a/KLnS1aeSIf/WeDCH0AP65Y8JyQXb7m4C9epBt4KILUMHNLdh3yRkaqo4NWOUjh9I3FN6TxAI2XJeNrRnmrcVuc1op10Sfj+5ezgHeq1cU1dv2URYrfgZkruTzvqtimJmzVb0oawBHkakd8lHlBoj8DcEcWimeDnp7kEBlxax4L6bGNQsGUHS/9ZOie8je8S9yFFRPJl90uSD8bwu0GkUPsDNk5FWWJHziWsqmN9OWKOGziEgPQ54dz7pAJPV6d4LSxzwXMdVaxKZLZT0otV938Itlq4YgQXiV2GjcezjS3oyHNEZcypaF3 Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id ko15-20020a170907986f00b00a35c634880esi162901ejc.344.2024.02.05.11.42.14; Mon, 05 Feb 2024 11:42:14 -0800 (PST) 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 B072A68D14F; Mon, 5 Feb 2024 21:41:54 +0200 (EET) 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 EACC768D0EF for ; Mon, 5 Feb 2024 21:41:45 +0200 (EET) Received: from postfix03.mail.de (postfix03.bt.mail.de [10.0.121.127]) by shout01.mail.de (Postfix) with ESMTP id 8B5C5240D0F for ; Mon, 5 Feb 2024 20:41:45 +0100 (CET) Received: from smtp01.mail.de (smtp03.bt.mail.de [10.0.121.213]) by postfix03.mail.de (Postfix) with ESMTP id 6FCA380283 for ; Mon, 5 Feb 2024 20:41:45 +0100 (CET) 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 E9482240A70 for ; Mon, 5 Feb 2024 20:41:44 +0100 (CET) To: ffmpeg-devel@ffmpeg.org Date: Mon, 5 Feb 2024 20:41:39 +0100 Message-Id: <20240205194142.37049-3-thilo.borgmann@mail.de> In-Reply-To: <20240205194142.37049-1-thilo.borgmann@mail.de> References: <20240205194142.37049-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: 40303 X-purgate-ID: 154282::1707162105-30E261F9-9700687A/0/0 Subject: [FFmpeg-devel] [PATCH v10 2/5] 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: Diego Felix de Souza via ffmpeg-devel Reply-To: FFmpeg development discussions and patches Cc: thilo.borgmann@mail.de Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: 5XpoGF37JFiU 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/version.h | 2 +- libavcodec/webp.c | 704 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 654 insertions(+), 56 deletions(-) diff --git a/Changelog b/Changelog index c5fb21d198..87f4dbdef4 100644 --- a/Changelog +++ b/Changelog @@ -62,6 +62,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 033344304c..0f72769093 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/version.h b/libavcodec/version.h index f2f14eaed1..ecdbc51c74 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -30,7 +30,7 @@ #include "version_major.h" #define LIBAVCODEC_VERSION_MINOR 39 -#define LIBAVCODEC_VERSION_MICRO 100 +#define LIBAVCODEC_VERSION_MICRO 101 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ LIBAVCODEC_VERSION_MINOR, \ diff --git a/libavcodec/webp.c b/libavcodec/webp.c index 4fd107aa0c..4119ae679d 100644 --- a/libavcodec/webp.c +++ b/libavcodec/webp.c @@ -35,12 +35,15 @@ * Exif metadata * ICC profile * + * @author Josef Zlomek, Pexeso Inc. + * Animation + * * Unimplemented: - * - Animation * - XMP metadata */ #include "libavutil/imgutils.h" +#include "libavutil/colorspace.h" #define BITSTREAM_READER_LE #include "avcodec.h" @@ -67,6 +70,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, @@ -191,6 +202,8 @@ typedef struct ImageContext { typedef struct WebPContext { VP8Context v; /* VP8 Context used for lossy decoding */ GetBitContext gb; /* bitstream reader for main image chunk */ + ThreadFrame 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,7 +217,22 @@ typedef struct WebPContext { int has_iccp; /* set after an ICCP chunk has been processed */ 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 */ @@ -1089,7 +1117,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; } @@ -1303,7 +1330,6 @@ static int vp8_lossy_decode_frame(AVCodecContext *avctx, AVFrame *p, s->v.actually_webp = 1; } avctx->pix_fmt = s->has_alpha ? AV_PIX_FMT_YUVA420P : AV_PIX_FMT_YUV420P; - s->lossless = 0; if (data_size > INT_MAX) { av_log(avctx, AV_LOG_ERROR, "unsupported chunk size\n"); @@ -1332,40 +1358,17 @@ 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) { 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; - - if (bytestream2_get_le32(&gb) != MKTAG('W', 'E', 'B', 'P')) { - av_log(avctx, AV_LOG_ERROR, "missing WEBP tag\n"); - return AVERROR_INVALIDDATA; - } + bytestream2_init(&gb, data, size); while (bytestream2_get_bytes_left(&gb) > 8) { char chunk_str[5] = { 0 }; @@ -1376,6 +1379,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. */ @@ -1383,10 +1390,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_thread_release_ext_buffer(&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; @@ -1395,8 +1418,12 @@ 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; @@ -1405,14 +1432,16 @@ static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, 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; @@ -1420,7 +1449,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"); @@ -1429,8 +1458,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); @@ -1457,14 +1487,13 @@ static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, 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"); @@ -1477,7 +1506,7 @@ static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, goto exif_end; } - av_dict_copy(&p->metadata, exif_metadata, 0); + av_dict_copy(&s->frame->metadata, exif_metadata, 0); exif_end: av_dict_free(&exif_metadata); @@ -1492,21 +1521,64 @@ 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; - sd = av_frame_new_side_data(p, AV_FRAME_DATA_ICC_PROFILE, chunk_size); + sd = av_frame_new_side_data(s->frame, AV_FRAME_DATA_ICC_PROFILE, chunk_size); if (!sd) return AVERROR(ENOMEM); bytestream2_get_buffer(&gb, sd->data, chunk_size); 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; + bytestream2_skip(&gb, 3); // 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", @@ -1522,21 +1594,503 @@ 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->data[0] && + canvas->format == format && + canvas->width == s->canvas_width && + canvas->height == s->canvas_height) + return 0; + + // canvas changes within IPPP sequences will loose 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->data[0]) { + 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_thread_report_progress(&s->canvas_frame, INT_MAX, 0); + 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; + canvas->format = format; + canvas->width = s->canvas_width; + canvas->height = s->canvas_height; + + // 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_thread_release_ext_buffer(s->avctx, &s->canvas_frame); + ret = ff_thread_get_ext_buffer(s->avctx, &s->canvas_frame, AV_GET_BUFFER_FLAG_REF); + if (ret < 0) + return ret; + + 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 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; + + 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 /* 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) }; + 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 /* if (canvas->format == AV_PIX_FMT_YUVA420P) */ { + int src2_step[4] = { 1, 1, 1, 1 }; + 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); + } } - return avpkt->size; + return 0; +} + +static int copy_canvas_to_frame(WebPContext *s, AVFrame *frame, int key_frame) +{ + AVFrame *canvas = s->canvas_frame.f; + 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) { + int src2_step[4] = { 0 }; + 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) */ { + int src2_step[4] = { 0, 0, 0, 0 }; + 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 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; + AVFrame *canvas = s->canvas_frame.f; + 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); + if (ret < 0) + goto end; + + if (s->vp8x_flags & VP8X_FLAG_ANIMATION) { + // VP8 decoder might have changed the width and height of the frame + AVFrame *frame = s->frame; + ret = av_frame_copy_props(canvas, frame); + if (ret < 0) + return ret; + + ret = ff_set_dimensions(s->avctx, canvas->width, canvas->height); + if (ret < 0) + return ret; + + s->avctx->pix_fmt = canvas->format; + } + + ff_thread_finish_setup(s->avctx); + + 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_thread_await_progress(&s->canvas_frame, s->await_progress, 0); + + 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; + + ff_thread_report_progress(&s->canvas_frame, s->await_progress + 1, 0); + } + + p->pts = avpkt->pts; + } + + ret = avpkt->size; + +end: + 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); + s->avctx = avctx; s->pkt = av_packet_alloc(); - if (!s->pkt) + s->canvas_frame.f = av_frame_alloc(); + s->frame = av_frame_alloc(); + if (!s->pkt || !s->canvas_frame.f || !s->frame) { + av_packet_free(&s->pkt); + av_frame_free(&s->canvas_frame.f); + 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; return 0; } @@ -1546,6 +2100,9 @@ static av_cold int webp_decode_close(AVCodecContext *avctx) WebPContext *s = avctx->priv_data; av_packet_free(&s->pkt); + ff_thread_release_ext_buffer(&s->canvas_frame); + av_frame_free(&s->canvas_frame.f); + av_frame_free(&s->frame); if (s->initialized) return ff_vp8_decode_free(avctx); @@ -1553,15 +2110,56 @@ static av_cold int webp_decode_close(AVCodecContext *avctx) return 0; } +static void webp_decode_flush(AVCodecContext *avctx) +{ + WebPContext *s = avctx->priv_data; + + ff_thread_release_ext_buffer(&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; + int ret; + + if (dst == src) + return 0; + + ff_thread_release_ext_buffer(&wdst->canvas_frame); + if (wsrc->canvas_frame.f->data[0] && + (ret = ff_thread_ref_frame(&wdst->canvas_frame, &wsrc->canvas_frame)) < 0) + return ret; + + 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", CODEC_LONG_NAME("WebP image"), .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, .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_FRAME_THREADS, - .caps_internal = FF_CODEC_CAP_ICC_PROFILES, + .caps_internal = FF_CODEC_CAP_ICC_PROFILES | FF_CODEC_CAP_ALLOCATE_PROGRESS, }; From patchwork Mon Feb 5 19:41:40 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Diego Felix de Souza via ffmpeg-devel X-Patchwork-Id: 46056 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:7b08:b0:19e:8a94:b663 with SMTP id s8csp971999pzh; Mon, 5 Feb 2024 11:42:40 -0800 (PST) X-Google-Smtp-Source: AGHT+IF5vgT5HcigbOKhTwshShfh9Enlg6kmznyADK8sEeB5RboQbj/kal+h+RYqdU6YFFXZ9Woh X-Received: by 2002:a17:906:b78e:b0:a35:a7ba:c647 with SMTP id dt14-20020a170906b78e00b00a35a7bac647mr658801ejb.3.1707162160086; Mon, 05 Feb 2024 11:42:40 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1707162160; cv=none; d=google.com; s=arc-20160816; b=tAUBNv0D1X1rcLF93nAyxarbqmAtPeKlkS9UBsWVYxXg+hM7OrJgjf+TW2HmBE/+aZ 0GD5HvPpDcsfblKAjTsVFMZE7qED4CZe95lbsK7y4t++Oa5Tlpamv80ra7ZFfYXlgdB6 8YusJWSuwwhfEO8JTsPBvxPFDGTKnWzfZGwjiCJNKiTKDK+IGFgmuLOIhigZC2SppnEF kd+wfMvs+VH+RyQpviDYqfdvbEGp85/a7uinMAljujSpJneXLWhvS13lITNdS0QkD0pu Vw4+epqbGY11MKmMHLkUrsTz/c2DY8UaxS/dTle6sHCG73+MBdXXllldJ6eZHsYPyvwV myiA== 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=8UkhZHUhXs2MLzhFxd0LSQHHK42W4WABsVri5VzOtHk=; fh=6UarRAnudHUwVDH3LMoHBuI1vFxTdIF5q7bS4cGa0uc=; b=z7wIt4DzH+jUJJr2jU8ZcQBcfohRwFKHxPyFzU2azTNxR8gJTNV23nOC5VVohsmWlk 4GvSdC9plorrN623rDUPqcA3lz4RRs9DIDqcfecO8kzJE2nw+6azaa9pizWAV8YmtkJN EcdoIg7xu9Lf7jm5uFvDZ+K/IeuWmxOqNEh4b3EwxZRVPTVTiolK0+DD4x3xEaHfqqxf ErLyiZiB9ieWUCKn3RkhySS23NE9ivAPbsCYF326z5bPCuOQiEcHiXdMCldHPH+dgHH4 fpmxyGqqQ+dUE7uBASm58eW8HEobyIPVB906wkkmUQprKeAoYoZxNK46oTrXrSTUVJO1 elyA==; 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 X-Forwarded-Encrypted: i=0; AJvYcCXkCoCxV+Z5w2CUL0sgg8DmgrUwjIv+Kw+7lNC4l94UmN+4VMfXSGbPceT6ANXBqt54wTUh2sQJ2FKbKafwJBE8h1Iz6Nrt3yz96OH6NRyObQgOQuqKHRtzaNqU98DcblbSNYIwGY3W1JxEAQuw0dly8j1s/He2ibOHxEM7MiFgQ9rSn+YNpRi2nrrvoWRJpkRSc4+6OSVafY2zZsmofo2aSVzvXog9tDUHDFGzJ5OzjihloZ1pvL4KRcBGHaOZNjFuKCofbyrHWG75/X1897sWjZ0CMrjv/tRIxokL/eNcTtzFGYujQjB6SCWK4VjbrsfgrqeYbhF3GN6Uu9SgPlLbZpUd/iRkIwwrABLWI9XIfpzJS60VCpXvfexZwcwvp1hLMqCVyZQJHsRh1TKTdxmVPr07ydyIaCLNo2jCXmrhGOUNUg1+vrE06SVme1ASQRb+9C1KF0XzmwUJ0NhHGUatT35hj15VASLdSuGwkY8AMTgPBB6u0J98txp+ivx/pd/D8PsjVoB91jixDkBjKsu6yDFnYKlCr4r8a1rfv3Zn/3gMOCpOHLzyCwEGt3jHbva8KiqTcFrQvIJAPF0Os0rG2KUgcxnGRls0XBOb90KtayI1Eu5eL7OqnPcCr2v888bRzhywiJip66lD3Ozzq7BjC2P+MVE06mLdL7+qs5wGGYM6WZXbqxQaRveeDsY1u9vEOFm6jESVLk2ISPoAcmU0J2fd+NgbX2/2ImQ+OB+GM7XxMLMvZE183q5nRpb6zGIj9hKWjJCSaPT+sbJoz+fgmfYfF2YiPYfqxFMaeLhQ0NlZpC9w/MZs3ZSk71sOHhNNgHLbjloVXR7Qn+U3Ku56ZAGF3+ZQ+fbqoznstIQJTIlvwANkMkGn381t2iOXYKxBJqsv/vECMn3HAOPe3Eu4/3CN68K3N/5sGTIPnF4Oi9cxZ7yC2esPlkybHF8xm4fXPq 0kFl9nW/R6KIWQCZmaj10P0fkyeE4APDlmLOiG6du2xISHU4a5MLvmIyyojMnxfCYaJ9cVL4eVm27OsOpmWVfsUMMWyCR2gNRYuFs9vmKv1UaRPfHrwO0gvF/Q09UYOpI/aFzuPqLS1DSL6yXGQAdt1f9A+Gh2Zg+mmyzePh5NB07mlIK/EOIiOjYaa6GtfCscfxFg7Y/qIAJJkjNWRpGayOIBiMFzz3QyZ6+YmG2j3exOqmaEL4mPkjOYUEivQmdIP28fvskeXrtTQR79G3k4dnHZxnPA8ainaKD5pv+N2zC5e9tTAFfFmGdSI/XOoZJDvtvpOfLhfsAGOzO88cTaJwLqudcd5HwpO68UM3bjLqHAUocyCg0FV7KNG4TrMMFfm5Lw/F+Phg4vNcxdXnfAcAP9Glw5X3nziU4ltWpIveGAkU4VxEcLhx9Z92ue49mR9t/f4oxJcVgQdlnb1jraQnaF8Wa4BM1SanO5o9UQDq6/BGM6wo2PXIDkH6CxtqghYnKKzM4Mp6VhjdvzCgk/B/gocowC4FcopAP05d5dS1UtPATLMPsqRbTzTirKjHU1K2CfoBug4i7eqJwvn04OCmaHod68Xymq3FpqbmEJBp3ayknWG7Rdbmr4PY6PwJxM Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id gr7-20020a170906e2c700b00a298b1de849si164956ejb.648.2024.02.05.11.42.39; Mon, 05 Feb 2024 11:42:40 -0800 (PST) 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 C96A268D199; Mon, 5 Feb 2024 21:41:57 +0200 (EET) 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 32D5D68D17D for ; Mon, 5 Feb 2024 21:41:51 +0200 (EET) Received: from postfix02.mail.de (postfix02.bt.mail.de [10.0.121.126]) by shout01.mail.de (Postfix) with ESMTP id 218FF240D15 for ; Mon, 5 Feb 2024 20:41:46 +0100 (CET) Received: from smtp01.mail.de (smtp03.bt.mail.de [10.0.121.213]) by postfix02.mail.de (Postfix) with ESMTP id 08B50A00E2 for ; Mon, 5 Feb 2024 20:41:46 +0100 (CET) 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 9D940240A70 for ; Mon, 5 Feb 2024 20:41:45 +0100 (CET) To: ffmpeg-devel@ffmpeg.org Date: Mon, 5 Feb 2024 20:41:40 +0100 Message-Id: <20240205194142.37049-4-thilo.borgmann@mail.de> In-Reply-To: <20240205194142.37049-1-thilo.borgmann@mail.de> References: <20240205194142.37049-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: 6479 X-purgate-ID: 154282::1707162105-30E261F9-DC96EB2E/0/0 Subject: [FFmpeg-devel] [PATCH v10 3/5] 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: Diego Felix de Souza via ffmpeg-devel Reply-To: FFmpeg development discussions and patches Cc: thilo.borgmann@mail.de Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: 3dBLiYIjuWpH From: Thilo Borgmann --- libavcodec/webp.c | 142 +++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 72 deletions(-) diff --git a/libavcodec/webp.c b/libavcodec/webp.c index 4119ae679d..0931112546 100644 --- a/libavcodec/webp.c +++ b/libavcodec/webp.c @@ -1358,7 +1358,76 @@ 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->data[0] && + canvas->format == format && + 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->data[0]) { + 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_thread_report_progress(&s->canvas_frame, INT_MAX, 0); + 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; + canvas->format = format; + canvas->width = s->canvas_width; + canvas->height = s->canvas_height; + + // 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_thread_release_ext_buffer(&s->canvas_frame); + ret = ff_thread_get_ext_buffer(s->avctx, &s->canvas_frame, AV_GET_BUFFER_FLAG_REF); + if (ret < 0) + return ret; + + 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) @@ -1597,77 +1666,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->data[0] && - canvas->format == format && - canvas->width == s->canvas_width && - canvas->height == s->canvas_height) - return 0; - - // canvas changes within IPPP sequences will loose 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->data[0]) { - 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_thread_report_progress(&s->canvas_frame, INT_MAX, 0); - 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; - canvas->format = format; - canvas->width = s->canvas_width; - canvas->height = s->canvas_height; - - // 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_thread_release_ext_buffer(s->avctx, &s->canvas_frame); - ret = ff_thread_get_ext_buffer(s->avctx, &s->canvas_frame, AV_GET_BUFFER_FLAG_REF); - if (ret < 0) - return ret; - - 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 Mon Feb 5 19:41:41 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Diego Felix de Souza via ffmpeg-devel X-Patchwork-Id: 46054 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:7b08:b0:19e:8a94:b663 with SMTP id s8csp971868pzh; Mon, 5 Feb 2024 11:42:23 -0800 (PST) X-Google-Smtp-Source: AGHT+IEmtXr3sVoMiDAueFFoUO3Ta/9VF0XChmtwqDa3wMBXGWLpaqd2gyNKtcWhDrWQVDZ6Myuk X-Received: by 2002:a05:6402:695:b0:55f:5abf:96 with SMTP id f21-20020a056402069500b0055f5abf0096mr250582edy.17.1707162143235; Mon, 05 Feb 2024 11:42:23 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1707162143; cv=none; d=google.com; s=arc-20160816; b=GzcgspI9QD8TCjCV0AltKUDU/eOyr0q/QCnItSh4flBy6tNA8qlc+6JDBykdYrwoNe VQIWZY3LxeZkbjTePuW7rg4EjfXBunjmNusN0PLBzL+tKwx8hySATlM/oOSdbjQmgvms 9sIkykam0ICGLOgdsvaPwbqseCtjlKi4vM5nK+YC8tLstsut8bnQ5DMvL8qthTq3Xq74 nMUpNpRcHjuDLa08w1PDytaKXiTLcMAtikbgSJYXaXZJAMonH4VWAjsIxVzegOmkV1W4 zaL/gmfOp4wGrKuN+dGCBMeZbysX3A+VOQgX13UNbTXaf9hStXo18rFsBSk5AZ69+rAC +Pqw== 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=oSBXzj3usj66lEHHVy3UjO8a0e6TrNOPA3p/ispDwCw=; fh=UyTrxvOItuMUYNL+QwNfUT9oatfxOA1D11de2+rBaeo=; b=Q7UgDV9OA9mKLCV/0tIupnu5eizQp4g002cnNKoXOoQlaw1H2lcKQEqkkVQC8Uz1Vm qOYM6ISLyIL3Q6yQj+Bn20jAsZHJvfm5llkzz8s2AVyG/ynsnoYEW6Z/+UA7fMLpFcww 3/3/tXblnxTOkLSF7aR89Y6crgtyLi5yZqEnVdLSHKNdqmIDgiWgzZBN9Jyqai6BxI5D sfQkZgNKQmg55f28z6JDq0z2/JWrtZX7xrifVTwtACPuBgrrqKS0FoRWWS6lUAUdylFi MM85JP9+GzmTr/rQ6A694Y7gGjpj/PjVfo+ooSRrdHSHGCSTS3QOSiS+iABEzVC0fHK9 QIeA==; 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 X-Forwarded-Encrypted: i=0; AJvYcCWxiCxhYSL1PDGnJkac3K/VruQccEAMawsoN+8ddJfE+b2+Nvn76A88C0IUjcvfx/+J2Mxfp9iu7Z3bOriOdHU8yw0Ad71V7KGnmE992UyTn+/KbUfAFNVXiJGDPSlC9FQsWGUQ0CAOx+ycKHlJb4pBbrEGP8SPdpvUHgm7qx1+lTPaOsTaJLuLGxKQNBbstMtV6lN3jQasg34jhBLsopAlgl+IOVnIQ2Ir+CRj67VEUq7ie8ypl5hZSo6PtEJVtoqFNtLQ4chXUK9tLX13Qj9lJmZPRqrAZKYsm/WJESVmvkSIQWyjmelUSaNDjmMhz18JwGNxVtQzveTAlis5IfEbuSRev/ireCLhBfAqKcUP1uU7eO5kxDG/78c22LpyFASvPL/OYUAseKTtQro1K4y6ek3uGdfTa5B5Td2ZiEnEqakznC0W5X5+g2mx+39cuBDZ/WjoIUqxXu5E+d+4hu2xqo5nibWJqivX+/RQ/x0Jz7CMAW+HrVchl46Vw/qz4Nu3WpXjd95it9PWPV5Tsy4UNylgMPkn9rM8Eos/6Yblwcixy7JOsWW8N2DJbc1ss3Bosm/Pld2EpEGmcvbhdI5jK/gPdt5WV2Ynmacb+yBeFSIKDEcCXviLstEulMTsLfg5Hn8TEippI7szDQobZwq/h4bq2Z9YHsf8pyHwSHGKu1oGgvvVK8JFD8NoOD46ezuuEUn1N3sfxADoeI8aYrHWIyRh0Ser0yd9/Bde9W9LGjMVTxsEp16Df2KAGmUxPqm4WzKVk46dEGHzMdy8FdXHdEI4DYE+SRbqHeI/g5mMXD4nUXqmOVSQtcZas4L+QJUpy/cttdJPRRJE/kFaq+/TtGjbTqDsZl7lj7uroLydyW7+mD0lnkbc+SY8TRLzCrtLquYpgArxq0ox6Fdck31JiTdISrmH+4QVi3YAIyjmu4FAEtkjR3mRb/a50+ETws9Mfr JKEycNyOCRRXPVzHyLy7kUNCEglhx5oBT8uDcAmteaOlVqPoq9G2150cWc2soHhhjylYFT+UZ4+Li5LfygfRPz3TSdA9PqvcxDm8yW1WoeCgC+nWUQF3MSZg9Bo5ztcKWWTPb8N2loyX/OlBCiSvrqzX5LAsDZPC/D3QLDxdANfOJjWYjrBk+6Chu5BCtg4F3K4VS4Y29AgC3LZl29dppyGUPLOiwFavA1+kzM8twqBiv3FVii949vuRF4mI/Wo9np0a+omqGc6JbLn8HWKQ0TVQrUtoEtxrKrMTaN2eaYtvlhF7WG1FdFUiKAAcRCfiyaB/xmR4I+xtOZB46aqwotHILr1V47zlg9XAymrLWtNR3Pay83J6k4XqHCe3vAP/wE7LCIblE/jnAD/WdP5i3pQYKRAmos0/Fbtx1RzySJmI95M9kctcBBxr2ZPFk8H4SEK4Ijjpcn1YE3sIYyfpoSqPys9LhfSJ+SA29z734vM+axIfzpVlKJQ29+i2bUpq/selbJp6iK5JuieLjFj6x4XIESCwdyTQ6g+zTX3fu6HJSr7CUx3i8RDq/W8Nn6cc06Q6WscIvgIIp/hmU7kXaSUrS7z5yEBH0ijbV26otZmO0WDfrnf+GAX06l3D4E4opMbuw= Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id d20-20020aa7c1d4000000b005600c8a9991si211577edp.352.2024.02.05.11.42.22; Mon, 05 Feb 2024 11:42:23 -0800 (PST) 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 C466068D177; Mon, 5 Feb 2024 21:41:55 +0200 (EET) 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 0A13068D127 for ; Mon, 5 Feb 2024 21:41:47 +0200 (EET) Received: from postfix01.mail.de (postfix01.bt.mail.de [10.0.121.125]) by shout02.mail.de (Postfix) with ESMTP id A3E98240D33 for ; Mon, 5 Feb 2024 20:41:46 +0100 (CET) Received: from smtp01.mail.de (smtp03.bt.mail.de [10.0.121.213]) by postfix01.mail.de (Postfix) with ESMTP id 8C7F9801CB for ; Mon, 5 Feb 2024 20:41:46 +0100 (CET) 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 30737240A70 for ; Mon, 5 Feb 2024 20:41:46 +0100 (CET) To: ffmpeg-devel@ffmpeg.org Date: Mon, 5 Feb 2024 20:41:41 +0100 Message-Id: <20240205194142.37049-5-thilo.borgmann@mail.de> In-Reply-To: <20240205194142.37049-1-thilo.borgmann@mail.de> References: <20240205194142.37049-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: 21346 X-purgate-ID: 154282::1707162106-30E261F9-B6C04AAB/0/0 Subject: [FFmpeg-devel] [PATCH v10 4/5] 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: Diego Felix de Souza via ffmpeg-devel Reply-To: FFmpeg development discussions and patches Cc: thilo.borgmann@mail.de Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: TXLV1Xo8rBKY 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/version.h | 2 +- libavformat/webpdec.c | 383 ++++++++++++++++++++ tests/ref/fate/exif-image-webp | 8 +- tests/ref/fate/webp-rgb-lena-lossless | 2 +- tests/ref/fate/webp-rgb-lena-lossless-rgb24 | 2 +- tests/ref/fate/webp-rgb-lossless | 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, 425 insertions(+), 11 deletions(-) create mode 100644 libavformat/webpdec.c diff --git a/Changelog b/Changelog index 87f4dbdef4..cbf15f028a 100644 --- a/Changelog +++ b/Changelog @@ -63,6 +63,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 e4c5b560a6..fcb9f9ee3c 100644 --- a/doc/demuxers.texi +++ b/doc/demuxers.texi @@ -943,4 +943,32 @@ which in turn, acts as a ceiling for the size of scripts that can be read. Default is 1 MiB. @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 05b9b8a115..78ed0977c6 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -622,6 +622,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 b04b43cab3..c6a2308591 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -506,6 +506,7 @@ extern const AVInputFormat 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 AVInputFormat ff_webp_demuxer; extern const AVInputFormat ff_webvtt_demuxer; extern const FFOutputFormat ff_webvtt_muxer; extern const AVInputFormat ff_wsaud_demuxer; diff --git a/libavformat/version.h b/libavformat/version.h index de9cc8e31d..f4a26c2870 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -32,7 +32,7 @@ #include "version_major.h" #define LIBAVFORMAT_VERSION_MINOR 20 -#define LIBAVFORMAT_VERSION_MICRO 100 +#define LIBAVFORMAT_VERSION_MICRO 101 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ LIBAVFORMAT_VERSION_MINOR, \ diff --git a/libavformat/webpdec.c b/libavformat/webpdec.c new file mode 100644 index 0000000000..73c55ee585 --- /dev/null +++ b/libavformat/webpdec.c @@ -0,0 +1,383 @@ +/* + * 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 "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; + +/** + * 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) { + avio_skip(pb, 4); + 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 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); + } + } + + 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('V', 'P', '8', 'X'): + avio_seek(pb, chunk_size, SEEK_CUR); + break; + 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) + 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) + 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 (is_frame) + goto flush; + + 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; + + 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, +}; + +AVInputFormat ff_webp_demuxer = { + .name = "webp", + .long_name = NULL_IF_CONFIG_SMALL("WebP image"), + .priv_data_size = sizeof(WebPDemuxContext), + .read_probe = webp_probe, + .read_header = webp_read_header, + .read_packet = webp_read_packet, + .flags = AVFMT_GENERIC_INDEX, + .priv_class = &demuxer_class, +}; diff --git a/tests/ref/fate/exif-image-webp b/tests/ref/fate/exif-image-webp index 783abefc60..6ebe5f45b0 100644 --- a/tests/ref/fate/exif-image-webp +++ b/tests/ref/fate/exif-image-webp @@ -8,10 +8,10 @@ pkt_dts=0 pkt_dts_time=0.000000 best_effort_timestamp=0 best_effort_timestamp_time=0.000000 -pkt_duration=1 -pkt_duration_time=0.040000 -duration=1 -duration_time=0.040000 +pkt_duration=100 +pkt_duration_time=0.100000 +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-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 Mon Feb 5 19:41:42 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Diego Felix de Souza via ffmpeg-devel X-Patchwork-Id: 46055 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:7b08:b0:19e:8a94:b663 with SMTP id s8csp971929pzh; Mon, 5 Feb 2024 11:42:32 -0800 (PST) X-Google-Smtp-Source: AGHT+IFAZ81vanyo145zP+1h3u9hvjwHdfJolqsYA2x9dC1OoGuMBwEMISITc11OmTpQGLM5b+Di X-Received: by 2002:a2e:b54a:0:b0:2d0:9cc6:96fb with SMTP id a10-20020a2eb54a000000b002d09cc696fbmr85713ljn.44.1707162151865; Mon, 05 Feb 2024 11:42:31 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1707162151; cv=none; d=google.com; s=arc-20160816; b=yJ7aA73h8AOEindzQXEzNeIRT27J70zGO3O7c+yr1NTNJX3HHMxP1MWeQJgYh8urH3 SueBPp2lq0jAUakesh3UqPFEY51cr3xQK+cbWlGtiPIAuzdGmZVUnWaV/kD4EUI4M4Hi xHjcmLNVT6mBvQAqkNZIFHXOjhP4ni+FQb98WURn/tKYSIJwHofua02RTn6YTJoOkxTg Ez8vrPExPxh1eIdQJOF82zEw2a44YrH1VbyqF9qhzASWy2YJEytL74b21Rid39kL5yJK MZHPDrBRn7Uynl//hdUTBg7vN6rc+PTqHCqWC8b1PYB0ZK7GEY5XN8pmB+9dyjCxIET5 +YsA== 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=qzJCZYWClqhzMuylAUAhWRqPbxJt1FJj6ATGRlow5O4=; fh=btOTWVs/N+oR4CgcBZ4ej5xv2xf4yilgsofhOUmxDAQ=; b=iriFHGtHmLxnlIulpB1CO0jpLsB/MU+5Uxiq6E/bd7FUOkSaC10cyvHmX6R7wsd1oQ f5epPq2/JZT0ekW7ft9YfjuYj11HMtgtDMaVD40mj3lFhJYkmHrnGUVrKfpSgEPMvkRS U9CZKaiRCp1b/3ehDWBI9ZfzCjkl1Vu7sY2ZCJQgO0yHbYUH6LZw6npldHsCQC8SxQbR VgDwEbCqIOoIIuill56y9ofDJEkJ6GRN6icPSiDzjSDF+6bWye1nIYrA9Pxu7xQd6WF9 8Z0GHdYTIDWCrV2rssOJs3MzX3gxfT/KMTdwZpCWaxgYgm0hWS1RDeBdTBVIwXvJroOj v4nQ==; 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 X-Forwarded-Encrypted: i=0; AJvYcCUJpMaRub0pcG2hsnGKWGxCzPwKbi8d6yV++zXBBe3XT/vrgF8bW6wnb5pvzn5Wx62M68qq9wXF/Sa1QvtrcOcJE9UtTOxp7r/g/FvUgW74mCEToiGyzi6Ns863y3Q4i3JWi2Snh4k2O7SWMBVo9rvvdNPMUUqUf9XH+hVoIPyHHg+jbWfdJBBNlurrTlbkCmKnbfPueojcua5oUfk25lxQLIOgkA+ZU1kvnF5fPtXCjxszLFoOPjImCPHxl1L7K/Ejo8Z89atPg+x5i2BVZzc5BCZtgj5ZxkS1sdmgQnBVOYf/y0ereafYAC5fEAEFPuPbE00n/bgKlhHTy7BIXBFJTkfRuwC9qx6ZYiz9HzGtZ5PhFnse1hq9B5aBnooHxro9hO5x7LNIdsUxXjJ3q6scPPLASjqGYvccm7qqyEALd6AeH5sdm0iWr7arQdkjS+JHDDu4lJtez8odcekIbisa1JnUC7socBO9FMrzTKYN2cLXwVDu8LHE101B4kcJBfqwX1BfUdi+0xyRj+fMnVOX9Y/iPLEd8i5iHm4W6iXnoA7UahwgXmS0Fok36MuzcdfTRMPbpXOqvGtxj89j2MTOdEmWH9WyYPVcQvO9mzrDewytlnMrWTR2qoCEHf8eo1UET42IlKTfrZh0vpSETU6/JEwPgB21hPda+TafSgHGxsvtzekDDPB8PtkRK/2yJ/3owrIlecoW4Gf+KPLpKIgUBewGr7ZFgP6VY++27m4byhP0kDPjhj0WdrJPp4pO4mCjj+4wd3AkZlPlHtsrMJNh4LgGkd1HKSCvBaus7r9P4qHZIBBfDom2pIP9oTDDUZGXNXEIXWPOeJEjRuV1JBBtq1YPMsOhQkmw8Nl2eWQycJKZLNvHYMmnSVG5mso8l909pdAJ7S42oNNZ8hScI6jJrctTdf3w5spPiykKjzqlGkjVpWXomOuTBEPkD6XGGZVFqV ki8V1DlAt5YAV4RWoln8p793+HN06zuRu3CKFO+SgSpJ90NA/G+GLu29Fk0Pji8hkOHPSr4C08LkqvDLcGsQut068aOt6/FCLUSpeBBugejcSlJnxZAeXxbXjujMCE3p18NynhefDfF6+gbhZFs4jVarksaiedSkHuHEtbKasJQEe0tM3dDrE8Wkb6lBdV8B6CBfxRDWEywxXJ7VmnxWRa1YSUA2oyn3FT8lCv7P54qUNJI0pVeEnpl8hLpLNNxcAUBt3XOcXTWDzX4B70qWBSgDO9cTVtEj7NmN8opmzDWCJ13ZCuj3+l2Oc4r1cYIEKU4WaZddVrhYVhO0Oe34bhiyYENJQWzzDzw7u+ezg1UNS6OLKN2gfp8X1H5oE+8j71NB3PTirG2p3rzLUMdlgCr1p4l8wszQCJGegGlx92jZtAdknW9ITdthiQTKtDHvTONLTqifpLRmL/e/v1kQb0CDx38sAKKVl7wdOXZzwq5iosZlxFnSpE88XkBm854d4ds3wzD7wOf+TAGhqxo7y6yuucsB+fXHDuz2+/dtMEavAmgYCuME6LapVRgvwYuSaQd2r7g0uXdikuAVhwxlLWzzE2Z4sKHdEtJtpjdMf67FTzYqj7dSvd1IvJd4/GQqq2jb4= Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id w7-20020aa7d287000000b0055ed802c22bsi207559edq.528.2024.02.05.11.42.31; Mon, 05 Feb 2024 11:42:31 -0800 (PST) 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 CA2F568D193; Mon, 5 Feb 2024 21:41:56 +0200 (EET) 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 922FE68D177 for ; Mon, 5 Feb 2024 21:41:50 +0200 (EET) Received: from postfix01.mail.de (postfix01.bt.mail.de [10.0.121.125]) by shout02.mail.de (Postfix) with ESMTP id 47398240D45 for ; Mon, 5 Feb 2024 20:41:47 +0100 (CET) Received: from smtp01.mail.de (smtp03.bt.mail.de [10.0.121.213]) by postfix01.mail.de (Postfix) with ESMTP id 2FB3E801CB for ; Mon, 5 Feb 2024 20:41:47 +0100 (CET) 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 C0C2A240A3D for ; Mon, 5 Feb 2024 20:41:46 +0100 (CET) To: ffmpeg-devel@ffmpeg.org Date: Mon, 5 Feb 2024 20:41:42 +0100 Message-Id: <20240205194142.37049-6-thilo.borgmann@mail.de> In-Reply-To: <20240205194142.37049-1-thilo.borgmann@mail.de> References: <20240205194142.37049-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: 2185 X-purgate-ID: 154282::1707162107-30E261F9-6E91DAAA/0/0 Subject: [FFmpeg-devel] [PATCH v10 5/5] 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: Diego Felix de Souza via ffmpeg-devel Reply-To: FFmpeg development discussions and patches Cc: thilo.borgmann@mail.de Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: njnLT0GJmJdf From: Thilo Borgmann --- tests/fate/image.mak | 3 +++ tests/ref/fate/webp-anim | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/ref/fate/webp-anim diff --git a/tests/fate/image.mak b/tests/fate/image.mak index 400199c28a..2e0d1e8e3f 100644 --- a/tests/fate/image.mak +++ b/tests/fate/image.mak @@ -567,6 +567,9 @@ 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/130227-100431-6817p.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..fe7a53a235 --- /dev/null +++ b/tests/ref/fate/webp-anim @@ -0,0 +1,22 @@ +#tb 0: 2/25 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 100x70 +#sar 0: 0/1 +0, 0, 0, 1, 28000, 0x2023ba6e +0, 1, 1, 1, 28000, 0x4292b778 +0, 2, 2, 1, 28000, 0x9772a187 +0, 3, 3, 1, 28000, 0x9599bb3b +0, 4, 4, 1, 28000, 0xa1d6b949 +0, 5, 5, 1, 28000, 0x153bb9fc +0, 6, 6, 1, 28000, 0x6ba8d83b +0, 7, 7, 1, 28000, 0xed2a4316 +0, 8, 8, 12, 28000, 0xe7994c44 +0, 21, 21, 1, 28000, 0x15ec2f76 +0, 22, 22, 1, 28000, 0x96522a6b +0, 23, 23, 1, 28000, 0xbbae1e30 +0, 24, 24, 1, 28000, 0xa2baab83 +0, 25, 25, 1, 28000, 0x09f1aba0 +0, 26, 26, 1, 28000, 0x09f1aba0 +0, 27, 27, 1, 28000, 0xe761bbc0 +0, 28, 28, 125, 28000, 0xe761bbc0