2 * a64 video encoder - multicolor modes
3 * Copyright (c) 2009 Tobias Bindhammer
5 * This file is part of FFmpeg.
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.
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.
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
24 * a64 video encoder - multicolor modes
27 #include "a64colors.h"
28 #include "a64tables.h"
31 #include "libavutil/common.h"
32 #include "libavutil/intreadwrite.h"
35 #define CHARSET_CHARS 256
37 #define CROP_SCREENS 1
42 typedef struct A64Context
{
43 /* variables for multicolor modes */
47 unsigned mc_frame_counter
;
57 /* pts of the next packet that will be output */
62 static const int mc_colors
[5]={0x0,0xb,0xc,0xf,0x1};
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};
68 static void to_meta_with_crop(AVCodecContext
*avctx
, AVFrame
*p
, int *dest
)
70 int blockx
, blocky
, x
, y
;
72 int height
= FFMIN(avctx
->height
, C64YRES
);
73 int width
= FFMIN(avctx
->width
, C64XRES
);
74 uint8_t *src
= p
->data
[0];
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 */
94 static void render_charset(AVCodecContext
*avctx
, uint8_t *charset
,
97 A64Context
*c
= avctx
->priv_data
;
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];
110 /* generate lookup-tables for dither and index before looping */
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
;
120 if(i
>= c
->mc_pal_size
- 1) dither
[a
] = 0;
122 index2
[a
] = FFMIN(i
+ 1, c
->mc_pal_size
- 1);
125 /* and render charset */
126 for (charpos
= 0; charpos
< CHARSET_CHARS
; charpos
++) {
129 for (y
= 0; y
< 8; y
++) {
131 for (x
= 0; x
< 4; x
++) {
132 pix
= best_cb
[y
* 4 + x
];
134 /* accumulate error for brightest/darkest color */
135 if (index1
[pix
] >= 3)
136 highdiff
+= pix
- c
->mc_luma_vals
[3];
138 lowdiff
+= c
->mc_luma_vals
[1] - pix
;
144 if (interlaced_dither_patterns
[dither
[pix
]][(y
& 3) * 2 + 0][x
& 3])
145 row1
|= 3-(index2
[pix
] & 3);
147 row1
|= 3-(index1
[pix
] & 3);
149 if (interlaced_dither_patterns
[dither
[pix
]][(y
& 3) * 2 + 1][x
& 3])
150 row2
|= 3-(index2
[pix
] & 3);
152 row2
|= 3-(index1
[pix
] & 3);
155 if (multi_dither_patterns
[dither
[pix
]][(y
& 3)][x
& 3])
156 row1
|= 3-(index2
[pix
] & 3);
158 row1
|= 3-(index1
[pix
] & 3);
161 charset
[y
+0x000] = row1
;
162 if (INTERLACED
) charset
[y
+0x800] = row2
;
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
]);
170 for (x
= 0; x
< 32; x
++)
171 best_cb
[x
] = FFMAX(c
->mc_luma_vals
[1], best_cb
[x
]);
173 charpos
--; /* redo now adjusted char */
174 /* no adjustment needed, all fine */
176 /* advance pointers */
180 /* remember colorram value */
181 colrammap
[charpos
] = (highdiff
> 0);
186 static av_cold
int a64multi_close_encoder(AVCodecContext
*avctx
)
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
);
198 static av_cold
int a64multi_encode_init(AVCodecContext
*avctx
)
200 A64Context
*c
= avctx
->priv_data
;
202 av_lfg_init(&c
->randctx
, 1);
204 if (avctx
->global_quality
< 1) {
207 c
->mc_lifetime
= avctx
->global_quality
/= FF_QP2LAMBDA
;
210 av_log(avctx
, AV_LOG_INFO
, "charset lifetime set to %d frame(s)\n", c
->mc_lifetime
);
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
;
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;
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
);
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
);
237 avctx
->extradata_size
= 8 * 4;
238 AV_WB32(avctx
->extradata
, c
->mc_lifetime
);
239 AV_WB32(avctx
->extradata
+ 16, INTERLACED
);
241 avctx
->coded_frame
= av_frame_alloc();
242 if (!avctx
->coded_frame
) {
243 a64multi_close_encoder(avctx
);
244 return AVERROR(ENOMEM
);
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");
252 c
->next_pts
= AV_NOPTS_VALUE
;
257 static void a64_compress_colram(unsigned char *buf
, int *charmap
, uint8_t *colram
)
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;
272 static int a64multi_encode_frame(AVCodecContext
*avctx
, AVPacket
*pkt
,
273 const AVFrame
*pict
, int *got_packet
)
275 A64Context
*c
= avctx
->priv_data
;
276 AVFrame
*const p
= avctx
->coded_frame
;
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
;
292 int charset_size
= 0x800 * (INTERLACED
+ 1);
293 int colram_size
= 0x100 * c
->mc_use_5col
;
297 b_height
= FFMIN(avctx
->height
,C64YRES
) >> 3;
298 b_width
= FFMIN(avctx
->width
,C64XRES
) >> 3;
299 screen_size
= b_width
* b_height
;
301 b_height
= C64YRES
>> 3;
302 b_width
= C64XRES
>> 3;
306 /* no data, means end encoding asap */
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
) {
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 */
318 /* fill up mc_meta_charset with data until lifetime exceeds */
319 if (c
->mc_frame_counter
< c
->mc_lifetime
) {
321 p
->pict_type
= AV_PICTURE_TYPE_I
;
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 */
332 /* lifetime reached so now convert X frames at once */
333 if (c
->mc_frame_counter
== c
->mc_lifetime
) {
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)
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
);
346 /* create colorram map and a c64 readable charset */
347 render_charset(avctx
, charset
, colram
);
349 /* copy charset to buf */
350 memcpy(buf
, charset
, charset_size
);
352 /* advance pointers */
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
];
364 /* advance pointers */
366 req_size
+= screen_size
;
368 /* compress and copy colram to buf */
369 if (c
->mc_use_5col
) {
370 a64_compress_colram(buf
, charmap
, colram
);
371 /* advance pointers */
373 req_size
+= colram_size
;
376 /* advance to next charmap */
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
);
385 c
->mc_frame_counter
= 0;
387 pkt
->pts
= pkt
->dts
= c
->next_pts
;
388 c
->next_pts
= AV_NOPTS_VALUE
;
390 pkt
->size
= req_size
;
391 pkt
->flags
|= AV_PKT_FLAG_KEY
;
392 *got_packet
= !!req_size
;
397 #if CONFIG_A64MULTI_ENCODER
398 AVCodec ff_a64multi_encoder
= {
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
,
411 #if CONFIG_A64MULTI5_ENCODER
412 AVCodec ff_a64multi5_encoder
= {
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
,