From patchwork Sun Aug 15 07:29:29 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: 29514 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6602:2a4a:0:0:0:0 with SMTP id k10csp1027395iov; Sun, 15 Aug 2021 00:29:50 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzr16j8Z77+s23aHzzQkYUn4RBsx22nz5WAkOsGE9SN+GWXJGkz8jCIpB5bSXwM5MRMXOi8 X-Received: by 2002:a17:906:368e:: with SMTP id a14mr10805227ejc.60.1629012590829; Sun, 15 Aug 2021 00:29:50 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1629012590; cv=none; d=google.com; s=arc-20160816; b=GSjm01oaSgERvDS6kDmUpsyvngqgoZcSncU+5yTS8D3hpNEAjjU+Zzkq7QktYBBJNp rsMxqqCdbKainVZ91QzR8VJcH7FOup778UMSeUaG/X8odwNiDDFzm+VRA8s30AMUBgJb GcZB6VIy4jis96rZcHdAnIUjp+NhxE/EnB/EFddfd6aFtysSEwSykcNYm01eSJ08p2oL ut5W/D96qxPZgIhMY1yeBIc7Rgyx9NekniloLrGIwvurQ4AUJPYXiev7lMfM/UPthgzp HvGofBYezWRb8ug52Y+0rNV/vi9LM+RCfQHYyUkb4+Fzhnif7ljSlwjOiLS2Gd2Qvgdj rJOw== 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=hqNGTt9M5BBDuePJCHdLJAZaO1Ez9ORpeX3G1WWTWPQ=; b=u/SoT8J6nGL9LW2b+XiIvSN+c3qQV5ntMji/KDK9ceILYgJVwBQfk4ZEBFJF75uYgh HfNzNZ/+JTA12oYpBlYvtL1u3YVhM2KrS1cpa1vcAuKbmaEi1uirSlS4bWlJSuv6tcff JHiFdEuJ1hY/Sn47ekx83AaTVPyuWhlkwtNBQNFbjpCr+IfEnqAZLgqfYUeYoas0lrc5 g6rOGAjjfy7K2hnd9UsAHVq8o5rbYydFOK/s4cGBOcY0+6c/J4mE/8h2mqhw8axOLRtJ G/3RSfLivloM3lyhrYhSd23q7C328YE0FjkXkQfdmnvXcYtjSp1Xb6JYcVYZmltIL4Ff RhTg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=qpaZb7ap; 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 g17si7246307edv.391.2021.08.15.00.29.50; Sun, 15 Aug 2021 00:29:50 -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=qpaZb7ap; 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 1F6F6689EBD; Sun, 15 Aug 2021 10:29:45 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-ed1-f53.google.com (mail-ed1-f53.google.com [209.85.208.53]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 05843689DD9 for ; Sun, 15 Aug 2021 10:29:38 +0300 (EEST) Received: by mail-ed1-f53.google.com with SMTP id cn28so9768202edb.6 for ; Sun, 15 Aug 2021 00:29:37 -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=MziJpJ2pNYojw+sr34rPILRYpyv/rUnQU97PCMuFQvg=; b=qpaZb7apI/R1GJFjb5yWnLxVtVvSUyhtF8KTgYRAD45jtUDh+NjgsL1tNXFAjSw7r3 krnPIAd9r7UN/SEUm737N6IswbGc9nMWqN363hE6o9273jueXN3ZQLzmo91v1dYEuOAc wkx0QnecZ3xy70YgTh4ht8uYVhkD5GaHKGpJ3TGto8kzga3IxJIMnlWyt4w7lsGQiYWK x7tG8zdAObAsTRHXPBqLoRaH6QJt+LMPJXYxco3HRc2YDrSP7CFsl2Z2paceYDRbhmaN QS2WVqLDpJ0E/NktOt7eMelD6fwniAvMzIrdx68Jgp2yHl/zxjT8RHT5HF5sUOj8HlyE 72xg== 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=MziJpJ2pNYojw+sr34rPILRYpyv/rUnQU97PCMuFQvg=; b=tpHjTPcZVfXSUiq+Z6myfaI28xVMjTUAdKrE4avLeU34bE/TNpmyH+6zzbl+xm5nTJ VLDJGQw3wo3m5zx0b6u/uf9R/8EPA6FfBpSxszqyulVPn1aEZMdJb6TsbRq41l3S7QE+ p3RzS+n4vU26IS0BOIGtd8VtbEKi8K+i+q6mznN2c4lvQA/ep2ZkRWMA7vzA9eBxCBuL vWEFr09YdrKNYEmj09aX0pI/2q1Y0LChjOE46yvf8QapZLqFBeSrI2Dm7K1hTdKsALnM XmObwL/JLgfPRa7VflPjYTcACsn4WK4EmCQKlHtQlglHdFateZD3OH5ZWbNcbLg/hNHq oXYQ== X-Gm-Message-State: AOAM530RzmLUcNiCvb50DIRyikvu10UHG2XZIEK1hGmnzYhZ5OnK6wFF o9gOGkOrin6ll7wS61SRWBHELeb7LKM= X-Received: by 2002:aa7:df88:: with SMTP id b8mr13011060edy.163.1629012577520; Sun, 15 Aug 2021 00:29:37 -0700 (PDT) Received: from localhost.localdomain ([95.168.120.43]) by smtp.gmail.com with ESMTPSA id f20sm2437412ejz.30.2021.08.15.00.29.36 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 15 Aug 2021 00:29:37 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Sun, 15 Aug 2021 09:29:29 +0200 Message-Id: <20210815072929.24087-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: uirP8qBauws+ Signed-off-by: Paul B Mahol --- libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/smcenc.c | 561 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 563 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..b3b5368dcf --- /dev/null +++ b/libavcodec/smcenc.c @@ -0,0 +1,561 @@ +/* + * 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]; + + uint8_t color_pairs[COLORS_PER_TABLE][CPAIR]; + uint8_t color_quads[COLORS_PER_TABLE][CQUAD]; + uint8_t color_octets[COLORS_PER_TABLE][COCTET]; + + 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; +} + +#define CACHE_PAIR(x) \ + (s->color_pairs[i][0] == distinct_values[x] || \ + s->color_pairs[i][1] == distinct_values[x]) + +#define CACHE_QUAD(x) \ + (s->color_quads[i][0] == distinct_values[x] || \ + s->color_quads[i][1] == distinct_values[x] || \ + s->color_quads[i][2] == distinct_values[x] || \ + s->color_quads[i][3] == distinct_values[x]) + +#define CACHE_OCTET(x) \ + (s->color_octets[i][0] == distinct_values[x] || \ + s->color_octets[i][1] == distinct_values[x] || \ + s->color_octets[i][2] == distinct_values[x] || \ + s->color_octets[i][3] == distinct_values[x] || \ + s->color_octets[i][4] == distinct_values[x] || \ + s->color_octets[i][5] == distinct_values[x] || \ + s->color_octets[i][6] == distinct_values[x] || \ + s->color_octets[i][7] == distinct_values[x]) + +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]; + uint8_t *distinct_values = s->distinct_values; + 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; + int cache_index; + int distinct = 0; + int blocks = 0; + + while (prev_pixels && 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(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(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 > 0) { + distinct = 17; + blocks = intra_skip_blocks; + } + + if (intra_skip_blocks > 16 && intra_skip_blocks >= inter_skip_blocks && + intra_skip_blocks > 0) { + distinct = 18; + blocks = intra_skip_blocks; + } + + if (inter_skip_blocks > 0 && inter_skip_blocks > intra_skip_blocks && + inter_skip_blocks > 0) { + distinct = 19; + blocks = inter_skip_blocks; + } + + if (inter_skip_blocks > 16 && inter_skip_blocks > intra_skip_blocks && + inter_skip_blocks > 0) { + 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: + cache_index = -1; + for (int i = 0; i < COLORS_PER_TABLE; i++) { + if (CACHE_PAIR(0) && + CACHE_PAIR(1)) { + 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; + for (int i = 0; i < CPAIR; i++) { + s->color_pairs[color_table_index][i] = distinct_values[i]; + bytestream2_put_byte(pb, distinct_values[i]); + } + + color_pair_index++; + if (color_pair_index == COLORS_PER_TABLE) + color_pair_index = 0; + } + + for (int i = 0; i < blocks; i++) { + uint8_t value = s->color_pairs[color_table_index][1]; + uint16_t flags = 0; + int shift = 15; + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + flags |= (value == pixel_ptr[x + y * stride]) << shift; + shift--; + } + } + + bytestream2_put_be16(pb, flags); + + ADVANCE_BLOCK(pixel_ptr, row_ptr, 1) + } + break; + case 3: + case 4: + cache_index = -1; + for (int i = 0; i < COLORS_PER_TABLE; i++) { + if (CACHE_QUAD(0) && + CACHE_QUAD(1) && + CACHE_QUAD(2) && + CACHE_QUAD(3)) { + 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++) { + s->color_quads[color_table_index][i] = distinct_values[i]; + bytestream2_put_byte(pb, distinct_values[i]); + } + + 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; + + for (int k = 0; k < 4; k++) + quad[k] = s->color_quads[color_table_index][k]; + + 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: + cache_index = -1; + for (int i = 0; i < COLORS_PER_TABLE; i++) { + if (CACHE_OCTET(0) && + CACHE_OCTET(1) && + CACHE_OCTET(2) && + CACHE_OCTET(3) && + CACHE_OCTET(4) && + CACHE_OCTET(5) && + CACHE_OCTET(6) && + CACHE_OCTET(7)) { + 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++) { + s->color_octets[color_table_index][i] = distinct_values[i]; + bytestream2_put_byte(pb, distinct_values[i]); + } + + 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; + + for (int k = 0; k < 8; k++) + octet[k] = s->color_octets[color_table_index][k]; + + 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); + + 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}, +};