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