[FFmpeg-devel] [PATCH 3/4] apng: Add a basic APNG encoder

Donny Yang work at kota.moe
Tue Mar 31 14:37:24 CEST 2015


Signed-off-by: Donny Yang <work at kota.moe>
---
 configure              |   1 +
 libavcodec/Makefile    |   1 +
 libavcodec/allcodecs.c |   2 +-
 libavcodec/pngenc.c    | 151 +++++++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 144 insertions(+), 11 deletions(-)

diff --git a/configure b/configure
index 392946a..bc59271 100755
--- a/configure
+++ b/configure
@@ -2099,6 +2099,7 @@ amv_decoder_select="sp5x_decoder exif"
 amv_encoder_select="aandcttables jpegtables 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 f8e2732..dd1d850 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -145,6 +145,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 89acac1..ef3558a 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -108,7 +108,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 e7c343b..21e35f2 100644
--- a/libavcodec/pngenc.c
+++ b/libavcodec/pngenc.c
@@ -24,6 +24,7 @@
 #include "bytestream.h"
 #include "huffyuvencdsp.h"
 #include "png.h"
+#include "apng.h"
 
 #include "libavutil/avassert.h"
 #include "libavutil/libm.h"
@@ -53,6 +54,10 @@ typedef struct PNGEncContext {
     int bit_depth;
     int color_type;
     int bits_per_pixel;
+
+    // APNG
+    uint32_t palette_checksum;   // Used to ensure a single unique palette
+    uint32_t sequence_number;
 } PNGEncContext;
 
 static void png_get_interlaced_row(uint8_t *dst, int row_size,
@@ -216,9 +221,34 @@ static void png_write_chunk(uint8_t **f, uint32_t tag,
     bytestream_put_be32(f, crc);
 }
 
+static void png_write_image_data(AVCodecContext *avctx,
+                                 const uint8_t *buf, int length)
+{
+    PNGEncContext *s = avctx->priv_data;
+    uint32_t crc = crc32(0, Z_NULL, 0);
+
+    if (avctx->codec_id == AV_CODEC_ID_PNG || avctx->frame_number == 0)
+        return png_write_chunk(&s->bytestream, MKTAG('I', 'D', 'A', 'T'), buf, length);
+
+    bytestream_put_be32(&s->bytestream, length + 4);
+
+    bytestream_put_be32(&s->bytestream, MKBETAG('f', 'd', 'A', 'T'));
+    bytestream_put_be32(&s->bytestream, s->sequence_number);
+    crc = crc32(crc, s->bytestream - 8, 8);
+
+    crc = crc32(crc, buf, length);
+    memcpy(s->bytestream, buf, length);
+    s->bytestream += length;
+
+    bytestream_put_be32(&s->bytestream, crc);
+
+    ++s->sequence_number;
+}
+
 /* XXX: do filtering */
-static int png_write_row(PNGEncContext *s, const uint8_t *data, int size)
+static int png_write_row(AVCodecContext *avctx, const uint8_t *data, int size)
 {
+    PNGEncContext *s = avctx->priv_data;
     int ret;
 
     s->zstream.avail_in = size;
@@ -229,8 +259,7 @@ static int png_write_row(PNGEncContext *s, const uint8_t *data, int size)
             return -1;
         if (s->zstream.avail_out == 0) {
             if (s->bytestream_end - s->bytestream > IOBUF_SIZE + 100)
-                png_write_chunk(&s->bytestream,
-                                MKTAG('I', 'D', 'A', 'T'), s->buf, IOBUF_SIZE);
+                png_write_image_data(avctx, s->buf, IOBUF_SIZE);
             s->zstream.avail_out = IOBUF_SIZE;
             s->zstream.next_out  = s->buf;
         }
@@ -409,7 +438,7 @@ static int encode_frame(AVCodecContext *avctx, const AVFrame *pict)
                                                ptr, avctx->width);
                         crow = png_choose_filter(s, crow_buf, progressive_buf,
                                                  top, pass_row_size, s->bits_per_pixel >> 3);
-                        png_write_row(s, crow, pass_row_size + 1);
+                        png_write_row(avctx, crow, pass_row_size + 1);
                         top = progressive_buf;
                     }
             }
@@ -420,7 +449,7 @@ static int encode_frame(AVCodecContext *avctx, const AVFrame *pict)
             ptr = p->data[0] + y * p->linesize[0];
             crow = png_choose_filter(s, crow_buf, ptr, top,
                                      row_size, s->bits_per_pixel >> 3);
-            png_write_row(s, crow, row_size + 1);
+            png_write_row(avctx, crow, row_size + 1);
             top = ptr;
         }
     }
@@ -430,7 +459,7 @@ static int encode_frame(AVCodecContext *avctx, const AVFrame *pict)
         if (ret == Z_OK || ret == Z_STREAM_END) {
             len = IOBUF_SIZE - s->zstream.avail_out;
             if (len > 0 && s->bytestream_end - s->bytestream > len + 100) {
-                png_write_chunk(&s->bytestream, MKTAG('I', 'D', 'A', 'T'), s->buf, len);
+                png_write_image_data(avctx, s->buf, len);
             }
             s->zstream.avail_out = IOBUF_SIZE;
             s->zstream.next_out  = s->buf;
@@ -452,15 +481,15 @@ the_end:
     return ret;
 }
 
-static int encode(AVCodecContext *avctx, AVPacket *pkt,
-                  const AVFrame *pict, int *got_packet)
+static int encode_png(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, row_size);
+    enc_row_size    = deflateBound(&s->zstream, (avctx->width * s->bits_per_pixel + 7) >> 3);
     max_packet_size =
         FF_MIN_BUFFER_SIZE + // headers
         avctx->height * (
@@ -497,6 +526,81 @@ static int encode(AVCodecContext *avctx, AVPacket *pkt,
     return 0;
 }
 
+static int encode_apng(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;
+    uint8_t buf[26];
+
+    if (avctx->codec_id == AV_CODEC_ID_APNG && s->color_type == PNG_COLOR_TYPE_PALETTE) {
+        uint32_t checksum = crc32(crc32(0, Z_NULL, 0), pict->data[1], 256 * sizeof(uint32_t));
+
+        if (avctx->frame_number == 0) {
+            s->palette_checksum = checksum;
+        } else if (checksum != s->palette_checksum) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Input contains more than one unique palette. APNG does not support multiple palettes.\n");
+            return -1;
+        }
+    }
+
+    enc_row_size    = deflateBound(&s->zstream, (avctx->width * s->bits_per_pixel + 7) >> 3);
+    max_packet_size =
+        FF_MIN_BUFFER_SIZE + // headers
+        avctx->height * (
+            enc_row_size +
+            (4 + 12) * (((int64_t)enc_row_size + IOBUF_SIZE - 1) / IOBUF_SIZE) // fdAT * ceil(enc_row_size / IOBUF_SIZE)
+        );
+    if (max_packet_size > INT_MAX)
+        return AVERROR(ENOMEM);
+    ret = ff_alloc_packet2(avctx, pkt, max_packet_size);
+    if (ret)
+        return ret;
+
+    s->bytestream_start =
+    s->bytestream       = pkt->data;
+    s->bytestream_end   = pkt->data + pkt->size;
+
+    if (avctx->frame_number == 0) {
+        ret = encode_headers(avctx, pict);
+        if (ret)
+            return ret;
+
+        avctx->extradata = av_malloc(s->bytestream - s->bytestream_start);
+        if (!avctx->extradata)
+            return AVERROR(ENOMEM);
+        avctx->extradata_size = s->bytestream - s->bytestream_start;
+        memcpy(avctx->extradata, s->bytestream_start, s->bytestream - s->bytestream_start);
+
+        s->bytestream = s->bytestream_start;
+    }
+
+    AV_WB32(buf, s->sequence_number);
+    AV_WB32(buf + 4, avctx->width);
+    AV_WB32(buf + 8, avctx->height);
+    AV_WB32(buf + 12, 0); // x offset
+    AV_WB32(buf + 16, 0); // y offset
+    AV_WB16(buf + 20, 0); // delay numerator (filled in during muxing)
+    AV_WB16(buf + 22, 0); // delay denominator
+    buf[24] = APNG_DISPOSE_OP_BACKGROUND;
+    buf[25] = APNG_BLEND_OP_SOURCE;
+    png_write_chunk(&s->bytestream, MKTAG('f', 'c', 'T', 'L'), buf, 26);
+    ++s->sequence_number;
+
+    ret = encode_frame(avctx, pict);
+    if (ret)
+        return ret;
+
+    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;
@@ -624,6 +728,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"),
@@ -632,7 +743,7 @@ AVCodec ff_png_encoder = {
     .priv_data_size = sizeof(PNGEncContext),
     .init           = png_enc_init,
     .close          = png_enc_close,
-    .encode2        = encode,
+    .encode2        = encode_png,
     .capabilities   = CODEC_CAP_FRAME_THREADS | CODEC_CAP_INTRA_ONLY,
     .pix_fmts       = (const enum AVPixelFormat[]) {
         AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA,
@@ -644,3 +755,23 @@ 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_apng,
+    .pix_fmts       = (const enum AVPixelFormat[]) {
+        AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA,
+        AV_PIX_FMT_RGB48BE, AV_PIX_FMT_RGBA64BE,
+        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,
+};
-- 
2.3.4


More information about the ffmpeg-devel mailing list