Sorry for the delay. I was a bit busy this week.

On 25 March 2015 at 09:59, Michael Niedermayer <michae...@gmx.at> wrote:

> is there any advantage for multiple small IDATs ?
> if not i suggest to make the IDAT change to png as well in a seperate
> patch so that a single frame APNG and PNG produce identical data
> and especially so that the introduction of APNG does not change PNG
> files


Having multiple small IDAT sections is simply to make the encoder easier to
write.
For now, I've left it how it is, but now the APNG encoder and muxer
produces identical output to PNG before.

I've also changed my edits to libavcodec a fair bit as well after thinking
more carefully about the next steps in implementing a full APNG encoder.
The APNG encoder currently just outputs normal PNG headers and IDAT chunks,
while the muxer adds the necessary headers for APNG and converts the IDAT
chunks to fdAT where needed.

On 28 March 2015 at 18:24, Paul B Mahol <one...@gmail.com> wrote:

> Dana 28. 3. 2015. 04:56 osoba "Donny Yang" <w...@kota.moe> napisala je:
> > On 28 March 2015 at 04:36, Paul B Mahol <one...@gmail.com> wrote:
> >>
> >> The style of code inside patch do not match other files in codebase,
> >> look at style of other files inside codebase.
> >
> > I assume you're referring to the opening brace style on functions?
> >
> Yes.
>
Okay, I've fixed them.

Also, I just realised I'm not supposed to be sending more than one patch
per email.
I've put them again for this email, but what should I do instead next time?

From cdc40aa212d3c8a34b2895213f6bc63efe881df5 Mon Sep 17 00:00:00 2001
From: Donny Yang <w...@kota.moe>
Date: Sat, 28 Mar 2015 19:09:11 +1100
Subject: [PATCH 1/2] apng: Make PNG encoder only write headers once in APNG
 mode

Signed-off-by: Donny Yang <w...@kota.moe>
---
 configure              |   1 +
 libavcodec/Makefile    |   1 +
 libavcodec/allcodecs.c |   2 +-
 libavcodec/pngenc.c    | 326 ++++++++++++++++++++++++++++++-------------------
 libavcodec/version.h   |   2 +-
 5 files changed, 203 insertions(+), 129 deletions(-)

diff --git a/configure b/configure
index 017a9d2..8a30549 100755
--- a/configure
+++ b/configure
@@ -2096,6 +2096,7 @@ amv_decoder_select="sp5x_decoder exif"
 amv_encoder_select="aandcttables mpegvideoenc"
 ape_decoder_select="bswapdsp llauddsp"
 apng_decoder_select="zlib"
+apng_encoder_select="huffyuvencdsp zlib"
 asv1_decoder_select="blockdsp bswapdsp idctdsp"
 asv1_encoder_select="bswapdsp fdctdsp pixblockdsp"
 asv2_decoder_select="blockdsp bswapdsp idctdsp"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index b2d9c71..9a145d3 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -144,6 +144,7 @@ OBJS-$(CONFIG_ANM_DECODER)             += anm.o
 OBJS-$(CONFIG_ANSI_DECODER)            += ansi.o cga_data.o
 OBJS-$(CONFIG_APE_DECODER)             += apedec.o
 OBJS-$(CONFIG_APNG_DECODER)            += png.o pngdec.o pngdsp.o
+OBJS-$(CONFIG_APNG_ENCODER)            += png.o pngenc.o
 OBJS-$(CONFIG_SSA_DECODER)             += assdec.o ass.o ass_split.o
 OBJS-$(CONFIG_SSA_ENCODER)             += assenc.o ass.o
 OBJS-$(CONFIG_ASS_DECODER)             += assdec.o ass.o ass_split.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 10aad4c..2e5d558 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -107,7 +107,7 @@ void avcodec_register_all(void)
     REGISTER_ENCDEC (AMV,               amv);
     REGISTER_DECODER(ANM,               anm);
     REGISTER_DECODER(ANSI,              ansi);
-    REGISTER_DECODER(APNG,              apng);
+    REGISTER_ENCDEC (APNG,              apng);
     REGISTER_ENCDEC (ASV1,              asv1);
     REGISTER_ENCDEC (ASV2,              asv2);
     REGISTER_DECODER(AURA,              aura);
diff --git a/libavcodec/pngenc.c b/libavcodec/pngenc.c
index 9bdefc4..6959435 100644
--- a/libavcodec/pngenc.c
+++ b/libavcodec/pngenc.c
@@ -48,6 +48,11 @@ typedef struct PNGEncContext {
     uint8_t buf[IOBUF_SIZE];
     int dpi;                     ///< Physical pixel density, in dots per inch, if set
     int dpm;                     ///< Physical pixel density, in dots per meter, if set
+
+    int is_progressive;
+    int bit_depth;
+    int color_type;
+    int bits_per_pixel;
 } PNGEncContext;
 
 static void png_get_interlaced_row(uint8_t *dst, int row_size,
@@ -286,107 +291,9 @@ static int png_get_gama(enum AVColorTransferCharacteristic trc, uint8_t *buf)
     return 1;
 }
 
-static int encode_frame(AVCodecContext *avctx, AVPacket *pkt,
-                        const AVFrame *pict, int *got_packet)
+static int encode_headers(AVCodecContext *avctx, const AVFrame *pict)
 {
-    PNGEncContext *s       = avctx->priv_data;
-    const AVFrame *const p = pict;
-    int bit_depth, color_type, y, len, row_size, ret, is_progressive;
-    int bits_per_pixel, pass_row_size, enc_row_size;
-    int64_t max_packet_size;
-    int compression_level;
-    uint8_t *ptr, *top, *crow_buf, *crow;
-    uint8_t *crow_base       = NULL;
-    uint8_t *progressive_buf = NULL;
-    uint8_t *top_buf         = NULL;
-
-    is_progressive = !!(avctx->flags & CODEC_FLAG_INTERLACED_DCT);
-    switch (avctx->pix_fmt) {
-    case AV_PIX_FMT_RGBA64BE:
-        bit_depth = 16;
-        color_type = PNG_COLOR_TYPE_RGB_ALPHA;
-        break;
-    case AV_PIX_FMT_RGB48BE:
-        bit_depth = 16;
-        color_type = PNG_COLOR_TYPE_RGB;
-        break;
-    case AV_PIX_FMT_RGBA:
-        bit_depth  = 8;
-        color_type = PNG_COLOR_TYPE_RGB_ALPHA;
-        break;
-    case AV_PIX_FMT_RGB24:
-        bit_depth  = 8;
-        color_type = PNG_COLOR_TYPE_RGB;
-        break;
-    case AV_PIX_FMT_GRAY16BE:
-        bit_depth  = 16;
-        color_type = PNG_COLOR_TYPE_GRAY;
-        break;
-    case AV_PIX_FMT_GRAY8:
-        bit_depth  = 8;
-        color_type = PNG_COLOR_TYPE_GRAY;
-        break;
-    case AV_PIX_FMT_GRAY8A:
-        bit_depth = 8;
-        color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
-        break;
-    case AV_PIX_FMT_YA16BE:
-        bit_depth = 16;
-        color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
-        break;
-    case AV_PIX_FMT_MONOBLACK:
-        bit_depth  = 1;
-        color_type = PNG_COLOR_TYPE_GRAY;
-        break;
-    case AV_PIX_FMT_PAL8:
-        bit_depth  = 8;
-        color_type = PNG_COLOR_TYPE_PALETTE;
-        break;
-    default:
-        return -1;
-    }
-    bits_per_pixel = ff_png_get_nb_channels(color_type) * bit_depth;
-    row_size       = (avctx->width * bits_per_pixel + 7) >> 3;
-
-    s->zstream.zalloc = ff_png_zalloc;
-    s->zstream.zfree  = ff_png_zfree;
-    s->zstream.opaque = NULL;
-    compression_level = avctx->compression_level == FF_COMPRESSION_DEFAULT
-                      ? Z_DEFAULT_COMPRESSION
-                      : av_clip(avctx->compression_level, 0, 9);
-    ret = deflateInit2(&s->zstream, compression_level,
-                       Z_DEFLATED, 15, 8, Z_DEFAULT_STRATEGY);
-    if (ret != Z_OK)
-        return -1;
-
-    enc_row_size    = deflateBound(&s->zstream, row_size);
-    max_packet_size = avctx->height * (int64_t)(enc_row_size +
-                                       ((enc_row_size + IOBUF_SIZE - 1) / IOBUF_SIZE) * 12)
-                      + FF_MIN_BUFFER_SIZE;
-    if (max_packet_size > INT_MAX)
-        return AVERROR(ENOMEM);
-    if ((ret = ff_alloc_packet2(avctx, pkt, max_packet_size)) < 0)
-        return ret;
-
-    s->bytestream_start =
-    s->bytestream       = pkt->data;
-    s->bytestream_end   = pkt->data + pkt->size;
-
-    crow_base = av_malloc((row_size + 32) << (s->filter_type == PNG_FILTER_VALUE_MIXED));
-    if (!crow_base)
-        goto fail;
-    // pixel data should be aligned, but there's a control byte before it
-    crow_buf = crow_base + 15;
-    if (is_progressive) {
-        progressive_buf = av_malloc(row_size + 1);
-        if (!progressive_buf)
-            goto fail;
-    }
-    if (is_progressive) {
-        top_buf = av_malloc(row_size + 1);
-        if (!top_buf)
-            goto fail;
-    }
+    PNGEncContext *s = avctx->priv_data;
 
     /* write png header */
     AV_WB64(s->bytestream, PNGSIG);
@@ -394,14 +301,14 @@ static int encode_frame(AVCodecContext *avctx, AVPacket *pkt,
 
     AV_WB32(s->buf, avctx->width);
     AV_WB32(s->buf + 4, avctx->height);
-    s->buf[8]  = bit_depth;
-    s->buf[9]  = color_type;
+    s->buf[8]  = s->bit_depth;
+    s->buf[9]  = s->color_type;
     s->buf[10] = 0; /* compression type */
     s->buf[11] = 0; /* filter type */
-    s->buf[12] = is_progressive; /* interlace type */
-
+    s->buf[12] = s->is_progressive; /* interlace type */
     png_write_chunk(&s->bytestream, MKTAG('I', 'H', 'D', 'R'), s->buf, 13);
 
+    /* write physical information */
     if (s->dpm) {
       AV_WB32(s->buf, s->dpm);
       AV_WB32(s->buf + 4, s->dpm);
@@ -426,13 +333,13 @@ static int encode_frame(AVCodecContext *avctx, AVPacket *pkt,
         png_write_chunk(&s->bytestream, MKTAG('g', 'A', 'M', 'A'), s->buf, 4);
 
     /* put the palette if needed */
-    if (color_type == PNG_COLOR_TYPE_PALETTE) {
+    if (s->color_type == PNG_COLOR_TYPE_PALETTE) {
         int has_alpha, alpha, i;
         unsigned int v;
         uint32_t *palette;
-        uint8_t *alpha_ptr;
+        uint8_t *ptr, *alpha_ptr;
 
-        palette   = (uint32_t *)p->data[1];
+        palette   = (uint32_t *)pict->data[1];
         ptr       = s->buf;
         alpha_ptr = s->buf + 256 * 3;
         has_alpha = 0;
@@ -452,16 +359,48 @@ static int encode_frame(AVCodecContext *avctx, AVPacket *pkt,
         }
     }
 
-    /* now put each row */
+    return 0;
+}
+
+static int encode_frame(AVCodecContext *avctx, const AVFrame *pict)
+{
+    PNGEncContext *s       = avctx->priv_data;
+    const AVFrame *const p = pict;
+    int y, len, ret;
+    int row_size, pass_row_size;
+    uint8_t *ptr, *top, *crow_buf, *crow;
+    uint8_t *crow_base       = NULL;
+    uint8_t *progressive_buf = NULL;
+    uint8_t *top_buf         = NULL;
+
+    row_size = (avctx->width * s->bits_per_pixel + 7) >> 3;
+
+    crow_base = av_malloc((row_size + 32) << (s->filter_type == PNG_FILTER_VALUE_MIXED));
+    if (!crow_base) {
+        ret = AVERROR(ENOMEM);
+        goto the_end;
+    }
+    // pixel data should be aligned, but there's a control byte before it
+    crow_buf = crow_base + 15;
+    if (s->is_progressive) {
+        progressive_buf = av_malloc(row_size + 1);
+        top_buf = av_malloc(row_size + 1);
+        if (!progressive_buf || !top_buf) {
+            ret = AVERROR(ENOMEM);
+            goto the_end;
+        }
+    }
+
+    /* put each row */
     s->zstream.avail_out = IOBUF_SIZE;
     s->zstream.next_out  = s->buf;
-    if (is_progressive) {
+    if (s->is_progressive) {
         int pass;
 
         for (pass = 0; pass < NB_PASSES; pass++) {
             /* NOTE: a pass is completely omitted if no pixels would be
              * output */
-            pass_row_size = ff_png_pass_row_size(pass, bits_per_pixel, avctx->width);
+            pass_row_size = ff_png_pass_row_size(pass, s->bits_per_pixel, avctx->width);
             if (pass_row_size > 0) {
                 top = NULL;
                 for (y = 0; y < avctx->height; y++)
@@ -469,10 +408,10 @@ static int encode_frame(AVCodecContext *avctx, AVPacket *pkt,
                         ptr = p->data[0] + y * p->linesize[0];
                         FFSWAP(uint8_t *, progressive_buf, top_buf);
                         png_get_interlaced_row(progressive_buf, pass_row_size,
-                                               bits_per_pixel, pass,
+                                               s->bits_per_pixel, pass,
                                                ptr, avctx->width);
                         crow = png_choose_filter(s, crow_buf, progressive_buf,
-                                                 top, pass_row_size, bits_per_pixel >> 3);
+                                                 top, pass_row_size, s->bits_per_pixel >> 3);
                         png_write_row(s, crow, pass_row_size + 1);
                         top = progressive_buf;
                     }
@@ -483,7 +422,7 @@ static int encode_frame(AVCodecContext *avctx, AVPacket *pkt,
         for (y = 0; y < avctx->height; y++) {
             ptr = p->data[0] + y * p->linesize[0];
             crow = png_choose_filter(s, crow_buf, ptr, top,
-                                     row_size, bits_per_pixel >> 3);
+                                     row_size, s->bits_per_pixel >> 3);
             png_write_row(s, crow, row_size + 1);
             top = ptr;
         }
@@ -501,30 +440,76 @@ static int encode_frame(AVCodecContext *avctx, AVPacket *pkt,
             if (ret == Z_STREAM_END)
                 break;
         } else {
-            goto fail;
+            ret = -1;
+            goto the_end;
         }
     }
-    png_write_chunk(&s->bytestream, MKTAG('I', 'E', 'N', 'D'), NULL, 0);
 
-    pkt->size   = s->bytestream - s->bytestream_start;
-    pkt->flags |= AV_PKT_FLAG_KEY;
-    *got_packet = 1;
-    ret         = 0;
+    ret = 0;
 
 the_end:
-    av_free(crow_base);
-    av_free(progressive_buf);
-    av_free(top_buf);
-    deflateEnd(&s->zstream);
+    av_freep(&crow_base);
+    av_freep(&progressive_buf);
+    av_freep(&top_buf);
+    deflateReset(&s->zstream);
     return ret;
-fail:
-    ret = -1;
-    goto the_end;
+}
+
+static int encode(AVCodecContext *avctx, AVPacket *pkt,
+                        const AVFrame *pict, int *got_packet)
+{
+    PNGEncContext *s = avctx->priv_data;
+    int ret;
+    int enc_row_size;
+    size_t max_packet_size;
+
+    enc_row_size = deflateBound(&s->zstream, (avctx->width * s->bits_per_pixel + 7) >> 3);
+    max_packet_size =
+        8 +             // PNGSIG
+        13 + 12 +       // IHDR
+        9 + 12 +        // pHYs
+        1 + 12 +        // sRGB
+        32 + 12 +       // cHRM
+        4 + 12 +        // gAMA
+        256 * 3 + 12 +  // PLTE
+        256 + 12 +      // tRNS
+        avctx->height * (
+            enc_row_size +
+            12 * ((enc_row_size + IOBUF_SIZE - 1) / IOBUF_SIZE) // 12 * ceil(enc_row_size / IOBUF_SIZE)
+        );
+
+    ret = ff_alloc_packet2(avctx, pkt, max_packet_size < FF_MIN_BUFFER_SIZE ? FF_MIN_BUFFER_SIZE : max_packet_size);
+    if (ret)
+        return ret;
+    s->bytestream_start =
+    s->bytestream       = pkt->data;
+    s->bytestream_end   = pkt->data + pkt->size;
+
+    if (avctx->codec_id == AV_CODEC_ID_PNG || pict->pts == 0) {
+        ret = encode_headers(avctx, pict);
+        if (ret)
+            return ret;
+    }
+
+    ret = encode_frame(avctx, pict);
+    if (ret)
+        return ret;
+
+    if (avctx->codec_id == AV_CODEC_ID_PNG) {
+        png_write_chunk(&s->bytestream, MKTAG('I', 'E', 'N', 'D'), NULL, 0);
+    }
+
+    pkt->size = s->bytestream - s->bytestream_start;
+    pkt->flags |= AV_PKT_FLAG_KEY;
+    *got_packet = 1;
+
+    return 0;
 }
 
 static av_cold int png_enc_init(AVCodecContext *avctx)
 {
     PNGEncContext *s = avctx->priv_data;
+    int compression_level;
 
     switch (avctx->pix_fmt) {
     case AV_PIX_FMT_RGBA:
@@ -565,11 +550,70 @@ static av_cold int png_enc_init(AVCodecContext *avctx)
       s->dpm = s->dpi * 10000 / 254;
     }
 
+    s->is_progressive = !!(avctx->flags & CODEC_FLAG_INTERLACED_DCT);
+    switch (avctx->pix_fmt) {
+    case AV_PIX_FMT_RGBA64BE:
+        s->bit_depth = 16;
+        s->color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+        break;
+    case AV_PIX_FMT_RGB48BE:
+        s->bit_depth = 16;
+        s->color_type = PNG_COLOR_TYPE_RGB;
+        break;
+    case AV_PIX_FMT_RGBA:
+        s->bit_depth  = 8;
+        s->color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+        break;
+    case AV_PIX_FMT_RGB24:
+        s->bit_depth  = 8;
+        s->color_type = PNG_COLOR_TYPE_RGB;
+        break;
+    case AV_PIX_FMT_GRAY16BE:
+        s->bit_depth  = 16;
+        s->color_type = PNG_COLOR_TYPE_GRAY;
+        break;
+    case AV_PIX_FMT_GRAY8:
+        s->bit_depth  = 8;
+        s->color_type = PNG_COLOR_TYPE_GRAY;
+        break;
+    case AV_PIX_FMT_GRAY8A:
+        s->bit_depth = 8;
+        s->color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
+        break;
+    case AV_PIX_FMT_YA16BE:
+        s->bit_depth = 16;
+        s->color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
+        break;
+    case AV_PIX_FMT_MONOBLACK:
+        s->bit_depth  = 1;
+        s->color_type = PNG_COLOR_TYPE_GRAY;
+        break;
+    case AV_PIX_FMT_PAL8:
+        s->bit_depth  = 8;
+        s->color_type = PNG_COLOR_TYPE_PALETTE;
+        break;
+    default:
+        return -1;
+    }
+    s->bits_per_pixel = ff_png_get_nb_channels(s->color_type) * s->bit_depth;
+
+    s->zstream.zalloc = ff_png_zalloc;
+    s->zstream.zfree  = ff_png_zfree;
+    s->zstream.opaque = NULL;
+    compression_level = avctx->compression_level == FF_COMPRESSION_DEFAULT
+                      ? Z_DEFAULT_COMPRESSION
+                      : av_clip(avctx->compression_level, 0, 9);
+    if (deflateInit2(&s->zstream, compression_level, Z_DEFLATED, 15, 8, Z_DEFAULT_STRATEGY) != Z_OK)
+        return -1;
+
     return 0;
 }
 
 static av_cold int png_enc_close(AVCodecContext *avctx)
 {
+    PNGEncContext *s = avctx->priv_data;
+
+    deflateEnd(&s->zstream);
     av_frame_free(&avctx->coded_frame);
     return 0;
 }
@@ -589,6 +633,13 @@ static const AVClass pngenc_class = {
     .version    = LIBAVUTIL_VERSION_INT,
 };
 
+static const AVClass apngenc_class = {
+    .class_name = "APNG encoder",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
 AVCodec ff_png_encoder = {
     .name           = "png",
     .long_name      = NULL_IF_CONFIG_SMALL("PNG (Portable Network Graphics) image"),
@@ -597,7 +648,7 @@ AVCodec ff_png_encoder = {
     .priv_data_size = sizeof(PNGEncContext),
     .init           = png_enc_init,
     .close          = png_enc_close,
-    .encode2        = encode_frame,
+    .encode2        = encode,
     .capabilities   = CODEC_CAP_FRAME_THREADS | CODEC_CAP_INTRA_ONLY,
     .pix_fmts       = (const enum AVPixelFormat[]) {
         AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA,
@@ -609,3 +660,24 @@ AVCodec ff_png_encoder = {
     },
     .priv_class     = &pngenc_class,
 };
+
+AVCodec ff_apng_encoder = {
+    .name           = "apng",
+    .long_name      = NULL_IF_CONFIG_SMALL("APNG (Animated Portable Network Graphics) image"),
+    .type           = AVMEDIA_TYPE_VIDEO,
+    .id             = AV_CODEC_ID_APNG,
+    .priv_data_size = sizeof(PNGEncContext),
+    .init           = png_enc_init,
+    .close          = png_enc_close,
+    .encode2        = encode,
+    .capabilities   = CODEC_CAP_FRAME_THREADS | CODEC_CAP_INTRA_ONLY,
+    .pix_fmts       = (const enum AVPixelFormat[]) {
+        AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA,
+        AV_PIX_FMT_RGB48BE, AV_PIX_FMT_RGBA64BE,
+        // TODO: AV_PIX_FMT_PAL8,
+        AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY8A,
+        AV_PIX_FMT_GRAY16BE, AV_PIX_FMT_YA16BE,
+        AV_PIX_FMT_MONOBLACK, AV_PIX_FMT_NONE
+    },
+    .priv_class     = &apngenc_class,
+};
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 5a93c68..a351b08 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -29,7 +29,7 @@
 #include "libavutil/version.h"
 
 #define LIBAVCODEC_VERSION_MAJOR 56
-#define LIBAVCODEC_VERSION_MINOR  31
+#define LIBAVCODEC_VERSION_MINOR  32
 #define LIBAVCODEC_VERSION_MICRO 100
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
-- 
1.9.1

From a3ab910f8dce3b70a4281477289ef3aac32d72c1 Mon Sep 17 00:00:00 2001
From: Donny Yang <w...@kota.moe>
Date: Sat, 28 Mar 2015 19:06:24 +1100
Subject: [PATCH 2/2] apng: Add a basic APNG muxer

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    | 249 +++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/version.h    |   4 +-
 6 files changed, 256 insertions(+), 3 deletions(-)
 create mode 100644 libavformat/apngenc.c

diff --git a/Changelog b/Changelog
index 109a1b8..a2d4974 100644
--- a/Changelog
+++ b/Changelog
@@ -11,6 +11,7 @@ version <next>:
 - nvenc H265 encoder
 - Detelecine filter
 - Intel QSV-accelerated H.264 encoding
+- 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..7e3ea87
--- /dev/null
+++ b/libavformat/apngenc.c
@@ -0,0 +1,249 @@
+/*
+ * 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;
+    uint16_t last_delay_num;
+    uint16_t last_delay_den;
+
+    uint64_t acTL_offset;
+    uint32_t sequence_number;
+    uint32_t frame_number;
+
+    AVPacket *prev_packet;
+    uint16_t prev_delay_num;
+    uint16_t prev_delay_den;
+} APNGMuxContext;
+
+static int apng_write_header(AVFormatContext *format_context)
+{
+    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");
+    }
+
+    // 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];
+    int64_t delay_num_raw, delay_den_raw;
+    int delay_num, delay_den;
+
+    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) {
+            delay_num_raw = (packet->dts - apng->prev_packet->dts) * codec_stream->time_base.num;
+            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_num = apng->last_delay_num;
+            delay_den = apng->last_delay_den;
+        } else {
+            delay_num = apng->prev_delay_num;
+            delay_den = apng->prev_delay_den;
+        }
+        apng->prev_delay_num = delay_num;
+        apng->prev_delay_den = delay_den;
+
+        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_num", "Force delay numerator after the last frame", OFFSET(last_delay_num),
+      AV_OPT_TYPE_INT, { .i64 = 0 }, 0, USHRT_MAX, ENC },
+    { "final_delay_den", "Force delay denominator after the last frame", OFFSET(last_delay_den),
+      AV_OPT_TYPE_INT, { .i64 = 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, \
-- 
1.9.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
http://ffmpeg.org/mailman/listinfo/ffmpeg-devel

Reply via email to