diff options
author | Paul B Mahol <onemda@gmail.com> | 2021-08-12 15:36:04 +0200 |
---|---|---|
committer | Paul B Mahol <onemda@gmail.com> | 2021-08-18 08:55:56 +0200 |
commit | ed47a4a842952055bb62f468a66a3714bc61972c (patch) | |
tree | 56be56327d6003b7c07df6629bd369dbb91b1f6a | |
parent | 515e7fbce1cc555d9eaadad798d3d968ff468987 (diff) | |
download | ffmpeg-ed47a4a842952055bb62f468a66a3714bc61972c.tar.gz |
avcodec: add SMC encoder
-rw-r--r-- | Changelog | 1 | ||||
-rw-r--r-- | doc/general_contents.texi | 2 | ||||
-rw-r--r-- | libavcodec/Makefile | 1 | ||||
-rw-r--r-- | libavcodec/allcodecs.c | 1 | ||||
-rw-r--r-- | libavcodec/smcenc.c | 563 | ||||
-rw-r--r-- | libavcodec/version.h | 4 |
6 files changed, 569 insertions, 3 deletions
@@ -10,6 +10,7 @@ version <next>: - Concatf protocol - afwtdn audio filter - audio and video segment filters +- Apple Graphics (SMC) encoder version 4.4: diff --git a/doc/general_contents.texi b/doc/general_contents.texi index f3f4feab70..edc047e539 100644 --- a/doc/general_contents.texi +++ b/doc/general_contents.texi @@ -1018,7 +1018,7 @@ following image formats are supported: @item QuickTime 8BPS video @tab @tab X @item QuickTime Animation (RLE) video @tab X @tab X @tab fourcc: 'rle ' -@item QuickTime Graphics (SMC) @tab @tab X +@item QuickTime Graphics (SMC) @tab X @tab X @tab fourcc: 'smc ' @item QuickTime video (RPZA) @tab X @tab X @tab fourcc: rpza 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..5b0035b244 --- /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]; + + 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); + if (!pal) + return AVERROR(ENOMEM); + 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}, +}; diff --git a/libavcodec/version.h b/libavcodec/version.h index c2482428ca..8cdbb4ecf3 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -28,8 +28,8 @@ #include "libavutil/version.h" #define LIBAVCODEC_VERSION_MAJOR 59 -#define LIBAVCODEC_VERSION_MINOR 4 -#define LIBAVCODEC_VERSION_MICRO 101 +#define LIBAVCODEC_VERSION_MINOR 5 +#define LIBAVCODEC_VERSION_MICRO 100 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ LIBAVCODEC_VERSION_MINOR, \ |