From 08df7e3fdd76beac2db1138f9ea4b573be97c0f1 Mon Sep 17 00:00:00 2001
From: Paul B Mahol <onemda@gmail.com>
Date: Sun, 4 Aug 2019 13:48:44 +0200
Subject: [PATCH] avcodec: add msrle encoder

Simple and inefficient in size approach.

---
 libavcodec/Makefile    |   1 +
 libavcodec/allcodecs.c |   1 +
 libavcodec/msrleenc.c  | 322 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 324 insertions(+)
 create mode 100644 libavcodec/msrleenc.c

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 3cd73fbcc6..9b6e8527e6 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -482,6 +482,7 @@ OBJS-$(CONFIG_MSMPEG4V2_ENCODER)       += msmpeg4enc.o msmpeg4.o msmpeg4data.o
 OBJS-$(CONFIG_MSMPEG4V3_DECODER)       += msmpeg4dec.o msmpeg4.o msmpeg4data.o
 OBJS-$(CONFIG_MSMPEG4V3_ENCODER)       += msmpeg4enc.o msmpeg4.o msmpeg4data.o
 OBJS-$(CONFIG_MSRLE_DECODER)           += msrle.o msrledec.o
+OBJS-$(CONFIG_MSRLE_ENCODER)           += msrleenc.o
 OBJS-$(CONFIG_MSS1_DECODER)            += mss1.o mss12.o
 OBJS-$(CONFIG_MSS2_DECODER)            += mss2.o mss12.o mss2dsp.o wmv2data.o
 OBJS-$(CONFIG_MSVIDEO1_DECODER)        += msvideo1.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index d2f9a39ce5..39d72f6420 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -209,6 +209,7 @@ extern AVCodec ff_msmpeg4v3_encoder;
 extern AVCodec ff_msmpeg4v3_decoder;
 extern AVCodec ff_msmpeg4_crystalhd_decoder;
 extern AVCodec ff_msrle_decoder;
+extern AVCodec ff_msrle_encoder;
 extern AVCodec ff_mss1_decoder;
 extern AVCodec ff_mss2_decoder;
 extern AVCodec ff_msvideo1_encoder;
diff --git a/libavcodec/msrleenc.c b/libavcodec/msrleenc.c
new file mode 100644
index 0000000000..e553e94463
--- /dev/null
+++ b/libavcodec/msrleenc.c
@@ -0,0 +1,322 @@
+/*
+ * Microsoft RLE Video encoder
+ * Copyright (c) 2019 Paul B Mahol
+ *
+ * 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 "libavutil/imgutils.h"
+#include "libavutil/intreadwrite.h"
+#include "avcodec.h"
+#include "bytestream.h"
+#include "internal.h"
+
+typedef struct MsrleEncContext {
+    AVCodecContext *avctx;
+    PutByteContext pb;
+    AVFrame *previous_frame;
+    unsigned max_buf_size;
+    int pixel_size;
+    int stride;
+    int extra_data_updated;
+    int key_frame;                  /* Encoded frame is a key frame */
+} MsrleEncContext;
+
+static av_cold int msrle_encode_end(AVCodecContext *avctx)
+{
+    MsrleEncContext *s = avctx->priv_data;
+
+    av_frame_free(&s->previous_frame);
+
+    return 0;
+}
+
+static av_cold int msrle_encode_init(AVCodecContext *avctx)
+{
+    MsrleEncContext *s = avctx->priv_data;
+
+    if (av_image_check_size(avctx->width, avctx->height, 0, avctx) < 0)
+        return AVERROR(EINVAL);
+
+    s->avctx = avctx;
+
+    switch (avctx->pix_fmt) {
+    case AV_PIX_FMT_PAL8:
+        avctx->bits_per_coded_sample = 8;
+        s->pixel_size = 1;
+        break;
+    case AV_PIX_FMT_BGR24:
+        avctx->bits_per_coded_sample = 24;
+        s->pixel_size = 3;
+        break;
+    default:
+        av_log(avctx, AV_LOG_ERROR, "Unsupported colorspace.\n");
+        break;
+    }
+
+    s->previous_frame = av_frame_alloc();
+    if (!s->previous_frame) {
+        av_log(avctx, AV_LOG_ERROR, "Error allocating picture\n");
+        return AVERROR(ENOMEM);
+    }
+
+    s->stride = FFALIGN(avctx->width * avctx->bits_per_coded_sample, 32) / 8;
+    s->max_buf_size = avctx->height * s->stride;
+
+    return 0;
+}
+
+static void msrle_encode_skip(PutByteContext *pb, int width, int skip)
+{
+    while (skip > 0) {
+        int x, y;
+
+        x = FFMIN(skip % width, 255);
+        y = FFMIN(skip / width, 255);
+
+        bytestream2_put_byte(pb, 0);
+        bytestream2_put_byte(pb, 2);
+        bytestream2_put_byte(pb, x);
+        bytestream2_put_byte(pb, y);
+        skip -= x;
+        skip -= y * width;
+    }
+}
+
+static void msrle_encode_line(MsrleEncContext *s, const AVFrame *p, int line, int *sskip)
+{
+    uint8_t *pos, *src = p->data[0] + p->linesize[0] * line;
+    uint8_t *prev = s->key_frame ? NULL : s->previous_frame->data[0] + s->previous_frame->linesize[0] * line;
+    int repeat = 0;
+    int copy = 0;
+    int skip = prev ? *sskip : -1;
+
+    for (int i = 0; i < s->avctx->width; i++, src += s->pixel_size) {
+        int same = (i < s->avctx->width) && (memcmp(src, src + s->pixel_size, s->pixel_size) == 0);
+        int samep = prev ? memcmp(src, prev, s->pixel_size) == 0 : 0;
+
+        if (samep) {
+            skip++;
+        } else {
+            if (skip > copy && skip > repeat) {
+                msrle_encode_skip(&s->pb, s->avctx->width, skip);
+                skip = 0;
+                repeat = 0;
+                copy = 0;
+            }
+        }
+
+        if (!same) {
+            while (repeat > 0 && skip <= repeat) {
+                int run = FFMIN(255, repeat);
+
+                bytestream2_put_byte(&s->pb, run);
+                switch (s->pixel_size) {
+                case 3:
+                    bytestream2_put_le24(&s->pb, AV_RL24(src));
+                    break;
+                case 1:
+                    bytestream2_put_byte(&s->pb, src[0]);
+                    break;
+                }
+                repeat -= run;
+                skip = 0;
+            }
+            copy += !same;
+        } else {
+            pos = src - copy * s->pixel_size;
+
+            if (skip <= copy) {
+                while (copy > 2) {
+                    int run = FFMIN(255, copy);
+
+                    bytestream2_put_byte(&s->pb, 0);
+                    bytestream2_put_byte(&s->pb, run);
+                    bytestream2_put_buffer(&s->pb, pos, run * s->pixel_size);
+                    if (s->pixel_size == 1 && (run & 1))
+                        bytestream2_put_byte(&s->pb, 0);
+                    copy -= run;
+                    pos += s->pixel_size * run;
+                    skip = 0;
+                }
+
+                for (int j = 0; j < copy; j++) {
+                    bytestream2_put_byte(&s->pb, 1);
+                    switch (s->pixel_size) {
+                    case 3:
+                        bytestream2_put_le24(&s->pb, AV_RL24(pos));
+                        break;
+                    case 1:
+                        bytestream2_put_byte(&s->pb, pos[0]);
+                        break;
+                    }
+                    pos += s->pixel_size;
+                    skip = 0;
+                }
+            }
+
+            copy = 0;
+            repeat++;
+        }
+
+        if (prev)
+            prev += s->pixel_size;
+    }
+
+    while (repeat > 0 && skip <= repeat) {
+        int run = FFMIN(255, repeat);
+
+        bytestream2_put_byte(&s->pb, run);
+        switch (s->pixel_size) {
+        case 3:
+            bytestream2_put_le24(&s->pb, AV_RL24(src));
+            break;
+        case 1:
+            bytestream2_put_byte(&s->pb, src[0]);
+            break;
+        }
+        repeat -= run;
+        skip = 0;
+    }
+
+    if (skip <= copy) {
+        pos = src - copy * s->pixel_size;
+
+        while (copy > 2) {
+            int run = FFMIN(255, copy);
+
+            bytestream2_put_byte(&s->pb, 0);
+            bytestream2_put_byte(&s->pb, run);
+            bytestream2_put_buffer(&s->pb, pos, run * s->pixel_size);
+            if (s->pixel_size == 1 && (run & 1))
+                bytestream2_put_byte(&s->pb, 0);
+            copy -= run;
+            pos += s->pixel_size * run;
+            skip = 0;
+        }
+
+        for (int j = 0; j < copy; j++) {
+            bytestream2_put_byte(&s->pb, 1);
+            switch (s->pixel_size) {
+            case 3:
+                bytestream2_put_le24(&s->pb, AV_RL24(pos));
+                break;
+            case 1:
+                bytestream2_put_byte(&s->pb, pos[0]);
+                break;
+            }
+            skip = 0;
+            pos += s->pixel_size;
+        }
+    }
+
+    if (!prev || (skip <= copy && skip <= repeat)) {
+        bytestream2_put_byte(&s->pb, 0);
+        bytestream2_put_byte(&s->pb, 0);
+        skip = 0;
+    }
+
+    *sskip = skip;
+}
+
+static int encode_frame(MsrleEncContext *s, const AVFrame *p)
+{
+    int sskip = 0;
+
+    for (int i = s->avctx->height - 1; i >= 0; i--) {
+        msrle_encode_line(s, p, i, &sskip);
+
+        if (bytestream2_get_eof(&s->pb))
+            return 1;
+    }
+
+    bytestream2_put_byte(&s->pb, 0);
+    bytestream2_put_byte(&s->pb, 1);
+
+    return bytestream2_get_eof(&s->pb);
+}
+
+static int msrle_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
+                              const AVFrame *frame, int *got_packet)
+{
+    MsrleEncContext * const s = avctx->priv_data;
+    uint8_t *side_data;
+    int ret;
+
+    if ((ret = ff_alloc_packet2(avctx, pkt, s->max_buf_size, 0)) < 0)
+        return ret;
+
+    if (avctx->pix_fmt == AV_PIX_FMT_PAL8 && !s->extra_data_updated) {
+        side_data = av_packet_new_side_data(pkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE);
+        if (!side_data)
+            return AVERROR(ENOMEM);
+        memcpy(side_data, frame->data[1], AVPALETTE_SIZE);
+        s->extra_data_updated = 1;
+    }
+
+    if (avctx->gop_size == 0 || (avctx->frame_number % avctx->gop_size) == 0) {
+        /* I-Frame */
+        s->key_frame = 1;
+    } else {
+        /* P-Frame */
+        s->key_frame = 0;
+    }
+
+    bytestream2_init_writer(&s->pb, pkt->data, pkt->size);
+
+    if (encode_frame(s, frame) == 0) {
+        pkt->size = bytestream2_tell_p(&s->pb);
+    } else {
+        uint8_t *src = frame->data[0];
+        uint8_t *dst = pkt->data;
+
+        for (int i = 0; i < avctx->height; i++) {
+            memcpy(dst, src, avctx->width * s->pixel_size);
+            dst += s->stride;
+            src += frame->linesize[0];
+        }
+        pkt->size = s->max_buf_size;
+    }
+
+    /* save the current frame */
+    av_frame_unref(s->previous_frame);
+    ret = av_frame_ref(s->previous_frame, frame);
+    if (ret < 0) {
+        av_log(avctx, AV_LOG_ERROR, "cannot add reference\n");
+        return ret;
+    }
+
+    if (s->key_frame)
+        pkt->flags |= AV_PKT_FLAG_KEY;
+    *got_packet = 1;
+
+    return 0;
+}
+
+AVCodec ff_msrle_encoder = {
+    .name           = "msrle",
+    .long_name      = NULL_IF_CONFIG_SMALL("Microsoft RLE"),
+    .type           = AVMEDIA_TYPE_VIDEO,
+    .id             = AV_CODEC_ID_MSRLE,
+    .priv_data_size = sizeof(MsrleEncContext),
+    .init           = msrle_encode_init,
+    .encode2        = msrle_encode_frame,
+    .close          = msrle_encode_end,
+    .pix_fmts       = (const enum AVPixelFormat[]){
+        AV_PIX_FMT_BGR24, AV_PIX_FMT_PAL8, AV_PIX_FMT_NONE
+    },
+};
-- 
2.22.0

