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