diff options
Diffstat (limited to 'libavformat/gifdec.c')
-rw-r--r-- | libavformat/gifdec.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/libavformat/gifdec.c b/libavformat/gifdec.c new file mode 100644 index 0000000000..bb4c6ec6e6 --- /dev/null +++ b/libavformat/gifdec.c @@ -0,0 +1,337 @@ +/* + * 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; + /** + * 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 max_delay; + int default_delay; + + /** + * loop options + */ + int total_iter; + int iter_count; + int ignore_loop; +} 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 resync(AVIOContext *pb) +{ + int i; + for (i = 0; i < 6; i++) { + int b = avio_r8(pb); + if (b != gif87a_sig[i] && b != gif89a_sig[i]) + i = -(b != 'G'); + if (avio_feof(pb)) + return AVERROR_EOF; + } + return 0; +} + +static int gif_read_header(AVFormatContext *s) +{ + GIFDemuxContext *gdc = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st; + int width, height, ret; + + if ((ret = resync(pb)) < 0) + return ret; + + gdc->delay = gdc->default_delay; + width = avio_rl16(pb); + height = avio_rl16(pb); + + if (width == 0 || 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 = width; + st->codec->height = 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; + gdc->delay = FFMIN(gdc->delay, gdc->max_delay); + + /* skip the rest of the Graphic Control Extension block */ + if ((ret = avio_skip(pb, sb_size - 3)) < 0 ) + return ret; + } else if (ext_label == GIF_APP_EXT_LABEL) { + uint8_t data[256]; + + sb_size = avio_r8(pb); + ret = avio_read(pb, data, sb_size); + if (ret < 0 || !sb_size) + return ret; + + if (sb_size == strlen(NETSCAPE_EXT_STR)) { + sb_size = avio_r8(pb); + ret = avio_read(pb, data, sb_size); + if (ret < 0 || !sb_size) + return ret; + + if (sb_size == 3 && data[0] == 1) { + gdc->total_iter = AV_RL16(data+1); + + if (gdc->total_iter == 0) + gdc->total_iter = -1; + } + } + } + + 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) { +parse_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; + + /* global 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; + } + } else { + avio_seek(pb, -ret, SEEK_CUR); + ret = AVERROR_EOF; + } + + while (GIF_TRAILER != (block_label = avio_r8(pb)) && !avio_feof(pb)) { + if (block_label == GIF_EXTENSION_INTRODUCER) { + if ((ret = gif_read_ext (s)) < 0 ) + goto resync; + } 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"); + goto resync; + } + + if ((ret = gif_skip_subblocks(pb)) < 0) + goto resync; + + 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->duration = gdc->delay; + + /* Graphic Control Extension's scope is single frame. + * Remove its influence. */ + gdc->delay = gdc->default_delay; + frame_parsed = 1; + + break; + } else { + av_log(s, AV_LOG_ERROR, "invalid block label\n"); +resync: + if (!keyframe) + avio_seek(pb, frame_start, SEEK_SET); + if ((ret = resync(pb)) < 0) + return ret; + frame_start = avio_tell(pb) - 6; + keyframe = 1; + goto parse_keyframe; + } + } + + if ((ret >= 0 && !frame_parsed) || ret == AVERROR_EOF) { + /* This might happen when there is no image block + * between extension blocks and GIF_TRAILER or EOF */ + if (!gdc->ignore_loop && (block_label == GIF_TRAILER || avio_feof(pb)) + && (gdc->total_iter < 0 || ++gdc->iter_count < gdc->total_iter)) + return avio_seek(pb, 0, SEEK_SET); + 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 }, + { "max_gif_delay", "maximum valid delay between frames (in hundredths of seconds)", offsetof(GIFDemuxContext, max_delay) , AV_OPT_TYPE_INT, {.i64 = 65535} , 0, 65535 , 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 }, + { "ignore_loop" , "ignore loop setting (netscape extension)" , offsetof(GIFDemuxContext, ignore_loop) , AV_OPT_TYPE_INT, {.i64 = 1} , 0, 1, 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, + .flags = AVFMT_GENERIC_INDEX, + .priv_class = &demuxer_class, +}; |