Dana 29. 3. 2015. 13:06 osoba "Donny Yang" <w...@kota.moe> napisala je: > > Additionally, update some documentation with support for APNG > Make sure muxer works with -c:v copy.
> Signed-off-by: Donny Yang <w...@kota.moe> > --- > Changelog | 1 + > doc/general.texi | 2 + > libavformat/Makefile | 1 + > libavformat/allformats.c | 2 +- > libavformat/apngenc.c | 250 +++++++++++++++++++++++++++++++++++++++++++++++ > libavformat/version.h | 4 +- > 6 files changed, 257 insertions(+), 3 deletions(-) > create mode 100644 libavformat/apngenc.c > > diff --git a/Changelog b/Changelog > index dcddca4..3264341 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..5190453 > --- /dev/null > +++ b/libavformat/apngenc.c > @@ -0,0 +1,250 @@ > +/* > + * 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 <stdarg.h> > +#include <zlib.h> > + > +#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" > + > +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); > +} > + > +typedef struct APNGMuxContext { > + AVClass *class; > + uint32_t plays; > + AVRational last_delay; > + > + uint64_t acTL_offset; > + uint32_t sequence_number; > + uint32_t frame_number; > + > + AVPacket *prev_packet; > + AVRational prev_delay; > +} APNGMuxContext; > + > +static int apng_write_header(AVFormatContext *format_context) > +{ > + APNGMuxContext *apng = format_context->priv_data; > + AVStream *codec_stream; > + > + 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); > + } > + codec_stream = format_context->streams[0]; > + > + if (codec_stream->time_base.num > USHRT_MAX || codec_stream->time_base.den > USHRT_MAX) { > + av_log(format_context, AV_LOG_WARNING, > + "Frame rate is too high or specified too precisely. Unable to copy losslessly.\n"); > + } > + > + 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); > + } > + > + // Real 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; > + uint8_t *frame_start; > + uint8_t buf[26]; > + AVRational delay; > + > + av_assert0(apng->prev_packet); > + frame_start = apng->prev_packet->data; > + > + if (apng->frame_number == 0 && !packet) { > + av_log(format_context, AV_LOG_WARNING, "Only a single frame so saving as a normal PNG.\n"); > + } else { > + if (apng->frame_number == 0) { > + frame_start += 8; // skip PNGSIG > + while (AV_RB32(frame_start + 4) != MKBETAG('I', 'D', 'A', 'T')) > + frame_start += AV_RB32(frame_start) + 12; > + > + // Write normal PNG headers > + avio_write(io_context, apng->prev_packet->data, frame_start - apng->prev_packet->data); > + > + // 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); > + } > + > + 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; > + av_reduce(&delay.num, &delay.den, delay_num_raw, delay_den_raw, USHRT_MAX); > + } else if (apng->last_delay.den > 0) { > + delay = apng->last_delay; > + } else { > + delay = apng->prev_delay; > + } > + apng->prev_delay = delay; > + > + AV_WB32(buf, apng->sequence_number); > + AV_WB32(buf + 4, codec_context->width); > + AV_WB32(buf + 8, codec_context->height); > + AV_WB32(buf + 12, 0); // x offset > + AV_WB32(buf + 16, 0); // y offset > + AV_WB16(buf + 20, delay.num); > + AV_WB16(buf + 22, delay.den); > + buf[24] = APNG_DISPOSE_OP_BACKGROUND; > + buf[25] = APNG_BLEND_OP_SOURCE; > + apng_write_chunk(io_context, MKBETAG('f', 'c', 'T', 'L'), buf, 26); > + ++apng->sequence_number; > + } > + > + if (apng->frame_number == 0) { > + avio_write(io_context, frame_start, apng->prev_packet->data + apng->prev_packet->size - frame_start); > + } else { > + // Need to replace IDAT chunks with fdAT chunks > + do { > + uint8_t *next_frame_start = frame_start + AV_RB32(frame_start) + 12; > + size_t frame_size = AV_RB32(frame_start); > + > + frame_start += 4; > + frame_size += 4; > + > + AV_WB32(frame_start, apng->sequence_number); > + apng_write_chunk(io_context, MKBETAG('f', 'd', 'A', 'T'), frame_start, frame_size); > + ++apng->sequence_number; > + > + frame_start = next_frame_start; > + } while (frame_start < 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, format_context->streams[0]->nb_frames); > + 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, > +}; > diff --git a/libavformat/version.h b/libavformat/version.h > index a183d7f..82a57ee 100644 > --- a/libavformat/version.h > +++ b/libavformat/version.h > @@ -30,8 +30,8 @@ > #include "libavutil/version.h" > > #define LIBAVFORMAT_VERSION_MAJOR 56 > -#define LIBAVFORMAT_VERSION_MINOR 26 > -#define LIBAVFORMAT_VERSION_MICRO 101 > +#define LIBAVFORMAT_VERSION_MINOR 27 > +#define LIBAVFORMAT_VERSION_MICRO 100 > > #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ > LIBAVFORMAT_VERSION_MINOR, \ > -- > 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