mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-12-23 12:43:46 +02:00
1d66a122df
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>
301 lines
9.8 KiB
C
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,
|
|
};
|