--- libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/codec_desc.c | 7 + libavcodec/codec_id.h | 1 + libavcodec/qmagedata.h | 133 +++++++ libavcodec/qmagedec.c | 836 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 979 insertions(+) create mode 100644 libavcodec/qmagedata.h create mode 100644 libavcodec/qmagedec.c
diff --git a/libavcodec/Makefile b/libavcodec/Makefile index a6e0e0b55e..eb724eab21 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -634,6 +634,7 @@ OBJS-$(CONFIG_QCELP_DECODER) += qcelpdec.o \ OBJS-$(CONFIG_QDM2_DECODER) += qdm2.o OBJS-$(CONFIG_QDMC_DECODER) += qdmc.o OBJS-$(CONFIG_QDRAW_DECODER) += qdrw.o +OBJS-$(CONFIG_QMAGE_DECODER) += qmagedec.o OBJS-$(CONFIG_QOA_DECODER) += qoadec.o OBJS-$(CONFIG_QOI_DECODER) += qoidec.o OBJS-$(CONFIG_QOI_ENCODER) += qoienc.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 0b559dfc58..9ae639859c 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -271,6 +271,7 @@ extern const FFCodec ff_prosumer_decoder; extern const FFCodec ff_psd_decoder; extern const FFCodec ff_ptx_decoder; extern const FFCodec ff_qdraw_decoder; +extern const FFCodec ff_qmage_decoder; extern const FFCodec ff_qoi_encoder; extern const FFCodec ff_qoi_decoder; extern const FFCodec ff_qpeg_decoder; diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c index aeac75a6c5..4d52fed5f5 100644 --- a/libavcodec/codec_desc.c +++ b/libavcodec/codec_desc.c @@ -1977,6 +1977,13 @@ static const AVCodecDescriptor codec_descriptors[] = { .long_name = NULL_IF_CONFIG_SMALL("RealVideo 6.0"), .props = AV_CODEC_PROP_LOSSY | AV_CODEC_PROP_REORDER, }, + { + .id = AV_CODEC_ID_QMAGE, + .type = AVMEDIA_TYPE_VIDEO, + .name = "qmage", + .long_name = NULL_IF_CONFIG_SMALL("Quram Qmage"), + .props = AV_CODEC_PROP_LOSSLESS, + }, /* various PCM "codecs" */ { diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h index 6bfaa02601..930efa3454 100644 --- a/libavcodec/codec_id.h +++ b/libavcodec/codec_id.h @@ -328,6 +328,7 @@ enum AVCodecID { AV_CODEC_ID_LEAD, AV_CODEC_ID_DNXUC, AV_CODEC_ID_RV60, + AV_CODEC_ID_QMAGE, /* various PCM "codecs" */ AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs diff --git a/libavcodec/qmagedata.h b/libavcodec/qmagedata.h new file mode 100644 index 0000000000..21fc8789f2 --- /dev/null +++ b/libavcodec/qmagedata.h @@ -0,0 +1,133 @@ +/* + * Quram Qmage image format decoder + * + * 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 + */ + +#include <stdint.h> + +static const struct { int8_t x, y; } qmage_dir[3] = { + [0] = {-1, 0}, + [1] = {0, -1}, + [2] = {-1,-1}, +}; + +static const uint16_t qmage_ori_delta[2][256] = { + { + 0xffe0, 0x0020, 0xffff, 0xf7e0, 0xf800, 0xffdf, 0x0001, 0x0800, + 0x0021, 0x0820, 0x0841, 0xf7bf, 0xf7df, 0x0821, 0xf7c0, 0xffbf, + 0x0041, 0xffc0, 0x0861, 0x0840, 0x001f, 0x0862, 0xffe1, 0xf79f, + 0x0040, 0xf7ff, 0xf820, 0x0842, 0x1082, 0x07e0, 0xf79e, 0x0801, + 0x1061, 0x1062, 0x07ff, 0xef7e, 0xef9f, 0xf801, 0xf7be, 0xefbf, + 0x0022, 0x0882, 0xefe0, 0xefc0, 0x07df, 0x1041, 0x081f, 0x10a3, + 0x0042, 0x1081, 0xef9e, 0xf7e1, 0xf821, 0xf77e, 0xff9f, 0x10a2, + 0x1040, 0xffbe, 0x1083, 0x1020, 0xef5d, 0xef7f, 0xffde, 0xef5e, + 0x0002, 0x0061, 0xf7a0, 0x18c3, 0xef7d, 0x0822, 0xff9e, 0xf000, + 0x0860, 0xe73d, 0xefa0, 0x0881, 0xefdf, 0x07bf, 0xf77f, 0x0062, + 0xfffe, 0x18a3, 0x10c3, 0x0883, 0x18e4, 0x1060, 0x0863, 0x1000, + 0x18a2, 0xe71c, 0xf7de, 0x1021, 0x1042, 0x18c4, 0xef3d, 0x18e3, + 0xf841, 0xe75d, 0x1881, 0xe73c, 0xe7c0, 0x18c2, 0xe75e, 0xf77d, + 0x10a4, 0x07c0, 0xf81f, 0x10c4, 0xff7e, 0x003f, 0xf840, 0xe79f, + 0x2104, 0xffa0, 0xffc1, 0x18a1, 0xe77f, 0xefbe, 0xf79d, 0x1882, + 0x10a1, 0xe71d, 0x1063, 0xe7a0, 0xe7e0, 0xf7c1, 0x1861, 0x08a3, + 0x1840, 0xe73e, 0x083f, 0xf001, 0xdefc, 0xf75e, 0x0060, 0x2125, + 0x07e1, 0x0fff, 0xef5f, 0xe77e, 0xefe1, 0xe6fc, 0x10c2, 0x08a2, + 0x0023, 0x1860, 0xef3c, 0xef3e, 0x0043, 0xf020, 0xf75d, 0x079f, + 0x101f, 0xdedb, 0xef5c, 0xffe2, 0xf822, 0x20e4, 0x001e, 0x0843, + 0xe7bf, 0x20e3, 0x0082, 0xdefb, 0xef9d, 0x1904, 0xff7f, 0x1820, + 0xe75f, 0x0063, 0xef80, 0x2105, 0x1905, 0xdefd, 0x2124, 0xdf1c, + 0x2103, 0x1080, 0xdf1d, 0xe6fb, 0x07be, 0x2945, 0xdedc, 0xf021, + 0x0fe0, 0xe71b, 0xefff, 0xf842, 0x20c2, 0x079e, 0xf802, 0x0fdf, + 0x20e2, 0x18e2, 0xe780, 0x07de, 0x1880, 0xdfa0, 0x20c1, 0xffbd, + 0x08a4, 0x10e4, 0x18c1, 0xdfc0, 0x103f, 0x0003, 0xdeba, 0xff7d, + 0x1841, 0xe800, 0x20a1, 0xefc1, 0xff9d, 0xf780, 0x18e5, 0x10e3, + 0x1084, 0x0081, 0x2966, 0xffdd, 0x08c3, 0x18a4, 0xdeda, 0xff5e, + 0xdf7f, 0x1883, 0xdebb, 0x2145, 0xd6bb, 0xf7bd, 0x20c3, 0x1903, + 0x077e, 0x0884, 0xf861, 0xe71e, 0x1001, 0xe75c, 0x0083, 0xd6ba, + 0x07fe, 0x2102, 0xdf80, 0x2081, 0x0880, 0xdf3e, 0x2946, 0xe6fd, + 0x2146, 0xef1c, 0xdf9f, 0xdf1e, 0xe73f, 0xe79e, 0x20a2, 0xd69a, + }, + { + 0xffe0, 0x0020, 0xffff, 0x0001, 0xf800, 0x0800, 0xf7df, 0xffdf, + 0xf7e0, 0x0021, 0xf7bf, 0x0821, 0x0820, 0x0841, 0xf7ff, 0x0801, + 0xffc0, 0x07e0, 0xf820, 0xf801, 0x07ff, 0xf7c0, 0xffe1, 0x0040, + 0x001f, 0xffbf, 0xf79f, 0x0840, 0xffff, 0x0041, 0x0861, 0xef7e, + 0x1082, 0xef9e, 0xf81f, 0xf7a0, 0x0001, 0xe73d, 0xef9f, 0x1062, + 0x0860, 0x1061, 0x18c3, 0x07df, 0xf7be, 0xef5e, 0xefbf, 0xf79e, + 0xf7e1, 0xf821, 0x10a2, 0xef7f, 0x0842, 0x07e1, 0xdefc, 0x1041, + 0x081f, 0x0862, 0x0002, 0x1081, 0x2104, 0xf81f, 0xefc0, 0x07e1, + 0xfffe, 0xffbe, 0xe75d, 0x2945, 0xffa0, 0xf780, 0x1040, 0xd6bb, + 0x0042, 0x18a3, 0x18e3, 0x0880, 0x0822, 0xff9f, 0x0022, 0xffc1, + 0x003f, 0xffde, 0xe71d, 0x3186, 0xef5f, 0xf7de, 0xce7a, 0x0060, + 0x10a1, 0xe75e, 0xf77e, 0xdedc, 0x39c7, 0xf77f, 0xc639, 0x18a2, + 0xefe0, 0x2124, 0x1080, 0x20e4, 0x0061, 0x1020, 0x1000, 0xefa0, + 0xff9e, 0x07c0, 0xdf1c, 0xbdf8, 0x1060, 0xf000, 0x4208, 0xd69b, + 0x0881, 0x5acb, 0x2965, 0x0882, 0xef80, 0xf840, 0xe77f, 0xa535, + 0xe73e, 0xce5a, 0x21ae, 0xf760, 0x1881, 0x4a49, 0xefbe, 0xb5b7, + 0x0062, 0xf7c1, 0x083f, 0x2925, 0xe73f, 0x4a4a, 0xef3f, 0x08a0, + 0xe75f, 0x1042, 0xad76, 0x18c2, 0x3126, 0xf7fe, 0x528a, 0xc619, + 0x31a6, 0xd6db, 0xff7e, 0x10c1, 0xe77e, 0xceda, 0x18c1, 0xb5b6, + 0x07bf, 0xf83f, 0x630c, 0xde52, 0x39e7, 0xefdf, 0xef5d, 0x1861, + 0x10a0, 0xe79f, 0x9cf4, 0xce9a, 0xff7f, 0x18a1, 0x1021, 0x0802, + 0xef7d, 0x1882, 0x3166, 0x0003, 0xf841, 0xf7bd, 0xe71f, 0xdf7f, + 0x10a3, 0x07c1, 0x18e1, 0x94b3, 0x2081, 0xbdd8, 0x6b4d, 0xef60, + 0x1083, 0xdf1d, 0xff7d, 0x0863, 0xf79d, 0xdf3e, 0xf77d, 0x4228, + 0xad56, 0xf7dd, 0x20e3, 0x52aa, 0xb597, 0x3967, 0xc699, 0x20c2, + 0x8c72, 0xff80, 0x001e, 0x39a7, 0xe6ff, 0xff5f, 0xc659, 0x0082, + 0x1901, 0x1063, 0x4a69, 0xef9d, 0x0843, 0xd6dc, 0xfffd, 0x738e, + 0x07fe, 0x8431, 0xffe2, 0xe7a0, 0xef40, 0x7bf0, 0xe71c, 0xff9d, + 0xef3d, 0xefff, 0x2924, 0xa515, 0xd73e, 0x8410, 0x28c2, 0x7bcf, + 0x1043, 0xf75e, 0x41e8, 0x10c3, 0xbe18, 0x5aeb, 0xd6dd, 0xdf5f, + 0x9cd4, 0x0883, 0x20a2, 0xf802, 0x632c, 0xe780, 0x0083, 0xdf5e, + 0xefbd, 0xff5d, 0x0823, 0x1880, 0x1022, 0xdefd, 0x18e4, 0xefde, + } +}; + +static const uint16_t qmage_diff[256] = { + 0x0001, 0x0003, 0x0100, 0x0002, 0x0008, 0x0007, 0x0006, 0x0300, + 0x0010, 0x0004, 0x0200, 0x0009, 0x0040, 0x0018, 0x0005, 0x0020, + 0x000c, 0x000e, 0x000f, 0x000a, 0x00c0, 0x0800, 0x0700, 0x0101, + 0x0400, 0x000b, 0x0030, 0x0011, 0x0080, 0x0600, 0x000d, 0x0012, + 0x001c, 0x0500, 0x001b, 0x001e, 0x0014, 0x001a, 0x0028, 0x0038, + 0x1000, 0x001f, 0x0019, 0x0016, 0x0060, 0x2000, 0x0013, 0x001d, + 0x0103, 0x0024, 0x0017, 0x0015, 0x0102, 0x01c0, 0x0f00, 0x003c, + 0x0301, 0x0c00, 0x1800, 0x0048, 0x0021, 0x0034, 0x0e00, 0x0202, + 0x002c, 0x0070, 0x0a00, 0x0303, 0x0036, 0x0201, 0x003f, 0x0d00, + 0x0180, 0x003e, 0x3000, 0x0900, 0x0078, 0x0022, 0x0050, 0x003a, + 0x0041, 0x0107, 0x0033, 0x0106, 0x0026, 0x002a, 0x00a0, 0x0023, + 0x0029, 0x0088, 0x0044, 0x003d, 0x00e0, 0x0032, 0x002e, 0x0039, + 0x0031, 0x002d, 0x00f0, 0x0140, 0x0b00, 0x003b, 0x0058, 0x4000, + 0x0037, 0x0035, 0x0068, 0x0302, 0x007c, 0x002f, 0x0027, 0x0064, + 0x0090, 0x0074, 0x0203, 0x0104, 0x006c, 0x1100, 0x03c0, 0x00ff, + 0x0025, 0xf000, 0x1f00, 0x0701, 0x0042, 0x007f, 0x002b, 0x0105, + 0x0054, 0x1c00, 0x004c, 0x0801, 0x0043, 0x6000, 0x005c, 0x007e, + 0x00e8, 0x0108, 0x00f8, 0xe000, 0x0206, 0x1e00, 0x0380, 0x0061, + 0x007a, 0x004e, 0x0601, 0x1001, 0x00c8, 0x8000, 0x1d00, 0x00d0, + 0x0072, 0x0049, 0x1600, 0x1a00, 0x0046, 0x7000, 0x010f, 0x0110, + 0x0076, 0x1200, 0x1400, 0x0404, 0x0606, 0x010e, 0x00fc, 0x1700, + 0x006e, 0x00fe, 0x1300, 0x0062, 0x0066, 0xc000, 0x0204, 0x0306, + 0x0063, 0x0707, 0x0280, 0x0602, 0x0055, 0x0047, 0x006a, 0x010c, + 0x0052, 0x0501, 0x00d8, 0x0307, 0x0073, 0x0109, 0x0808, 0x0401, + 0x004a, 0x2020, 0x005a, 0x0702, 0x00b0, 0x0045, 0x0207, 0x0304, + 0x0402, 0x005e, 0x010a, 0x0079, 0x3800, 0x00f4, 0x1500, 0x01e0, + 0x1b00, 0x0071, 0x1010, 0x00c1, 0x00e4, 0x0502, 0x0056, 0x007d, + 0x0081, 0x0077, 0x00cc, 0x0703, 0x010d, 0x0205, 0x0340, 0x5000, + 0x0082, 0x0067, 0xff00, 0x0120, 0x0069, 0x0098, 0x00c3, 0x1900, + 0x0065, 0x007b, 0x0240, 0x0603, 0x00ec, 0x0059, 0x00fa, 0x0403, + 0x0075, 0x006f, 0x3100, 0x3300, 0x004f, 0x00b8, 0x006d, 0x0208, + 0x004d, 0x0111, 0x0051, 0x020e, 0x00dc, 0x00c4, 0x2100, 0x00a8, +}; diff --git a/libavcodec/qmagedec.c b/libavcodec/qmagedec.c new file mode 100644 index 0000000000..8c3d6cbc60 --- /dev/null +++ b/libavcodec/qmagedec.c @@ -0,0 +1,836 @@ +/* + * Quram Qmage image format decoder + * Copyright (c) 2024 Peter Ross + * + * 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 + */ + +#include <inttypes.h> + +#include "avcodec.h" +#include "bytestream.h" +#include "codec_internal.h" +#include "copy_block.h" +#include "decode.h" +#include "get_bits.h" +#include "libavutil/mem.h" +#include "qmagedata.h" + +#define QMAGE_MAGIC 0x514d +#define QVERSION_1_43_LESS 0xb +#define QCODEC_V16_SHORT_INDEX 0 +#define QCODEC_W2_PASS 1 + +typedef struct { + AVFrame * last_frame; + + int qversion; + + int raw_type; + int transparency; + + int qp; + int not_comp; + int use_chroma_key; + int mode; + + int encoder_mode; + int is_dynamic_table; + int alpha_depth; + int depth; + int use_extra_exception; + + int width; + int height; + + int near_lossless; + + int android_support; + int is_gray_type; + int use_index_color; + int pre_multiplied; + int not_alpha_comp; + int is_opaque; + int nine_patched; + + int alpha_position; + int alpha_encoder_mode; + + int total_frame_number; + int current_frame_number; + int animation_delay_time; + int animation_no_repeat; + + int header_size; + + int color_count; +} Context; + +static void dump(AVCodecContext *avctx) +{ + const Context * ctx = avctx->priv_data; + av_log(avctx, AV_LOG_DEBUG, "qversion: 0x%x\n", ctx->qversion); + av_log(avctx, AV_LOG_DEBUG, "raw_type: %d\n", ctx->raw_type); + av_log(avctx, AV_LOG_DEBUG, "transparency: %d\n", ctx->transparency); + av_log(avctx, AV_LOG_DEBUG, "qp: %d\n", ctx->qp); + av_log(avctx, AV_LOG_DEBUG, "not_comp: %d\n", ctx->not_comp); + av_log(avctx, AV_LOG_DEBUG, "use_chroma_key: %d\n", ctx->use_chroma_key); + av_log(avctx, AV_LOG_DEBUG, "mode: %d\n", ctx->mode); + av_log(avctx, AV_LOG_DEBUG, "encoder_mode: %d\n", ctx->encoder_mode); + av_log(avctx, AV_LOG_DEBUG, "is_dynamic_table: %d\n", ctx->is_dynamic_table); + av_log(avctx, AV_LOG_DEBUG, "alpha_depth: %d\n", ctx->alpha_depth); + av_log(avctx, AV_LOG_DEBUG, "depth: %d\n", ctx->depth); + av_log(avctx, AV_LOG_DEBUG, "use_extra_exception: %d\n", ctx->use_extra_exception); + av_log(avctx, AV_LOG_DEBUG, "width: %d\n", ctx->width); + av_log(avctx, AV_LOG_DEBUG, "height: %d\n", ctx->height); + av_log(avctx, AV_LOG_DEBUG, "near_lossless: %d\n", ctx->near_lossless); + av_log(avctx, AV_LOG_DEBUG, "android_support: %d\n", ctx->android_support); + av_log(avctx, AV_LOG_DEBUG, "is_gray_type: %d\n", ctx->is_gray_type); + av_log(avctx, AV_LOG_DEBUG, "use_index_color: %d\n", ctx->use_index_color); + av_log(avctx, AV_LOG_DEBUG, "pre_multiplied: %d\n", ctx->pre_multiplied); + av_log(avctx, AV_LOG_DEBUG, "not_alpha_comp: %d\n", ctx->not_alpha_comp); + av_log(avctx, AV_LOG_DEBUG, "is_opaque: %d\n", ctx->is_opaque); + av_log(avctx, AV_LOG_DEBUG, "nine_patched: %d\n", ctx->nine_patched); + av_log(avctx, AV_LOG_DEBUG, "alpha_position: 0x%x\n", ctx->alpha_position); + av_log(avctx, AV_LOG_DEBUG, "total_frame_number: %d\n", ctx->total_frame_number); + av_log(avctx, AV_LOG_DEBUG, "current_frame_number: %d\n", ctx->current_frame_number); + av_log(avctx, AV_LOG_DEBUG, "animation_delay_time: %d\n", ctx->animation_delay_time); + av_log(avctx, AV_LOG_DEBUG, "animation_no_repeat: %d\n", ctx->animation_no_repeat); + av_log(avctx, AV_LOG_DEBUG, "header_size: %d\n", ctx->header_size); + av_log(avctx, AV_LOG_DEBUG, "color_count: %d\n", ctx->color_count); +} + +static int decode_header(AVCodecContext *avctx, AVPacket *avpkt) +{ + Context * ctx = avctx->priv_data; + GetByteContext gb; + int flags4, flags5, flags10, flags11; + + if (avpkt->size < 12) + return AVERROR_INVALIDDATA; + + bytestream2_init(&gb, avpkt->data, avpkt->size); + + if (bytestream2_get_be16(&gb) != QMAGE_MAGIC) { + av_log(avctx, AV_LOG_ERROR, "bad magic number\n"); + return AVERROR_INVALIDDATA; + } + + ctx->qversion = bytestream2_get_byte(&gb); + + ctx->raw_type = bytestream2_get_byte(&gb); + switch (ctx->raw_type) { + case 0: //RGB565 + ctx->transparency = 0; + break; + case 3: //RGBA5658 + case 6: //RGBA + ctx->transparency = 1; + break; + default: + avpriv_request_sample(avctx, "raw_type=%d", ctx->raw_type); + return AVERROR_PATCHWELCOME; + } + + flags4 = bytestream2_get_byte(&gb); + ctx->qp = flags4 & 0x1f; + ctx->not_comp = !!(flags4 & 0x20); + ctx->use_chroma_key = !!(flags4 & 0x40); + ctx->mode = !!(flags4 & 0x80); + + flags5 = bytestream2_get_byte(&gb); + if (ctx->qversion == QVERSION_1_43_LESS) + ctx->encoder_mode = flags5 & 0x7; + else if (ctx->qversion > QVERSION_1_43_LESS) + ctx->encoder_mode = flags5 & 0xf; + else + ctx->encoder_mode = 0; + if (ctx->qversion > QVERSION_1_43_LESS) + ctx->is_dynamic_table = !!(flags5 & 0x10); + else + ctx->is_dynamic_table = 0; + ctx->alpha_depth = (flags5 & 0x20) ? 2 : 1; + ctx->depth = (flags5 & 0x40) ? 2 : 1; + ctx->use_extra_exception = !!(flags5 & 0x80); + + ctx->width = bytestream2_get_le16(&gb); + ctx->height = bytestream2_get_le16(&gb); + + flags10 = bytestream2_get_byte(&gb); + ctx->near_lossless = !!(flags10 & 0x40); + + flags11 = bytestream2_get_byte(&gb); + ctx->android_support = !!(flags11 & 0x4); + ctx->is_gray_type = !!(flags11 & 0x4); + ctx->use_index_color = !!(flags11 & 0x8); + ctx->pre_multiplied = !!(flags11 & 0x10); + ctx->not_alpha_comp = !!(flags11 & 0x40); + ctx->is_opaque = !!(flags11 & 0x20); + ctx->nine_patched = !!(flags11 & 0x80); + + if (ctx->qversion == QVERSION_1_43_LESS) { + if (ctx->transparency || ctx->mode) + ctx->alpha_position = bytestream2_get_le32(&gb); + ctx->alpha_encoder_mode = ctx->encoder_mode; + } else if (ctx->qversion > QVERSION_1_43_LESS) { + int flags14; + ctx->alpha_position = bytestream2_get_le16(&gb); + flags14 = bytestream2_get_byte(&gb); + ctx->alpha_encoder_mode = flags14 & 0xf; + bytestream2_skip(&gb, 1); + } + + if (ctx->mode) { + ctx->total_frame_number = bytestream2_get_le16(&gb); + ctx->current_frame_number = bytestream2_get_le16(&gb); + ctx->animation_delay_time = bytestream2_get_le16(&gb); + ctx->animation_no_repeat = bytestream2_get_byte(&gb); + bytestream2_skip(&gb, 1); + } else { + ctx->total_frame_number = ctx->current_frame_number = 1; + } + + if (ctx->qversion > QVERSION_1_43_LESS) { + if (!ctx->mode || ctx->current_frame_number <= 1) + ctx->alpha_position *= 4; + } + + if (ctx->mode) { + ctx->header_size = 24; + } else { + ctx->header_size = ctx->transparency ? 16 : 12; + } + + if (ctx->use_index_color) { + if (ctx->nine_patched) + bytestream2_skip(&gb, 4); + ctx->color_count = bytestream2_get_le32(&gb); + } + + dump(avctx); + + return 0; +} + +static uint16_t get_pixel(AVCodecContext * avctx, const uint8_t * src, int linesize, int x, int y) +{ + if (x >= 0 && x < avctx->width && y >= 0 && y < avctx->height) + return AV_RN16A(src + y*linesize + x*2); + return 0; +} + +static void decode_pixel_inter(AVCodecContext * avctx, int copy, const uint16_t * ori_delta, GetBitContext * gb1, GetBitContext *gb2, GetByteContext * gb3, uint8_t * dst, const uint8_t * ref, int ref_linesize, int ref_x, int ref_y) +{ + if (copy) { + AV_WN16A(dst, get_pixel(avctx, ref, ref_linesize, ref_x, ref_y)); + } else { + int nb_bits = get_bits(gb2, 3); + if (nb_bits == 7) { + AV_WN16A(dst, bytestream2_get_le16(gb3)); + } else { + int idx = get_bits(gb1, nb_bits + 1); + uint16_t delta = ori_delta[idx + (2 << nb_bits) - 2]; + AV_WN16A(dst, get_pixel(avctx, ref, ref_linesize, ref_x, ref_y) + delta); + } + } +} + +static void copy_edge(uint8_t * dst, int linesize, int width, int height) +{ + for (int j = 0; j < height; j++) + for (int i = 0; i < width; i++) + AV_WN16A(dst + j*linesize + i*2, AV_RN16A(dst + j*linesize - 2)); +} + +static int decode_a9ll(AVCodecContext *avctx, uint8_t * data, int size, uint8_t * dst, int dst_linesize) +{ + Context * s = avctx->priv_data; + GetBitContext gb1, gb2; + GetByteContext gb3; + int gb1_start, gb3_start, ret; + const uint16_t * ori_delta; + uint16_t ori_delta_local[512]; + + if (size < s->header_size + 8) + return AVERROR_INVALIDDATA; + gb1_start = AV_RL32(data + s->header_size); + gb3_start = AV_RL32(data + s->header_size + 4); + if (gb1_start < s->header_size + 8 || gb1_start > size || gb3_start < s->header_size + 8 || gb3_start > size) + return AVERROR_INVALIDDATA; + if ((ret = init_get_bits8(&gb1, data + s->header_size + 8, size - s->header_size - 8)) < 0) + return ret; + if ((ret = init_get_bits8(&gb2, data + gb1_start, size - gb1_start)) < 0) + return ret; + bytestream2_init(&gb3, data + gb3_start, size - gb3_start); + + if (s->is_dynamic_table) { + uint8_t sign[512]; + for (int i = 0; i < 512; i++) + sign[i] = bytestream2_get_byte(&gb3); + for (int i = 0; i < 512; i++) { + int v = bytestream2_get_le16(&gb3); + ori_delta_local[i] = sign[i] ? v : -v; + } + ori_delta = ori_delta_local + 1; + } else + ori_delta = qmage_ori_delta[s->qversion != QVERSION_1_43_LESS]; + + if (s->use_extra_exception) { + avpriv_request_sample(avctx, "use_extra_exception"); + return AVERROR_INVALIDDATA; + } + + for (int y = 0; y < avctx->height; y += 4) { + for (int x = 0; x < avctx->width; x += 4) { + int mode = get_bits(&gb1, 2); + if (mode < 3) { + int cbp = bytestream2_get_le16(&gb3); + int k = 0; + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + if (x + i < avctx->width && y + j < avctx->height) { + decode_pixel_inter(avctx, cbp & (1 << k), ori_delta, &gb1, &gb2, &gb3, + dst + (y+j)*dst_linesize + (x+i)*2, + dst, dst_linesize, + x+i+qmage_dir[mode].x, y+j+qmage_dir[mode].y); + k++; + } + } + } + } else { + if (x > 0) + copy_edge(dst + y*dst_linesize + x*2, dst_linesize, + FFMIN(avctx->width - x, 4), FFMIN(avctx->height - y, 4)); + } + } + } + + return 0; +} + +static void copy_block4x4(uint8_t * dst, int dst_linesize, const uint8_t * ref, int ref_linesize) +{ + copy_block8(dst, ref, dst_linesize, ref_linesize, 4); +} + +static void copy_block16x16(uint8_t * dst, int dst_linesize, const uint8_t * ref, int ref_linesize) +{ + copy_block16(dst, ref, dst_linesize, ref_linesize, 16); + copy_block16(dst + 16, ref + 16, dst_linesize, ref_linesize, 16); +} + +static void decode_pixel(AVCodecContext * avctx, GetBitContext * gb1, GetByteContext * gb2, const uint16_t * ori_delta, uint8_t * dst, const uint8_t * ref, int ref_linesize, int ref_x, int ref_y) +{ + int skip = get_bits1(gb1); + if (skip) { + AV_WN16A(dst, get_pixel(avctx, ref, ref_linesize, ref_x, ref_y)); + } else { + int nb_bits = get_bits(gb1, 3); + if (nb_bits == 7) { + AV_WN16A(dst, bytestream2_get_le16(gb2)); + } else { + int idx = get_bits(gb1, nb_bits + 1); + uint16_t delta = ori_delta[idx + (2 << nb_bits) - 2]; + AV_WN16A(dst, get_pixel(avctx, ref, ref_linesize, ref_x, ref_y) + delta); + } + } +} + +static void decode_block3_ani(AVCodecContext *avctx, GetBitContext * gb1, GetByteContext * gb2, int x, int y, uint8_t * dst, int linesize, const uint8_t * ref, int ref_linesize, int mv_x, int mv_y, const uint16_t * ori_delta) +{ + Context * s = avctx->priv_data; + int mode = get_bits(gb1, 3); + if (s->qp == 0 || get_bits1(gb1)) { + if (mode < 3) { + for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) + decode_pixel(avctx, gb1, gb2, ori_delta, + dst + (y+j)*linesize + (x+i)*2, + dst, linesize, x+i+qmage_dir[mode].x, y+j+qmage_dir[mode].y); + } else if (mode == 3) { + if (x > 0) + copy_edge(dst + y*linesize + x*2, linesize, 4, 4); + } else if (mode == 4) { + for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) + decode_pixel(avctx, gb1, gb2, ori_delta, + dst + (y+j)*linesize + (x+i)*2, + ref, ref_linesize, x+i, y+j); + } else if (mode == 5) { + copy_block4x4(dst + y*linesize + x*2, linesize, + ref + y*ref_linesize + x*2, ref_linesize); + } else if (mode == 6) { + for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) + decode_pixel(avctx, gb1, gb2, ori_delta, + dst + (y+j)*linesize + (x+i)*2, + ref, ref_linesize, x+i+mv_x, y+j+mv_y); + } else { + if (x+mv_x < 0 || x+mv_x+4 > avctx->width || + y+mv_y < 0 || y+mv_y+4 > avctx->height) { + av_log(avctx, AV_LOG_WARNING, "offscreen mv"); + return; + } + copy_block4x4(dst + y*linesize + x*2, linesize, + ref + (y+mv_y)*ref_linesize + (x+mv_x)*2, ref_linesize); + } + } else { + avpriv_request_sample(avctx, "qp"); + } +} + +static void decode_block2_ani(AVCodecContext *avctx, GetBitContext * gb1, GetByteContext * gb2, int x, int y, uint8_t * dst, int linesize, const uint16_t * ori_delta) +{ + Context * s = avctx->priv_data; + int mode = get_bits(gb1, 2); + if (s->qp == 0 || get_bits1(gb1)) { + if (mode < 3) { + for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) + decode_pixel(avctx, gb1, gb2, ori_delta, + dst + (y+j)*linesize + (x+i)*2, + dst, linesize, x+i+qmage_dir[mode].x, y+j+qmage_dir[mode].y); + } else { + if (x > 0) + copy_edge(dst + y*linesize + x*2, linesize, 4, 4); + } + } else { + avpriv_request_sample(avctx, "qp"); + } +} + +static int decode_mb_ani(AVCodecContext *avctx, GetBitContext * gb1, GetByteContext * gb2, int x, int y, uint8_t * dst, int linesize, const uint8_t * ref, int ref_linesize, const uint16_t * ori_delta) +{ + if (get_bits1(gb1)) { + if (get_bits1(gb1)) { + copy_block16x16(dst + y*linesize + x*2, linesize, ref + y*ref_linesize + x*2, ref_linesize); + } else { + int mv_x, mv_y; + if (!get_bits1(gb1)) { + mv_x = get_bits(gb1, 8) - 0x7f; + mv_y = get_bits(gb1, 7) - 0x3f; + if (x+mv_x < 0 || x+mv_x+16 > avctx->width || + y+mv_y < 0 || y+mv_y+16 > avctx->height) { + av_log(avctx, AV_LOG_WARNING, "offscreen mv"); + return AVERROR_INVALIDDATA; + } + + if (get_bits1(gb1)) { + copy_block16x16(dst + y*linesize + x*2, linesize, + ref + (y+mv_y)*ref_linesize + (x+mv_x)*2, ref_linesize); + return 0; + } + } else { + mv_x = mv_y = 0; + } + for (int j = 0; j < 16; j += 4) + for (int i = 0; i < 16; i += 4) + decode_block3_ani(avctx, gb1, gb2, x+i, y+j, dst, linesize, ref, ref_linesize, mv_x, mv_y, ori_delta); + } + } else { + for (int j = 0; j < 16; j += 4) + for (int i = 0; i < 16; i += 4) + decode_block2_ani(avctx, gb1, gb2, x+i, y+j, dst, linesize, ori_delta); + } + return 0; +} + +static int decode_mbedge_ani(AVCodecContext *avctx, GetBitContext * gb1, GetByteContext * gb2, int xpos, int ypos, + uint8_t * dst, int linesize, const uint8_t * ref, int ref_linesize, const uint16_t * ori_delta) +{ + if (get_bits1(gb1)) { + avpriv_request_sample(avctx, "skip edge"); + return AVERROR_INVALIDDATA; + } + + for (int y = ypos; y < FFMIN(ypos + 16, avctx->height); y += 4) { + for (int x = xpos; x < FFMIN(xpos + 16, avctx->width); x += 4) { + if (x + 4 <= avctx->width && y + 4 <= avctx->height) { + int mode = get_bits(gb1, 2); + if (mode < 3) { + for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) + if (x + i < avctx->width && y + j < avctx->height) { + decode_pixel(avctx, gb1, gb2, ori_delta, + dst + (y+j)*linesize + (x+i)*2, + dst, linesize, x+i+qmage_dir[mode].x, y+j+qmage_dir[mode].y); + } + } else { + if (x > 0) + copy_edge(dst + y*linesize + x*2, linesize, FFMIN(avctx->width - x, 4), FFMIN(avctx->height - y, 4)); + } + } else { + for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) + if (x + i < avctx->width && y + j < avctx->height) + AV_WN16A(dst + (y+j)*linesize + (x+i)*2, bytestream2_get_le16(gb2)); + } + } + } + return 0; +} + +static int decode_a9ll_ani(AVCodecContext *avctx, const uint8_t * data, int size, uint8_t * dst, int dst_linesize, const uint8_t * ref, int ref_linesize) +{ + Context * s = avctx->priv_data; + GetBitContext gb1; + GetByteContext gb2; + int gb1_start; + const uint16_t * ori_delta; + int ret; + + if (size < s->header_size + 8) + return AVERROR_INVALIDDATA; + + gb1_start = AV_RL32(data + s->header_size); + if (gb1_start < s->header_size + 8 || gb1_start > size) + return AVERROR_INVALIDDATA; + if ((ret = init_get_bits8(&gb1, data + s->header_size + 8, size - s->header_size - 8)) < 0) + return ret; + bytestream2_init(&gb2, data + gb1_start, size - gb1_start); + + ori_delta = qmage_ori_delta[s->qversion != QVERSION_1_43_LESS]; + + for (int y = 0; y < avctx->height; y += 16) { + for (int x = 0; x < avctx->width; x += 16) { + if (avctx->width - x >= 16 && avctx->height - y >= 16) { + if (decode_mb_ani(avctx, &gb1, &gb2, x, y, dst, dst_linesize, ref, ref_linesize, ori_delta) < 0) + return AVERROR_INVALIDDATA; + } else { + if (decode_mbedge_ani(avctx, &gb1, &gb2, x, y, dst, dst_linesize, ref, ref_linesize, ori_delta) < 0) + return AVERROR_INVALIDDATA; + } + } + } + return 0; +} + +static void memset32(uint8_t * dst, uint32_t v, int count) +{ + for (int i = 0; i < count; i++) + AV_WN32A(dst + i * 4, v); +} + +static int read_value(GetByteContext * gb) +{ + int v = 0; + while (bytestream2_peek_byte(gb) == 0xff) { + bytestream2_skip(gb, 1); + v += 0xff; + } + return v + bytestream2_get_byte(gb); +} + +static int decode_w2_aligned(AVCodecContext * avctx, GetByteContext * gb1, GetByteContext * gb2, GetByteContext * gb3, const uint8_t * data, int size, uint8_t * dst) +{ + int counter = 0, idx, run; + uint32_t val; + int dim = avctx->width * avctx->height * 2; + do { + idx = read_value(gb1); + if (!idx) { + val = bytestream2_get_le32(gb3); + AV_WN32A(dst + counter, val); + counter += 4; + } else { + idx--; + if (idx * 4 + 4 > size - 16) + return AVERROR_INVALIDDATA; + val = AV_RL32(data + 16 + idx * 4); + run = read_value(gb2) + 1; + + memset32(dst + counter, val, FFMIN(run, (dim - counter) / 4)); + counter += 4 * run; + } + } while (counter < dim); + return 0; +} + +static int decode_w2_unaligned(AVCodecContext * avctx, GetByteContext * gb1, GetByteContext * gb2, GetByteContext * gb3, const uint8_t * data, int size, uint8_t * dst, int dst_linesize) +{ + int x = 0, y = 0, idx, run; + uint16_t v1, v2; + + while (1) { + idx = read_value(gb1); + if (!idx) { +#define WRITE_PIXEL(v) \ + AV_WN16A(dst + y*dst_linesize + x*2, v); \ + x++; \ + if (x >= avctx->width) { \ + x = 0; \ + y++; \ + if (y >= avctx->height) \ + return 0; \ + } + v1 = bytestream2_get_le16(gb3); + v2 = bytestream2_get_le16(gb3); + WRITE_PIXEL(v1) + WRITE_PIXEL(v2) + } else { + idx--; + if (idx * 4 + 4 > size - 16) + return AVERROR_INVALIDDATA; + v1 = AV_RL16(data + 16 + idx * 4); + v2 = AV_RL16(data + 16 + idx * 4 + 2); + run = read_value(gb2) + 1; + for (int i = 0; i < run; i++) { + WRITE_PIXEL(v1) + WRITE_PIXEL(v2) + } + } + } +#undef WRITE_PIXEL + return 0; +} + +static int decode_w2_pass_depth1(AVCodecContext *avctx, const uint8_t * data, int size, uint8_t * dst, int dst_linesize) +{ + int cnt_table, size_idx, size_run; + int start1, start2, start3; + GetByteContext gb1, gb2, gb3; + + if (size < 16) + return AVERROR_INVALIDDATA; + + cnt_table = AV_RL32(data); + size_idx = AV_RL32(data + 4); + size_run = AV_RL32(data + 8); + + start1 = 16 + cnt_table*4; + start2 = start1 + size_idx; + start3 = start2 + size_run; + + if (start1 >= size || start2 >= size || start3 > size) + return AVERROR_INVALIDDATA; + + bytestream2_init(&gb1, data + start1, size - start1); + bytestream2_init(&gb2, data + start2, size - start2); + bytestream2_init(&gb3, data + start3, size - start3); + + if (dst_linesize == avctx->width * 2) + return decode_w2_aligned(avctx, &gb1, &gb2, &gb3, data, size, dst); + + return decode_w2_unaligned(avctx, &gb1, &gb2, &gb3, data, size, dst, dst_linesize); +} + +static int strip1(GetBitContext * gb1, GetByteContext * gb2, GetByteContext * gb3, int * rel, uint8_t * dst, int d_pos) +{ + AV_WN32A(dst + d_pos, bytestream2_get_le32(gb3)); + d_pos += 4; + for (int i = 0; i < 6; i++) { + uint16_t v; + if (!(i & 1)) { + if (!get_bits1(gb1)) + *rel = get_bits1(gb1) ? bytestream2_get_byte(gb2) + : bytestream2_get_le16(gb3); + } + if (!get_bits1(gb1)) { + if (!get_bits1(gb1)) { + int pos = d_pos - *rel*2; + if (pos < 0) + return -1; + v = AV_RN16A(dst + pos) ^ qmage_diff[bytestream2_get_byte(gb2)]; + } else { + v = bytestream2_get_ne16(gb3); + } + } else { + int pos = d_pos - *rel*2; + if (pos < 0) + return -1; + v = AV_RN16A(dst + d_pos - *rel*2); + } + AV_WN16A(dst + d_pos, v); + d_pos += 2; + } + return 0; +} + +static int strip2(GetBitContext * gb1, GetByteContext * gb2, GetByteContext * gb3, int * rel, uint8_t * dst, int d_pos) +{ + int mask = bytestream2_get_byte(gb2); + for (int i = 0; i < 8; i++) { + uint16_t v; + if (!(i & 1)) { + if (!get_bits1(gb1)) { + *rel = get_bits1(gb1) ? bytestream2_get_byte(gb2) + : bytestream2_get_le16(gb3); + } + } + if (!(mask & (1 << (7 - i)))) { + if (!get_bits1(gb1)) { + int pos = d_pos - *rel*2; + if (pos < 0) + return -1; + v = AV_RN16A(dst + pos) ^ qmage_diff[bytestream2_get_byte(gb2)]; + } else { + v = bytestream2_get_ne16(gb3); + } + } else { + int pos = d_pos - *rel*2; + if (pos < 0) + return -1; + v = AV_RN16A(dst + d_pos - *rel* 2); + } + AV_WN16A(dst + d_pos, v); + d_pos += 2; + } + return 0; +} + +static int decode_w2_pass_depth2(AVCodecContext *avctx, const uint8_t * data, int size, uint8_t * dst, int dst_linesize) +{ + int bsize, ret; + uint8_t * bdata; + int len1, len2, rel = 1, d_pos; + GetBitContext gb1; + GetByteContext gb2, gb3; + + if (size < 12) + return AVERROR_INVALIDDATA; + + bsize = AV_RL32(data); + if (bsize < 16) + return AVERROR_INVALIDDATA; + + bdata = av_malloc(bsize); + if (!bdata) + return AVERROR(ENOMEM); + + len1 = AV_RL32(data + 4); + len2 = AV_RL32(data + 8); + if ((ret = init_get_bits8(&gb1, data + 12, size - 12)) < 0) { + free(bdata); + return ret; + } + bytestream2_init(&gb2, data + 12 + len1, size - 12 - len1); + bytestream2_init(&gb3, data + 12 + len1 + len2, size - 12 - len1 - len2); + + if (strip1(&gb1, &gb2, &gb3, &rel, bdata, 0) < 0) { + free(bdata); + return AVERROR_INVALIDDATA; + } + + for (d_pos = 16; d_pos < (bsize & ~15); d_pos += 16) { + if (!get_bits1(&gb1)) { + if (!get_bits1(&gb1)) { + bytestream2_get_buffer(&gb3, bdata + d_pos, 16); + } else { + if (d_pos - rel*2 < 0) { + free(bdata); + return AVERROR_INVALIDDATA; + } + for (int j = 0; j < 8; j++) + AV_WN16A(bdata + d_pos + j*2, AV_RN16A(bdata + d_pos - rel*2 + j*2)); + } + } else { + if (strip2(&gb1, &gb2, &gb3, &rel, bdata, d_pos) < 0) { + free(bdata); + return AVERROR_INVALIDDATA; + } + } + } + + if (bsize & 15) + bytestream2_get_buffer(&gb2, bdata + d_pos, bsize & 15); + + ret = decode_w2_pass_depth1(avctx, bdata, bsize, dst, dst_linesize); + av_free(bdata); + return ret; +} + +static av_cold int qmage_decode_init(AVCodecContext *avctx) +{ + Context * s = avctx->priv_data; + + avctx->pix_fmt = AV_PIX_FMT_RGB565; + + s->last_frame = av_frame_alloc(); + if (!s->last_frame) + return AVERROR(ENOMEM); + + return 0; +} + +static av_cold int qmage_decode_close(AVCodecContext *avctx) +{ + Context *s = avctx->priv_data; + av_frame_free(&s->last_frame); + return 0; +} + +static int qmage_decode_frame(AVCodecContext *avctx, AVFrame *frame, + int *got_frame, AVPacket *avpkt) +{ + Context * s = avctx->priv_data; + int ret; + + ret = decode_header(avctx, avpkt); + if (ret < 0) + return ret; + + ret = ff_get_buffer(avctx, frame, 0); + if (ret < 0) + return ret; + + if (s->mode) { + if (s->current_frame_number == 1) { + frame->flags |= AV_FRAME_FLAG_KEY; + decode_a9ll(avctx, avpkt->data, avpkt->size, frame->data[0], frame->linesize[0]); + } else { + decode_a9ll_ani(avctx, avpkt->data, avpkt->size, frame->data[0], frame->linesize[0], + s->last_frame->data[0], s->last_frame->linesize[0]); + } + } else { + frame->flags |= AV_FRAME_FLAG_KEY; + switch(s->encoder_mode) { + case QCODEC_W2_PASS: break; + default: + avpriv_request_sample(avctx, "encoder_mode=%d", s->encoder_mode); + return AVERROR_INVALIDDATA; + } + switch (s->depth) { + case 1: + decode_w2_pass_depth1(avctx, avpkt->data + s->header_size, avpkt->size - s->header_size, frame->data[0], frame->linesize[0]); + break; + case 2: + decode_w2_pass_depth2(avctx, avpkt->data + s->header_size, avpkt->size - s->header_size, frame->data[0], frame->linesize[0]); + break; + default: + return AVERROR_INVALIDDATA; + } + } + + if ((ret = av_frame_replace(s->last_frame, frame)) < 0) + return ret; + + *got_frame = 1; + + return avpkt->size; +} + +const FFCodec ff_qmage_decoder = { + .p.name = "qmage", + CODEC_LONG_NAME("Quram Qmage"), + .p.type = AVMEDIA_TYPE_VIDEO, + .p.id = AV_CODEC_ID_QMAGE, + .p.capabilities = AV_CODEC_CAP_DR1, + .priv_data_size = sizeof(Context), + .init = qmage_decode_init, + .close = qmage_decode_close, + FF_CODEC_DECODE_CB(qmage_decode_frame), +}; -- 2.45.2 -- Peter (A907 E02F A6E5 0CD2 34CD 20D2 6760 79C5 AC40 DD6B)
signature.asc
Description: PGP signature
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".