[FFmpeg-devel] [PATCH 2/2] libavformat: add WebP demuxer

Nicolas George george at nsup.org
Thu Jul 9 12:44:08 EEST 2020


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 <josef at 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
>  
> + at section webp
> +
> +Animated WebP demuxer.
> +
> +It accepts the following options:
> +
> + at table @option

> + at item min_delay
> +Set the minimum valid delay between frames in milliseconds.
> +Range is 0 to 60000. Default value is 10.
> +
> + at 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.
> +
> + at 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.

> +
> + at 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?

> + at 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.


> +    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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <https://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20200709/13f7636f/attachment.sig>


More information about the ffmpeg-devel mailing list