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