mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-12-23 12:43:46 +02:00
lavc/msrleenc: Add msrle encoder
Keyframes are marked automagically
This commit is contained in:
parent
4cc40c050a
commit
8e53233f68
@ -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
|
||||
|
@ -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
|
||||
|
@ -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).
|
||||
|
||||
@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
|
||||
|
||||
MPEG-2 video encoder.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
303
libavcodec/msrleenc.c
Normal file
303
libavcodec/msrleenc.c
Normal file
@ -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-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
|
||||
|
||||
|
4
tests/ref/vsynth/vsynth1-msrle
Normal file
4
tests/ref/vsynth/vsynth1-msrle
Normal file
@ -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
|
4
tests/ref/vsynth/vsynth2-msrle
Normal file
4
tests/ref/vsynth/vsynth2-msrle
Normal file
@ -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
|
4
tests/ref/vsynth/vsynth3-msrle
Normal file
4
tests/ref/vsynth/vsynth3-msrle
Normal file
@ -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
|
4
tests/ref/vsynth/vsynth_lena-msrle
Normal file
4
tests/ref/vsynth/vsynth_lena-msrle
Normal file
@ -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…
Reference in New Issue
Block a user