1/* 2 * a64 video encoder - multicolor modes 3 * Copyright (c) 2009 Tobias Bindhammer 4 * 5 * This file is part of Libav. 6 * 7 * Libav is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * Libav is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with Libav; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22/** 23 * @file 24 * a64 video encoder - multicolor modes 25 */ 26 27#include "a64enc.h" 28#include "a64colors.h" 29#include "a64tables.h" 30#include "elbg.h" 31#include "libavutil/intreadwrite.h" 32 33#define DITHERSTEPS 8 34#define CHARSET_CHARS 256 35#define INTERLACED 1 36#define CROP_SCREENS 1 37 38/* gray gradient */ 39static const int mc_colors[5]={0x0,0xb,0xc,0xf,0x1}; 40 41/* other possible gradients - to be tested */ 42//static const int mc_colors[5]={0x0,0x8,0xa,0xf,0x7}; 43//static const int mc_colors[5]={0x0,0x9,0x8,0xa,0x3}; 44 45static void to_meta_with_crop(AVCodecContext *avctx, AVFrame *p, int *dest) 46{ 47 int blockx, blocky, x, y; 48 int luma = 0; 49 int height = FFMIN(avctx->height, C64YRES); 50 int width = FFMIN(avctx->width , C64XRES); 51 uint8_t *src = p->data[0]; 52 53 for (blocky = 0; blocky < C64YRES; blocky += 8) { 54 for (blockx = 0; blockx < C64XRES; blockx += 8) { 55 for (y = blocky; y < blocky + 8 && y < C64YRES; y++) { 56 for (x = blockx; x < blockx + 8 && x < C64XRES; x += 2) { 57 if(x < width && y < height) { 58 /* build average over 2 pixels */ 59 luma = (src[(x + 0 + y * p->linesize[0])] + 60 src[(x + 1 + y * p->linesize[0])]) / 2; 61 /* write blocks as linear data now so they are suitable for elbg */ 62 dest[0] = luma; 63 } 64 dest++; 65 } 66 } 67 } 68 } 69} 70 71static void render_charset(AVCodecContext *avctx, uint8_t *charset, 72 uint8_t *colrammap) 73{ 74 A64Context *c = avctx->priv_data; 75 uint8_t row1, row2; 76 int charpos, x, y; 77 int a, b; 78 uint8_t pix; 79 int lowdiff, highdiff; 80 int *best_cb = c->mc_best_cb; 81 static uint8_t index1[256]; 82 static uint8_t index2[256]; 83 static uint8_t dither[256]; 84 int i; 85 int distance; 86 87 /* generate lookup-tables for dither and index before looping */ 88 i = 0; 89 for (a=0; a < 256; a++) { 90 if(i < c->mc_pal_size -1 && a == c->mc_luma_vals[i + 1]) { 91 distance = c->mc_luma_vals[i + 1] - c->mc_luma_vals[i]; 92 for(b = 0; b <= distance; b++) { 93 dither[c->mc_luma_vals[i] + b] = b * (DITHERSTEPS - 1) / distance; 94 } 95 i++; 96 } 97 if(i >= c->mc_pal_size - 1) dither[a] = 0; 98 index1[a] = i; 99 index2[a] = FFMIN(i + 1, c->mc_pal_size - 1); 100 } 101 102 /* and render charset */ 103 for (charpos = 0; charpos < CHARSET_CHARS; charpos++) { 104 lowdiff = 0; 105 highdiff = 0; 106 for (y = 0; y < 8; y++) { 107 row1 = 0; row2 = 0; 108 for (x = 0; x < 4; x++) { 109 pix = best_cb[y * 4 + x]; 110 111 /* accumulate error for brightest/darkest color */ 112 if (index1[pix] >= 3) 113 highdiff += pix - c->mc_luma_vals[3]; 114 if (index1[pix] < 1) 115 lowdiff += c->mc_luma_vals[1] - pix; 116 117 row1 <<= 2; 118 119 if (INTERLACED) { 120 row2 <<= 2; 121 if (interlaced_dither_patterns[dither[pix]][(y & 3) * 2 + 0][x & 3]) 122 row1 |= 3-(index2[pix] & 3); 123 else 124 row1 |= 3-(index1[pix] & 3); 125 126 if (interlaced_dither_patterns[dither[pix]][(y & 3) * 2 + 1][x & 3]) 127 row2 |= 3-(index2[pix] & 3); 128 else 129 row2 |= 3-(index1[pix] & 3); 130 } 131 else { 132 if (multi_dither_patterns[dither[pix]][(y & 3)][x & 3]) 133 row1 |= 3-(index2[pix] & 3); 134 else 135 row1 |= 3-(index1[pix] & 3); 136 } 137 } 138 charset[y+0x000] = row1; 139 if (INTERLACED) charset[y+0x800] = row2; 140 } 141 /* do we need to adjust pixels? */ 142 if (highdiff > 0 && lowdiff > 0 && c->mc_use_5col) { 143 if (lowdiff > highdiff) { 144 for (x = 0; x < 32; x++) 145 best_cb[x] = FFMIN(c->mc_luma_vals[3], best_cb[x]); 146 } else { 147 for (x = 0; x < 32; x++) 148 best_cb[x] = FFMAX(c->mc_luma_vals[1], best_cb[x]); 149 } 150 charpos--; /* redo now adjusted char */ 151 /* no adjustment needed, all fine */ 152 } else { 153 /* advance pointers */ 154 best_cb += 32; 155 charset += 8; 156 157 /* remember colorram value */ 158 colrammap[charpos] = (highdiff > 0); 159 } 160 } 161} 162 163static av_cold int a64multi_close_encoder(AVCodecContext *avctx) 164{ 165 A64Context *c = avctx->priv_data; 166 av_free(c->mc_meta_charset); 167 av_free(c->mc_best_cb); 168 av_free(c->mc_charset); 169 av_free(c->mc_charmap); 170 av_free(c->mc_colram); 171 return 0; 172} 173 174static av_cold int a64multi_init_encoder(AVCodecContext *avctx) 175{ 176 A64Context *c = avctx->priv_data; 177 int a; 178 av_lfg_init(&c->randctx, 1); 179 180 if (avctx->global_quality < 1) { 181 c->mc_lifetime = 4; 182 } else { 183 c->mc_lifetime = avctx->global_quality /= FF_QP2LAMBDA; 184 } 185 186 av_log(avctx, AV_LOG_INFO, "charset lifetime set to %d frame(s)\n", c->mc_lifetime); 187 188 c->mc_frame_counter = 0; 189 c->mc_use_5col = avctx->codec->id == CODEC_ID_A64_MULTI5; 190 c->mc_pal_size = 4 + c->mc_use_5col; 191 192 /* precalc luma values for later use */ 193 for (a = 0; a < c->mc_pal_size; a++) { 194 c->mc_luma_vals[a]=a64_palette[mc_colors[a]][0] * 0.30 + 195 a64_palette[mc_colors[a]][1] * 0.59 + 196 a64_palette[mc_colors[a]][2] * 0.11; 197 } 198 199 if (!(c->mc_meta_charset = av_malloc(32000 * c->mc_lifetime * sizeof(int))) || 200 !(c->mc_best_cb = av_malloc(CHARSET_CHARS * 32 * sizeof(int))) || 201 !(c->mc_charmap = av_mallocz(1000 * c->mc_lifetime * sizeof(int))) || 202 !(c->mc_colram = av_mallocz(CHARSET_CHARS * sizeof(uint8_t))) || 203 !(c->mc_charset = av_malloc(0x800 * (INTERLACED+1) * sizeof(uint8_t)))) { 204 av_log(avctx, AV_LOG_ERROR, "Failed to allocate buffer memory.\n"); 205 return AVERROR(ENOMEM); 206 } 207 208 /* set up extradata */ 209 if (!(avctx->extradata = av_mallocz(8 * 4 + FF_INPUT_BUFFER_PADDING_SIZE))) { 210 av_log(avctx, AV_LOG_ERROR, "Failed to allocate memory for extradata.\n"); 211 return AVERROR(ENOMEM); 212 } 213 avctx->extradata_size = 8 * 4; 214 AV_WB32(avctx->extradata, c->mc_lifetime); 215 AV_WB32(avctx->extradata + 16, INTERLACED); 216 217 avcodec_get_frame_defaults(&c->picture); 218 avctx->coded_frame = &c->picture; 219 avctx->coded_frame->pict_type = AV_PICTURE_TYPE_I; 220 avctx->coded_frame->key_frame = 1; 221 if (!avctx->codec_tag) 222 avctx->codec_tag = AV_RL32("a64m"); 223 224 return 0; 225} 226 227static void a64_compress_colram(unsigned char *buf, int *charmap, uint8_t *colram) 228{ 229 int a; 230 uint8_t temp; 231 /* only needs to be done in 5col mode */ 232 /* XXX could be squeezed to 0x80 bytes */ 233 for (a = 0; a < 256; a++) { 234 temp = colram[charmap[a + 0x000]] << 0; 235 temp |= colram[charmap[a + 0x100]] << 1; 236 temp |= colram[charmap[a + 0x200]] << 2; 237 if (a < 0xe8) temp |= colram[charmap[a + 0x300]] << 3; 238 buf[a] = temp << 2; 239 } 240} 241 242static int a64multi_encode_frame(AVCodecContext *avctx, unsigned char *buf, 243 int buf_size, void *data) 244{ 245 A64Context *c = avctx->priv_data; 246 AVFrame *pict = data; 247 AVFrame *const p = (AVFrame *) & c->picture; 248 249 int frame; 250 int x, y; 251 int b_height; 252 int b_width; 253 254 int req_size; 255 256 int *charmap = c->mc_charmap; 257 uint8_t *colram = c->mc_colram; 258 uint8_t *charset = c->mc_charset; 259 int *meta = c->mc_meta_charset; 260 int *best_cb = c->mc_best_cb; 261 262 int charset_size = 0x800 * (INTERLACED + 1); 263 int colram_size = 0x100 * c->mc_use_5col; 264 int screen_size; 265 266 if(CROP_SCREENS) { 267 b_height = FFMIN(avctx->height,C64YRES) >> 3; 268 b_width = FFMIN(avctx->width ,C64XRES) >> 3; 269 screen_size = b_width * b_height; 270 } else { 271 b_height = C64YRES >> 3; 272 b_width = C64XRES >> 3; 273 screen_size = 0x400; 274 } 275 276 /* no data, means end encoding asap */ 277 if (!data) { 278 /* all done, end encoding */ 279 if (!c->mc_lifetime) return 0; 280 /* no more frames in queue, prepare to flush remaining frames */ 281 if (!c->mc_frame_counter) { 282 c->mc_lifetime = 0; 283 } 284 /* still frames in queue so limit lifetime to remaining frames */ 285 else c->mc_lifetime = c->mc_frame_counter; 286 /* still new data available */ 287 } else { 288 /* fill up mc_meta_charset with data until lifetime exceeds */ 289 if (c->mc_frame_counter < c->mc_lifetime) { 290 *p = *pict; 291 p->pict_type = AV_PICTURE_TYPE_I; 292 p->key_frame = 1; 293 to_meta_with_crop(avctx, p, meta + 32000 * c->mc_frame_counter); 294 c->mc_frame_counter++; 295 /* lifetime is not reached so wait for next frame first */ 296 return 0; 297 } 298 } 299 300 /* lifetime reached so now convert X frames at once */ 301 if (c->mc_frame_counter == c->mc_lifetime) { 302 req_size = 0; 303 /* any frames to encode? */ 304 if (c->mc_lifetime) { 305 /* calc optimal new charset + charmaps */ 306 ff_init_elbg(meta, 32, 1000 * c->mc_lifetime, best_cb, CHARSET_CHARS, 50, charmap, &c->randctx); 307 ff_do_elbg (meta, 32, 1000 * c->mc_lifetime, best_cb, CHARSET_CHARS, 50, charmap, &c->randctx); 308 309 /* create colorram map and a c64 readable charset */ 310 render_charset(avctx, charset, colram); 311 312 /* copy charset to buf */ 313 memcpy(buf,charset, charset_size); 314 315 /* advance pointers */ 316 buf += charset_size; 317 charset += charset_size; 318 req_size += charset_size; 319 } 320 /* no charset so clean buf */ 321 else memset(buf, 0, charset_size); 322 323 /* write x frames to buf */ 324 for (frame = 0; frame < c->mc_lifetime; frame++) { 325 /* copy charmap to buf. buf is uchar*, charmap is int*, so no memcpy here, sorry */ 326 for (y = 0; y < b_height; y++) { 327 for (x = 0; x < b_width; x++) { 328 buf[y * b_width + x] = charmap[y * b_width + x]; 329 } 330 } 331 /* advance pointers */ 332 buf += screen_size; 333 req_size += screen_size; 334 335 /* compress and copy colram to buf */ 336 if (c->mc_use_5col) { 337 a64_compress_colram(buf, charmap, colram); 338 /* advance pointers */ 339 buf += colram_size; 340 req_size += colram_size; 341 } 342 343 /* advance to next charmap */ 344 charmap += 1000; 345 } 346 347 AV_WB32(avctx->extradata + 4, c->mc_frame_counter); 348 AV_WB32(avctx->extradata + 8, charset_size); 349 AV_WB32(avctx->extradata + 12, screen_size + colram_size); 350 351 /* reset counter */ 352 c->mc_frame_counter = 0; 353 354 if (req_size > buf_size) { 355 av_log(avctx, AV_LOG_ERROR, "buf size too small (need %d, got %d)\n", req_size, buf_size); 356 return -1; 357 } 358 return req_size; 359 } 360 return 0; 361} 362 363AVCodec ff_a64multi_encoder = { 364 .name = "a64multi", 365 .type = AVMEDIA_TYPE_VIDEO, 366 .id = CODEC_ID_A64_MULTI, 367 .priv_data_size = sizeof(A64Context), 368 .init = a64multi_init_encoder, 369 .encode = a64multi_encode_frame, 370 .close = a64multi_close_encoder, 371 .pix_fmts = (const enum PixelFormat[]) {PIX_FMT_GRAY8, PIX_FMT_NONE}, 372 .long_name = NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64"), 373 .capabilities = CODEC_CAP_DELAY, 374}; 375 376AVCodec ff_a64multi5_encoder = { 377 .name = "a64multi5", 378 .type = AVMEDIA_TYPE_VIDEO, 379 .id = CODEC_ID_A64_MULTI5, 380 .priv_data_size = sizeof(A64Context), 381 .init = a64multi_init_encoder, 382 .encode = a64multi_encode_frame, 383 .close = a64multi_close_encoder, 384 .pix_fmts = (const enum PixelFormat[]) {PIX_FMT_GRAY8, PIX_FMT_NONE}, 385 .long_name = NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64, extended with 5th color (colram)"), 386 .capabilities = CODEC_CAP_DELAY, 387}; 388