Hi, On Thu, Jul 9, 2020 at 11:44 AM Nicolas George <geo...@nsup.org> wrote:
> Josef Zlomek (12020-07-08): > > Fixes: 4907 > > > > Adds support for demuxing of animated WebP. > > > > The WebP demuxer splits the input stream into packets containing one > frame. > > It also sets the timing information properly. > > Thanks for the patch. A few comments. > > > > > Signed-off-by: Josef Zlomek <jo...@pex.com> > > --- > > Changelog | 1 + > > doc/demuxers.texi | 28 ++++ > > libavformat/Makefile | 1 + > > libavformat/allformats.c | 1 + > > libavformat/version.h | 2 +- > > libavformat/webpdec.c | 322 +++++++++++++++++++++++++++++++++++++++ > > 6 files changed, 354 insertions(+), 1 deletion(-) > > create mode 100644 libavformat/webpdec.c > > > > diff --git a/Changelog b/Changelog > > index 1e41040a8e..fc0bbdca45 100644 > > --- a/Changelog > > +++ b/Changelog > > @@ -6,6 +6,7 @@ version <next>: > > - MacCaption demuxer > > - PGX decoder > > - animated WebP parser/decoder > > +- animated WebP demuxer > > > > > > version 4.3: > > diff --git a/doc/demuxers.texi b/doc/demuxers.texi > > index 3c15ab9eee..9b5932308b 100644 > > --- a/doc/demuxers.texi > > +++ b/doc/demuxers.texi > > @@ -832,4 +832,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 > > +Set the minimum valid delay between frames in milliseconds. > > +Range is 0 to 60000. Default value is 10. > > + > > +@item max_webp_delay > > +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 > > +Set the default delay between frames in milliseconds. > > +Range is 0 to 60000. Default value is 100. > > Make these durations, with option type AV_OPT_TYPE_DURATION and internal > semantic in microseconds. > > > + > > +@item ignore_loop > > +WebP files can contain information to loop a certain number of times (or > > +infinitely). If @option{ignore_loop} is set to 1, then the loop setting > > +from the input will be ignored and looping will not occur. If set to 0, > > +then looping will occur and will cycle the number of times according to > > +the WebP. Default value is 1. > > Make it boolean. > > Why default to true? > > > +@end table > > + > > @c man end DEMUXERS > > diff --git a/libavformat/Makefile b/libavformat/Makefile > > index 26af859a28..93793de45d 100644 > > --- a/libavformat/Makefile > > +++ b/libavformat/Makefile > > @@ -557,6 +557,7 @@ OBJS-$(CONFIG_WEBM_MUXER) += > matroskaenc.o matroska.o \ > > wv.o vorbiscomment.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 f8527b1fd4..389273ea52 100644 > > --- a/libavformat/allformats.c > > +++ b/libavformat/allformats.c > > @@ -455,6 +455,7 @@ extern AVOutputFormat ff_webm_muxer; > > extern AVInputFormat ff_webm_dash_manifest_demuxer; > > extern AVOutputFormat ff_webm_dash_manifest_muxer; > > extern AVOutputFormat ff_webm_chunk_muxer; > > +extern AVInputFormat ff_webp_demuxer; > > extern AVOutputFormat ff_webp_muxer; > > extern AVInputFormat ff_webvtt_demuxer; > > extern AVOutputFormat ff_webvtt_muxer; > > diff --git a/libavformat/version.h b/libavformat/version.h > > index 75c03fde0a..33cebed85e 100644 > > --- a/libavformat/version.h > > +++ b/libavformat/version.h > > @@ -32,7 +32,7 @@ > > // Major bumping may affect Ticket5467, 5421, 5451(compatibility with > Chromium) > > // Also please add any ticket numbers that you believe might be > affected here > > #define LIBAVFORMAT_VERSION_MAJOR 58 > > -#define LIBAVFORMAT_VERSION_MINOR 48 > > +#define LIBAVFORMAT_VERSION_MINOR 49 > > #define LIBAVFORMAT_VERSION_MICRO 100 > > > > #define LIBAVFORMAT_VERSION_INT > AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ > > diff --git a/libavformat/webpdec.c b/libavformat/webpdec.c > > new file mode 100644 > > index 0000000000..8d6e6df9c0 > > --- /dev/null > > +++ b/libavformat/webpdec.c > > @@ -0,0 +1,322 @@ > > +/* > > + * 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 "libavutil/intreadwrite.h" > > +#include "libavutil/opt.h" > > +#include "avformat.h" > > +#include "internal.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 total_iter; > > + int iter_count; > > + int ignore_loop; > > + > > + int nb_frames; > > + int remaining_size; > > +} 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 (p->filename && ff_guess_image2_codec(p->filename)) { > > + 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 resync(AVFormatContext *s) > > +{ > > + WebPDemuxContext *wdc = s->priv_data; > > + AVIOContext *pb = s->pb; > > + int i; > > + uint64_t state = 0; > > + > > + for (i = 0; i < 12; i++) { > > + state = (state << 8) | avio_r8(pb); > > + if (i == 11) { > > + if ((uint32_t) state == MKBETAG('W', 'E', 'B', 'P')) > > + return 0; > > + i -= 4; > > + } > > + if (i == 7) { > > + if ((state >> 32) != MKBETAG('R', 'I', 'F', 'F')) { > > + i--; > > + } else { > > + uint32_t fsize = av_bswap32(state); > > + if (!(fsize > 15 && fsize <= UINT32_MAX - 10)) { > > + i -= 4; > > + } else { > > + wdc->remaining_size = fsize - 4; > > + } > > + } > > + } > > + if (avio_feof(pb)) > > + return AVERROR_EOF; > > + } > > + return 0; > > +} > > + > > +static int webp_read_header(AVFormatContext *s) > > +{ > > + WebPDemuxContext *wdc = s->priv_data; > > + AVIOContext *pb = s->pb; > > + AVStream *st; > > + int ret, n; > > + int64_t nb_frames = 0, duration = 0; > > + int width = 0, height = 0; > > + uint32_t chunk_type, chunk_size; > > + > > + ret = resync(s); > > + if (ret < 0) > > + return ret; > > + > > + st = avformat_new_stream(s, NULL); > > + if (!st) > > + return AVERROR(ENOMEM); > > + > > + st->codecpar->width = 0; > > + st->codecpar->height = 0; > > + wdc->delay = wdc->default_delay; > > + > > + while (1) { > > + chunk_type = avio_rl32(pb); > > + chunk_size = avio_rl32(pb); > > + if (chunk_size == UINT32_MAX) > > + return AVERROR_INVALIDDATA; > > + chunk_size += chunk_size & 1; > > chunk_size needs to be validated better than that. Otherwise, > chunk_size-10 or such can underflow and that could be bad. > > > + if (avio_feof(pb)) > > + break; > > + > > + switch (chunk_type) { > > + case MKTAG('V', 'P', '8', 'X'): > > + avio_skip(pb, 4); > > + width = avio_rl24(pb) + 1; > > + height = avio_rl24(pb) + 1; > > + break; > > + case MKTAG('V', 'P', '8', ' '): > > + avio_skip(pb, 6); > > + width = avio_rl16(pb) & 0x3fff; > > + height = avio_rl16(pb) & 0x3fff; > > + duration += wdc->delay; > > + nb_frames++; > > + avio_skip(pb, chunk_size - 10); > > + break; > > + case MKTAG('V', 'P', '8', 'L'): > > + avio_skip(pb, 1); > > + n = avio_rl32(pb); > > + width = (n & 0x3fff) + 1; /* first 14 bits */ > > + height = ((n >> 14) & 0x3fff) + 1; /* next 14 bits */ > > + duration += wdc->delay; > > + nb_frames++; > > + avio_skip(pb, chunk_size - 5); > > + break; > > + case MKTAG('A', 'N', 'M', 'F'): > > + avio_skip(pb, 6); > > + width = avio_rl24(pb) + 1; > > + height = avio_rl24(pb) + 1; > > + wdc->delay = avio_rl24(pb); > > + if (wdc->delay < wdc->min_delay) > > + wdc->delay = wdc->default_delay; > > + wdc->delay = FFMIN(wdc->delay, wdc->max_delay); > > + duration += wdc->delay; > > + nb_frames++; > > + avio_skip(pb, chunk_size - 15); > > + break; > > + default: > > + avio_skip(pb, chunk_size); > > + } > > + > > + if (avio_feof(pb)) > > + break; > > + > > + if (st->codecpar->width == 0 && width > 0) > > + st->codecpar->width = width; > > + if (st->codecpar->height == 0 && height > 0) > > + st->codecpar->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->start_time = 0; > > + st->duration = duration; > > + st->nb_frames = nb_frames; > > + > > + /* jump to start because WebP decoder needs header data too */ > > + if (avio_seek(pb, 0, SEEK_SET) != 0) > > + return AVERROR(EIO); > > + 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; > > + int64_t frame_start = avio_tell(pb), frame_end; > > + uint32_t chunk_type, chunk_size; > > + int is_frame = 0; > > + > > + if (wdc->remaining_size == 0) { > > + ret = resync(s); > > + if (ret == AVERROR_EOF) { > > + if (!wdc->ignore_loop && avio_feof(pb) > > + && (wdc->total_iter < 0 || ++wdc->iter_count < > wdc->total_iter)) > > + return avio_seek(pb, 0, SEEK_SET); > > + return AVERROR_EOF; > > + } > > + if (ret < 0) > > + return ret; > > + > > + wdc->delay = wdc->default_delay; > > + } > > + > > + while (!is_frame && 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 (wdc->remaining_size < 8 + chunk_size) > > + return AVERROR_INVALIDDATA; > > + wdc->remaining_size -= 8 + chunk_size; > > + > > + switch (chunk_type) { > > + case MKTAG('A', 'N', 'I', 'M'): > > + avio_skip(pb, 4); > > + wdc->total_iter = avio_rl16(pb); > > + if (wdc->total_iter == 0) > > + wdc->total_iter = -1; > > + ret = avio_skip(pb, chunk_size - 6); > > + break; > > + case MKTAG('A', 'N', 'M', 'F'): > > + avio_skip(pb, 12); > > + wdc->delay = avio_rl24(pb); > > + if (wdc->delay < wdc->min_delay) > > + wdc->delay = wdc->default_delay; > > + wdc->delay = FFMIN(wdc->delay, wdc->max_delay); > > + wdc->nb_frames++; > > + is_frame = 1; > > + ret = avio_skip(pb, chunk_size - 15); > > + break; > > + case MKTAG('V', 'P', '8', ' '): > > + case MKTAG('V', 'P', '8', 'L'): > > + wdc->nb_frames++; > > + is_frame = 1; > > + /* fallthrough */ > > + default: > > + ret = avio_skip(pb, chunk_size); > > + break; > > + } > > + > > + if (ret < 0) > > + return ret; > > + } > > + > > + frame_end = avio_tell(pb); > > + > > > + if (avio_seek(pb, frame_start, SEEK_SET) != frame_start) > > + return AVERROR(EIO); > > If avio_seek() returns an error, please forward it instead of inventing > EIO. > > > + > > + ret = av_get_packet(pb, pkt, frame_end - frame_start); > > + if (ret < 0) > > + return ret; > > + > > > + pkt->flags |= AV_PKT_FLAG_KEY; > > You, yesterday: > > The memcpy is needed. The frames of the WebP animation do not cover the > > whole canvas of the picture, i.e. the decoded VP8 frame is just a > > sub-rectangle of the canvas. The frame changes just a part of the > > canvas. > > That means the packet is not a key frame. > FYI, the conditions under which a packet can be considered a 'key-frame' (that is: seekable) are listed here: https://github.com/webmproject/libwebp/blob/master/src/demux/anim_decode.c#L176 in libwebp. skal/ > > > > + pkt->stream_index = 0; > > + pkt->duration = is_frame ? wdc->delay : 0; > > What is the packet, if it is not a frame? > > Where is the PTS? > > > + > > + 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, > > +}; > > Regards, > > -- > Nicolas George > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > To unsubscribe, visit link above, or email > ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe". _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".