/* * 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 "codec_internal.h" #include "encode.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 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; \ cur_x += 4; \ if (pixel_ptr - row_ptr >= width) \ { \ row_ptr += stride * 4; \ pixel_ptr = row_ptr; \ cur_y += 4; \ cur_x = 0; \ } \ } \ } 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) { const uint8_t *src_pixels = (const uint8_t *)frame->data[0]; const ptrdiff_t stride = frame->linesize[0]; const uint8_t *prev_pixels = (const uint8_t *)s->prev_frame->data[0]; const ptrdiff_t prev_stride = s->prev_frame->linesize[0]; uint8_t *distinct_values = s->distinct_values; const uint8_t *pixel_ptr, *row_ptr; const int height = frame->height; const int width = frame->width; 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; int cur_y = 0; int cur_x = 0; /* Number of 4x4 blocks in frame. */ total_blocks = ((width + 3) / 4) * ((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; int frame_y = cur_y; int frame_x = cur_x; while (prev_pixels && s->key_frame == 0 && block_counter + inter_skip_blocks < total_blocks) { const int y_size = FFMIN(4, height - cur_y); const int x_size = FFMIN(4, width - cur_x); int compare = 0; for (int y = 0; y < y_size; y++) { const uint8_t *prev_pixel_ptr = prev_pixels + (y + cur_y) * prev_stride + cur_x; compare |= !!memcmp(prev_pixel_ptr, pixel_ptr + y * stride, x_size); if (compare) break; } if (compare) break; inter_skip_blocks++; if (inter_skip_blocks >= 256) break; ADVANCE_BLOCK(pixel_ptr, row_ptr, 1) } pixel_ptr = xpixel_ptr; row_ptr = xrow_ptr; cur_y = frame_y; cur_x = frame_x; while (block_counter > 0 && block_counter + intra_skip_blocks < total_blocks) { const int y_size = FFMIN(4, height - cur_y); const int x_size = FFMIN(4, width - cur_x); const ptrdiff_t offset = xpixel_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 + (width & 3) : sx - 4; const uint8_t *old_pixel_ptr = src_pixels + nx + ny * stride; int compare = 0; for (int y = 0; y < y_size; y++) { compare |= !!memcmp(old_pixel_ptr + y * stride, pixel_ptr + y * stride, x_size); if (compare) break; } if (compare) break; intra_skip_blocks++; if (intra_skip_blocks >= 256) break; ADVANCE_BLOCK(pixel_ptr, row_ptr, 1) } pixel_ptr = xpixel_ptr; row_ptr = xrow_ptr; cur_y = frame_y; cur_x = frame_x; while (block_counter + coded_blocks < total_blocks && coded_blocks < 256) { const int y_size = FFMIN(4, height - cur_y); const int x_size = FFMIN(4, width - cur_x); const int nb_elements = x_size * y_size; uint8_t block_values[16] = { 0 }; for (int y = 0; y < y_size; y++) memcpy(block_values + y * x_size, pixel_ptr + y * stride, x_size); qsort(block_values, nb_elements, sizeof(block_values[0]), smc_cmp_values); s->next_nb_distinct = count_distinct_items(block_values, s->next_distinct_values, nb_elements); 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; coded_blocks++; if (coded_distinct > 1 && coded_blocks >= 16) break; ADVANCE_BLOCK(pixel_ptr, row_ptr, 1) } pixel_ptr = xpixel_ptr; row_ptr = xrow_ptr; cur_y = frame_y; cur_x = frame_x; blocks = coded_distinct <= 8 ? coded_blocks : 0; distinct = coded_distinct; if (intra_skip_blocks >= blocks && intra_skip_blocks >= inter_skip_blocks) { distinct = 17; blocks = intra_skip_blocks; } if (intra_skip_blocks > 16 && intra_skip_blocks >= inter_skip_blocks && intra_skip_blocks >= blocks) { distinct = 18; blocks = intra_skip_blocks; } if (inter_skip_blocks >= blocks && inter_skip_blocks > intra_skip_blocks) { distinct = 19; blocks = inter_skip_blocks; } if (inter_skip_blocks > 16 && inter_skip_blocks > intra_skip_blocks && inter_skip_blocks >= blocks) { distinct = 20; blocks = inter_skip_blocks; } if (blocks == 0) { blocks = coded_blocks; distinct = coded_distinct; } 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++) { const int y_size = FFMIN(4, height - cur_y); const int x_size = FFMIN(4, width - cur_x); uint8_t value = s->color_pairs[color_table_index][1]; uint16_t flags = 0; int shift = 15; for (int y = 0; y < y_size; y++) { for (int x = 0; x < x_size; x++) { flags |= (value == pixel_ptr[x + y * stride]) << shift; shift--; } shift -= 4 - x_size; } 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++) { const int y_size = FFMIN(4, height - cur_y); const int x_size = FFMIN(4, width - cur_x); 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 < y_size; y++) { for (int x = 0; x < x_size; 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; } shift -= 2 * (4 - x_size); } 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++) { const int y_size = FFMIN(4, height - cur_y); const int x_size = FFMIN(4, width - cur_x); 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 < y_size; y++) { for (int x = 0; x < x_size; 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; } shift -= 3 * (4 - x_size); } 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++) { const int y_size = FFMIN(4, height - cur_y); const int x_size = FFMIN(4, width - cur_x); for (int y = 0; y < y_size; y++) { for (int x = 0; x < x_size; x++) bytestream2_put_byte(pb, pixel_ptr[x + y * stride]); for (int x = x_size; x < 4; x++) bytestream2_put_byte(pb, 0); } for (int y = y_size; y < 4; y++) { for (int x = 0; x < 4; x++) bytestream2_put_byte(pb, 0); } 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; PutByteContext pb; 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_num % avctx->gop_size) == 0) { s->key_frame = 1; } else { s->key_frame = 0; } bytestream2_init_writer(&pb, pkt->data, pkt->size); bytestream2_put_be32(&pb, 0x00); pal = av_packet_new_side_data(pkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE); if (!pal) return AVERROR(ENOMEM); memcpy(pal, frame->data[1], AVPALETTE_SIZE); smc_encode_stream(s, pict, &pb); av_shrink_packet(pkt, bytestream2_tell_p(&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 FFCodec ff_smc_encoder = { .p.name = "smc", CODEC_LONG_NAME("QuickTime Graphics (SMC)"), .p.type = AVMEDIA_TYPE_VIDEO, .p.id = AV_CODEC_ID_SMC, .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE, .priv_data_size = sizeof(SMCContext), .init = smc_encode_init, FF_CODEC_ENCODE_CB(smc_encode_frame), .close = smc_encode_end, .p.pix_fmts = (const enum AVPixelFormat[]) { AV_PIX_FMT_PAL8, AV_PIX_FMT_NONE}, };