[FFmpeg-cvslog] lavc/msrleenc: Add msrle encoder
Tomas Härdin
git at videolan.org
Tue Jun 20 15:38:15 EEST 2023
ffmpeg | branch: master | Tomas Härdin <git at haerdin.se> | Thu Jun 8 11:57:53 2023 +0200| [8e53233f68a441cb4f1177bc3c237768830ac58e] | committer: Tomas Härdin
lavc/msrleenc: Add msrle encoder
Keyframes are marked automagically
> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=8e53233f68a441cb4f1177bc3c237768830ac58e
---
Changelog | 1 +
MAINTAINERS | 1 +
doc/encoders.texi | 14 ++
libavcodec/Makefile | 1 +
libavcodec/allcodecs.c | 1 +
libavcodec/msrleenc.c | 303 +++++++++++++++++++++++++++++++++++++
tests/fate/vcodec.mak | 3 +
tests/ref/vsynth/vsynth1-msrle | 4 +
tests/ref/vsynth/vsynth2-msrle | 4 +
tests/ref/vsynth/vsynth3-msrle | 4 +
tests/ref/vsynth/vsynth_lena-msrle | 4 +
11 files changed, 340 insertions(+)
diff --git a/Changelog b/Changelog
index fb47d2652b..9cf3df8d6f 100644
--- a/Changelog
+++ b/Changelog
@@ -20,6 +20,7 @@ version <next>:
- Essential Video Coding parser, muxer and demuxer
- Essential Video Coding frame merge bsf
- bwdif_cuda filter
+- Microsoft RLE video encoder
version 6.0:
- Radiance HDR image support
diff --git a/MAINTAINERS b/MAINTAINERS
index f6a0f9bcb5..fe60eca88d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -210,6 +210,7 @@ Codecs:
mqc* Nicolas Bertrand
msmpeg4.c, msmpeg4data.h Michael Niedermayer
msrle.c Mike Melanson
+ msrleenc.c Tomas Härdin
msvideo1.c Mike Melanson
nuv.c Reimar Doeffinger
nvdec*, nvenc* Timo Rothenpieler
diff --git a/doc/encoders.texi b/doc/encoders.texi
index 20cb8a1421..25d6b7f09e 100644
--- a/doc/encoders.texi
+++ b/doc/encoders.texi
@@ -3061,6 +3061,20 @@ Video encoders can take input in either of nv12 or yuv420p form
(some encoders support both, some support only either - in practice,
nv12 is the safer choice, especially among HW encoders).
+ at section Microsoft RLE
+
+Microsoft RLE aka MSRLE encoder.
+Only 8-bit palette mode supported.
+Compatible with Windows 3.1 and Windows 95.
+
+ at subsection Options
+
+ at table @option
+ at item g @var{integer}
+Keyframe interval.
+A keyframe is inserted at least every @code{-g} frames, sometimes sooner.
+ at end table
+
@section mpeg2
MPEG-2 video encoder.
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 723bfa25c7..0e4d27f37b 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -553,6 +553,7 @@ OBJS-$(CONFIG_MSA1_DECODER) += mss3.o
OBJS-$(CONFIG_MSCC_DECODER) += mscc.o
OBJS-$(CONFIG_MSNSIREN_DECODER) += siren.o
OBJS-$(CONFIG_MSP2_DECODER) += msp2dec.o
+OBJS-$(CONFIG_MSRLE_ENCODER) += msrleenc.o
OBJS-$(CONFIG_MSRLE_DECODER) += msrle.o msrledec.o
OBJS-$(CONFIG_MSS1_DECODER) += mss1.o mss12.o
OBJS-$(CONFIG_MSS2_DECODER) += mss2.o mss12.o mss2dsp.o wmv2data.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 11c136ef59..8775d15a4f 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -228,6 +228,7 @@ extern const FFCodec ff_msmpeg4v3_encoder;
extern const FFCodec ff_msmpeg4v3_decoder;
extern const FFCodec ff_msmpeg4_crystalhd_decoder;
extern const FFCodec ff_msp2_decoder;
+extern const FFCodec ff_msrle_encoder;
extern const FFCodec ff_msrle_decoder;
extern const FFCodec ff_mss1_decoder;
extern const FFCodec ff_mss2_decoder;
diff --git a/libavcodec/msrleenc.c b/libavcodec/msrleenc.c
new file mode 100644
index 0000000000..b73aa5e384
--- /dev/null
+++ b/libavcodec/msrleenc.c
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2023 Tomas Härdin
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * MSRLE encoder
+ * @see https://wiki.multimedia.cx/index.php?title=Microsoft_RLE
+ */
+
+// TODO: pal4 mode?
+
+#include "bytestream.h"
+#include "codec_internal.h"
+#include "encode.h"
+
+typedef struct MSRLEContext {
+ const AVClass *class;
+ int curframe;
+ AVFrame *last_frame;
+} MSRLEContext;
+
+static av_cold int msrle_encode_init(AVCodecContext *avctx)
+{
+ avctx->bits_per_coded_sample = 8;
+ return 0;
+}
+
+static void write_run(AVCodecContext *avctx, uint8_t **data, int len, int value)
+{
+ // we're allowed to write odd runs
+ while (len >= 255) {
+ bytestream_put_byte(data, 255);
+ bytestream_put_byte(data, value);
+ len -= 255;
+ }
+ if (len >= 1) {
+ // this is wasteful when len == 1 and sometimes when len == 2
+ // but sometimes we have no choice. also write_absolute()
+ // relies on this
+ bytestream_put_byte(data, len);
+ bytestream_put_byte(data, value);
+ }
+}
+
+static void write_absolute(AVCodecContext *avctx, uint8_t **data, uint8_t *line, int len)
+{
+ // writing 255 would be wasteful here due to the padding requirement
+ while (len >= 254) {
+ bytestream_put_byte(data, 0);
+ bytestream_put_byte(data, 254);
+ bytestream_put_buffer(data, line, 254);
+ line += 254;
+ len -= 254;
+ }
+ if (len == 1) {
+ // it's less wasteful to write single pixels as runs
+ // not to mention that absolute mode requires >= 3 pixels
+ write_run(avctx, data, 1, line[0]);
+ } else if (len == 2) {
+ write_run(avctx, data, 1, line[0]);
+ write_run(avctx, data, 1, line[1]);
+ } else if (len > 0) {
+ bytestream_put_byte(data, 0);
+ bytestream_put_byte(data, len);
+ bytestream_put_buffer(data, line, len);
+ if (len & 1)
+ bytestream_put_byte(data, 0);
+ }
+}
+
+static void write_delta(AVCodecContext *avctx, uint8_t **data, int delta)
+{
+ // we let the yskip logic handle the case where we want to delta
+ // to following lines. it's not perfect but it's easier than finding
+ // the optimal combination of end-of-lines and deltas to reach any
+ // following position including places where dx < 0
+ while (delta >= 255) {
+ bytestream_put_byte(data, 0);
+ bytestream_put_byte(data, 2);
+ bytestream_put_byte(data, 255);
+ bytestream_put_byte(data, 0);
+ delta -= 255;
+ }
+ if (delta > 0) {
+ bytestream_put_byte(data, 0);
+ bytestream_put_byte(data, 2);
+ bytestream_put_byte(data, delta);
+ bytestream_put_byte(data, 0);
+ }
+}
+
+static void write_yskip(AVCodecContext *avctx, uint8_t **data, int yskip)
+{
+ if (yskip < 4)
+ return;
+ // we have yskip*2 nul bytess
+ *data -= 2*yskip;
+ // the end-of-line counts as one skip
+ yskip--;
+ while (yskip >= 255) {
+ bytestream_put_byte(data, 0);
+ bytestream_put_byte(data, 2);
+ bytestream_put_byte(data, 0);
+ bytestream_put_byte(data, 255);
+ yskip -= 255;
+ }
+ if (yskip > 0) {
+ bytestream_put_byte(data, 0);
+ bytestream_put_byte(data, 2);
+ bytestream_put_byte(data, 0);
+ bytestream_put_byte(data, yskip);
+ }
+ bytestream_put_be16(data, 0x0000);
+}
+
+// used both to encode lines in keyframes and to encode lines between deltas
+static void encode_line(AVCodecContext *avctx, uint8_t **data, uint8_t *line, int length)
+{
+ int run = 0, last = -1, absstart = 0;
+ if (length == 0)
+ return;
+ for (int x = 0; x < length; x++) {
+ if (last == line[x]) {
+ run++;
+ if (run == 3)
+ write_absolute(avctx, data, &line[absstart], x - absstart - 2);
+ } else {
+ if (run >= 3) {
+ write_run(avctx, data, run, last);
+ absstart = x;
+ }
+ run = 1;
+ }
+ last = line[x];
+ }
+ if (run >= 3)
+ write_run(avctx, data, run, last);
+ else
+ write_absolute(avctx, data, &line[absstart], length - absstart);
+}
+
+static int encode(AVCodecContext *avctx, AVPacket *pkt,
+ const AVFrame *pict, int keyframe, int *got_keyframe)
+{
+ MSRLEContext *s = avctx->priv_data;
+ uint8_t *data = pkt->data;
+
+ /* Compare the current frame to the last frame, or code the entire frame
+ if keyframe != 0. We're continually outputting pairs of bytes:
+
+ 00 00 end of line
+ 00 01 end of bitmap
+ 00 02 dx dy delta. move pointer to x+dx, y+dy
+ 00 ll dd dd .. absolute (verbatim) mode. ll >= 3
+ rr dd run. rr >= 1
+
+ For keyframes we only have absolute mode and runs at our disposal, and
+ we are not allowed to end a line early. If this happens when keyframe == 0
+ then *got_keyframe is set to 1 and s->curframe is reset.
+ */
+ *got_keyframe = 1; // set to zero whenever we use a feature that makes this a not-keyframe
+
+ if (keyframe) {
+ for (int y = avctx->height-1; y >= 0; y--) {
+ uint8_t *line = &pict->data[0][y*pict->linesize[0]];
+ encode_line(avctx, &data, line, avctx->width);
+ bytestream_put_be16(&data, 0x0000); // end of line
+ }
+ } else {
+ // compare to previous frame
+ int yskip = 0; // we can encode large skips using deltas
+ for (int y = avctx->height-1; y >= 0; y--) {
+ uint8_t *line = &pict->data[0][y*pict->linesize[0]];
+ uint8_t *prev = &s->last_frame->data[0][y*s->last_frame->linesize[0]];
+ // we need at least 5 pixels in a row for a delta to be worthwhile
+ int delta = 0, linestart = 0, encoded = 0;
+ for (int x = 0; x < avctx->width; x++) {
+ if (line[x] == prev[x]) {
+ delta++;
+ if (delta == 5) {
+ int len = x - linestart - 4;
+ if (len > 0) {
+ write_yskip(avctx, &data, yskip);
+ yskip = 0;
+ encode_line(avctx, &data, &line[linestart], len);
+ encoded = 1;
+ }
+ linestart = -1;
+ }
+ } else {
+ if (delta >= 5) {
+ write_yskip(avctx, &data, yskip);
+ yskip = 0;
+ write_delta(avctx, &data, delta);
+ *got_keyframe = 0;
+ encoded = 1;
+ }
+ delta = 0;
+ if (linestart == -1)
+ linestart = x;
+ }
+ }
+ if (delta < 5) {
+ write_yskip(avctx, &data, yskip);
+ yskip = 0;
+ encode_line(avctx, &data, &line[linestart], avctx->width - linestart);
+ encoded = 1;
+ } else
+ *got_keyframe = 0;
+ bytestream_put_be16(&data, 0x0000); // end of line
+ if (!encoded)
+ yskip++;
+ else
+ yskip = 0;
+ }
+ write_yskip(avctx, &data, yskip);
+ }
+ bytestream_put_be16(&data, 0x0001); // end of bitmap
+ pkt->size = data - pkt->data;
+ return 0;}
+
+static int msrle_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
+ const AVFrame *pict, int *got_packet)
+{
+ MSRLEContext *s = avctx->priv_data;
+ int ret, got_keyframe;
+
+ if ((ret = ff_alloc_packet(avctx, pkt, (
+ avctx->width*2 /* worst case = rle every pixel */ + 2 /*end of line */
+ ) * avctx->height + 2 /* end of bitmap */ + AV_INPUT_BUFFER_MIN_SIZE)))
+ return ret;
+
+ if (pict->data[1]) {
+ uint8_t *side_data = av_packet_new_side_data(pkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE);
+ memcpy(side_data, pict->data[1], AVPALETTE_SIZE);
+ }
+
+ if ((ret = encode(avctx, pkt, pict, s->curframe == 0, &got_keyframe)))
+ return ret;
+
+ if (got_keyframe) {
+ pkt->flags |= AV_PKT_FLAG_KEY;
+ s->curframe = 0;
+ }
+ if (++s->curframe >= avctx->gop_size)
+ s->curframe = 0;
+ *got_packet = 1;
+
+ if (!s->last_frame)
+ s->last_frame = av_frame_alloc();
+ else
+ av_frame_unref(s->last_frame);
+
+ av_frame_ref(s->last_frame, pict);
+ return 0;
+}
+
+static int msrle_encode_close(AVCodecContext *avctx)
+{
+ MSRLEContext *s = avctx->priv_data;
+ av_frame_free(&s->last_frame);
+ return 0;
+}
+
+static const AVClass msrle_class = {
+ .class_name = "Microsoft RLE encoder",
+ .item_name = av_default_item_name,
+ .version = LIBAVUTIL_VERSION_INT,
+};
+
+const FFCodec ff_msrle_encoder = {
+ .p.name = "msrle",
+ CODEC_LONG_NAME("Microsoft RLE"),
+ .p.type = AVMEDIA_TYPE_VIDEO,
+ .p.id = AV_CODEC_ID_MSRLE,
+ .p.capabilities = AV_CODEC_CAP_DR1,
+ .priv_data_size = sizeof(MSRLEContext),
+ .init = msrle_encode_init,
+ FF_CODEC_ENCODE_CB(msrle_encode_frame),
+ .close = msrle_encode_close,
+ .p.pix_fmts = (const enum AVPixelFormat[]){
+ AV_PIX_FMT_PAL8, AV_PIX_FMT_NONE
+ },
+ .p.priv_class = &msrle_class,
+ .caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
+};
diff --git a/tests/fate/vcodec.mak b/tests/fate/vcodec.mak
index fbee264a9d..ef8904613a 100644
--- a/tests/fate/vcodec.mak
+++ b/tests/fate/vcodec.mak
@@ -338,6 +338,9 @@ fate-vsynth%-msmpeg4: ENCOPTS = -qscale 10
FATE_VCODEC-$(call ENCDEC, MSMPEG4V2, AVI) += msmpeg4v2
fate-vsynth%-msmpeg4v2: ENCOPTS = -qscale 10
+FATE_VCODEC_SCALE-$(call ENCDEC, MSRLE, AVI) += msrle
+fate-vsynth%-msrle: CODEC = msrle
+
FATE_VCODEC_SCALE-$(call ENCDEC, PNG, AVI) += mpng
fate-vsynth%-mpng: CODEC = png
diff --git a/tests/ref/vsynth/vsynth1-msrle b/tests/ref/vsynth/vsynth1-msrle
new file mode 100644
index 0000000000..6174ff57f8
--- /dev/null
+++ b/tests/ref/vsynth/vsynth1-msrle
@@ -0,0 +1,4 @@
+b19bc15e2c5866f3c7942aad47ce0261 *tests/data/fate/vsynth1-msrle.avi
+5216296 tests/data/fate/vsynth1-msrle.avi
+f142ee03bf9f37bb2e1902fe32366bbf *tests/data/fate/vsynth1-msrle.out.rawvideo
+stddev: 8.69 PSNR: 29.34 MAXDIFF: 64 bytes: 7603200/ 7603200
diff --git a/tests/ref/vsynth/vsynth2-msrle b/tests/ref/vsynth/vsynth2-msrle
new file mode 100644
index 0000000000..dd579b6ee9
--- /dev/null
+++ b/tests/ref/vsynth/vsynth2-msrle
@@ -0,0 +1,4 @@
+850744d6d38ab09adb2fbd685d5df740 *tests/data/fate/vsynth2-msrle.avi
+4556642 tests/data/fate/vsynth2-msrle.avi
+df26a524cad8ebf0cf5ba4376c5f115f *tests/data/fate/vsynth2-msrle.out.rawvideo
+stddev: 7.57 PSNR: 30.54 MAXDIFF: 35 bytes: 7603200/ 7603200
diff --git a/tests/ref/vsynth/vsynth3-msrle b/tests/ref/vsynth/vsynth3-msrle
new file mode 100644
index 0000000000..9cc92be036
--- /dev/null
+++ b/tests/ref/vsynth/vsynth3-msrle
@@ -0,0 +1,4 @@
+ee8f4d86f117d69919be69fbc976981a *tests/data/fate/vsynth3-msrle.avi
+72866 tests/data/fate/vsynth3-msrle.avi
+fa6042492a3116c1ae9a32b487caa677 *tests/data/fate/vsynth3-msrle.out.rawvideo
+stddev: 8.88 PSNR: 29.16 MAXDIFF: 51 bytes: 86700/ 86700
diff --git a/tests/ref/vsynth/vsynth_lena-msrle b/tests/ref/vsynth/vsynth_lena-msrle
new file mode 100644
index 0000000000..67226f05a2
--- /dev/null
+++ b/tests/ref/vsynth/vsynth_lena-msrle
@@ -0,0 +1,4 @@
+9654924690cbaf6348ea798e442e819c *tests/data/fate/vsynth_lena-msrle.avi
+4671320 tests/data/fate/vsynth_lena-msrle.avi
+db453693ceae6f65c173dd716ee2662e *tests/data/fate/vsynth_lena-msrle.out.rawvideo
+stddev: 8.07 PSNR: 29.99 MAXDIFF: 32 bytes: 7603200/ 7603200
More information about the ffmpeg-cvslog
mailing list