Libav 0.7.1
|
00001 /* 00002 * a64 video encoder - multicolor modes 00003 * Copyright (c) 2009 Tobias Bindhammer 00004 * 00005 * This file is part of Libav. 00006 * 00007 * Libav is free software; you can redistribute it and/or 00008 * modify it under the terms of the GNU Lesser General Public 00009 * License as published by the Free Software Foundation; either 00010 * version 2.1 of the License, or (at your option) any later version. 00011 * 00012 * Libav is distributed in the hope that it will be useful, 00013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 * Lesser General Public License for more details. 00016 * 00017 * You should have received a copy of the GNU Lesser General Public 00018 * License along with Libav; if not, write to the Free Software 00019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00020 */ 00021 00027 #include "a64enc.h" 00028 #include "a64colors.h" 00029 #include "a64tables.h" 00030 #include "elbg.h" 00031 #include "libavutil/intreadwrite.h" 00032 00033 #define DITHERSTEPS 8 00034 #define CHARSET_CHARS 256 00035 #define INTERLACED 1 00036 #define CROP_SCREENS 1 00037 00038 /* gray gradient */ 00039 static const int mc_colors[5]={0x0,0xb,0xc,0xf,0x1}; 00040 00041 /* other possible gradients - to be tested */ 00042 //static const int mc_colors[5]={0x0,0x8,0xa,0xf,0x7}; 00043 //static const int mc_colors[5]={0x0,0x9,0x8,0xa,0x3}; 00044 00045 static void to_meta_with_crop(AVCodecContext *avctx, AVFrame *p, int *dest) 00046 { 00047 int blockx, blocky, x, y; 00048 int luma = 0; 00049 int height = FFMIN(avctx->height, C64YRES); 00050 int width = FFMIN(avctx->width , C64XRES); 00051 uint8_t *src = p->data[0]; 00052 00053 for (blocky = 0; blocky < C64YRES; blocky += 8) { 00054 for (blockx = 0; blockx < C64XRES; blockx += 8) { 00055 for (y = blocky; y < blocky + 8 && y < C64YRES; y++) { 00056 for (x = blockx; x < blockx + 8 && x < C64XRES; x += 2) { 00057 if(x < width && y < height) { 00058 /* build average over 2 pixels */ 00059 luma = (src[(x + 0 + y * p->linesize[0])] + 00060 src[(x + 1 + y * p->linesize[0])]) / 2; 00061 /* write blocks as linear data now so they are suitable for elbg */ 00062 dest[0] = luma; 00063 } 00064 dest++; 00065 } 00066 } 00067 } 00068 } 00069 } 00070 00071 static void render_charset(AVCodecContext *avctx, uint8_t *charset, 00072 uint8_t *colrammap) 00073 { 00074 A64Context *c = avctx->priv_data; 00075 uint8_t row1, row2; 00076 int charpos, x, y; 00077 int a, b; 00078 uint8_t pix; 00079 int lowdiff, highdiff; 00080 int *best_cb = c->mc_best_cb; 00081 static uint8_t index1[256]; 00082 static uint8_t index2[256]; 00083 static uint8_t dither[256]; 00084 int i; 00085 int distance; 00086 00087 /* generate lookup-tables for dither and index before looping */ 00088 i = 0; 00089 for (a=0; a < 256; a++) { 00090 if(i < c->mc_pal_size -1 && a == c->mc_luma_vals[i + 1]) { 00091 distance = c->mc_luma_vals[i + 1] - c->mc_luma_vals[i]; 00092 for(b = 0; b <= distance; b++) { 00093 dither[c->mc_luma_vals[i] + b] = b * (DITHERSTEPS - 1) / distance; 00094 } 00095 i++; 00096 } 00097 if(i >= c->mc_pal_size - 1) dither[a] = 0; 00098 index1[a] = i; 00099 index2[a] = FFMIN(i + 1, c->mc_pal_size - 1); 00100 } 00101 00102 /* and render charset */ 00103 for (charpos = 0; charpos < CHARSET_CHARS; charpos++) { 00104 lowdiff = 0; 00105 highdiff = 0; 00106 for (y = 0; y < 8; y++) { 00107 row1 = 0; row2 = 0; 00108 for (x = 0; x < 4; x++) { 00109 pix = best_cb[y * 4 + x]; 00110 00111 /* accumulate error for brightest/darkest color */ 00112 if (index1[pix] >= 3) 00113 highdiff += pix - c->mc_luma_vals[3]; 00114 if (index1[pix] < 1) 00115 lowdiff += c->mc_luma_vals[1] - pix; 00116 00117 row1 <<= 2; 00118 00119 if (INTERLACED) { 00120 row2 <<= 2; 00121 if (interlaced_dither_patterns[dither[pix]][(y & 3) * 2 + 0][x & 3]) 00122 row1 |= 3-(index2[pix] & 3); 00123 else 00124 row1 |= 3-(index1[pix] & 3); 00125 00126 if (interlaced_dither_patterns[dither[pix]][(y & 3) * 2 + 1][x & 3]) 00127 row2 |= 3-(index2[pix] & 3); 00128 else 00129 row2 |= 3-(index1[pix] & 3); 00130 } 00131 else { 00132 if (multi_dither_patterns[dither[pix]][(y & 3)][x & 3]) 00133 row1 |= 3-(index2[pix] & 3); 00134 else 00135 row1 |= 3-(index1[pix] & 3); 00136 } 00137 } 00138 charset[y+0x000] = row1; 00139 if (INTERLACED) charset[y+0x800] = row2; 00140 } 00141 /* do we need to adjust pixels? */ 00142 if (highdiff > 0 && lowdiff > 0 && c->mc_use_5col) { 00143 if (lowdiff > highdiff) { 00144 for (x = 0; x < 32; x++) 00145 best_cb[x] = FFMIN(c->mc_luma_vals[3], best_cb[x]); 00146 } else { 00147 for (x = 0; x < 32; x++) 00148 best_cb[x] = FFMAX(c->mc_luma_vals[1], best_cb[x]); 00149 } 00150 charpos--; /* redo now adjusted char */ 00151 /* no adjustment needed, all fine */ 00152 } else { 00153 /* advance pointers */ 00154 best_cb += 32; 00155 charset += 8; 00156 00157 /* remember colorram value */ 00158 colrammap[charpos] = (highdiff > 0); 00159 } 00160 } 00161 } 00162 00163 static av_cold int a64multi_close_encoder(AVCodecContext *avctx) 00164 { 00165 A64Context *c = avctx->priv_data; 00166 av_free(c->mc_meta_charset); 00167 av_free(c->mc_best_cb); 00168 av_free(c->mc_charset); 00169 av_free(c->mc_charmap); 00170 av_free(c->mc_colram); 00171 return 0; 00172 } 00173 00174 static av_cold int a64multi_init_encoder(AVCodecContext *avctx) 00175 { 00176 A64Context *c = avctx->priv_data; 00177 int a; 00178 av_lfg_init(&c->randctx, 1); 00179 00180 if (avctx->global_quality < 1) { 00181 c->mc_lifetime = 4; 00182 } else { 00183 c->mc_lifetime = avctx->global_quality /= FF_QP2LAMBDA; 00184 } 00185 00186 av_log(avctx, AV_LOG_INFO, "charset lifetime set to %d frame(s)\n", c->mc_lifetime); 00187 00188 c->mc_frame_counter = 0; 00189 c->mc_use_5col = avctx->codec->id == CODEC_ID_A64_MULTI5; 00190 c->mc_pal_size = 4 + c->mc_use_5col; 00191 00192 /* precalc luma values for later use */ 00193 for (a = 0; a < c->mc_pal_size; a++) { 00194 c->mc_luma_vals[a]=a64_palette[mc_colors[a]][0] * 0.30 + 00195 a64_palette[mc_colors[a]][1] * 0.59 + 00196 a64_palette[mc_colors[a]][2] * 0.11; 00197 } 00198 00199 if (!(c->mc_meta_charset = av_malloc(32000 * c->mc_lifetime * sizeof(int))) || 00200 !(c->mc_best_cb = av_malloc(CHARSET_CHARS * 32 * sizeof(int))) || 00201 !(c->mc_charmap = av_mallocz(1000 * c->mc_lifetime * sizeof(int))) || 00202 !(c->mc_colram = av_mallocz(CHARSET_CHARS * sizeof(uint8_t))) || 00203 !(c->mc_charset = av_malloc(0x800 * (INTERLACED+1) * sizeof(uint8_t)))) { 00204 av_log(avctx, AV_LOG_ERROR, "Failed to allocate buffer memory.\n"); 00205 return AVERROR(ENOMEM); 00206 } 00207 00208 /* set up extradata */ 00209 if (!(avctx->extradata = av_mallocz(8 * 4 + FF_INPUT_BUFFER_PADDING_SIZE))) { 00210 av_log(avctx, AV_LOG_ERROR, "Failed to allocate memory for extradata.\n"); 00211 return AVERROR(ENOMEM); 00212 } 00213 avctx->extradata_size = 8 * 4; 00214 AV_WB32(avctx->extradata, c->mc_lifetime); 00215 AV_WB32(avctx->extradata + 16, INTERLACED); 00216 00217 avcodec_get_frame_defaults(&c->picture); 00218 avctx->coded_frame = &c->picture; 00219 avctx->coded_frame->pict_type = AV_PICTURE_TYPE_I; 00220 avctx->coded_frame->key_frame = 1; 00221 if (!avctx->codec_tag) 00222 avctx->codec_tag = AV_RL32("a64m"); 00223 00224 return 0; 00225 } 00226 00227 static void a64_compress_colram(unsigned char *buf, int *charmap, uint8_t *colram) 00228 { 00229 int a; 00230 uint8_t temp; 00231 /* only needs to be done in 5col mode */ 00232 /* XXX could be squeezed to 0x80 bytes */ 00233 for (a = 0; a < 256; a++) { 00234 temp = colram[charmap[a + 0x000]] << 0; 00235 temp |= colram[charmap[a + 0x100]] << 1; 00236 temp |= colram[charmap[a + 0x200]] << 2; 00237 if (a < 0xe8) temp |= colram[charmap[a + 0x300]] << 3; 00238 buf[a] = temp << 2; 00239 } 00240 } 00241 00242 static int a64multi_encode_frame(AVCodecContext *avctx, unsigned char *buf, 00243 int buf_size, void *data) 00244 { 00245 A64Context *c = avctx->priv_data; 00246 AVFrame *pict = data; 00247 AVFrame *const p = (AVFrame *) & c->picture; 00248 00249 int frame; 00250 int x, y; 00251 int b_height; 00252 int b_width; 00253 00254 int req_size; 00255 00256 int *charmap = c->mc_charmap; 00257 uint8_t *colram = c->mc_colram; 00258 uint8_t *charset = c->mc_charset; 00259 int *meta = c->mc_meta_charset; 00260 int *best_cb = c->mc_best_cb; 00261 00262 int charset_size = 0x800 * (INTERLACED + 1); 00263 int colram_size = 0x100 * c->mc_use_5col; 00264 int screen_size; 00265 00266 if(CROP_SCREENS) { 00267 b_height = FFMIN(avctx->height,C64YRES) >> 3; 00268 b_width = FFMIN(avctx->width ,C64XRES) >> 3; 00269 screen_size = b_width * b_height; 00270 } else { 00271 b_height = C64YRES >> 3; 00272 b_width = C64XRES >> 3; 00273 screen_size = 0x400; 00274 } 00275 00276 /* no data, means end encoding asap */ 00277 if (!data) { 00278 /* all done, end encoding */ 00279 if (!c->mc_lifetime) return 0; 00280 /* no more frames in queue, prepare to flush remaining frames */ 00281 if (!c->mc_frame_counter) { 00282 c->mc_lifetime = 0; 00283 } 00284 /* still frames in queue so limit lifetime to remaining frames */ 00285 else c->mc_lifetime = c->mc_frame_counter; 00286 /* still new data available */ 00287 } else { 00288 /* fill up mc_meta_charset with data until lifetime exceeds */ 00289 if (c->mc_frame_counter < c->mc_lifetime) { 00290 *p = *pict; 00291 p->pict_type = AV_PICTURE_TYPE_I; 00292 p->key_frame = 1; 00293 to_meta_with_crop(avctx, p, meta + 32000 * c->mc_frame_counter); 00294 c->mc_frame_counter++; 00295 /* lifetime is not reached so wait for next frame first */ 00296 return 0; 00297 } 00298 } 00299 00300 /* lifetime reached so now convert X frames at once */ 00301 if (c->mc_frame_counter == c->mc_lifetime) { 00302 req_size = 0; 00303 /* any frames to encode? */ 00304 if (c->mc_lifetime) { 00305 /* calc optimal new charset + charmaps */ 00306 ff_init_elbg(meta, 32, 1000 * c->mc_lifetime, best_cb, CHARSET_CHARS, 50, charmap, &c->randctx); 00307 ff_do_elbg (meta, 32, 1000 * c->mc_lifetime, best_cb, CHARSET_CHARS, 50, charmap, &c->randctx); 00308 00309 /* create colorram map and a c64 readable charset */ 00310 render_charset(avctx, charset, colram); 00311 00312 /* copy charset to buf */ 00313 memcpy(buf,charset, charset_size); 00314 00315 /* advance pointers */ 00316 buf += charset_size; 00317 charset += charset_size; 00318 req_size += charset_size; 00319 } 00320 /* no charset so clean buf */ 00321 else memset(buf, 0, charset_size); 00322 00323 /* write x frames to buf */ 00324 for (frame = 0; frame < c->mc_lifetime; frame++) { 00325 /* copy charmap to buf. buf is uchar*, charmap is int*, so no memcpy here, sorry */ 00326 for (y = 0; y < b_height; y++) { 00327 for (x = 0; x < b_width; x++) { 00328 buf[y * b_width + x] = charmap[y * b_width + x]; 00329 } 00330 } 00331 /* advance pointers */ 00332 buf += screen_size; 00333 req_size += screen_size; 00334 00335 /* compress and copy colram to buf */ 00336 if (c->mc_use_5col) { 00337 a64_compress_colram(buf, charmap, colram); 00338 /* advance pointers */ 00339 buf += colram_size; 00340 req_size += colram_size; 00341 } 00342 00343 /* advance to next charmap */ 00344 charmap += 1000; 00345 } 00346 00347 AV_WB32(avctx->extradata + 4, c->mc_frame_counter); 00348 AV_WB32(avctx->extradata + 8, charset_size); 00349 AV_WB32(avctx->extradata + 12, screen_size + colram_size); 00350 00351 /* reset counter */ 00352 c->mc_frame_counter = 0; 00353 00354 if (req_size > buf_size) { 00355 av_log(avctx, AV_LOG_ERROR, "buf size too small (need %d, got %d)\n", req_size, buf_size); 00356 return -1; 00357 } 00358 return req_size; 00359 } 00360 return 0; 00361 } 00362 00363 AVCodec ff_a64multi_encoder = { 00364 .name = "a64multi", 00365 .type = AVMEDIA_TYPE_VIDEO, 00366 .id = CODEC_ID_A64_MULTI, 00367 .priv_data_size = sizeof(A64Context), 00368 .init = a64multi_init_encoder, 00369 .encode = a64multi_encode_frame, 00370 .close = a64multi_close_encoder, 00371 .pix_fmts = (const enum PixelFormat[]) {PIX_FMT_GRAY8, PIX_FMT_NONE}, 00372 .long_name = NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64"), 00373 .capabilities = CODEC_CAP_DELAY, 00374 }; 00375 00376 AVCodec ff_a64multi5_encoder = { 00377 .name = "a64multi5", 00378 .type = AVMEDIA_TYPE_VIDEO, 00379 .id = CODEC_ID_A64_MULTI5, 00380 .priv_data_size = sizeof(A64Context), 00381 .init = a64multi_init_encoder, 00382 .encode = a64multi_encode_frame, 00383 .close = a64multi_close_encoder, 00384 .pix_fmts = (const enum PixelFormat[]) {PIX_FMT_GRAY8, PIX_FMT_NONE}, 00385 .long_name = NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64, extended with 5th color (colram)"), 00386 .capabilities = CODEC_CAP_DELAY, 00387 };