lavc/msrleenc: Add msrle encoder

Keyframes are marked automagically
pull/389/head
Tomas Härdin 2 years ago
parent 4cc40c050a
commit 8e53233f68
  1. 1
      Changelog
  2. 1
      MAINTAINERS
  3. 14
      doc/encoders.texi
  4. 1
      libavcodec/Makefile
  5. 1
      libavcodec/allcodecs.c
  6. 303
      libavcodec/msrleenc.c
  7. 3
      tests/fate/vcodec.mak
  8. 4
      tests/ref/vsynth/vsynth1-msrle
  9. 4
      tests/ref/vsynth/vsynth2-msrle
  10. 4
      tests/ref/vsynth/vsynth3-msrle
  11. 4
      tests/ref/vsynth/vsynth_lena-msrle

@ -20,6 +20,7 @@ version <next>:
- Essential Video Coding parser, muxer and demuxer - Essential Video Coding parser, muxer and demuxer
- Essential Video Coding frame merge bsf - Essential Video Coding frame merge bsf
- bwdif_cuda filter - bwdif_cuda filter
- Microsoft RLE video encoder
version 6.0: version 6.0:
- Radiance HDR image support - Radiance HDR image support

@ -210,6 +210,7 @@ Codecs:
mqc* Nicolas Bertrand mqc* Nicolas Bertrand
msmpeg4.c, msmpeg4data.h Michael Niedermayer msmpeg4.c, msmpeg4data.h Michael Niedermayer
msrle.c Mike Melanson msrle.c Mike Melanson
msrleenc.c Tomas Härdin
msvideo1.c Mike Melanson msvideo1.c Mike Melanson
nuv.c Reimar Doeffinger nuv.c Reimar Doeffinger
nvdec*, nvenc* Timo Rothenpieler nvdec*, nvenc* Timo Rothenpieler

@ -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, (some encoders support both, some support only either - in practice,
nv12 is the safer choice, especially among HW encoders). nv12 is the safer choice, especially among HW encoders).
@section Microsoft RLE
Microsoft RLE aka MSRLE encoder.
Only 8-bit palette mode supported.
Compatible with Windows 3.1 and Windows 95.
@subsection Options
@table @option
@item g @var{integer}
Keyframe interval.
A keyframe is inserted at least every @code{-g} frames, sometimes sooner.
@end table
@section mpeg2 @section mpeg2
MPEG-2 video encoder. MPEG-2 video encoder.

@ -553,6 +553,7 @@ OBJS-$(CONFIG_MSA1_DECODER) += mss3.o
OBJS-$(CONFIG_MSCC_DECODER) += mscc.o OBJS-$(CONFIG_MSCC_DECODER) += mscc.o
OBJS-$(CONFIG_MSNSIREN_DECODER) += siren.o OBJS-$(CONFIG_MSNSIREN_DECODER) += siren.o
OBJS-$(CONFIG_MSP2_DECODER) += msp2dec.o OBJS-$(CONFIG_MSP2_DECODER) += msp2dec.o
OBJS-$(CONFIG_MSRLE_ENCODER) += msrleenc.o
OBJS-$(CONFIG_MSRLE_DECODER) += msrle.o msrledec.o OBJS-$(CONFIG_MSRLE_DECODER) += msrle.o msrledec.o
OBJS-$(CONFIG_MSS1_DECODER) += mss1.o mss12.o OBJS-$(CONFIG_MSS1_DECODER) += mss1.o mss12.o
OBJS-$(CONFIG_MSS2_DECODER) += mss2.o mss12.o mss2dsp.o wmv2data.o OBJS-$(CONFIG_MSS2_DECODER) += mss2.o mss12.o mss2dsp.o wmv2data.o

@ -228,6 +228,7 @@ extern const FFCodec ff_msmpeg4v3_encoder;
extern const FFCodec ff_msmpeg4v3_decoder; extern const FFCodec ff_msmpeg4v3_decoder;
extern const FFCodec ff_msmpeg4_crystalhd_decoder; extern const FFCodec ff_msmpeg4_crystalhd_decoder;
extern const FFCodec ff_msp2_decoder; extern const FFCodec ff_msp2_decoder;
extern const FFCodec ff_msrle_encoder;
extern const FFCodec ff_msrle_decoder; extern const FFCodec ff_msrle_decoder;
extern const FFCodec ff_mss1_decoder; extern const FFCodec ff_mss1_decoder;
extern const FFCodec ff_mss2_decoder; extern const FFCodec ff_mss2_decoder;

@ -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,
};

@ -338,6 +338,9 @@ fate-vsynth%-msmpeg4: ENCOPTS = -qscale 10
FATE_VCODEC-$(call ENCDEC, MSMPEG4V2, AVI) += msmpeg4v2 FATE_VCODEC-$(call ENCDEC, MSMPEG4V2, AVI) += msmpeg4v2
fate-vsynth%-msmpeg4v2: ENCOPTS = -qscale 10 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_VCODEC_SCALE-$(call ENCDEC, PNG, AVI) += mpng
fate-vsynth%-mpng: CODEC = png fate-vsynth%-mpng: CODEC = png

@ -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

@ -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

@ -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

@ -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
Loading…
Cancel
Save