From 5603b2bf6e00f0f7d96448535f9fe6c293308225 Mon Sep 17 00:00:00 2001 From: Vitaliy E Sugrobov Date: Fri, 30 Nov 2012 12:58:56 +0400 Subject: [PATCH] Gif demuxer Gif demuxer is capable of extracting multiple frames from gif file. In conjunction with gif decoder it implements support for reading animated gifs. Demuxer has two options available to user: default_delay and min_delay. These options are for protection from too rapid gif animations. In practice it is standard approach to slow down rendering of this kind of gifs. If you try to play gif with delay between frames of one hundredth of second (100fps) using one of major web browsers, you get significantly slower playback, around 10 fps. This is because browser detects that delay value is less than some threshold (usually 2 hundredths of second) and reset it to default value (usually 10 hundredths of second, which corresponds to 10fps). Manipulating these options user can achieve the same effect during conversion to some video format. Otherwise user can set them to not protect from rapid animations at all. The other case when these options necessary is for gif images encoded according to gif87a standard since prior to gif89a there was no delay information included in file. Bump lavf minor version. Signed-off-by: Vitaliy E Sugrobov --- doc/general.texi | 2 +- libavformat/Makefile | 1 + libavformat/allformats.c | 2 +- libavformat/gifdec.c | 292 +++++++++++++++++++++++++++++++++++++++ libavformat/version.h | 2 +- 5 files changed, 296 insertions(+), 3 deletions(-) create mode 100755 libavformat/gifdec.c diff --git a/doc/general.texi b/doc/general.texi index 762b94eff9..9a5dfcdf33 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -215,7 +215,7 @@ library: @item G.723.1 @tab X @tab X @item G.729 BIT @tab X @tab X @item G.729 raw @tab @tab X -@item GIF Animation @tab X @tab +@item GIF Animation @tab X @tab X @item GXF @tab X @tab X @tab General eXchange Format SMPTE 360M, used by Thomson Grass Valley playout servers. diff --git a/libavformat/Makefile b/libavformat/Makefile index 3aa3f4f10b..bc4e076823 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -113,6 +113,7 @@ OBJS-$(CONFIG_FOURXM_DEMUXER) += 4xm.o OBJS-$(CONFIG_FRAMECRC_MUXER) += framecrcenc.o framehash.o OBJS-$(CONFIG_FRAMEMD5_MUXER) += md5enc.o framehash.o OBJS-$(CONFIG_GIF_MUXER) += gif.o +OBJS-$(CONFIG_GIF_DEMUXER) += gifdec.o OBJS-$(CONFIG_GSM_DEMUXER) += gsmdec.o OBJS-$(CONFIG_GXF_DEMUXER) += gxf.o OBJS-$(CONFIG_GXF_MUXER) += gxfenc.o audiointerleave.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index e183d4013c..2c32cc28fd 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -111,7 +111,7 @@ void av_register_all(void) REGISTER_MUXDEMUX (G722, g722); REGISTER_MUXDEMUX (G723_1, g723_1); REGISTER_DEMUXER (G729, g729); - REGISTER_MUXER (GIF, gif); + REGISTER_MUXDEMUX (GIF, gif); REGISTER_DEMUXER (GSM, gsm); REGISTER_MUXDEMUX (GXF, gxf); REGISTER_MUXDEMUX (H261, h261); diff --git a/libavformat/gifdec.c b/libavformat/gifdec.c new file mode 100755 index 0000000000..f566239098 --- /dev/null +++ b/libavformat/gifdec.c @@ -0,0 +1,292 @@ +/* + * 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" +#include "libavcodec/gif.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; + +/** + * 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) +{ + /* check magick */ + if (memcmp(p->buf, gif87a_sig, 6) && memcmp(p->buf, gif89a_sig, 6)) + return 0; + + /* width or height contains zero? */ + if (!AV_RL16(&p->buf[6]) || !AV_RL16(&p->buf[8])) + return 0; + + return AVPROBE_SCORE_MAX; +} + +static int gif_read_header(AVFormatContext *s) +{ + GIFDemuxContext *gdc = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st; + int ret; + + /* skip 6-byte magick */ + if ((ret = avio_skip(pb, 6)) < 0) + return ret; + + gdc->delay = gdc->default_delay; + gdc->width = avio_rl16(pb); + gdc->height = avio_rl16(pb); + + if (gdc->width == 0 || gdc->height == 0) + return AVERROR_INVALIDDATA; + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + + /* GIF format operates with time in "hundredths of second", + * therefore timebase is 1/100 */ + avpriv_set_pts_info(st, 64, 1, 100); + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = AV_CODEC_ID_GIF; + st->codec->width = gdc->width; + st->codec->height = gdc->height; + + /* jump to start because gif decoder needs header data too */ + if (avio_seek(pb, 0, SEEK_SET) != 0) + return AVERROR(EIO); + + return 0; +} + +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) +{ + 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, + keyframe, 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) { + keyframe = memcmp(buf, gif87a_sig, 6) == 0 || + memcmp(buf, gif89a_sig, 6) == 0; + } else if (ret < 0) { + return ret; + } else { + keyframe = 0; + } + + if (keyframe) { + /* 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)) { + 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, frame_end - frame_start); + if (ret < 0) + return ret; + + if (keyframe) + 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; + + /* 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[] = { + { "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 }, + { "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 }, + { NULL }, +}; + +static const AVClass demuxer_class = { + .class_name = "GIF demuxer", + .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 e0e6e4b7f0..4bea059735 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 40 +#define LIBAVFORMAT_VERSION_MINOR 41 #define LIBAVFORMAT_VERSION_MICRO 100 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \