Signed-off-by: Paul B Mahol <one...@gmail.com> --- 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}, +}; -- 2.17.1 _______________________________________________ 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".