[FFmpeg-devel] [PATCH] avformat/gifdec: cleanup
James Almer
jamrial at gmail.com
Mon May 22 19:04:00 EEST 2023
On 5/22/2023 12:49 PM, Paul B Mahol wrote:
> On 5/21/23, Paul B Mahol <onemda at gmail.com> wrote:
>> Attached patches.
>>
>> This finally removes giant hacks in gif demuxer and allows using gif
>> files via pipe reliably.
>>
>
> Now with smaller diff.
[...]
> From 208d1e83ae9aef8e9b37007df16569cdd4cf25d2 Mon Sep 17 00:00:00 2001
> From: Paul B Mahol <onemda at gmail.com>
> Date: Sat, 20 May 2023 14:13:27 +0200
> Subject: [PATCH 2/2] avformat/gifdec: switch to using gif parser
>
> Update fate tests, more correct as timebase is no more reduced.
This is not the case anymore.
>
> Signed-off-by: Paul B Mahol <onemda at gmail.com>
> ---
> libavcodec/gifdec.c | 11 +-
> libavformat/gifdec.c | 244 +++++++++++--------------------------------
> tests/ref/lavf/gif | 2 +-
> 3 files changed, 70 insertions(+), 187 deletions(-)
>
> diff --git a/libavcodec/gifdec.c b/libavcodec/gifdec.c
> index 0835c5bdd0..f2ab783ef0 100644
> --- a/libavcodec/gifdec.c
> +++ b/libavcodec/gifdec.c
> @@ -472,10 +472,6 @@ static int gif_decode_frame(AVCodecContext *avctx, AVFrame *rframe,
>
> bytestream2_init(&s->gb, avpkt->data, avpkt->size);
>
> - s->frame->pts = avpkt->pts;
> - s->frame->pkt_dts = avpkt->dts;
> - s->frame->duration = avpkt->duration;
> -
> if (avpkt->size >= 6) {
> s->keyframe = memcmp(avpkt->data, gif87a_sig, 6) == 0 ||
> memcmp(avpkt->data, gif89a_sig, 6) == 0;
> @@ -522,6 +518,13 @@ static int gif_decode_frame(AVCodecContext *avctx, AVFrame *rframe,
>
> if ((ret = av_frame_ref(rframe, s->frame)) < 0)
> return ret;
> + if (s->keyframe) {
> + rframe->pict_type = AV_PICTURE_TYPE_I;
> + rframe->flags |= AV_FRAME_FLAG_KEY;
> + } else {
> + rframe->pict_type = AV_PICTURE_TYPE_P;
> + rframe->flags &= ~AV_FRAME_FLAG_KEY;
> + }
> *got_frame = 1;
>
> return bytestream2_tell(&s->gb);
> diff --git a/libavformat/gifdec.c b/libavformat/gifdec.c
> index 1977f46e3a..11fcde36b7 100644
> --- a/libavformat/gifdec.c
> +++ b/libavformat/gifdec.c
> @@ -28,9 +28,12 @@
> #include "libavutil/bprint.h"
> #include "libavutil/intreadwrite.h"
> #include "libavutil/opt.h"
> +#include "avio_internal.h"
> #include "internal.h"
> #include "libavcodec/gif.h"
>
> +#define GIF_PACKET_SIZE 1024
> +
> typedef struct GIFDemuxContext {
> const AVClass *class;
> /**
> @@ -53,9 +56,6 @@ typedef struct GIFDemuxContext {
> int total_iter;
> int iter_count;
> int ignore_loop;
> -
> - int nb_frames;
> - int last_duration;
> } GIFDemuxContext;
>
> /**
> @@ -84,8 +84,8 @@ static int gif_probe(const AVProbeData *p)
>
> static int resync(AVIOContext *pb)
> {
> - int i;
> - for (i = 0; i < 6; i++) {
> + ffio_ensure_seekback(pb, 13);
> + for (int i = 0; i < 6; i++) {
> int b = avio_r8(pb);
> if (b != gif87a_sig[i] && b != gif89a_sig[i])
> i = -(b != 'G');
> @@ -132,6 +132,9 @@ static int gif_read_header(AVFormatContext *s)
> if (!st)
> return AVERROR(ENOMEM);
>
> + if (!(pb->seekable & AVIO_SEEKABLE_NORMAL))
> + goto skip;
> +
> if (flags & 0x80)
> avio_skip(pb, 3 * (1 << ((flags & 0x07) + 1)));
>
> @@ -158,15 +161,37 @@ static int gif_read_header(AVFormatContext *s)
>
> avio_skip(pb, 1);
> delay = avio_rl16(pb);
> - if (delay < gdc->min_delay)
> - delay = gdc->default_delay;
> - delay = FFMIN(delay, gdc->max_delay);
> + delay = delay ? delay : gdc->default_delay;
> duration += delay;
> avio_skip(pb, 1);
> } else {
> avio_skip(pb, block_size);
> }
> gif_skip_subblocks(pb);
> + } else if (subtype == GIF_APP_EXT_LABEL) {
> + uint8_t data[256];
> + int sb_size;
> +
> + sb_size = avio_r8(pb);
> + ret = avio_read(pb, data, sb_size);
> + if (ret < 0 || !sb_size)
> + break;
> +
> + if (sb_size == strlen(NETSCAPE_EXT_STR)) {
> + sb_size = avio_r8(pb);
> + ret = avio_read(pb, data, sb_size);
> + if (ret < 0 || !sb_size)
> + break;
> +
> + if (sb_size == 3 && data[0] == 1) {
> + gdc->total_iter = AV_RL16(data+1);
> + av_log(s, AV_LOG_DEBUG, "Loop count is %d\n", gdc->total_iter);
> +
> + if (gdc->total_iter == 0)
> + gdc->total_iter = -1;
> + }
> + }
> + gif_skip_subblocks(pb);
> } else {
> gif_skip_subblocks(pb);
> }
> @@ -183,203 +208,57 @@ static int gif_read_header(AVFormatContext *s)
> }
> }
>
> +skip:
> + /* jump to start because gif decoder needs header data too */
> + if (avio_seek(pb, 0, SEEK_SET) != 0)
> + return AVERROR(EIO);
> +
> /* GIF format operates with time in "hundredths of second",
> * therefore timebase is 1/100 */
> avpriv_set_pts_info(st, 64, 1, 100);
> + ffstream(st)->need_parsing = AVSTREAM_PARSE_FULL_RAW;
> st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
> st->codecpar->codec_id = AV_CODEC_ID_GIF;
> st->codecpar->width = width;
> st->codecpar->height = height;
> + if (nb_frames > 1) {
> + av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
> + 100, duration / nb_frames, INT_MAX);
> + } else if (duration) {
> + st->avg_frame_rate = (AVRational) { 100, duration };
> + }
> st->start_time = 0;
> st->duration = duration;
> st->nb_frames = nb_frames;
> - if (n) {
> - st->codecpar->sample_aspect_ratio.num = n + 15;
> - st->codecpar->sample_aspect_ratio.den = 64;
> - }
> -
> - /* jump to start because gif decoder needs header data too */
> - if (avio_seek(pb, 0, SEEK_SET) != 0)
> - return AVERROR(EIO);
> + if (n)
> + st->codecpar->sample_aspect_ratio = av_make_q(n + 15, 64);
>
> return 0;
> }
>
> -static int gif_read_ext(AVFormatContext *s)
> +static int gif_read_packet(AVFormatContext *s, AVPacket *pkt)
> {
> 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);
> - av_log(s, AV_LOG_DEBUG, "Loop count is %d\n", gdc->total_iter);
> -
> - if (gdc->total_iter == 0)
> - gdc->total_iter = -1;
> - }
> - }
> + if ((pb->seekable & AVIO_SEEKABLE_NORMAL) &&
> + !gdc->ignore_loop && avio_feof(pb) &&
> + (gdc->total_iter < 0 || (++gdc->iter_count < gdc->total_iter))) {
> + avio_seek(pb, 0, SEEK_SET);
> }
> -
> - if ((ret = gif_skip_subblocks(pb)) < 0)
> + if ((ret = av_new_packet(pkt, GIF_PACKET_SIZE)) < 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) {
> + pkt->pos = avio_tell(pb);
> + pkt->stream_index = 0;
> + ret = avio_read_partial(pb, pkt->data, GIF_PACKET_SIZE);
> + if (ret < 0) {
> + av_packet_unref(pkt);
> 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;
> -
> - gdc->nb_frames ++;
> - gdc->last_duration = pkt->duration;
> -
> - /* 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) {
> - if (gdc->nb_frames == 1) {
> - s->streams[0]->r_frame_rate = (AVRational) {100, gdc->last_duration};
> - }
> - /* 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;
> + av_shrink_packet(pkt, ret);
> + return ret;
> }
>
> static const AVOption options[] = {
> @@ -405,6 +284,7 @@ const AVInputFormat ff_gif_demuxer = {
> .read_probe = gif_probe,
> .read_header = gif_read_header,
> .read_packet = gif_read_packet,
> - .flags = AVFMT_GENERIC_INDEX,
> + .flags = AVFMT_GENERIC_INDEX | AVFMT_NOTIMESTAMPS,
> + .extensions = "gif",
> .priv_class = &demuxer_class,
> };
> diff --git a/tests/ref/lavf/gif b/tests/ref/lavf/gif
> index fc94b9df3d..30eb85325a 100644
> --- a/tests/ref/lavf/gif
> +++ b/tests/ref/lavf/gif
> @@ -1,3 +1,3 @@
> e35f5ea283bbcb249818e0078ec72664 *tests/data/lavf/lavf.gif
> 2011766 tests/data/lavf/lavf.gif
> -tests/data/lavf/lavf.gif CRC=0x2429faff
> +tests/data/lavf/lavf.gif CRC=0x563cec26
> --
> 2.39.1
TEST gif-demux
--- /home/Ethaniel/FFmpeg/tests/ref/fate/gif-demux 2023-05-21
22:13:04.358066200 -0300
+++ tests/data/fate/gif-demux 2023-05-22 13:00:37.259301700 -0300
@@ -38,4 +38,4 @@
0, 74, 74, 2, 4633, 0x8f64fda7, F=0x0
0, 76, 76, 2, 4700, 0x45f40805, F=0x0
0, 78, 78, 2, 5117, 0x4eb4c5fb, F=0x0
-0, 80, 80, 2, 5370, 0xb10c6910, F=0x0
+0, 80, 80, 2, 5371, 0x1a66694b, F=0x0
Test gif-demux failed. Look at tests/data/fate/gif-demux.err for details.
And
TEST lavf-gif
--- /home/Ethaniel/FFmpeg/tests/ref/lavf/gif 2023-05-22
13:00:14.259590500 -0300
+++ tests/data/fate/lavf-gif 2023-05-22 13:01:19.316371100 -0300
@@ -1,3 +1,3 @@
e35f5ea283bbcb249818e0078ec72664 *tests/data/lavf/lavf.gif
2011766 tests/data/lavf/lavf.gif
-tests/data/lavf/lavf.gif CRC=0x563cec26
+tests/data/lavf/lavf.gif CRC=0x2429faff
Test lavf-gif failed. Look at tests/data/fate/lavf-gif.err for details.
This one giving the same result as pre-patch. Are you sure you ran fate
on a clean tree and build folder?
More information about the ffmpeg-devel
mailing list