Dana 31. 3. 2015. 22:59 osoba "Donny Yang" <w...@kota.moe> napisala je: > > Additionally, update some documentation with support for APNG > > Signed-off-by: Donny Yang <w...@kota.moe> > --- > Changelog | 1 + > doc/general.texi | 2 + > libavformat/Makefile | 1 + > libavformat/allformats.c | 2 +- > libavformat/apngenc.c | 269 +++++++++++++++++++++++++++++++++++++++++++++++ > 5 files changed, 274 insertions(+), 1 deletion(-) > create mode 100644 libavformat/apngenc.c > > diff --git a/Changelog b/Changelog > index 75da156..ba0dff5 100644 > --- a/Changelog > +++ b/Changelog > @@ -12,6 +12,7 @@ version <next>: > - Detelecine filter > - Intel QSV-accelerated H.264 encoding > - MMAL-accelerated H.264 decoding > +- basic APNG encoder and muxer > > > version 2.6: > diff --git a/doc/general.texi b/doc/general.texi > index 85ee219..589b423 100644 > --- a/doc/general.texi > +++ b/doc/general.texi > @@ -222,6 +222,7 @@ library: > @tab Audio format used on the Nintendo Gamecube. > @item AFC @tab @tab X > @tab Audio format used on the Nintendo Gamecube. > +@item APNG @tab X @tab X > @item ASF @tab X @tab X > @item AST @tab X @tab X > @tab Audio format used on the Nintendo Wii. > @@ -508,6 +509,7 @@ following image formats are supported: > @item Alias PIX @tab X @tab X > @tab Alias/Wavefront PIX image format > @item animated GIF @tab X @tab X > +@item APNG @tab X @tab X > @item BMP @tab X @tab X > @tab Microsoft BMP image > @item BRender PIX @tab @tab X > diff --git a/libavformat/Makefile b/libavformat/Makefile > index 2118ff2..5082101 100644 > --- a/libavformat/Makefile > +++ b/libavformat/Makefile > @@ -80,6 +80,7 @@ OBJS-$(CONFIG_ANM_DEMUXER) += anm.o > OBJS-$(CONFIG_APC_DEMUXER) += apc.o > OBJS-$(CONFIG_APE_DEMUXER) += ape.o apetag.o img2.o > OBJS-$(CONFIG_APNG_DEMUXER) += apngdec.o > +OBJS-$(CONFIG_APNG_MUXER) += apngenc.o > OBJS-$(CONFIG_AQTITLE_DEMUXER) += aqtitledec.o subtitles.o > OBJS-$(CONFIG_ASF_DEMUXER) += asfdec.o asf.o asfcrypt.o \ > avlanguage.o > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index 26ccc27..ca45db8 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -74,7 +74,7 @@ void av_register_all(void) > REGISTER_DEMUXER (ANM, anm); > REGISTER_DEMUXER (APC, apc); > REGISTER_DEMUXER (APE, ape); > - REGISTER_DEMUXER (APNG, apng); > + REGISTER_MUXDEMUX(APNG, apng); > REGISTER_DEMUXER (AQTITLE, aqtitle); > REGISTER_MUXDEMUX(ASF, asf); > REGISTER_MUXDEMUX(ASS, ass); > diff --git a/libavformat/apngenc.c b/libavformat/apngenc.c > new file mode 100644 > index 0000000..0e9af89 > --- /dev/null > +++ b/libavformat/apngenc.c > @@ -0,0 +1,269 @@ > +/* > + * APNG muxer > + * Copyright (c) 2015 Donny Yang > + * > + * first version by Donny Yang <w...@kota.moe> > + * > + * 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 > + */ > + > +#include <zlib.h>
This is missing dependency on zlib in configure. > + > +#include "avformat.h" > +#include "internal.h" > +#include "libavutil/avassert.h" > +#include "libavutil/intreadwrite.h" > +#include "libavutil/log.h" > +#include "libavutil/opt.h" > +#include "libavcodec/png.h" > +#include "libavcodec/apng.h" > + > +typedef struct APNGMuxContext { > + AVClass *class; > + uint32_t plays; > + AVRational last_delay; > + > + uint64_t acTL_offset; > + uint32_t frame_number; > + > + AVPacket *prev_packet; > + AVRational prev_delay; > + > + int framerate_warned; > +} APNGMuxContext; > + > +static uint8_t *apng_find_chunk(uint32_t tag, uint8_t *buf, size_t length) > +{ > + size_t b; > + for (b = 0; b < length; b += AV_RB32(buf + b) + 12) > + if (AV_RB32(&buf[b + 4]) == tag) > + return &buf[b]; > + return NULL; > +} > + > +static void apng_write_chunk(AVIOContext *io_context, uint32_t tag, > + uint8_t *buf, size_t length) > +{ > + uint32_t crc; > + uint8_t tagbuf[4]; > + > + avio_wb32(io_context, length); > + crc = crc32(0, Z_NULL, 0); > + AV_WB32(tagbuf, tag); > + crc = crc32(crc, tagbuf, 4); > + avio_wb32(io_context, tag); > + if (length > 0) { > + crc = crc32(crc, buf, length); > + avio_write(io_context, buf, length); > + } > + avio_wb32(io_context, crc); > +} > + > +static int apng_write_header(AVFormatContext *format_context) > +{ > + APNGMuxContext *apng = format_context->priv_data; > + > + if (format_context->nb_streams != 1 || > + format_context->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO || > + format_context->streams[0]->codec->codec_id != AV_CODEC_ID_APNG) { > + av_log(format_context, AV_LOG_ERROR, > + "APNG muxer supports only a single video APNG stream.\n"); > + return AVERROR(EINVAL); > + } > + > + if (apng->last_delay.num > USHRT_MAX || apng->last_delay.den > USHRT_MAX) { > + av_reduce(&apng->last_delay.num, &apng->last_delay.den, > + apng->last_delay.num, apng->last_delay.den, USHRT_MAX); > + av_log(format_context, AV_LOG_WARNING, > + "Last frame delay is too precise. Reducing to %d/%d (%f).\n", > + apng->last_delay.num, apng->last_delay.den, (double)apng->last_delay.num / apng->last_delay.den); > + } > + > + avio_wb64(format_context->pb, PNGSIG); > + // Remaining headers are written when they are copied from the encoder > + > + return 0; > +} > + > +static void flush_packet(AVFormatContext *format_context, AVPacket *packet) > +{ > + APNGMuxContext *apng = format_context->priv_data; > + AVIOContext *io_context = format_context->pb; > + AVStream *codec_stream = format_context->streams[0]; > + AVCodecContext *codec_context = codec_stream->codec; > + > + av_assert0(apng->prev_packet); > + > + if (apng->frame_number == 0 && !packet) { > + uint8_t *existing_acTL_chunk; > + uint8_t *existing_fcTL_chunk; > + > + av_log(format_context, AV_LOG_WARNING, "Only a single frame so saving as a normal PNG.\n"); > + > + // Write normal PNG headers without acTL chunk > + existing_acTL_chunk = apng_find_chunk(MKBETAG('a', 'c', 'T', 'L'), codec_context->extradata, codec_context->extradata_size); > + if (existing_acTL_chunk) { > + uint8_t *chunk_after_acTL = existing_acTL_chunk + AV_RB32(existing_acTL_chunk) + 12; > + avio_write(io_context, codec_context->extradata, existing_acTL_chunk - codec_context->extradata); > + avio_write(io_context, chunk_after_acTL, codec_context->extradata + codec_context->extradata_size - chunk_after_acTL); > + } else { > + avio_write(io_context, codec_context->extradata, codec_context->extradata_size); > + } > + > + // Write frame data without fcTL chunk > + existing_fcTL_chunk = apng_find_chunk(MKBETAG('f', 'c', 'T', 'L'), apng->prev_packet->data, apng->prev_packet->size); > + if (existing_fcTL_chunk) { > + uint8_t *chunk_after_fcTL = existing_fcTL_chunk + AV_RB32(existing_fcTL_chunk) + 12; > + avio_write(io_context, apng->prev_packet->data, existing_fcTL_chunk - apng->prev_packet->data); > + avio_write(io_context, chunk_after_fcTL, apng->prev_packet->data + apng->prev_packet->size - chunk_after_fcTL); > + } else { > + avio_write(io_context, apng->prev_packet->data, apng->prev_packet->size); > + } > + } else { > + uint8_t *existing_fcTL_chunk; > + > + if (apng->frame_number == 0) { > + uint8_t *existing_acTL_chunk; > + > + // Write normal PNG headers > + avio_write(io_context, codec_context->extradata, codec_context->extradata_size); > + > + existing_acTL_chunk = apng_find_chunk(MKBETAG('a', 'c', 'T', 'L'), codec_context->extradata, codec_context->extradata_size); > + if (!existing_acTL_chunk) { > + uint8_t buf[8]; > + // Write animation control header > + apng->acTL_offset = avio_tell(io_context); > + AV_WB32(buf, UINT_MAX); // number of frames (filled in later) > + AV_WB32(buf + 4, apng->plays); > + apng_write_chunk(io_context, MKBETAG('a', 'c', 'T', 'L'), buf, 8); > + } > + } > + > + existing_fcTL_chunk = apng_find_chunk(MKBETAG('f', 'c', 'T', 'L'), apng->prev_packet->data, apng->prev_packet->size); > + if (existing_fcTL_chunk) { > + AVRational delay; > + > + existing_fcTL_chunk += 8; > + delay.num = AV_RB16(existing_fcTL_chunk + 20); > + delay.den = AV_RB16(existing_fcTL_chunk + 22); > + > + if (delay.num == 0 && delay.den == 0) { > + if (packet) { > + int64_t delay_num_raw = (packet->dts - apng->prev_packet->dts) * codec_stream->time_base.num; > + int64_t delay_den_raw = codec_stream->time_base.den; > + if (!av_reduce(&delay.num, &delay.den, delay_num_raw, delay_den_raw, USHRT_MAX) && > + !apng->framerate_warned) { > + av_log(format_context, AV_LOG_WARNING, > + "Frame rate is too high or specified too precisely. Unable to copy losslessly.\n"); > + apng->framerate_warned = 1; > + } > + } else if (apng->last_delay.den > 0) { > + delay = apng->last_delay; > + } else { > + delay = apng->prev_delay; > + } > + > + // Update frame control header with new delay > + AV_WB16(existing_fcTL_chunk + 20, delay.num); > + AV_WB16(existing_fcTL_chunk + 22, delay.den); > + AV_WB32(existing_fcTL_chunk + 26, crc32(crc32(0, Z_NULL, 0), existing_fcTL_chunk - 4, 26 + 4)); > + } > + apng->prev_delay = delay; > + } > + > + // Write frame data > + avio_write(io_context, apng->prev_packet->data, apng->prev_packet->size); > + } > + ++apng->frame_number; > + > + av_free_packet(apng->prev_packet); > + if (packet) > + av_copy_packet(apng->prev_packet, packet); > +} > + > +static int apng_write_packet(AVFormatContext *format_context, AVPacket *packet) > +{ > + APNGMuxContext *apng = format_context->priv_data; > + > + if (!apng->prev_packet) { > + apng->prev_packet = av_malloc(sizeof(*apng->prev_packet)); > + if (!apng->prev_packet) > + return AVERROR(ENOMEM); > + > + av_copy_packet(apng->prev_packet, packet); > + } else { > + flush_packet(format_context, packet); > + } > + > + return 0; > +} > + > +static int apng_write_trailer(AVFormatContext *format_context) > +{ > + APNGMuxContext *apng = format_context->priv_data; > + AVIOContext *io_context = format_context->pb; > + uint8_t buf[8]; > + > + if (apng->prev_packet) { > + flush_packet(format_context, NULL); > + av_freep(&apng->prev_packet); > + } > + > + apng_write_chunk(io_context, MKBETAG('I', 'E', 'N', 'D'), NULL, 0); > + > + if (apng->acTL_offset && io_context->seekable) { > + avio_seek(io_context, apng->acTL_offset, SEEK_SET); > + > + AV_WB32(buf, apng->frame_number); > + AV_WB32(buf + 4, apng->plays); > + apng_write_chunk(io_context, MKBETAG('a', 'c', 'T', 'L'), buf, 8); > + } > + > + return 0; > +} > + > +#define OFFSET(x) offsetof(APNGMuxContext, x) > +#define ENC AV_OPT_FLAG_ENCODING_PARAM > +static const AVOption options[] = { > + { "plays", "Number of times to play the output: 0 - infinite loop, 1 - no loop", OFFSET(plays), > + AV_OPT_TYPE_INT, { .i64 = 1 }, 0, UINT_MAX, ENC }, > + { "final_delay", "Force delay after the last frame", OFFSET(last_delay), > + AV_OPT_TYPE_RATIONAL, { .dbl = 0 }, 0, USHRT_MAX, ENC }, > + { NULL }, > +}; > + > +static const AVClass apng_muxer_class = { > + .class_name = "APNG muxer", > + .item_name = av_default_item_name, > + .version = LIBAVUTIL_VERSION_INT, > + .option = options, > +}; > + > +AVOutputFormat ff_apng_muxer = { > + .name = "apng", > + .long_name = NULL_IF_CONFIG_SMALL("Animated Portable Network Graphics"), > + .mime_type = "image/png", > + .extensions = "png", > + .priv_data_size = sizeof(APNGMuxContext), > + .audio_codec = AV_CODEC_ID_NONE, > + .video_codec = AV_CODEC_ID_APNG, > + .write_header = apng_write_header, > + .write_packet = apng_write_packet, > + .write_trailer = apng_write_trailer, > + .priv_class = &apng_muxer_class, > + .flags = AVFMT_VARIABLE_FPS, > +}; > -- > 2.3.4 > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel