[FFmpeg-devel] [PATCH 2/2] lavc/msrleenc: Add msrle encoder
Andreas Rheinhardt
andreas.rheinhardt at outlook.com
Sat Jun 10 21:34:01 EEST 2023
Tomas Härdin:
> +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]];
Should be const.
> + // 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);
Wouldn't it be simpler to allocate this frame during init and then use
av_frame_replace() here?
Apart from that: You need to check the av_frame_ref().
> + 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,
> +};
An AVClass is pointless without options, so you should just remove it
(and the AVClass* from the context).
> +
> +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,
> +};
More information about the ffmpeg-devel
mailing list