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