[FFmpeg-devel] [PATCH] Animated GIF Support

Paul B Mahol onemda at gmail.com
Thu Oct 18 13:35:56 CEST 2012


On 10/18/12, Vitaliy Sugrobov <vsugrob at hotmail.com> wrote:
>
[...]

>
> Added gif demuxer. Changed gif decoder: now it supports all disposal methods.
>
> Signed-off-by: Vitaliy E Sugrobov <vsugrob at hotmail.com>
> ---
>  libavcodec/gifdec.c      |  315 +++++++++++++++++++++++++++++++++++++---------
>  libavcodec/version.h     |    2 +-
>  libavformat/Makefile     |    1 +
>  libavformat/allformats.c |    2 +-
>  libavformat/gifdec.c     |  309 +++++++++++++++++++++++++++++++++++++++++++++
>  libavformat/version.h    |    2 +-
>  6 files changed, 568 insertions(+), 63 deletions(-)
>  mode change 100644 => 100755 libavcodec/gifdec.c
>  create mode 100755 libavformat/gifdec.c
>
> diff --git a/libavcodec/gifdec.c b/libavcodec/gifdec.c
> old mode 100644
> new mode 100755
> index 3e7799f..8ba61ae
> --- a/libavcodec/gifdec.c
> +++ b/libavcodec/gifdec.c
> @@ -2,6 +2,7 @@
>   * GIF decoder
>   * Copyright (c) 2003 Fabrice Bellard
>   * Copyright (c) 2006 Baptiste Coudurier
> + * Copyright (c) 2012 Vitaliy E Sugrobov
>   *
>   * This file is part of FFmpeg.
>   *
> @@ -32,20 +33,36 @@
>  #define GCE_DISPOSAL_BACKGROUND 2
>  #define GCE_DISPOSAL_RESTORE    3
>
> +/* This value is intentionally set to "transparent white" color.
> + * It is much better to have white background instead of black
> + * when gif image converted to format which not support transparency.
> + */
> +#define GIF_TRANSPARENT_COLOR    0x00ffffff

put this into AVOption, do not hardcode it here.
> +
>  typedef struct GifState {
>      AVFrame picture;
>      int screen_width;
>      int screen_height;
> +    int has_global_palette;
>      int bits_per_pixel;
> +    uint32_t bg_color;
>      int background_color_index;
[...]
>      ret = gif_parse_next_image(s);
>      if (ret < 0)
>          return ret;
> @@ -322,6 +513,10 @@ static av_cold int gif_decode_close(AVCodecContext *avctx)
>      ff_lzw_decode_close(&s->lzw);
>      if(s->picture.data[0])
>          avctx->release_buffer(avctx, &s->picture);
> +
> +    av_free(s->idx_line);
> +    av_free(s->stored_img);

av_freep()
> +
>      return 0;
>  }
>
> diff --git a/libavcodec/version.h b/libavcodec/version.h
> index 17e7468..b2d5c1b 100644
> --- a/libavcodec/version.h
> +++ b/libavcodec/version.h
> @@ -29,7 +29,7 @@
>  #include "libavutil/avutil.h"
>
[...]
> +++ b/libavformat/gifdec.c
> @@ -0,0 +1,309 @@
> +/*
> + * GIF demuxer
> + * Copyright (c) 2012 Vitaliy E Sugrobov
> + *
> + * 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
> + * GIF demuxer.
> + */
> +
> +#include "avformat.h"
> +#include "libavutil/intreadwrite.h"
> +#include "libavutil/opt.h"
> +#include "internal.h"
> +
> +typedef struct GIFDemuxContext {
> +    const AVClass *class;
> +    uint32_t width;
> +    uint32_t height;
> +    /**
> +     * Time span in hundredths of second before
> +     * the next frame should be drawn on screen.
> +     */
> +    int delay;
> +    /**
> +     * Minimum allowed delay between frames in hundredths of
> +     * second. Values below this threshold considered to be
> +     * invalid and set to value of default_delay.
> +     */
> +    int min_delay;
> +    int default_delay;
> +    int total_duration; ///< In hundredths of second.
> +    int frame_idx;
> +} GIFDemuxContext;
> +
> +static const uint8_t gif87a_sig[6] = "GIF87a";
> +static const uint8_t gif89a_sig[6] = "GIF89a";

Duplicated from lavc/gifdec. This should be instead in header and that
is patch prior to this one.
> +
> +#define GIF_TRAILER                 0x3b
> +#define GIF_EXTENSION_INTRODUCER    0x21
> +#define GIF_IMAGE_SEPARATOR         0x2c
> +#define GIF_GCE_EXT_LABEL           0xf9
> +
> +/**
> + * Major web browsers display gifs at ~10-15fps when rate
> + * is not explicitly set or have too low values. We assume default rate to be 10.
> + * Default delay = 100hundredths of second / 10fps = 10hos per frame.
> + */
> +#define GIF_DEFAULT_DELAY   10
> +/**
> + * By default delay values less than this threshold considered to be invalid.
> + */
> +#define GIF_MIN_DELAY       2
> +
> +static int gif_probe(AVProbeData *p)
> +{
> +    if (p->buf_size < 6)
> +        return 0;
> +
> +    /* check magick */
[..]
> +
> +static int gif_skip_subblocks(AVIOContext *pb)
> +{
> +    int sb_size, ret = 0;
> +
> +    while (0x00 != (sb_size = avio_r8(pb))) {
> +        if ((ret = avio_skip(pb, sb_size)) < 0)
> +            return ret;
> +    }
> +
> +    return ret;
> +}
> +
> +static int gif_read_ext (AVFormatContext *s)

extra whitespace
> +{
> +    GIFDemuxContext *gdc = s->priv_data;
> +    AVIOContext *pb = s->pb;
> +    int sb_size, ext_label = avio_r8(pb);
> +    int ret;
> +
> +    if (ext_label == GIF_GCE_EXT_LABEL) {
> +        if ((sb_size = avio_r8(pb)) < 4) {
> +            av_log(s, AV_LOG_FATAL, "Graphic Control Extension block's size less than 4.\n");
> +            return AVERROR_INVALIDDATA;
> +        }
> +
> +        /* skip packed fields */
> +        if ((ret = avio_skip(pb, 1)) < 0)
> +            return ret;
> +
> +        gdc->delay = avio_rl16(pb);
> +
> +        if (gdc->delay < gdc->min_delay)
> +            gdc->delay = gdc->default_delay;
> +
> +        /* skip the rest of the Graphic Control Extension block */
> +        if ((ret = avio_skip(pb, sb_size - 3)) < 0 )
> +            return ret;
> +    }
> +
> +    if ((ret = gif_skip_subblocks(pb)) < 0)
> +        return ret;
> +
> +    return 0;
> +}
> +
> +static int gif_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> +    GIFDemuxContext *gdc = s->priv_data;
> +    AVIOContext *pb = s->pb;
> +    int packed_fields, block_label, ct_size,
> +        is_first_frame, frame_parsed = 0, ret;
> +    int64_t frame_start = avio_tell(pb), frame_end;
> +    unsigned char buf[6];
> +
> +    if ((ret = avio_read(pb, buf, 6)) == 6) {
> +        is_first_frame = memcmp(buf, gif87a_sig, 6) == 0 ||
> +                         memcmp(buf, gif89a_sig, 6) == 0;
> +    } else if (ret < 0)
> +        return ret;
> +    else
> +        is_first_frame = 0;

i would use {, } anway.
> +
> +    if (is_first_frame) {
> +        /* skip 2 bytes of width and 2 of height */
> +        if ((ret = avio_skip(pb, 4)) < 0)
> +            return ret;
> +
> +        packed_fields = avio_r8(pb);
> +
> +        /* skip 1 byte of Background Color Index and 1 byte of Pixel Aspect Ratio */
> +        if ((ret = avio_skip(pb, 2)) < 0)
> +            return ret;
> +
> +        /* glogal color table presence */
> +        if (packed_fields & 0x80) {
> +            ct_size = 3 * (1 << ((packed_fields & 0x07) + 1));
> +
> +            if ((ret = avio_skip(pb, ct_size)) < 0)
> +                return ret;
> +        }
> +
> +        gdc->total_duration = 0;
> +        gdc->frame_idx      = 0;
> +    } else {
> +        avio_seek(pb, -ret, SEEK_CUR);
> +        ret = AVERROR_EOF;
> +    }
> +
> +    while(GIF_TRAILER != (block_label = avio_r8(pb)) && !url_feof(pb)) {
nit: whitespace between while and (

> +        if (block_label == GIF_EXTENSION_INTRODUCER) {
> +            if ((ret = gif_read_ext (s)) < 0 )
> +                return ret;
> +        } else if (block_label == GIF_IMAGE_SEPARATOR) {
> +            /* skip to last byte of Image Descriptor header */
> +            if ((ret = avio_skip(pb, 8)) < 0)
> +                return ret;
> +
> +            packed_fields = avio_r8(pb);
> +
> +            /* local color table presence */
> +            if (packed_fields & 0x80) {
> +                ct_size = 3 * (1 << ((packed_fields & 0x07) + 1));
> +
> +                if ((ret = avio_skip(pb, ct_size)) < 0)
> +                    return ret;
> +            }
> +
> +            /* read LZW Minimum Code Size */
> +            if (avio_r8(pb) < 1) {
> +                av_log(s, AV_LOG_ERROR, "lzw minimum code size must be >= 1\n");
> +                return AVERROR_INVALIDDATA;
> +            }
> +
> +            if ((ret = gif_skip_subblocks(pb)) < 0)
> +                return ret;
> +
> +            frame_end = avio_tell(pb);
> +
> +            if (avio_seek(pb, frame_start, SEEK_SET) != frame_start)
> +                return AVERROR(EIO);
> +
> +            ret = av_get_packet(pb, pkt, (int)(frame_end - frame_start));
> +            if (ret < 0)
> +                return ret;
> +
> +            if (is_first_frame)
> +                pkt->flags |= AV_PKT_FLAG_KEY;
> +
> +            pkt->stream_index = 0;
> +            pkt->pts = gdc->total_duration;
> +            gdc->total_duration += gdc->delay;
> +            pkt->duration = gdc->delay;
> +            pkt->dts = gdc->frame_idx;
> +
> +            /* Avoid invalid timestamps. */
> +            if (pkt->pts < pkt->dts)
> +                pkt->pts = pkt->dts;

Is that really needed? Timestamps are not stored in gif bitstream.
> +
> +            /* Graphic Control Extension's scope is single frame.
> +             * Remove its influence. */
> +            gdc->delay = gdc->default_delay;
> +            gdc->frame_idx++;
> +            frame_parsed = 1;
> +
> +            break;
> +        } else {
> +            av_log(s, AV_LOG_ERROR, "invalid block label\n");
> +            return AVERROR_INVALIDDATA;
> +        }
> +    }
> +
> +    if (ret >= 0 && !frame_parsed) {
> +        /* This might happen when there is no image block
> +         * between extension blocks and GIF_TRAILER or EOF */
> +        return  AVERROR_EOF;
> +    } else
> +        return ret;
> +}
> +
> +static const AVOption options[] = {
> +    { "gif_min_delay"    , "minimum valid delay between frames (in hundredths of second)", offsetof(GIFDemuxContext, min_delay)    , AV_OPT_TYPE_INT, {.i64 = GIF_MIN_DELAY}    , 0, 100 * 60, AV_OPT_FLAG_DECODING_PARAM },
> +    { "gif_default_delay", "default delay between frames (in hundredths of second)"      , offsetof(GIFDemuxContext, default_delay), AV_OPT_TYPE_INT, {.i64 = GIF_DEFAULT_DELAY}, 0, 100 * 60, AV_OPT_FLAG_DECODING_PARAM },

I do not see much point in gif_ prefix.
> +    { NULL },
> +};
> +
> +static const AVClass demuxer_class = {
> +    .class_name = "GIFDemuxContext",
> +    .item_name  = av_default_item_name,
> +    .option     = options,
> +    .version    = LIBAVUTIL_VERSION_INT,
> +    .category   = AV_CLASS_CATEGORY_DEMUXER,
> +};
> +
> +AVInputFormat ff_gif_demuxer = {
> +    .name           = "gif",
> +    .long_name      = NULL_IF_CONFIG_SMALL("CompuServe Graphics Interchange Format (GIF)"),
> +    .priv_data_size = sizeof(GIFDemuxContext),
> +    .read_probe     = gif_probe,
> +    .read_header    = gif_read_header,
> +    .read_packet    = gif_read_packet,
> +    .priv_class     = &demuxer_class,
> +};
> diff --git a/libavformat/version.h b/libavformat/version.h
> index 749b9e0..fe31a7d 100644
> --- a/libavformat/version.h
> +++ b/libavformat/version.h
> @@ -30,7 +30,7 @@
>  #include "libavutil/avutil.h"
>
>  #define LIBAVFORMAT_VERSION_MAJOR 54
> -#define LIBAVFORMAT_VERSION_MINOR 33
> +#define LIBAVFORMAT_VERSION_MINOR 34
>  #define LIBAVFORMAT_VERSION_MICRO 100
>
>  #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
> --
> 1.7.2.5


Missing doc change for demuxer.


More information about the ffmpeg-devel mailing list