FFmpeg/libavcodec/msrleenc.c
Andreas Rheinhardt 1d66a122df avcodec/avcodec: Deprecate AV_INPUT_BUFFER_MIN_SIZE
It used to be used with preallocated packet buffers with
the old encode API, but said API is no more and therefore
there is no reason for this to be public any more.
So deprecate it and use an internal replacement
for the encoders using it as an upper bound for the
size of their headers.

Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
2024-02-21 00:24:44 +01:00

301 lines
9.8 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 {
int curframe;
AVFrame *last_frame;
} MSRLEContext;
static av_cold int msrle_encode_init(AVCodecContext *avctx)
{
MSRLEContext *s = avctx->priv_data;
avctx->bits_per_coded_sample = 8;
s->last_frame = av_frame_alloc();
if (!s->last_frame)
return AVERROR(ENOMEM);
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,
const 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,
const 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--) {
const uint8_t *line = &pict->data[0][y*pict->linesize[0]];
const 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 */ + FF_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);
if (!side_data)
return AVERROR(ENOMEM);
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;
return av_frame_replace(s->last_frame, pict);
}
static int msrle_encode_close(AVCodecContext *avctx)
{
MSRLEContext *s = avctx->priv_data;
av_frame_free(&s->last_frame);
return 0;
}
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
},
.caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
};