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