mirror of https://github.com/FFmpeg/FFmpeg.git
parent
4cc40c050a
commit
8e53233f68
11 changed files with 340 additions and 0 deletions
@ -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, |
||||||
|
}; |
@ -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…
Reference in new issue