From 80614508f4982dce3904c5773f3252485afb9e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Sat, 19 Oct 2013 19:39:28 +0200 Subject: [PATCH] avformat/image2: allow muxing gif files. --- libavformat/gif.c | 85 ++++++++++++++++++++++++++++++--------------------- libavformat/gif.h | 26 ++++++++++++++++ libavformat/img2enc.c | 23 ++++++++++++++ 3 files changed, 100 insertions(+), 34 deletions(-) create mode 100644 libavformat/gif.h diff --git a/libavformat/gif.c b/libavformat/gif.c index f6e7625..cc8ae50 100644 --- a/libavformat/gif.c +++ b/libavformat/gif.c @@ -23,6 +23,7 @@ #include "avformat.h" #include "internal.h" +#include "gif.h" #include "libavutil/avassert.h" #include "libavutil/imgutils.h" #include "libavutil/log.h" @@ -76,55 +77,53 @@ typedef struct { int duration; } GIFContext; -static int gif_write_header(AVFormatContext *s) +int ff_gif_write_header(AVCodecContext *video_enc, AVIOContext *pb, int loop) { - GIFContext *gif = s->priv_data; - AVIOContext *pb = s->pb; - AVCodecContext *video_enc; int width, height; uint32_t palette[AVPALETTE_COUNT]; - if (s->nb_streams != 1 || - s->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO || - s->streams[0]->codec->codec_id != AV_CODEC_ID_GIF) { - av_log(s, AV_LOG_ERROR, - "GIF muxer supports only a single video GIF stream.\n"); - return AVERROR(EINVAL); - } - - video_enc = s->streams[0]->codec; width = video_enc->width; height = video_enc->height; - avpriv_set_pts_info(s->streams[0], 64, 1, 100); if (avpriv_set_systematic_pal2(palette, video_enc->pix_fmt) < 0) { av_assert0(video_enc->pix_fmt == AV_PIX_FMT_PAL8); - gif_image_write_header(pb, width, height, gif->loop, NULL); + gif_image_write_header(pb, width, height, loop, NULL); } else { - gif_image_write_header(pb, width, height, gif->loop, palette); + gif_image_write_header(pb, width, height, loop, palette); } - avio_flush(s->pb); + avio_flush(pb); return 0; } -static int flush_packet(AVFormatContext *s, AVPacket *new) +static int gif_write_header(AVFormatContext *s) { GIFContext *gif = s->priv_data; + + if (s->nb_streams != 1 || + s->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO || + s->streams[0]->codec->codec_id != AV_CODEC_ID_GIF) { + av_log(s, AV_LOG_ERROR, + "GIF muxer supports only a single video GIF stream.\n"); + return AVERROR(EINVAL); + } + + avpriv_set_pts_info(s->streams[0], 64, 1, 100); + + return ff_gif_write_header(s->streams[0]->codec, s->pb, gif->loop); +} + +int ff_gif_write_packet(void *log_ctx, AVIOContext *pb, AVPacket *pkt, int duration) +{ int size; - AVIOContext *pb = s->pb; uint8_t flags = 0x4, transparent_color_index = 0x1f; const uint32_t *palette; - AVPacket *pkt = gif->prev_pkt; - - if (!pkt) - return 0; /* Mark one colour as transparent if the input palette contains at least * one colour that is more than 50% transparent. */ palette = (uint32_t*)av_packet_get_side_data(pkt, AV_PKT_DATA_PALETTE, &size); if (palette && size != AVPALETTE_SIZE) { - av_log(s, AV_LOG_ERROR, "Invalid palette extradata\n"); + av_log(log_ctx, AV_LOG_ERROR, "Invalid palette extradata\n"); return AVERROR_INVALIDDATA; } if (palette) { @@ -141,22 +140,37 @@ static int flush_packet(AVFormatContext *s, AVPacket *new) flags |= 0x1; /* Transparent Color Flag */ } - if (new && new->pts != AV_NOPTS_VALUE) - gif->duration = av_clip_uint16(new->pts - gif->prev_pkt->pts); - else if (!new && gif->last_delay >= 0) - gif->duration = gif->last_delay; - /* graphic control extension block */ avio_w8(pb, 0x21); avio_w8(pb, 0xf9); avio_w8(pb, 0x04); /* block size */ avio_w8(pb, flags); - avio_wl16(pb, gif->duration); + avio_wl16(pb, duration); avio_w8(pb, transparent_color_index); avio_w8(pb, 0x00); avio_write(pb, pkt->data, pkt->size); + return 0; +} + +static int flush_packet(AVFormatContext *s, AVPacket *new) +{ + int ret; + GIFContext *gif = s->priv_data; + + if (!gif->prev_pkt) + return 0; + + if (new && new->pts != AV_NOPTS_VALUE) + gif->duration = av_clip_uint16(new->pts - gif->prev_pkt->pts); + else if (!new && gif->last_delay >= 0) + gif->duration = gif->last_delay; + + ret = ff_gif_write_packet(s, s->pb, gif->prev_pkt, gif->duration); + if (ret < 0) + return ret; + av_free_packet(gif->prev_pkt); if (new) av_copy_packet(gif->prev_pkt, new); @@ -177,16 +191,19 @@ static int gif_write_packet(AVFormatContext *s, AVPacket *pkt) return flush_packet(s, pkt); } +int ff_gif_write_trailer(AVIOContext *pb) +{ + avio_w8(pb, 0x3b); + return 0; +} + static int gif_write_trailer(AVFormatContext *s) { GIFContext *gif = s->priv_data; - AVIOContext *pb = s->pb; flush_packet(s, NULL); av_freep(&gif->prev_pkt); - avio_w8(pb, 0x3b); - - return 0; + return ff_gif_write_trailer(s->pb); } #define OFFSET(x) offsetof(GIFContext, x) diff --git a/libavformat/gif.h b/libavformat/gif.h new file mode 100644 index 0000000..05f5fd6 --- /dev/null +++ b/libavformat/gif.h @@ -0,0 +1,26 @@ +/* + * 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 + */ + +#ifndef AVFORMAT_GIF_H +#define AVFORMAT_GIF_H + +int ff_gif_write_header(AVCodecContext *video_enc, AVIOContext *pb, int loop); +int ff_gif_write_packet(void *log_ctx, AVIOContext *pb, AVPacket *pkt, int duration); +int ff_gif_write_trailer(AVIOContext *pb); + +#endif /* AVFORMAT_GIF_H */ diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c index 8adf352..725f01c 100644 --- a/libavformat/img2enc.c +++ b/libavformat/img2enc.c @@ -21,6 +21,7 @@ */ #include "libavutil/intreadwrite.h" +#include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/log.h" #include "libavutil/opt.h" @@ -28,6 +29,7 @@ #include "avformat.h" #include "avio_internal.h" #include "internal.h" +#include "gif.h" typedef struct { const AVClass *class; /**< Class for private options. */ @@ -54,6 +56,18 @@ static int write_header(AVFormatContext *s) else img->is_pipe = 1; + if (st->codec->codec_id == AV_CODEC_ID_GIF) { + if (img->is_pipe) { + av_log(s, AV_LOG_ERROR, "Use the 'gif' format instead of 'image2pipe' " + "for continuous output GIF stream\n"); + return AVERROR(EINVAL); + } + if (!CONFIG_GIF_MUXER) { + av_log(s, AV_LOG_ERROR, "'image2' muxer relies on the 'gif' muxer for GIF muxing\n"); + return AVERROR(EINVAL); + } + } + str = strrchr(img->path, '.'); img->split_planes = str && !av_strcasecmp(str + 1, "y") @@ -104,6 +118,12 @@ static int write_packet(AVFormatContext *s, AVPacket *pkt) break; filename[strlen(filename) - 1] = ((int[]){'U','V','A','x'})[i]; } + + if (CONFIG_GIF_MUXER && codec->codec_id == AV_CODEC_ID_GIF) { + av_assert0(!img->split_planes); + ff_gif_write_header(codec, pb[0], -1); + } + } else { pb[0] = s->pb; } @@ -124,6 +144,9 @@ static int write_packet(AVFormatContext *s, AVPacket *pkt) avio_write(pb[3], pkt->data + ysize + 2*usize, ysize); avio_close(pb[3]); } + } else if (CONFIG_GIF_MUXER && codec->codec_id == AV_CODEC_ID_GIF) { + ff_gif_write_packet(s, pb[0], pkt, 0); // XXX: we loose timing in the process + ff_gif_write_trailer(pb[0]); } else { avio_write(pb[0], pkt->data, pkt->size); } -- 1.8.4.1