From patchwork Sat Aug 14 16:55:37 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 29512 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6602:2a4a:0:0:0:0 with SMTP id k10csp586887iov; Sat, 14 Aug 2021 09:55:55 -0700 (PDT) X-Google-Smtp-Source: ABdhPJxwVRRnD4MUoQOx2w4Rx+yUzUpUGYeV4pSwAQr0fLx/IN9QHEvO9kVk2xsqGbZQrYfNablf X-Received: by 2002:a05:6402:51cb:: with SMTP id r11mr10247438edd.270.1628960155672; Sat, 14 Aug 2021 09:55:55 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1628960155; cv=none; d=google.com; s=arc-20160816; b=MXGvk4W/OLqKaeQaP1QR1BL4S4B0wKhyJK4OQHlfKBCNoCjo+zeziBSmHlzTzcJyH/ qlWP8VqyOoyN4bpcOFprohUW1Ryk3RrFLdlmUKwNFGB0EJpT6yeoWB6z/OywVridwB8/ WXe2FgCDlrGSkSUhhJlSasRkQjz235gHoSuB+qyxB57rJsAqk49acA4/qJbssmhOcVAi TePYXJGPJLeVDE5dKFxEpFgzMBU03IuIA8oNlfA1VFUlGpREKChDcxVvK3GVvGvz4bNj NJ6WH5UnEzWWfRdTt98ZKQ7veZiKtqiLqmy28XvXFBqwa9wPQkKzN80AJ2DkTnouRt10 kneg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:mime-version:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:message-id:date:to:from:dkim-signature :delivered-to; bh=z72CDZgbFMMkIcGXsoU3cYKD1BXSINxtxWH/A9h3Qxc=; b=uAsyhSocnv+VEkxMEZy1oI+U+e5OlHjaWaRfcAL4uVacGNtb0tihewnF7yzoa5HYaY 9FmlYqTUO5O2rvaUDm06LUG9fh6QPdaN2bvYspDfMkcl3Vi3FrYDHik3P+7Gm/u7kk4O bRuLz5VHL9TAzmUY0NKXI4aONvK7OX3HJz4DIKTzlhNBiG7gqDjQb2fgDFspCL2fDwVs 0FN4F3PjKAKyKHEu3bASc/uV2X1HYTyX8/pfjyb9go4RKto69xAqXZOCvnTADcwUOL5c YLjdZ+rtsEklm42lqz5ywroSCqbUdvH/QXh/NXNLBVGzNWo/qI7H0UsSxDCyns3AaIpR tTEg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=G97c3nXD; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id jz19si6313881ejb.37.2021.08.14.09.55.55; Sat, 14 Aug 2021 09:55:55 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=G97c3nXD; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 1F2A668A75B; Sat, 14 Aug 2021 19:55:53 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-ej1-f43.google.com (mail-ej1-f43.google.com [209.85.218.43]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 58C3C68A717 for ; Sat, 14 Aug 2021 19:55:46 +0300 (EEST) Received: by mail-ej1-f43.google.com with SMTP id lo4so23981033ejb.7 for ; Sat, 14 Aug 2021 09:55:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id; bh=O8EO+x007ummjlfODXl/6pefjOHRPHsSjUzG8CWDZ1I=; b=G97c3nXD4NwWm1xGqPsOVhoAJaJwlowmcYmjLaTgI9gCRgtP4cigFlgJBLBSKhGB6q h6n3XJEtD36UgB3VyZAd/25PzTJ9mFCGTfukfli0rWE8Rzs+SsIcqZyuYT58KpFwELkX 6UxPF0FJ4iYvNoGzzKv3a1VNUnmSbWMrR3p8uTbpBGMo13awGsdXpf/e4Oit7rQP70Y1 IcAIEdbxF2Pl2DfqRCoga7NVhi3zcHYfJIP5+9vfKXBg2R9n/Hq3LAAHTc1lniQ2eSmZ 3Qh49wSHQ/ZK35qpEEx3xTq6tayJGu2mv9Laou3lM2OlbaNp9Dz/zFdNXRq2QJc6Ehhy sMtg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id; bh=O8EO+x007ummjlfODXl/6pefjOHRPHsSjUzG8CWDZ1I=; b=qGcJM4AeOBL8HXuEYpGjKnB6J84NuUf/v8t/0vIelVaPlFAHnqzyDzm6Imu0ijI5ww BrundQQJb4MYEjd/O3a8BL5YJdJGrLkH4rsU9kB8zX/7vAyfUqxhG3ViVsW4qGmCGuyN hl+qphGFiu9S8OeDQO6UV5JcPj5a/wAnK/sKrD3faxOUUw6zvRAL8OrED8oH2NYnLWwk zQvKIbpc86oiMOHRS3+/1coeRHnsmUPb4wCTf2Srhe3nwGyVxPoEi9wxIYs6Z6buK9Cl FDw0vhG0+A8Ca9pNf/stHLizKG9HvcKQ3iBC2+5B9+5OLDHotjCZchwxVOmhYcwbqlOC o3EA== X-Gm-Message-State: AOAM533giF9Ay6WhVLA45s5yRFGfhpBXlXo0yw/Ey1uVM/OlvUIicLBO 7kiClYA9N0c+qyVHamZkUktKSYBy+TI= X-Received: by 2002:a17:906:3fd7:: with SMTP id k23mr8319765ejj.176.1628960145746; Sat, 14 Aug 2021 09:55:45 -0700 (PDT) Received: from localhost.localdomain ([95.168.118.67]) by smtp.gmail.com with ESMTPSA id a2sm2523143edm.72.2021.08.14.09.55.44 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 14 Aug 2021 09:55:45 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Sat, 14 Aug 2021 18:55:37 +0200 Message-Id: <20210814165537.9550-1-onemda@gmail.com> X-Mailer: git-send-email 2.17.1 Subject: [FFmpeg-devel] [PATCH] avcodec: add SMC encoder 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 MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: PXiEuTMqmg0z Signed-off-by: Paul B Mahol --- libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/smcenc.c | 563 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 565 insertions(+) create mode 100644 libavcodec/smcenc.c diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 9a6adb9903..0dc564f5dc 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -621,6 +621,7 @@ OBJS-$(CONFIG_SIMBIOSIS_IMX_DECODER) += imx.o OBJS-$(CONFIG_SMACKAUD_DECODER) += smacker.o OBJS-$(CONFIG_SMACKER_DECODER) += smacker.o OBJS-$(CONFIG_SMC_DECODER) += smc.o +OBJS-$(CONFIG_SMC_ENCODER) += smcenc.o OBJS-$(CONFIG_SNOW_DECODER) += snowdec.o snow.o snow_dwt.o OBJS-$(CONFIG_SNOW_ENCODER) += snowenc.o snow.o snow_dwt.o \ h263.o h263data.o ituh263enc.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 71bc21aa05..c087b91148 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -299,6 +299,7 @@ extern const AVCodec ff_sgirle_decoder; extern const AVCodec ff_sheervideo_decoder; extern const AVCodec ff_simbiosis_imx_decoder; extern const AVCodec ff_smacker_decoder; +extern const AVCodec ff_smc_encoder; extern const AVCodec ff_smc_decoder; extern const AVCodec ff_smvjpeg_decoder; extern const AVCodec ff_snow_encoder; diff --git a/libavcodec/smcenc.c b/libavcodec/smcenc.c new file mode 100644 index 0000000000..686b063630 --- /dev/null +++ b/libavcodec/smcenc.c @@ -0,0 +1,563 @@ +/* + * QuickTime Graphics (SMC) Video Encoder + * Copyright (c) 2021 The FFmpeg project + * + * 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 smcenc.c + * QT SMC Video Encoder by Paul B. Mahol + */ + +#include "libavutil/common.h" + +#include "avcodec.h" +#include "encode.h" +#include "internal.h" +#include "bytestream.h" + +#define CPAIR 2 +#define CQUAD 4 +#define COCTET 8 + +#define COLORS_PER_TABLE 256 + +typedef struct SMCContext { + AVFrame *prev_frame; // buffer for previous source frame + PutByteContext pb; + + uint8_t mono_value; + int nb_distinct; + int next_nb_distinct; + uint8_t distinct_values[16]; + uint8_t next_distinct_values[16]; + + uint16_t color_pairs[COLORS_PER_TABLE]; + uint32_t color_quads[COLORS_PER_TABLE]; + uint64_t color_octets[COLORS_PER_TABLE]; + + int first_frame; // flag set to one when the first frame is being processed + // so that comparisons with previous frame data in not attempted + int key_frame; +} SMCContext; + +#define ADVANCE_BLOCK(pixel_ptr, row_ptr, nb_blocks) \ +{ \ + for (int block = 0; block < nb_blocks && pixel_ptr && row_ptr; block++) { \ + pixel_ptr += 4; \ + if (pixel_ptr - row_ptr >= width) \ + { \ + row_ptr += stride * 4; \ + pixel_ptr = row_ptr; \ + } \ + } \ +} + +static int smc_cmp_values(const void *a, const void *b) +{ + const uint8_t *aa = a, *bb = b; + + return FFDIFFSIGN(aa[0], bb[0]); +} + +static int count_distinct_items(const uint8_t *block_values, + uint8_t *distinct_values, + int size) +{ + int n = 1; + + distinct_values[0] = block_values[0]; + for (int i = 1; i < size; i++) { + if (block_values[i] != block_values[i-1]) { + distinct_values[n] = block_values[i]; + n++; + } + } + + return n; +} + +static void smc_encode_stream(SMCContext *s, const AVFrame *frame) +{ + PutByteContext *pb = &s->pb; + const uint8_t *src_pixels = (const uint8_t *)frame->data[0]; + const int stride = frame->linesize[0]; + const uint8_t *prev_pixels = (const uint8_t *)s->prev_frame->data[0]; + const uint8_t *pixel_ptr, *row_ptr; + const int width = frame->width; + uint8_t block_values[16]; + int block_counter = 0; + int color_pair_index = 0; + int color_quad_index = 0; + int color_octet_index = 0; + int color_table_index; /* indexes to color pair, quad, or octet tables */ + int total_blocks; + + memset(s->color_pairs, 0, sizeof(s->color_pairs)); + memset(s->color_quads, 0, sizeof(s->color_quads)); + memset(s->color_octets, 0, sizeof(s->color_octets)); + + /* Number of 4x4 blocks in frame. */ + total_blocks = ((frame->width + 3) / 4) * ((frame->height + 3) / 4); + + pixel_ptr = row_ptr = src_pixels; + + while (block_counter < total_blocks) { + const uint8_t *xpixel_ptr = pixel_ptr; + const uint8_t *xrow_ptr = row_ptr; + int intra_skip_blocks = 0; + int inter_skip_blocks = 0; + int coded_distinct = 0; + int coded_blocks = 0; + uint16_t new_pair; + uint32_t new_quad; + uint64_t new_octet; + int cache_index; + int distinct = 0; + int blocks = 0; + + while (s->first_frame == 0 && s->key_frame == 0 && block_counter + inter_skip_blocks < total_blocks) { + int compare = 0; + + for (int y = 0; y < 4; y++) { + const ptrdiff_t offset = pixel_ptr - src_pixels; + const uint8_t *prev_pixel_ptr = prev_pixels + offset; + + compare |= memcmp(prev_pixel_ptr + y * stride, pixel_ptr + y * stride, 4); + if (compare) + break; + } + + if (compare) + break; + + if (inter_skip_blocks >= 256) + break; + inter_skip_blocks++; + + ADVANCE_BLOCK(pixel_ptr, row_ptr, 1) + } + + pixel_ptr = xpixel_ptr; + row_ptr = xrow_ptr; + + while (block_counter > 0 && block_counter + intra_skip_blocks < total_blocks) { + const ptrdiff_t offset = pixel_ptr - src_pixels; + const int sy = offset / stride; + const int sx = offset % stride; + const int ny = sx < 4 ? sy - 4 : sy; + const int nx = sx < 4 ? width - 4 : sx - 4; + const uint8_t *old_pixel_ptr = src_pixels + nx + ny * stride; + int compare = 0; + + for (int y = 0; y < 4; y++) { + compare |= memcmp(old_pixel_ptr + y * stride, pixel_ptr + y * stride, 4); + if (compare) + break; + } + + if (compare) + break; + + if (intra_skip_blocks >= 256) + break; + intra_skip_blocks++; + ADVANCE_BLOCK(pixel_ptr, row_ptr, 1) + } + + pixel_ptr = xpixel_ptr; + row_ptr = xrow_ptr; + + while (block_counter + coded_blocks < total_blocks && coded_blocks < 256) { + for (int y = 0; y < 4; y++) + memcpy(block_values + y * 4, pixel_ptr + y * stride, 4); + + qsort(block_values, 16, sizeof(block_values[0]), smc_cmp_values); + s->next_nb_distinct = count_distinct_items(block_values, s->next_distinct_values, 16); + if (coded_blocks == 0) { + memcpy(s->distinct_values, s->next_distinct_values, sizeof(s->distinct_values)); + s->nb_distinct = s->next_nb_distinct; + } else { + if (s->next_nb_distinct != s->nb_distinct || + memcmp(s->distinct_values, s->next_distinct_values, s->nb_distinct)) { + break; + } + } + s->mono_value = block_values[0]; + + coded_distinct = s->nb_distinct; + ADVANCE_BLOCK(pixel_ptr, row_ptr, 1) + coded_blocks++; + if (coded_distinct > 1 && coded_blocks >= 16) + break; + } + + pixel_ptr = xpixel_ptr; + row_ptr = xrow_ptr; + + blocks = coded_blocks; + distinct = coded_distinct; + + if (intra_skip_blocks > 0 && intra_skip_blocks > inter_skip_blocks && + intra_skip_blocks >= coded_blocks * coded_distinct) { + distinct = 17; + blocks = intra_skip_blocks; + } + + if (intra_skip_blocks > 16 && intra_skip_blocks > inter_skip_blocks && + intra_skip_blocks >= coded_blocks * coded_distinct) { + distinct = 18; + blocks = intra_skip_blocks; + } + + if (inter_skip_blocks > 0 && inter_skip_blocks > intra_skip_blocks && + inter_skip_blocks >= coded_blocks * coded_distinct) { + distinct = 19; + blocks = inter_skip_blocks; + } + + if (inter_skip_blocks > 16 && inter_skip_blocks > intra_skip_blocks && + inter_skip_blocks >= coded_blocks * coded_distinct) { + distinct = 20; + blocks = inter_skip_blocks; + } + + switch (distinct) { + case 1: + if (blocks <= 16) { + bytestream2_put_byte(pb, 0x60 | (blocks - 1)); + } else { + bytestream2_put_byte(pb, 0x70); + bytestream2_put_byte(pb, blocks - 1); + } + bytestream2_put_byte(pb, s->mono_value); + ADVANCE_BLOCK(pixel_ptr, row_ptr, blocks) + break; + case 2: + new_pair = 0; + for (int i = 0; i < CPAIR; i++) + new_pair |= s->distinct_values[i] << (i * 8); + + cache_index = -1; + for (int i = 0; i < COLORS_PER_TABLE; i++) { + if (s->color_pairs[i] == new_pair) { + cache_index = i; + break; + } + } + + if (cache_index >= 0) { + bytestream2_put_byte(pb, 0x90 | (blocks - 1)); + bytestream2_put_byte(pb, cache_index); + color_table_index = cache_index; + } else { + bytestream2_put_byte(pb, 0x80 | (blocks - 1)); + + color_table_index = color_pair_index; + s->color_pairs[color_table_index] = new_pair; + for (int i = 0; i < CPAIR; i++) + bytestream2_put_byte(pb, s->distinct_values[i]); + + color_pair_index++; + if (color_pair_index == COLORS_PER_TABLE) + color_pair_index = 0; + } + + for (int i = 0; i < blocks; i++) { + uint16_t pair = s->color_pairs[color_table_index] >> 8; + uint16_t flags = 0; + int shift = 15; + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + flags |= (pair == pixel_ptr[x + y * stride]) << shift; + shift--; + } + } + + bytestream2_put_be16(pb, flags); + + ADVANCE_BLOCK(pixel_ptr, row_ptr, 1) + } + break; + case 3: + case 4: + new_quad = 0; + for (int i = 0; i < CQUAD; i++) + new_quad |= ((uint32_t)(s->distinct_values[i])) << (i * 8); + + cache_index = -1; + for (int i = 0; i < COLORS_PER_TABLE; i++) { + if (s->color_quads[i] == new_quad) { + cache_index = i; + break; + } + } + + if (cache_index >= 0) { + bytestream2_put_byte(pb, 0xB0 | (blocks - 1)); + bytestream2_put_byte(pb, cache_index); + color_table_index = cache_index; + } else { + bytestream2_put_byte(pb, 0xA0 | (blocks - 1)); + + color_table_index = color_quad_index; + for (int i = 0; i < CQUAD; i++) + bytestream2_put_byte(pb, s->distinct_values[i]); + s->color_quads[color_table_index] = new_quad; + + color_quad_index++; + if (color_quad_index == COLORS_PER_TABLE) + color_quad_index = 0; + } + + for (int i = 0; i < blocks; i++) { + uint32_t flags = 0; + uint8_t quad[4]; + int shift = 30; + + quad[0] = (s->color_quads[color_table_index] >> 0) & 0xFFu; + quad[1] = (s->color_quads[color_table_index] >> 8) & 0xFFu; + quad[2] = (s->color_quads[color_table_index] >> 16) & 0xFFu; + quad[3] = (s->color_quads[color_table_index] >> 24) & 0xFFu; + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + int pixel = pixel_ptr[x + y * stride]; + uint32_t idx = 0; + + for (int w = 0; w < CQUAD; w++) { + if (quad[w] == pixel) { + idx = w; + break; + } + } + + flags |= idx << shift; + shift -= 2; + } + } + + bytestream2_put_be32(pb, flags); + + ADVANCE_BLOCK(pixel_ptr, row_ptr, 1) + } + break; + case 5: + case 6: + case 7: + case 8: + new_octet = 0; + for (int i = 0; i < COCTET; i++) + new_octet |= ((uint64_t)(s->distinct_values[i])) << (i * 8); + + cache_index = -1; + for (int i = 0; i < COLORS_PER_TABLE; i++) { + if (s->color_octets[i] == new_octet) { + cache_index = i; + break; + } + } + + if (cache_index >= 0) { + bytestream2_put_byte(pb, 0xD0 | (blocks - 1)); + bytestream2_put_byte(pb, cache_index); + color_table_index = cache_index; + } else { + bytestream2_put_byte(pb, 0xC0 | (blocks - 1)); + + color_table_index = color_octet_index; + for (int i = 0; i < COCTET; i++) + bytestream2_put_byte(pb, s->distinct_values[i]); + s->color_octets[color_table_index] = new_octet; + + color_octet_index++; + if (color_octet_index == COLORS_PER_TABLE) + color_octet_index = 0; + } + + for (int i = 0; i < blocks; i++) { + uint64_t flags = 0; + uint8_t octet[8]; + int shift = 45; + + octet[0] = (s->color_octets[color_table_index] >> 0) & 0xFFu; + octet[1] = (s->color_octets[color_table_index] >> 8) & 0xFFu; + octet[2] = (s->color_octets[color_table_index] >> 16) & 0xFFu; + octet[3] = (s->color_octets[color_table_index] >> 24) & 0xFFu; + octet[4] = (s->color_octets[color_table_index] >> 32) & 0xFFu; + octet[5] = (s->color_octets[color_table_index] >> 40) & 0xFFu; + octet[6] = (s->color_octets[color_table_index] >> 48) & 0xFFu; + octet[7] = (s->color_octets[color_table_index] >> 56) & 0xFFu; + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + int pixel = pixel_ptr[x + y * stride]; + uint64_t idx = 0; + + for (int w = 0; w < COCTET; w++) { + if (octet[w] == pixel) { + idx = w; + break; + } + } + + flags |= idx << shift; + shift -= 3; + } + } + + bytestream2_put_be16(pb, ((flags >> 32) & 0xFFF0) | ((flags >> 8) & 0xF)); + bytestream2_put_be16(pb, ((flags >> 20) & 0xFFF0) | ((flags >> 4) & 0xF)); + bytestream2_put_be16(pb, ((flags >> 8) & 0xFFF0) | ((flags >> 0) & 0xF)); + + ADVANCE_BLOCK(pixel_ptr, row_ptr, 1) + } + break; + default: + bytestream2_put_byte(pb, 0xE0 | (blocks - 1)); + for (int i = 0; i < blocks; i++) { + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) + bytestream2_put_byte(pb, pixel_ptr[x + y * stride]); + } + + ADVANCE_BLOCK(pixel_ptr, row_ptr, 1) + } + break; + case 17: + bytestream2_put_byte(pb, 0x20 | (blocks - 1)); + ADVANCE_BLOCK(pixel_ptr, row_ptr, blocks) + break; + case 18: + bytestream2_put_byte(pb, 0x30); + bytestream2_put_byte(pb, blocks - 1); + ADVANCE_BLOCK(pixel_ptr, row_ptr, blocks) + break; + case 19: + bytestream2_put_byte(pb, 0x00 | (blocks - 1)); + ADVANCE_BLOCK(pixel_ptr, row_ptr, blocks) + break; + case 20: + bytestream2_put_byte(pb, 0x10); + bytestream2_put_byte(pb, blocks - 1); + ADVANCE_BLOCK(pixel_ptr, row_ptr, blocks) + break; + } + + block_counter += blocks; + } +} + +static int smc_encode_init(AVCodecContext *avctx) +{ + SMCContext *s = avctx->priv_data; + + avctx->bits_per_coded_sample = 8; + + s->prev_frame = av_frame_alloc(); + if (!s->prev_frame) + return AVERROR(ENOMEM); + + return 0; +} + +static int smc_encode_frame(AVCodecContext *avctx, AVPacket *pkt, + const AVFrame *frame, int *got_packet) +{ + SMCContext *s = avctx->priv_data; + const AVFrame *pict = frame; + uint8_t *pal; + int ret; + + ret = ff_alloc_packet(avctx, pkt, 8LL * avctx->height * avctx->width); + if (ret < 0) + return ret; + + if (avctx->gop_size == 0 || !s->prev_frame->data[0] || + (avctx->frame_number % avctx->gop_size) == 0) { + s->key_frame = 1; + } else { + s->key_frame = 0; + } + + bytestream2_init_writer(&s->pb, pkt->data, pkt->size); + + bytestream2_put_be32(&s->pb, 0x00); + + if (!s->prev_frame->data[0]) { + s->first_frame = 1; + s->prev_frame->format = pict->format; + s->prev_frame->width = pict->width; + s->prev_frame->height = pict->height; + ret = av_frame_get_buffer(s->prev_frame, 0); + if (ret < 0) + return ret; + } else { + s->first_frame = 0; + } + + pal = av_packet_new_side_data(pkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE); + memcpy(pal, frame->data[1], AVPALETTE_SIZE); + + smc_encode_stream(s, pict); + + av_shrink_packet(pkt, bytestream2_tell_p(&s->pb)); + + pkt->data[0] = 0x0; + + // write chunk length + AV_WB24(pkt->data + 1, pkt->size); + + av_frame_unref(s->prev_frame); + ret = av_frame_ref(s->prev_frame, frame); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "cannot add reference\n"); + return ret; + } + + if (s->key_frame) + pkt->flags |= AV_PKT_FLAG_KEY; + + *got_packet = 1; + + return 0; +} + +static int smc_encode_end(AVCodecContext *avctx) +{ + SMCContext *s = (SMCContext *)avctx->priv_data; + + av_frame_free(&s->prev_frame); + + return 0; +} + +const AVCodec ff_smc_encoder = { + .name = "smc", + .long_name = NULL_IF_CONFIG_SMALL("QuickTime Graphics (SMC)"), + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_SMC, + .priv_data_size = sizeof(SMCContext), + .init = smc_encode_init, + .encode2 = smc_encode_frame, + .close = smc_encode_end, + .caps_internal = FF_CODEC_CAP_INIT_THREADSAFE, + .pix_fmts = (const enum AVPixelFormat[]) { AV_PIX_FMT_PAL8, + AV_PIX_FMT_NONE}, +};