ffmpeg/libavcodec/msrleenc.c

304 lines
9.9 KiB
C

/*
* 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,
};