From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.ffmpeg.org (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id 6508344AE3 for ; Wed, 13 Aug 2025 10:42:21 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id A0FCA68CE36; Wed, 13 Aug 2025 13:42:16 +0300 (EEST) Received: from 2daa80421c55 (code.ffmpeg.org [188.245.149.3]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTPS id 1EACF680355 for ; Wed, 13 Aug 2025 13:42:15 +0300 (EEST) MIME-Version: 1.0 From: Manuel Lauss To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] =?utf-8?q?=5BPATCH=5D_avcodec/sanm=3A_BL16_codec_?= =?utf-8?q?updates_=28PR_=2320233=29?= X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Message-Id: <20250813104216.A0FCA68CE36@ffbox0-bg.ffmpeg.org> Date: Wed, 13 Aug 2025 13:42:16 +0300 (EEST) Archived-At: List-Archive: List-Post: PR #20233 opened by Manuel Lauss (mlauss2) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20233 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20233.patch Blocky-16 is the internal name of the 16bit-SANM codec. * add support for BL16 subcompression types 1 and 7: Like codec47_comp1(), they decode a quarter-sized keyframe and interpolate the missing pixels with a mixing function. Type 1 delivers pixels as 16-bit values, while type 7 delivers them as 8-bit indices into the large codebook. They are implemented in the DLL used by the game "X-Wing Alliance", in "Grim Fandango Remaster" and "Indiana Jones and the Infernal Machine", but I haven't yet found any title which actually uses them. * prefix the functions related to it with bl16_ * structure the subcompression handler like the other (old) codecs. This lets at least gcc inline all the subcodecs into the main function. All the test videos I have still play without issues. >From 9ab275d17551c11562d1a86700534191155e1e60 Mon Sep 17 00:00:00 2001 From: Manuel Lauss Date: Wed, 13 Aug 2025 12:26:49 +0200 Subject: [PATCH] avcodec/sanm: BL16 codec updates Blocky-16 is the internal name of the 16bit-SANM codec. - add support for BL16 subcompression types 1 and 7: Like codec47_comp1(), they decode a quarter-sized keyframe and interpolate the missing pixels with a mixing function. Type 1 delivers pixels as 16-bit values, while type 7 delivers them as 8-bit indices into the large codebook. They are implemented in the DLL used by the game "X-Wing Alliance", but I haven't yet found any title which actually uses them. - prefix the functions related to it with bl16_ - structure the subcompression handler like the other (old) codecs. This lets at least gcc inline all the subcodecs into the main function. Signed-off-by: Manuel Lauss --- libavcodec/sanm.c | 281 +++++++++++++++++++++++++++++++--------------- 1 file changed, 193 insertions(+), 88 deletions(-) diff --git a/libavcodec/sanm.c b/libavcodec/sanm.c index d345f58846..c24a358a9a 100644 --- a/libavcodec/sanm.c +++ b/libavcodec/sanm.c @@ -1880,7 +1880,7 @@ static int process_xpal(SANMVideoContext *ctx, int size) return 0; } -static int decode_0(SANMVideoContext *ctx) +static int bl16_decode_0(SANMVideoContext *ctx) { uint16_t *frm = ctx->frm0; int x, y; @@ -1897,13 +1897,66 @@ static int decode_0(SANMVideoContext *ctx) return 0; } -static int decode_nop(SANMVideoContext *ctx) + +/* BL16 pixel interpolation function, see tgsmush.dll c690 */ +static inline uint16_t bl16_c1_avg_col(uint16_t c1, uint16_t c2) { - avpriv_request_sample(ctx->avctx, "Unknown/unsupported compression type"); - return AVERROR_PATCHWELCOME; + return (((c2 & 0x07e0) + (c1 & 0x07e0)) & 0x00fc0) | + (((c2 & 0xf800) + (c1 & 0xf800)) & 0x1f000) | + (((c2 & 0x001f) + (c1 & 0x001f))) >> 1; } -static void copy_block(uint16_t *pdest, uint16_t *psrc, int block_size, ptrdiff_t pitch) +/* Quarter-sized keyframe encoded as stream of 16bit pixel values. Interpolate + * missing pixels by averaging the colors of immediate neighbours. + * Identical to codec47_comp1() but with 16bit-pixels. tgsmush.dll c6f0 + */ +static int bl16_decode_1(SANMVideoContext *ctx) +{ + uint16_t hh, hw, c1, c2, c3, *dst1, *dst2; + + if (bytestream2_get_bytes_left(&ctx->gb) < ((ctx->width * ctx->height) / 2)) + return AVERROR_INVALIDDATA; + + hh = (ctx->height + 1) >> 1; + dst1 = (uint16_t *)ctx->frm0 + ctx->pitch; /* start with line 1 */ + while (hh--) { + hw = (ctx->width - 1) >> 1; + c1 = bytestream2_get_le16u(&ctx->gb); + dst1[0] = c1; + dst1[1] = c1; + dst2 = dst1 + 2; + while (hw--) { + c2 = bytestream2_get_le16u(&ctx->gb); + c3 = bl16_c1_avg_col(c1, c2); + *dst2++ = c3; + *dst2++ = c2; + c1 = c2; + } + dst1 += ctx->pitch * 2; /* skip to overnext line */ + } + /* line 0 is a copy of line 1 */ + memcpy(ctx->frm0, ctx->frm0 + ctx->pitch, ctx->pitch); + + /* complete the skipped lines by averaging from the pixels in the lines + * above and below + */ + dst1 = ctx->frm0 + (ctx->pitch * 2); + hh = (ctx->height - 1) >> 1; + while (hh--) { + hw = ctx->width; + dst2 = dst1; + while (hw--) { + c1 = *(dst2 - ctx->pitch); /* pixel from line above */ + c2 = *(dst2 + ctx->pitch); /* pixel from line below */ + c3 = bl16_c1_avg_col(c1, c2); + *dst2++ = c3; + } + dst1 += ctx->pitch * 2; + } + return 0; +} + +static void bl16_copy_block(uint16_t *pdest, uint16_t *psrc, int block_size, ptrdiff_t pitch) { uint8_t *dst = (uint8_t *)pdest; uint8_t *src = (uint8_t *)psrc; @@ -1922,7 +1975,7 @@ static void copy_block(uint16_t *pdest, uint16_t *psrc, int block_size, ptrdiff_ } } -static void fill_block(uint16_t *pdest, uint16_t color, int block_size, ptrdiff_t pitch) +static void bl16_fill_block(uint16_t *pdest, uint16_t color, int block_size, ptrdiff_t pitch) { int x, y; @@ -1932,7 +1985,7 @@ static void fill_block(uint16_t *pdest, uint16_t color, int block_size, ptrdiff_ *pdest++ = color; } -static int draw_glyph(SANMVideoContext *ctx, uint16_t *dst, int index, +static int bl16_draw_glyph(SANMVideoContext *ctx, uint16_t *dst, int index, uint16_t fg_color, uint16_t bg_color, int block_size, ptrdiff_t pitch) { @@ -1954,7 +2007,7 @@ static int draw_glyph(SANMVideoContext *ctx, uint16_t *dst, int index, return 0; } -static int opcode_0xf7(SANMVideoContext *ctx, int cx, int cy, int block_size, ptrdiff_t pitch) +static int bl16_opcode_0xf7(SANMVideoContext *ctx, int cx, int cy, int block_size, ptrdiff_t pitch) { uint16_t *dst = ctx->frm0 + cx + cy * ctx->pitch; @@ -1983,12 +2036,12 @@ static int opcode_0xf7(SANMVideoContext *ctx, int cx, int cy, int block_size, pt bgcolor = ctx->codebook[bytestream2_get_byteu(&ctx->gb)]; fgcolor = ctx->codebook[bytestream2_get_byteu(&ctx->gb)]; - draw_glyph(ctx, dst, glyph, fgcolor, bgcolor, block_size, pitch); + bl16_draw_glyph(ctx, dst, glyph, fgcolor, bgcolor, block_size, pitch); } return 0; } -static int opcode_0xf8(SANMVideoContext *ctx, int cx, int cy, int block_size, ptrdiff_t pitch) +static int bl16_opcode_0xf8(SANMVideoContext *ctx, int cx, int cy, int block_size, ptrdiff_t pitch) { uint16_t *dst = ctx->frm0 + cx + cy * ctx->pitch; @@ -2011,12 +2064,12 @@ static int opcode_0xf8(SANMVideoContext *ctx, int cx, int cy, int block_size, pt bgcolor = bytestream2_get_le16u(&ctx->gb); fgcolor = bytestream2_get_le16u(&ctx->gb); - draw_glyph(ctx, dst, glyph, fgcolor, bgcolor, block_size, pitch); + bl16_draw_glyph(ctx, dst, glyph, fgcolor, bgcolor, block_size, pitch); } return 0; } -static int good_mvec(SANMVideoContext *ctx, int cx, int cy, int mx, int my, +static int bl16_good_mvec(SANMVideoContext *ctx, int cx, int cy, int mx, int my, int block_size) { int start_pos = cx + mx + (cy + my) * ctx->pitch; @@ -2032,7 +2085,7 @@ static int good_mvec(SANMVideoContext *ctx, int cx, int cy, int mx, int my, return good; } -static int codec2subblock(SANMVideoContext *ctx, int cx, int cy, int blk_size) +static int bl16_codec2subblock(SANMVideoContext *ctx, int cx, int cy, int blk_size) { int16_t mx, my, index; int opcode; @@ -2047,8 +2100,8 @@ static int codec2subblock(SANMVideoContext *ctx, int cx, int cy, int blk_size) mx = motion_vectors[opcode][0]; my = motion_vectors[opcode][1]; - if (good_mvec(ctx, cx, cy, mx, my, blk_size)) { - copy_block(ctx->frm0 + cx + ctx->pitch * cy, + if (bl16_good_mvec(ctx, cx, cy, mx, my, blk_size)) { + bl16_copy_block(ctx->frm0 + cx + ctx->pitch * cy, ctx->frm2 + cx + mx + ctx->pitch * (cy + my), blk_size, ctx->pitch); } @@ -2061,55 +2114,55 @@ static int codec2subblock(SANMVideoContext *ctx, int cx, int cy, int blk_size) mx = index % ctx->width; my = index / ctx->width; - if (good_mvec(ctx, cx, cy, mx, my, blk_size)) { - copy_block(ctx->frm0 + cx + ctx->pitch * cy, - ctx->frm2 + cx + mx + ctx->pitch * (cy + my), - blk_size, ctx->pitch); + if (bl16_good_mvec(ctx, cx, cy, mx, my, blk_size)) { + bl16_copy_block(ctx->frm0 + cx + ctx->pitch * cy, + ctx->frm2 + cx + mx + ctx->pitch * (cy + my), + blk_size, ctx->pitch); } break; case 0xF6: - copy_block(ctx->frm0 + cx + ctx->pitch * cy, - ctx->frm1 + cx + ctx->pitch * cy, - blk_size, ctx->pitch); + bl16_copy_block(ctx->frm0 + cx + ctx->pitch * cy, + ctx->frm1 + cx + ctx->pitch * cy, + blk_size, ctx->pitch); break; case 0xF7: - opcode_0xf7(ctx, cx, cy, blk_size, ctx->pitch); + bl16_opcode_0xf7(ctx, cx, cy, blk_size, ctx->pitch); break; case 0xF8: - opcode_0xf8(ctx, cx, cy, blk_size, ctx->pitch); + bl16_opcode_0xf8(ctx, cx, cy, blk_size, ctx->pitch); break; case 0xF9: case 0xFA: case 0xFB: case 0xFC: - fill_block(ctx->frm0 + cx + cy * ctx->pitch, + bl16_fill_block(ctx->frm0 + cx + cy * ctx->pitch, ctx->small_codebook[opcode - 0xf9], blk_size, ctx->pitch); break; case 0xFD: if (bytestream2_get_bytes_left(&ctx->gb) < 1) return AVERROR_INVALIDDATA; - fill_block(ctx->frm0 + cx + cy * ctx->pitch, + bl16_fill_block(ctx->frm0 + cx + cy * ctx->pitch, ctx->codebook[bytestream2_get_byteu(&ctx->gb)], blk_size, ctx->pitch); break; case 0xFE: if (bytestream2_get_bytes_left(&ctx->gb) < 2) return AVERROR_INVALIDDATA; - fill_block(ctx->frm0 + cx + cy * ctx->pitch, + bl16_fill_block(ctx->frm0 + cx + cy * ctx->pitch, bytestream2_get_le16u(&ctx->gb), blk_size, ctx->pitch); break; case 0xFF: if (blk_size == 2) { - opcode_0xf8(ctx, cx, cy, blk_size, ctx->pitch); + bl16_opcode_0xf8(ctx, cx, cy, blk_size, ctx->pitch); } else { blk_size >>= 1; - if (codec2subblock(ctx, cx, cy, blk_size)) + if (bl16_codec2subblock(ctx, cx, cy, blk_size)) return AVERROR_INVALIDDATA; - if (codec2subblock(ctx, cx + blk_size, cy, blk_size)) + if (bl16_codec2subblock(ctx, cx + blk_size, cy, blk_size)) return AVERROR_INVALIDDATA; - if (codec2subblock(ctx, cx, cy + blk_size, blk_size)) + if (bl16_codec2subblock(ctx, cx, cy + blk_size, blk_size)) return AVERROR_INVALIDDATA; - if (codec2subblock(ctx, cx + blk_size, cy + blk_size, blk_size)) + if (bl16_codec2subblock(ctx, cx + blk_size, cy + blk_size, blk_size)) return AVERROR_INVALIDDATA; } break; @@ -2117,31 +2170,19 @@ static int codec2subblock(SANMVideoContext *ctx, int cx, int cy, int blk_size) return 0; } -static int decode_2(SANMVideoContext *ctx) +static int bl16_decode_2(SANMVideoContext *ctx) { int cx, cy, ret; for (cy = 0; cy < ctx->aligned_height; cy += 8) for (cx = 0; cx < ctx->aligned_width; cx += 8) - if (ret = codec2subblock(ctx, cx, cy, 8)) + if (ret = bl16_codec2subblock(ctx, cx, cy, 8)) return ret; return 0; } -static int decode_3(SANMVideoContext *ctx) -{ - memcpy(ctx->frm0, ctx->frm2, ctx->frm2_size); - return 0; -} - -static int decode_4(SANMVideoContext *ctx) -{ - memcpy(ctx->frm0, ctx->frm1, ctx->frm1_size); - return 0; -} - -static int decode_5(SANMVideoContext *ctx) +static int bl16_decode_5(SANMVideoContext *ctx) { #if HAVE_BIGENDIAN uint16_t *frm; @@ -2164,7 +2205,7 @@ static int decode_5(SANMVideoContext *ctx) return 0; } -static int decode_6(SANMVideoContext *ctx) +static int bl16_decode_6(SANMVideoContext *ctx) { int npixels = ctx->npixels; uint16_t *frm = ctx->frm0; @@ -2179,7 +2220,58 @@ static int decode_6(SANMVideoContext *ctx) return 0; } -static int decode_8(SANMVideoContext *ctx) +/* Quarter-sized keyframe encoded as stream of codebook indices. Interpolate + * missing pixels by averaging the colors of immediate neighbours. + * Identical to codec47_comp1(), but without the interpolation table. + * tgsmush.dll c6f0 + */ +static int bl16_decode_7(SANMVideoContext *ctx) +{ + uint16_t hh, hw, c1, c2, c3, *dst1, *dst2; + + if (bytestream2_get_bytes_left(&ctx->gb) < ((ctx->width * ctx->height) / 4)) + return AVERROR_INVALIDDATA; + + hh = (ctx->height + 1) >> 1; + dst1 = (uint16_t *)ctx->frm0 + ctx->pitch; /* start with line 1 */ + while (hh--) { + hw = (ctx->width - 1) >> 1; + c1 = ctx->codebook[bytestream2_get_byteu(&ctx->gb)]; + dst1[0] = c1; /* leftmost 2 pixels of a row are identical */ + dst1[1] = c1; + dst2 = dst1 + 2; + while (hw--) { + c2 = ctx->codebook[bytestream2_get_byteu(&ctx->gb)]; + c3 = bl16_c1_avg_col(c1, c2); + *dst2++ = c3; + *dst2++ = c2; + c1 = c2; + } + dst1 += ctx->pitch * 2; /* skip to overnext line */ + } + /* line 0 is a copy of line 1 */ + memcpy(ctx->frm0, ctx->frm0 + ctx->pitch, ctx->pitch); + + /* complete the skipped lines by averaging from the pixels in the lines + * above and below. + */ + dst1 = ctx->frm0 + (ctx->pitch * 2); + hh = (ctx->height - 1) >> 1; + while (hh--) { + hw = ctx->width; + dst2 = dst1; + while (hw--) { + c1 = *(dst2 - ctx->pitch); /* pixel from line above */ + c2 = *(dst2 + ctx->pitch); /* pixel from line below */ + c3 = bl16_c1_avg_col(c1, c2); + *dst2++ = c3; + } + dst1 += ctx->pitch * 2; + } + return 0; +} + +static int bl16_decode_8(SANMVideoContext *ctx) { uint16_t *pdest = ctx->frm0; uint8_t *rsrc; @@ -2201,14 +2293,7 @@ static int decode_8(SANMVideoContext *ctx) return 0; } -typedef int (*frm_decoder)(SANMVideoContext *ctx); - -static const frm_decoder v1_decoders[] = { - decode_0, decode_nop, decode_2, decode_3, decode_4, decode_5, - decode_6, decode_nop, decode_8 -}; - -static int read_frame_header(SANMVideoContext *ctx, SANMFrameHeader *hdr) +static int bl16_read_frame_header(SANMVideoContext *ctx, SANMFrameHeader *hdr) { int i, ret; @@ -2248,7 +2333,7 @@ static int read_frame_header(SANMVideoContext *ctx, SANMFrameHeader *hdr) return 0; } -static void fill_frame(uint16_t *pbuf, int buf_size, uint16_t color) +static void bl16_fill_frame(uint16_t *pbuf, int buf_size, uint16_t color) { if (buf_size--) { *pbuf++ = color; @@ -2278,6 +2363,54 @@ static int copy_output(SANMVideoContext *ctx, SANMFrameHeader *hdr) return 0; } +static int decode_bl16(AVCodecContext *avctx) +{ + SANMVideoContext *ctx = avctx->priv_data; + SANMFrameHeader header; + int ret; + + if ((ret = bl16_read_frame_header(ctx, &header))) + return ret; + + ctx->rotate_code = header.rotate_code; + if (!header.seq_num) { + ctx->frame->flags |= AV_FRAME_FLAG_KEY; + ctx->frame->pict_type = AV_PICTURE_TYPE_I; + bl16_fill_frame(ctx->frm1, ctx->npixels, header.bg_color); + bl16_fill_frame(ctx->frm2, ctx->npixels, header.bg_color); + } else { + ctx->frame->flags &= ~AV_FRAME_FLAG_KEY; + ctx->frame->pict_type = AV_PICTURE_TYPE_P; + } + + switch (header.codec) { + case 0: ret = bl16_decode_0(ctx); break; + case 1: ret = bl16_decode_1(ctx); break; + case 2: ret = bl16_decode_2(ctx); break; + case 3: memcpy(ctx->frm0, ctx->frm2, ctx->frm2_size); ret = 0; break; + case 4: memcpy(ctx->frm0, ctx->frm1, ctx->frm1_size); ret = 0; break; + case 5: ret = bl16_decode_5(ctx); break; + case 6: ret = bl16_decode_6(ctx); break; + case 7: ret = bl16_decode_7(ctx); break; + case 8: ret = bl16_decode_8(ctx); break; + default: + avpriv_request_sample(avctx, "BL16 Subcodec %d", header.codec); + return AVERROR_PATCHWELCOME; + break; + } + + if (ret) { + av_log(avctx, AV_LOG_ERROR, + "BL16 Subcodec %d: error decoding frame.\n", header.codec); + return ret; + } + + if ((ret = copy_output(ctx, &header))) + return ret; + + return 0; +} + static int decode_frame(AVCodecContext *avctx, AVFrame *frame, int *got_frame_ptr, AVPacket *pkt) { @@ -2388,38 +2521,10 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame, *got_frame_ptr = 1; } } else { - SANMFrameHeader header; - - if ((ret = read_frame_header(ctx, &header))) + /* SANM Blocky-16 (BL16) video codec chunk */ + if (ret = decode_bl16(avctx)) return ret; - - ctx->rotate_code = header.rotate_code; - if (!header.seq_num) { - ctx->frame->flags |= AV_FRAME_FLAG_KEY; - ctx->frame->pict_type = AV_PICTURE_TYPE_I; - fill_frame(ctx->frm1, ctx->npixels, header.bg_color); - fill_frame(ctx->frm2, ctx->npixels, header.bg_color); - } else { - ctx->frame->flags &= ~AV_FRAME_FLAG_KEY; - ctx->frame->pict_type = AV_PICTURE_TYPE_P; - } - - if (header.codec < FF_ARRAY_ELEMS(v1_decoders)) { - if ((ret = v1_decoders[header.codec](ctx))) { - av_log(avctx, AV_LOG_ERROR, - "Subcodec %d: error decoding frame.\n", header.codec); - return ret; - } - } else { - avpriv_request_sample(avctx, "Subcodec %d", header.codec); - return AVERROR_PATCHWELCOME; - } - - if ((ret = copy_output(ctx, &header))) - return ret; - *got_frame_ptr = 1; - } if (ctx->rotate_code) rotate_bufs(ctx, ctx->rotate_code); -- 2.49.1 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".