yading@10: /* yading@10: * a64 video encoder - multicolor modes yading@10: * Copyright (c) 2009 Tobias Bindhammer yading@10: * yading@10: * This file is part of FFmpeg. yading@10: * yading@10: * FFmpeg is free software; you can redistribute it and/or yading@10: * modify it under the terms of the GNU Lesser General Public yading@10: * License as published by the Free Software Foundation; either yading@10: * version 2.1 of the License, or (at your option) any later version. yading@10: * yading@10: * FFmpeg is distributed in the hope that it will be useful, yading@10: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@10: * Lesser General Public License for more details. yading@10: * yading@10: * You should have received a copy of the GNU Lesser General Public yading@10: * License along with FFmpeg; if not, write to the Free Software yading@10: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@10: */ yading@10: yading@10: /** yading@10: * @file yading@10: * a64 video encoder - multicolor modes yading@10: */ yading@10: yading@10: #include "a64colors.h" yading@10: #include "a64tables.h" yading@10: #include "elbg.h" yading@10: #include "internal.h" yading@10: #include "libavutil/common.h" yading@10: #include "libavutil/intreadwrite.h" yading@10: yading@10: #define DITHERSTEPS 8 yading@10: #define CHARSET_CHARS 256 yading@10: #define INTERLACED 1 yading@10: #define CROP_SCREENS 1 yading@10: yading@10: #define C64XRES 320 yading@10: #define C64YRES 200 yading@10: yading@10: typedef struct A64Context { yading@10: /* general variables */ yading@10: AVFrame picture; yading@10: yading@10: /* variables for multicolor modes */ yading@10: AVLFG randctx; yading@10: int mc_lifetime; yading@10: int mc_use_5col; yading@10: unsigned mc_frame_counter; yading@10: int *mc_meta_charset; yading@10: int *mc_charmap; yading@10: int *mc_best_cb; yading@10: int mc_luma_vals[5]; yading@10: uint8_t *mc_charset; yading@10: uint8_t *mc_colram; yading@10: uint8_t *mc_palette; yading@10: int mc_pal_size; yading@10: yading@10: /* pts of the next packet that will be output */ yading@10: int64_t next_pts; yading@10: } A64Context; yading@10: yading@10: /* gray gradient */ yading@10: static const int mc_colors[5]={0x0,0xb,0xc,0xf,0x1}; yading@10: yading@10: /* other possible gradients - to be tested */ yading@10: //static const int mc_colors[5]={0x0,0x8,0xa,0xf,0x7}; yading@10: //static const int mc_colors[5]={0x0,0x9,0x8,0xa,0x3}; yading@10: yading@10: static void to_meta_with_crop(AVCodecContext *avctx, AVFrame *p, int *dest) yading@10: { yading@10: int blockx, blocky, x, y; yading@10: int luma = 0; yading@10: int height = FFMIN(avctx->height, C64YRES); yading@10: int width = FFMIN(avctx->width , C64XRES); yading@10: uint8_t *src = p->data[0]; yading@10: yading@10: for (blocky = 0; blocky < C64YRES; blocky += 8) { yading@10: for (blockx = 0; blockx < C64XRES; blockx += 8) { yading@10: for (y = blocky; y < blocky + 8 && y < C64YRES; y++) { yading@10: for (x = blockx; x < blockx + 8 && x < C64XRES; x += 2) { yading@10: if(x < width && y < height) { yading@10: /* build average over 2 pixels */ yading@10: luma = (src[(x + 0 + y * p->linesize[0])] + yading@10: src[(x + 1 + y * p->linesize[0])]) / 2; yading@10: /* write blocks as linear data now so they are suitable for elbg */ yading@10: dest[0] = luma; yading@10: } yading@10: dest++; yading@10: } yading@10: } yading@10: } yading@10: } yading@10: } yading@10: yading@10: static void render_charset(AVCodecContext *avctx, uint8_t *charset, yading@10: uint8_t *colrammap) yading@10: { yading@10: A64Context *c = avctx->priv_data; yading@10: uint8_t row1, row2; yading@10: int charpos, x, y; yading@10: int a, b; yading@10: uint8_t pix; yading@10: int lowdiff, highdiff; yading@10: int *best_cb = c->mc_best_cb; yading@10: static uint8_t index1[256]; yading@10: static uint8_t index2[256]; yading@10: static uint8_t dither[256]; yading@10: int i; yading@10: int distance; yading@10: yading@10: /* generate lookup-tables for dither and index before looping */ yading@10: i = 0; yading@10: for (a=0; a < 256; a++) { yading@10: if(i < c->mc_pal_size -1 && a == c->mc_luma_vals[i + 1]) { yading@10: distance = c->mc_luma_vals[i + 1] - c->mc_luma_vals[i]; yading@10: for(b = 0; b <= distance; b++) { yading@10: dither[c->mc_luma_vals[i] + b] = b * (DITHERSTEPS - 1) / distance; yading@10: } yading@10: i++; yading@10: } yading@10: if(i >= c->mc_pal_size - 1) dither[a] = 0; yading@10: index1[a] = i; yading@10: index2[a] = FFMIN(i + 1, c->mc_pal_size - 1); yading@10: } yading@10: yading@10: /* and render charset */ yading@10: for (charpos = 0; charpos < CHARSET_CHARS; charpos++) { yading@10: lowdiff = 0; yading@10: highdiff = 0; yading@10: for (y = 0; y < 8; y++) { yading@10: row1 = 0; row2 = 0; yading@10: for (x = 0; x < 4; x++) { yading@10: pix = best_cb[y * 4 + x]; yading@10: yading@10: /* accumulate error for brightest/darkest color */ yading@10: if (index1[pix] >= 3) yading@10: highdiff += pix - c->mc_luma_vals[3]; yading@10: if (index1[pix] < 1) yading@10: lowdiff += c->mc_luma_vals[1] - pix; yading@10: yading@10: row1 <<= 2; yading@10: yading@10: if (INTERLACED) { yading@10: row2 <<= 2; yading@10: if (interlaced_dither_patterns[dither[pix]][(y & 3) * 2 + 0][x & 3]) yading@10: row1 |= 3-(index2[pix] & 3); yading@10: else yading@10: row1 |= 3-(index1[pix] & 3); yading@10: yading@10: if (interlaced_dither_patterns[dither[pix]][(y & 3) * 2 + 1][x & 3]) yading@10: row2 |= 3-(index2[pix] & 3); yading@10: else yading@10: row2 |= 3-(index1[pix] & 3); yading@10: } yading@10: else { yading@10: if (multi_dither_patterns[dither[pix]][(y & 3)][x & 3]) yading@10: row1 |= 3-(index2[pix] & 3); yading@10: else yading@10: row1 |= 3-(index1[pix] & 3); yading@10: } yading@10: } yading@10: charset[y+0x000] = row1; yading@10: if (INTERLACED) charset[y+0x800] = row2; yading@10: } yading@10: /* do we need to adjust pixels? */ yading@10: if (highdiff > 0 && lowdiff > 0 && c->mc_use_5col) { yading@10: if (lowdiff > highdiff) { yading@10: for (x = 0; x < 32; x++) yading@10: best_cb[x] = FFMIN(c->mc_luma_vals[3], best_cb[x]); yading@10: } else { yading@10: for (x = 0; x < 32; x++) yading@10: best_cb[x] = FFMAX(c->mc_luma_vals[1], best_cb[x]); yading@10: } yading@10: charpos--; /* redo now adjusted char */ yading@10: /* no adjustment needed, all fine */ yading@10: } else { yading@10: /* advance pointers */ yading@10: best_cb += 32; yading@10: charset += 8; yading@10: yading@10: /* remember colorram value */ yading@10: colrammap[charpos] = (highdiff > 0); yading@10: } yading@10: } yading@10: } yading@10: yading@10: static av_cold int a64multi_close_encoder(AVCodecContext *avctx) yading@10: { yading@10: A64Context *c = avctx->priv_data; yading@10: av_free(c->mc_meta_charset); yading@10: av_free(c->mc_best_cb); yading@10: av_free(c->mc_charset); yading@10: av_free(c->mc_charmap); yading@10: av_free(c->mc_colram); yading@10: return 0; yading@10: } yading@10: yading@10: static av_cold int a64multi_init_encoder(AVCodecContext *avctx) yading@10: { yading@10: A64Context *c = avctx->priv_data; yading@10: int a; yading@10: av_lfg_init(&c->randctx, 1); yading@10: yading@10: if (avctx->global_quality < 1) { yading@10: c->mc_lifetime = 4; yading@10: } else { yading@10: c->mc_lifetime = avctx->global_quality /= FF_QP2LAMBDA; yading@10: } yading@10: yading@10: av_log(avctx, AV_LOG_INFO, "charset lifetime set to %d frame(s)\n", c->mc_lifetime); yading@10: yading@10: c->mc_frame_counter = 0; yading@10: c->mc_use_5col = avctx->codec->id == AV_CODEC_ID_A64_MULTI5; yading@10: c->mc_pal_size = 4 + c->mc_use_5col; yading@10: yading@10: /* precalc luma values for later use */ yading@10: for (a = 0; a < c->mc_pal_size; a++) { yading@10: c->mc_luma_vals[a]=a64_palette[mc_colors[a]][0] * 0.30 + yading@10: a64_palette[mc_colors[a]][1] * 0.59 + yading@10: a64_palette[mc_colors[a]][2] * 0.11; yading@10: } yading@10: yading@10: if (!(c->mc_meta_charset = av_malloc(32000 * c->mc_lifetime * sizeof(int))) || yading@10: !(c->mc_best_cb = av_malloc(CHARSET_CHARS * 32 * sizeof(int))) || yading@10: !(c->mc_charmap = av_mallocz(1000 * c->mc_lifetime * sizeof(int))) || yading@10: !(c->mc_colram = av_mallocz(CHARSET_CHARS * sizeof(uint8_t))) || yading@10: !(c->mc_charset = av_malloc(0x800 * (INTERLACED+1) * sizeof(uint8_t)))) { yading@10: av_log(avctx, AV_LOG_ERROR, "Failed to allocate buffer memory.\n"); yading@10: return AVERROR(ENOMEM); yading@10: } yading@10: yading@10: /* set up extradata */ yading@10: if (!(avctx->extradata = av_mallocz(8 * 4 + FF_INPUT_BUFFER_PADDING_SIZE))) { yading@10: av_log(avctx, AV_LOG_ERROR, "Failed to allocate memory for extradata.\n"); yading@10: return AVERROR(ENOMEM); yading@10: } yading@10: avctx->extradata_size = 8 * 4; yading@10: AV_WB32(avctx->extradata, c->mc_lifetime); yading@10: AV_WB32(avctx->extradata + 16, INTERLACED); yading@10: yading@10: avcodec_get_frame_defaults(&c->picture); yading@10: avctx->coded_frame = &c->picture; yading@10: avctx->coded_frame->pict_type = AV_PICTURE_TYPE_I; yading@10: avctx->coded_frame->key_frame = 1; yading@10: if (!avctx->codec_tag) yading@10: avctx->codec_tag = AV_RL32("a64m"); yading@10: yading@10: c->next_pts = AV_NOPTS_VALUE; yading@10: yading@10: return 0; yading@10: } yading@10: yading@10: static void a64_compress_colram(unsigned char *buf, int *charmap, uint8_t *colram) yading@10: { yading@10: int a; yading@10: uint8_t temp; yading@10: /* only needs to be done in 5col mode */ yading@10: /* XXX could be squeezed to 0x80 bytes */ yading@10: for (a = 0; a < 256; a++) { yading@10: temp = colram[charmap[a + 0x000]] << 0; yading@10: temp |= colram[charmap[a + 0x100]] << 1; yading@10: temp |= colram[charmap[a + 0x200]] << 2; yading@10: if (a < 0xe8) temp |= colram[charmap[a + 0x300]] << 3; yading@10: buf[a] = temp << 2; yading@10: } yading@10: } yading@10: yading@10: static int a64multi_encode_frame(AVCodecContext *avctx, AVPacket *pkt, yading@10: const AVFrame *pict, int *got_packet) yading@10: { yading@10: A64Context *c = avctx->priv_data; yading@10: AVFrame *const p = &c->picture; yading@10: yading@10: int frame; yading@10: int x, y; yading@10: int b_height; yading@10: int b_width; yading@10: yading@10: int req_size, ret; yading@10: uint8_t *buf = NULL; yading@10: yading@10: int *charmap = c->mc_charmap; yading@10: uint8_t *colram = c->mc_colram; yading@10: uint8_t *charset = c->mc_charset; yading@10: int *meta = c->mc_meta_charset; yading@10: int *best_cb = c->mc_best_cb; yading@10: yading@10: int charset_size = 0x800 * (INTERLACED + 1); yading@10: int colram_size = 0x100 * c->mc_use_5col; yading@10: int screen_size; yading@10: yading@10: if(CROP_SCREENS) { yading@10: b_height = FFMIN(avctx->height,C64YRES) >> 3; yading@10: b_width = FFMIN(avctx->width ,C64XRES) >> 3; yading@10: screen_size = b_width * b_height; yading@10: } else { yading@10: b_height = C64YRES >> 3; yading@10: b_width = C64XRES >> 3; yading@10: screen_size = 0x400; yading@10: } yading@10: yading@10: /* no data, means end encoding asap */ yading@10: if (!pict) { yading@10: /* all done, end encoding */ yading@10: if (!c->mc_lifetime) return 0; yading@10: /* no more frames in queue, prepare to flush remaining frames */ yading@10: if (!c->mc_frame_counter) { yading@10: c->mc_lifetime = 0; yading@10: } yading@10: /* still frames in queue so limit lifetime to remaining frames */ yading@10: else c->mc_lifetime = c->mc_frame_counter; yading@10: /* still new data available */ yading@10: } else { yading@10: /* fill up mc_meta_charset with data until lifetime exceeds */ yading@10: if (c->mc_frame_counter < c->mc_lifetime) { yading@10: *p = *pict; yading@10: p->pict_type = AV_PICTURE_TYPE_I; yading@10: p->key_frame = 1; yading@10: to_meta_with_crop(avctx, p, meta + 32000 * c->mc_frame_counter); yading@10: c->mc_frame_counter++; yading@10: if (c->next_pts == AV_NOPTS_VALUE) yading@10: c->next_pts = pict->pts; yading@10: /* lifetime is not reached so wait for next frame first */ yading@10: return 0; yading@10: } yading@10: } yading@10: yading@10: /* lifetime reached so now convert X frames at once */ yading@10: if (c->mc_frame_counter == c->mc_lifetime) { yading@10: req_size = 0; yading@10: /* any frames to encode? */ yading@10: if (c->mc_lifetime) { yading@10: req_size = charset_size + c->mc_lifetime*(screen_size + colram_size); yading@10: if ((ret = ff_alloc_packet2(avctx, pkt, req_size)) < 0) yading@10: return ret; yading@10: buf = pkt->data; yading@10: yading@10: /* calc optimal new charset + charmaps */ yading@10: ff_init_elbg(meta, 32, 1000 * c->mc_lifetime, best_cb, CHARSET_CHARS, 50, charmap, &c->randctx); yading@10: ff_do_elbg (meta, 32, 1000 * c->mc_lifetime, best_cb, CHARSET_CHARS, 50, charmap, &c->randctx); yading@10: yading@10: /* create colorram map and a c64 readable charset */ yading@10: render_charset(avctx, charset, colram); yading@10: yading@10: /* copy charset to buf */ yading@10: memcpy(buf, charset, charset_size); yading@10: yading@10: /* advance pointers */ yading@10: buf += charset_size; yading@10: charset += charset_size; yading@10: } yading@10: yading@10: /* write x frames to buf */ yading@10: for (frame = 0; frame < c->mc_lifetime; frame++) { yading@10: /* copy charmap to buf. buf is uchar*, charmap is int*, so no memcpy here, sorry */ yading@10: for (y = 0; y < b_height; y++) { yading@10: for (x = 0; x < b_width; x++) { yading@10: buf[y * b_width + x] = charmap[y * b_width + x]; yading@10: } yading@10: } yading@10: /* advance pointers */ yading@10: buf += screen_size; yading@10: req_size += screen_size; yading@10: yading@10: /* compress and copy colram to buf */ yading@10: if (c->mc_use_5col) { yading@10: a64_compress_colram(buf, charmap, colram); yading@10: /* advance pointers */ yading@10: buf += colram_size; yading@10: req_size += colram_size; yading@10: } yading@10: yading@10: /* advance to next charmap */ yading@10: charmap += 1000; yading@10: } yading@10: yading@10: AV_WB32(avctx->extradata + 4, c->mc_frame_counter); yading@10: AV_WB32(avctx->extradata + 8, charset_size); yading@10: AV_WB32(avctx->extradata + 12, screen_size + colram_size); yading@10: yading@10: /* reset counter */ yading@10: c->mc_frame_counter = 0; yading@10: yading@10: pkt->pts = pkt->dts = c->next_pts; yading@10: c->next_pts = AV_NOPTS_VALUE; yading@10: yading@10: pkt->size = req_size; yading@10: pkt->flags |= AV_PKT_FLAG_KEY; yading@10: *got_packet = !!req_size; yading@10: } yading@10: return 0; yading@10: } yading@10: yading@10: #if CONFIG_A64MULTI_ENCODER yading@10: AVCodec ff_a64multi_encoder = { yading@10: .name = "a64multi", yading@10: .type = AVMEDIA_TYPE_VIDEO, yading@10: .id = AV_CODEC_ID_A64_MULTI, yading@10: .priv_data_size = sizeof(A64Context), yading@10: .init = a64multi_init_encoder, yading@10: .encode2 = a64multi_encode_frame, yading@10: .close = a64multi_close_encoder, yading@10: .pix_fmts = (const enum AVPixelFormat[]) {AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE}, yading@10: .long_name = NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64"), yading@10: .capabilities = CODEC_CAP_DELAY, yading@10: }; yading@10: #endif yading@10: #if CONFIG_A64MULTI5_ENCODER yading@10: AVCodec ff_a64multi5_encoder = { yading@10: .name = "a64multi5", yading@10: .type = AVMEDIA_TYPE_VIDEO, yading@10: .id = AV_CODEC_ID_A64_MULTI5, yading@10: .priv_data_size = sizeof(A64Context), yading@10: .init = a64multi_init_encoder, yading@10: .encode2 = a64multi_encode_frame, yading@10: .close = a64multi_close_encoder, yading@10: .pix_fmts = (const enum AVPixelFormat[]) {AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE}, yading@10: .long_name = NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64, extended with 5th color (colram)"), yading@10: .capabilities = CODEC_CAP_DELAY, yading@10: }; yading@10: #endif