| 1 | /* |
| 2 | * a64 video encoder - multicolor modes |
| 3 | * Copyright (c) 2009 Tobias Bindhammer |
| 4 | * |
| 5 | * This file is part of FFmpeg. |
| 6 | * |
| 7 | * FFmpeg 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 | * FFmpeg 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 FFmpeg; 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 "a64colors.h" |
| 28 | #include "a64tables.h" |
| 29 | #include "elbg.h" |
| 30 | #include "internal.h" |
| 31 | #include "libavutil/common.h" |
| 32 | #include "libavutil/intreadwrite.h" |
| 33 | |
| 34 | #define DITHERSTEPS 8 |
| 35 | #define CHARSET_CHARS 256 |
| 36 | #define INTERLACED 1 |
| 37 | #define CROP_SCREENS 1 |
| 38 | |
| 39 | #define C64XRES 320 |
| 40 | #define C64YRES 200 |
| 41 | |
| 42 | typedef struct A64Context { |
| 43 | /* variables for multicolor modes */ |
| 44 | AVLFG randctx; |
| 45 | int mc_lifetime; |
| 46 | int mc_use_5col; |
| 47 | unsigned mc_frame_counter; |
| 48 | int *mc_meta_charset; |
| 49 | int *mc_charmap; |
| 50 | int *mc_best_cb; |
| 51 | int mc_luma_vals[5]; |
| 52 | uint8_t *mc_charset; |
| 53 | uint8_t *mc_colram; |
| 54 | uint8_t *mc_palette; |
| 55 | int mc_pal_size; |
| 56 | |
| 57 | /* pts of the next packet that will be output */ |
| 58 | int64_t next_pts; |
| 59 | } A64Context; |
| 60 | |
| 61 | /* gray gradient */ |
| 62 | static const int mc_colors[5]={0x0,0xb,0xc,0xf,0x1}; |
| 63 | |
| 64 | /* other possible gradients - to be tested */ |
| 65 | //static const int mc_colors[5]={0x0,0x8,0xa,0xf,0x7}; |
| 66 | //static const int mc_colors[5]={0x0,0x9,0x8,0xa,0x3}; |
| 67 | |
| 68 | static void to_meta_with_crop(AVCodecContext *avctx, AVFrame *p, int *dest) |
| 69 | { |
| 70 | int blockx, blocky, x, y; |
| 71 | int luma = 0; |
| 72 | int height = FFMIN(avctx->height, C64YRES); |
| 73 | int width = FFMIN(avctx->width , C64XRES); |
| 74 | uint8_t *src = p->data[0]; |
| 75 | |
| 76 | for (blocky = 0; blocky < C64YRES; blocky += 8) { |
| 77 | for (blockx = 0; blockx < C64XRES; blockx += 8) { |
| 78 | for (y = blocky; y < blocky + 8 && y < C64YRES; y++) { |
| 79 | for (x = blockx; x < blockx + 8 && x < C64XRES; x += 2) { |
| 80 | if(x < width && y < height) { |
| 81 | /* build average over 2 pixels */ |
| 82 | luma = (src[(x + 0 + y * p->linesize[0])] + |
| 83 | src[(x + 1 + y * p->linesize[0])]) / 2; |
| 84 | /* write blocks as linear data now so they are suitable for elbg */ |
| 85 | dest[0] = luma; |
| 86 | } |
| 87 | dest++; |
| 88 | } |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | static void render_charset(AVCodecContext *avctx, uint8_t *charset, |
| 95 | uint8_t *colrammap) |
| 96 | { |
| 97 | A64Context *c = avctx->priv_data; |
| 98 | uint8_t row1, row2; |
| 99 | int charpos, x, y; |
| 100 | int a, b; |
| 101 | uint8_t pix; |
| 102 | int lowdiff, highdiff; |
| 103 | int *best_cb = c->mc_best_cb; |
| 104 | static uint8_t index1[256]; |
| 105 | static uint8_t index2[256]; |
| 106 | static uint8_t dither[256]; |
| 107 | int i; |
| 108 | int distance; |
| 109 | |
| 110 | /* generate lookup-tables for dither and index before looping */ |
| 111 | i = 0; |
| 112 | for (a=0; a < 256; a++) { |
| 113 | if(i < c->mc_pal_size -1 && a == c->mc_luma_vals[i + 1]) { |
| 114 | distance = c->mc_luma_vals[i + 1] - c->mc_luma_vals[i]; |
| 115 | for(b = 0; b <= distance; b++) { |
| 116 | dither[c->mc_luma_vals[i] + b] = b * (DITHERSTEPS - 1) / distance; |
| 117 | } |
| 118 | i++; |
| 119 | } |
| 120 | if(i >= c->mc_pal_size - 1) dither[a] = 0; |
| 121 | index1[a] = i; |
| 122 | index2[a] = FFMIN(i + 1, c->mc_pal_size - 1); |
| 123 | } |
| 124 | |
| 125 | /* and render charset */ |
| 126 | for (charpos = 0; charpos < CHARSET_CHARS; charpos++) { |
| 127 | lowdiff = 0; |
| 128 | highdiff = 0; |
| 129 | for (y = 0; y < 8; y++) { |
| 130 | row1 = 0; row2 = 0; |
| 131 | for (x = 0; x < 4; x++) { |
| 132 | pix = best_cb[y * 4 + x]; |
| 133 | |
| 134 | /* accumulate error for brightest/darkest color */ |
| 135 | if (index1[pix] >= 3) |
| 136 | highdiff += pix - c->mc_luma_vals[3]; |
| 137 | if (index1[pix] < 1) |
| 138 | lowdiff += c->mc_luma_vals[1] - pix; |
| 139 | |
| 140 | row1 <<= 2; |
| 141 | |
| 142 | if (INTERLACED) { |
| 143 | row2 <<= 2; |
| 144 | if (interlaced_dither_patterns[dither[pix]][(y & 3) * 2 + 0][x & 3]) |
| 145 | row1 |= 3-(index2[pix] & 3); |
| 146 | else |
| 147 | row1 |= 3-(index1[pix] & 3); |
| 148 | |
| 149 | if (interlaced_dither_patterns[dither[pix]][(y & 3) * 2 + 1][x & 3]) |
| 150 | row2 |= 3-(index2[pix] & 3); |
| 151 | else |
| 152 | row2 |= 3-(index1[pix] & 3); |
| 153 | } |
| 154 | else { |
| 155 | if (multi_dither_patterns[dither[pix]][(y & 3)][x & 3]) |
| 156 | row1 |= 3-(index2[pix] & 3); |
| 157 | else |
| 158 | row1 |= 3-(index1[pix] & 3); |
| 159 | } |
| 160 | } |
| 161 | charset[y+0x000] = row1; |
| 162 | if (INTERLACED) charset[y+0x800] = row2; |
| 163 | } |
| 164 | /* do we need to adjust pixels? */ |
| 165 | if (highdiff > 0 && lowdiff > 0 && c->mc_use_5col) { |
| 166 | if (lowdiff > highdiff) { |
| 167 | for (x = 0; x < 32; x++) |
| 168 | best_cb[x] = FFMIN(c->mc_luma_vals[3], best_cb[x]); |
| 169 | } else { |
| 170 | for (x = 0; x < 32; x++) |
| 171 | best_cb[x] = FFMAX(c->mc_luma_vals[1], best_cb[x]); |
| 172 | } |
| 173 | charpos--; /* redo now adjusted char */ |
| 174 | /* no adjustment needed, all fine */ |
| 175 | } else { |
| 176 | /* advance pointers */ |
| 177 | best_cb += 32; |
| 178 | charset += 8; |
| 179 | |
| 180 | /* remember colorram value */ |
| 181 | colrammap[charpos] = (highdiff > 0); |
| 182 | } |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | static av_cold int a64multi_close_encoder(AVCodecContext *avctx) |
| 187 | { |
| 188 | A64Context *c = avctx->priv_data; |
| 189 | av_frame_free(&avctx->coded_frame); |
| 190 | av_freep(&c->mc_meta_charset); |
| 191 | av_freep(&c->mc_best_cb); |
| 192 | av_freep(&c->mc_charset); |
| 193 | av_freep(&c->mc_charmap); |
| 194 | av_freep(&c->mc_colram); |
| 195 | return 0; |
| 196 | } |
| 197 | |
| 198 | static av_cold int a64multi_encode_init(AVCodecContext *avctx) |
| 199 | { |
| 200 | A64Context *c = avctx->priv_data; |
| 201 | int a; |
| 202 | av_lfg_init(&c->randctx, 1); |
| 203 | |
| 204 | if (avctx->global_quality < 1) { |
| 205 | c->mc_lifetime = 4; |
| 206 | } else { |
| 207 | c->mc_lifetime = avctx->global_quality /= FF_QP2LAMBDA; |
| 208 | } |
| 209 | |
| 210 | av_log(avctx, AV_LOG_INFO, "charset lifetime set to %d frame(s)\n", c->mc_lifetime); |
| 211 | |
| 212 | c->mc_frame_counter = 0; |
| 213 | c->mc_use_5col = avctx->codec->id == AV_CODEC_ID_A64_MULTI5; |
| 214 | c->mc_pal_size = 4 + c->mc_use_5col; |
| 215 | |
| 216 | /* precalc luma values for later use */ |
| 217 | for (a = 0; a < c->mc_pal_size; a++) { |
| 218 | c->mc_luma_vals[a]=a64_palette[mc_colors[a]][0] * 0.30 + |
| 219 | a64_palette[mc_colors[a]][1] * 0.59 + |
| 220 | a64_palette[mc_colors[a]][2] * 0.11; |
| 221 | } |
| 222 | |
| 223 | if (!(c->mc_meta_charset = av_malloc_array(c->mc_lifetime, 32000 * sizeof(int))) || |
| 224 | !(c->mc_best_cb = av_malloc(CHARSET_CHARS * 32 * sizeof(int))) || |
| 225 | !(c->mc_charmap = av_mallocz_array(c->mc_lifetime, 1000 * sizeof(int))) || |
| 226 | !(c->mc_colram = av_mallocz(CHARSET_CHARS * sizeof(uint8_t))) || |
| 227 | !(c->mc_charset = av_malloc(0x800 * (INTERLACED+1) * sizeof(uint8_t)))) { |
| 228 | av_log(avctx, AV_LOG_ERROR, "Failed to allocate buffer memory.\n"); |
| 229 | return AVERROR(ENOMEM); |
| 230 | } |
| 231 | |
| 232 | /* set up extradata */ |
| 233 | if (!(avctx->extradata = av_mallocz(8 * 4 + FF_INPUT_BUFFER_PADDING_SIZE))) { |
| 234 | av_log(avctx, AV_LOG_ERROR, "Failed to allocate memory for extradata.\n"); |
| 235 | return AVERROR(ENOMEM); |
| 236 | } |
| 237 | avctx->extradata_size = 8 * 4; |
| 238 | AV_WB32(avctx->extradata, c->mc_lifetime); |
| 239 | AV_WB32(avctx->extradata + 16, INTERLACED); |
| 240 | |
| 241 | avctx->coded_frame = av_frame_alloc(); |
| 242 | if (!avctx->coded_frame) { |
| 243 | a64multi_close_encoder(avctx); |
| 244 | return AVERROR(ENOMEM); |
| 245 | } |
| 246 | |
| 247 | avctx->coded_frame->pict_type = AV_PICTURE_TYPE_I; |
| 248 | avctx->coded_frame->key_frame = 1; |
| 249 | if (!avctx->codec_tag) |
| 250 | avctx->codec_tag = AV_RL32("a64m"); |
| 251 | |
| 252 | c->next_pts = AV_NOPTS_VALUE; |
| 253 | |
| 254 | return 0; |
| 255 | } |
| 256 | |
| 257 | static void a64_compress_colram(unsigned char *buf, int *charmap, uint8_t *colram) |
| 258 | { |
| 259 | int a; |
| 260 | uint8_t temp; |
| 261 | /* only needs to be done in 5col mode */ |
| 262 | /* XXX could be squeezed to 0x80 bytes */ |
| 263 | for (a = 0; a < 256; a++) { |
| 264 | temp = colram[charmap[a + 0x000]] << 0; |
| 265 | temp |= colram[charmap[a + 0x100]] << 1; |
| 266 | temp |= colram[charmap[a + 0x200]] << 2; |
| 267 | if (a < 0xe8) temp |= colram[charmap[a + 0x300]] << 3; |
| 268 | buf[a] = temp << 2; |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | static int a64multi_encode_frame(AVCodecContext *avctx, AVPacket *pkt, |
| 273 | const AVFrame *pict, int *got_packet) |
| 274 | { |
| 275 | A64Context *c = avctx->priv_data; |
| 276 | AVFrame *const p = avctx->coded_frame; |
| 277 | |
| 278 | int frame; |
| 279 | int x, y; |
| 280 | int b_height; |
| 281 | int b_width; |
| 282 | |
| 283 | int req_size, ret; |
| 284 | uint8_t *buf = NULL; |
| 285 | |
| 286 | int *charmap = c->mc_charmap; |
| 287 | uint8_t *colram = c->mc_colram; |
| 288 | uint8_t *charset = c->mc_charset; |
| 289 | int *meta = c->mc_meta_charset; |
| 290 | int *best_cb = c->mc_best_cb; |
| 291 | |
| 292 | int charset_size = 0x800 * (INTERLACED + 1); |
| 293 | int colram_size = 0x100 * c->mc_use_5col; |
| 294 | int screen_size; |
| 295 | |
| 296 | if(CROP_SCREENS) { |
| 297 | b_height = FFMIN(avctx->height,C64YRES) >> 3; |
| 298 | b_width = FFMIN(avctx->width ,C64XRES) >> 3; |
| 299 | screen_size = b_width * b_height; |
| 300 | } else { |
| 301 | b_height = C64YRES >> 3; |
| 302 | b_width = C64XRES >> 3; |
| 303 | screen_size = 0x400; |
| 304 | } |
| 305 | |
| 306 | /* no data, means end encoding asap */ |
| 307 | if (!pict) { |
| 308 | /* all done, end encoding */ |
| 309 | if (!c->mc_lifetime) return 0; |
| 310 | /* no more frames in queue, prepare to flush remaining frames */ |
| 311 | if (!c->mc_frame_counter) { |
| 312 | c->mc_lifetime = 0; |
| 313 | } |
| 314 | /* still frames in queue so limit lifetime to remaining frames */ |
| 315 | else c->mc_lifetime = c->mc_frame_counter; |
| 316 | /* still new data available */ |
| 317 | } else { |
| 318 | /* fill up mc_meta_charset with data until lifetime exceeds */ |
| 319 | if (c->mc_frame_counter < c->mc_lifetime) { |
| 320 | *p = *pict; |
| 321 | p->pict_type = AV_PICTURE_TYPE_I; |
| 322 | p->key_frame = 1; |
| 323 | to_meta_with_crop(avctx, p, meta + 32000 * c->mc_frame_counter); |
| 324 | c->mc_frame_counter++; |
| 325 | if (c->next_pts == AV_NOPTS_VALUE) |
| 326 | c->next_pts = pict->pts; |
| 327 | /* lifetime is not reached so wait for next frame first */ |
| 328 | return 0; |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | /* lifetime reached so now convert X frames at once */ |
| 333 | if (c->mc_frame_counter == c->mc_lifetime) { |
| 334 | req_size = 0; |
| 335 | /* any frames to encode? */ |
| 336 | if (c->mc_lifetime) { |
| 337 | req_size = charset_size + c->mc_lifetime*(screen_size + colram_size); |
| 338 | if ((ret = ff_alloc_packet2(avctx, pkt, req_size)) < 0) |
| 339 | return ret; |
| 340 | buf = pkt->data; |
| 341 | |
| 342 | /* calc optimal new charset + charmaps */ |
| 343 | avpriv_init_elbg(meta, 32, 1000 * c->mc_lifetime, best_cb, CHARSET_CHARS, 50, charmap, &c->randctx); |
| 344 | avpriv_do_elbg (meta, 32, 1000 * c->mc_lifetime, best_cb, CHARSET_CHARS, 50, charmap, &c->randctx); |
| 345 | |
| 346 | /* create colorram map and a c64 readable charset */ |
| 347 | render_charset(avctx, charset, colram); |
| 348 | |
| 349 | /* copy charset to buf */ |
| 350 | memcpy(buf, charset, charset_size); |
| 351 | |
| 352 | /* advance pointers */ |
| 353 | buf += charset_size; |
| 354 | } |
| 355 | |
| 356 | /* write x frames to buf */ |
| 357 | for (frame = 0; frame < c->mc_lifetime; frame++) { |
| 358 | /* copy charmap to buf. buf is uchar*, charmap is int*, so no memcpy here, sorry */ |
| 359 | for (y = 0; y < b_height; y++) { |
| 360 | for (x = 0; x < b_width; x++) { |
| 361 | buf[y * b_width + x] = charmap[y * b_width + x]; |
| 362 | } |
| 363 | } |
| 364 | /* advance pointers */ |
| 365 | buf += screen_size; |
| 366 | req_size += screen_size; |
| 367 | |
| 368 | /* compress and copy colram to buf */ |
| 369 | if (c->mc_use_5col) { |
| 370 | a64_compress_colram(buf, charmap, colram); |
| 371 | /* advance pointers */ |
| 372 | buf += colram_size; |
| 373 | req_size += colram_size; |
| 374 | } |
| 375 | |
| 376 | /* advance to next charmap */ |
| 377 | charmap += 1000; |
| 378 | } |
| 379 | |
| 380 | AV_WB32(avctx->extradata + 4, c->mc_frame_counter); |
| 381 | AV_WB32(avctx->extradata + 8, charset_size); |
| 382 | AV_WB32(avctx->extradata + 12, screen_size + colram_size); |
| 383 | |
| 384 | /* reset counter */ |
| 385 | c->mc_frame_counter = 0; |
| 386 | |
| 387 | pkt->pts = pkt->dts = c->next_pts; |
| 388 | c->next_pts = AV_NOPTS_VALUE; |
| 389 | |
| 390 | pkt->size = req_size; |
| 391 | pkt->flags |= AV_PKT_FLAG_KEY; |
| 392 | *got_packet = !!req_size; |
| 393 | } |
| 394 | return 0; |
| 395 | } |
| 396 | |
| 397 | #if CONFIG_A64MULTI_ENCODER |
| 398 | AVCodec ff_a64multi_encoder = { |
| 399 | .name = "a64multi", |
| 400 | .long_name = NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64"), |
| 401 | .type = AVMEDIA_TYPE_VIDEO, |
| 402 | .id = AV_CODEC_ID_A64_MULTI, |
| 403 | .priv_data_size = sizeof(A64Context), |
| 404 | .init = a64multi_encode_init, |
| 405 | .encode2 = a64multi_encode_frame, |
| 406 | .close = a64multi_close_encoder, |
| 407 | .pix_fmts = (const enum AVPixelFormat[]) {AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE}, |
| 408 | .capabilities = CODEC_CAP_DELAY, |
| 409 | }; |
| 410 | #endif |
| 411 | #if CONFIG_A64MULTI5_ENCODER |
| 412 | AVCodec ff_a64multi5_encoder = { |
| 413 | .name = "a64multi5", |
| 414 | .long_name = NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64, extended with 5th color (colram)"), |
| 415 | .type = AVMEDIA_TYPE_VIDEO, |
| 416 | .id = AV_CODEC_ID_A64_MULTI5, |
| 417 | .priv_data_size = sizeof(A64Context), |
| 418 | .init = a64multi_encode_init, |
| 419 | .encode2 = a64multi_encode_frame, |
| 420 | .close = a64multi_close_encoder, |
| 421 | .pix_fmts = (const enum AVPixelFormat[]) {AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE}, |
| 422 | .capabilities = CODEC_CAP_DELAY, |
| 423 | }; |
| 424 | #endif |