2 * a64 video encoder - multicolor modes
3 * Copyright (c) 2009 Tobias Bindhammer
5 * This file is part of Libav.
7 * Libav 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 * Libav 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 Libav; 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
28 #include "a64colors.h"
29 #include "a64tables.h"
32 #include "libavutil/common.h"
33 #include "libavutil/intreadwrite.h"
36 #define CHARSET_CHARS 256
38 #define CROP_SCREENS 1
41 static const int mc_colors
[5]={0x0,0xb,0xc,0xf,0x1};
43 /* other possible gradients - to be tested */
44 //static const int mc_colors[5]={0x0,0x8,0xa,0xf,0x7};
45 //static const int mc_colors[5]={0x0,0x9,0x8,0xa,0x3};
47 static void to_meta_with_crop(AVCodecContext
*avctx
, AVFrame
*p
, int *dest
)
49 int blockx
, blocky
, x
, y
;
51 int height
= FFMIN(avctx
->height
, C64YRES
);
52 int width
= FFMIN(avctx
->width
, C64XRES
);
53 uint8_t *src
= p
->data
[0];
55 for (blocky
= 0; blocky
< C64YRES
; blocky
+= 8) {
56 for (blockx
= 0; blockx
< C64XRES
; blockx
+= 8) {
57 for (y
= blocky
; y
< blocky
+ 8 && y
< C64YRES
; y
++) {
58 for (x
= blockx
; x
< blockx
+ 8 && x
< C64XRES
; x
+= 2) {
59 if(x
< width
&& y
< height
) {
60 /* build average over 2 pixels */
61 luma
= (src
[(x
+ 0 + y
* p
->linesize
[0])] +
62 src
[(x
+ 1 + y
* p
->linesize
[0])]) / 2;
63 /* write blocks as linear data now so they are suitable for elbg */
73 static void render_charset(AVCodecContext
*avctx
, uint8_t *charset
,
76 A64Context
*c
= avctx
->priv_data
;
81 int lowdiff
, highdiff
;
82 int *best_cb
= c
->mc_best_cb
;
83 static uint8_t index1
[256];
84 static uint8_t index2
[256];
85 static uint8_t dither
[256];
89 /* generate lookup-tables for dither and index before looping */
91 for (a
=0; a
< 256; a
++) {
92 if(i
< c
->mc_pal_size
-1 && a
== c
->mc_luma_vals
[i
+ 1]) {
93 distance
= c
->mc_luma_vals
[i
+ 1] - c
->mc_luma_vals
[i
];
94 for(b
= 0; b
<= distance
; b
++) {
95 dither
[c
->mc_luma_vals
[i
] + b
] = b
* (DITHERSTEPS
- 1) / distance
;
99 if(i
>= c
->mc_pal_size
- 1) dither
[a
] = 0;
101 index2
[a
] = FFMIN(i
+ 1, c
->mc_pal_size
- 1);
104 /* and render charset */
105 for (charpos
= 0; charpos
< CHARSET_CHARS
; charpos
++) {
108 for (y
= 0; y
< 8; y
++) {
110 for (x
= 0; x
< 4; x
++) {
111 pix
= best_cb
[y
* 4 + x
];
113 /* accumulate error for brightest/darkest color */
114 if (index1
[pix
] >= 3)
115 highdiff
+= pix
- c
->mc_luma_vals
[3];
117 lowdiff
+= c
->mc_luma_vals
[1] - pix
;
123 if (interlaced_dither_patterns
[dither
[pix
]][(y
& 3) * 2 + 0][x
& 3])
124 row1
|= 3-(index2
[pix
] & 3);
126 row1
|= 3-(index1
[pix
] & 3);
128 if (interlaced_dither_patterns
[dither
[pix
]][(y
& 3) * 2 + 1][x
& 3])
129 row2
|= 3-(index2
[pix
] & 3);
131 row2
|= 3-(index1
[pix
] & 3);
134 if (multi_dither_patterns
[dither
[pix
]][(y
& 3)][x
& 3])
135 row1
|= 3-(index2
[pix
] & 3);
137 row1
|= 3-(index1
[pix
] & 3);
140 charset
[y
+0x000] = row1
;
141 if (INTERLACED
) charset
[y
+0x800] = row2
;
143 /* do we need to adjust pixels? */
144 if (highdiff
> 0 && lowdiff
> 0 && c
->mc_use_5col
) {
145 if (lowdiff
> highdiff
) {
146 for (x
= 0; x
< 32; x
++)
147 best_cb
[x
] = FFMIN(c
->mc_luma_vals
[3], best_cb
[x
]);
149 for (x
= 0; x
< 32; x
++)
150 best_cb
[x
] = FFMAX(c
->mc_luma_vals
[1], best_cb
[x
]);
152 charpos
--; /* redo now adjusted char */
153 /* no adjustment needed, all fine */
155 /* advance pointers */
159 /* remember colorram value */
160 colrammap
[charpos
] = (highdiff
> 0);
165 static av_cold
int a64multi_close_encoder(AVCodecContext
*avctx
)
167 A64Context
*c
= avctx
->priv_data
;
168 av_free(c
->mc_meta_charset
);
169 av_free(c
->mc_best_cb
);
170 av_free(c
->mc_charset
);
171 av_free(c
->mc_charmap
);
172 av_free(c
->mc_colram
);
176 static av_cold
int a64multi_init_encoder(AVCodecContext
*avctx
)
178 A64Context
*c
= avctx
->priv_data
;
180 av_lfg_init(&c
->randctx
, 1);
182 if (avctx
->global_quality
< 1) {
185 c
->mc_lifetime
= avctx
->global_quality
/= FF_QP2LAMBDA
;
188 av_log(avctx
, AV_LOG_INFO
, "charset lifetime set to %d frame(s)\n", c
->mc_lifetime
);
190 c
->mc_frame_counter
= 0;
191 c
->mc_use_5col
= avctx
->codec
->id
== AV_CODEC_ID_A64_MULTI5
;
192 c
->mc_pal_size
= 4 + c
->mc_use_5col
;
194 /* precalc luma values for later use */
195 for (a
= 0; a
< c
->mc_pal_size
; a
++) {
196 c
->mc_luma_vals
[a
]=a64_palette
[mc_colors
[a
]][0] * 0.30 +
197 a64_palette
[mc_colors
[a
]][1] * 0.59 +
198 a64_palette
[mc_colors
[a
]][2] * 0.11;
201 if (!(c
->mc_meta_charset
= av_malloc(32000 * c
->mc_lifetime
* sizeof(int))) ||
202 !(c
->mc_best_cb
= av_malloc(CHARSET_CHARS
* 32 * sizeof(int))) ||
203 !(c
->mc_charmap
= av_mallocz(1000 * c
->mc_lifetime
* sizeof(int))) ||
204 !(c
->mc_colram
= av_mallocz(CHARSET_CHARS
* sizeof(uint8_t))) ||
205 !(c
->mc_charset
= av_malloc(0x800 * (INTERLACED
+1) * sizeof(uint8_t)))) {
206 av_log(avctx
, AV_LOG_ERROR
, "Failed to allocate buffer memory.\n");
207 return AVERROR(ENOMEM
);
210 /* set up extradata */
211 if (!(avctx
->extradata
= av_mallocz(8 * 4 + FF_INPUT_BUFFER_PADDING_SIZE
))) {
212 av_log(avctx
, AV_LOG_ERROR
, "Failed to allocate memory for extradata.\n");
213 return AVERROR(ENOMEM
);
215 avctx
->extradata_size
= 8 * 4;
216 AV_WB32(avctx
->extradata
, c
->mc_lifetime
);
217 AV_WB32(avctx
->extradata
+ 16, INTERLACED
);
219 avcodec_get_frame_defaults(&c
->picture
);
220 avctx
->coded_frame
= &c
->picture
;
221 avctx
->coded_frame
->pict_type
= AV_PICTURE_TYPE_I
;
222 avctx
->coded_frame
->key_frame
= 1;
223 if (!avctx
->codec_tag
)
224 avctx
->codec_tag
= AV_RL32("a64m");
226 c
->next_pts
= AV_NOPTS_VALUE
;
231 static void a64_compress_colram(unsigned char *buf
, int *charmap
, uint8_t *colram
)
235 /* only needs to be done in 5col mode */
236 /* XXX could be squeezed to 0x80 bytes */
237 for (a
= 0; a
< 256; a
++) {
238 temp
= colram
[charmap
[a
+ 0x000]] << 0;
239 temp
|= colram
[charmap
[a
+ 0x100]] << 1;
240 temp
|= colram
[charmap
[a
+ 0x200]] << 2;
241 if (a
< 0xe8) temp
|= colram
[charmap
[a
+ 0x300]] << 3;
246 static int a64multi_encode_frame(AVCodecContext
*avctx
, AVPacket
*pkt
,
247 const AVFrame
*pict
, int *got_packet
)
249 A64Context
*c
= avctx
->priv_data
;
250 AVFrame
*const p
= &c
->picture
;
260 int *charmap
= c
->mc_charmap
;
261 uint8_t *colram
= c
->mc_colram
;
262 uint8_t *charset
= c
->mc_charset
;
263 int *meta
= c
->mc_meta_charset
;
264 int *best_cb
= c
->mc_best_cb
;
266 int charset_size
= 0x800 * (INTERLACED
+ 1);
267 int colram_size
= 0x100 * c
->mc_use_5col
;
271 b_height
= FFMIN(avctx
->height
,C64YRES
) >> 3;
272 b_width
= FFMIN(avctx
->width
,C64XRES
) >> 3;
273 screen_size
= b_width
* b_height
;
275 b_height
= C64YRES
>> 3;
276 b_width
= C64XRES
>> 3;
280 /* no data, means end encoding asap */
282 /* all done, end encoding */
283 if (!c
->mc_lifetime
) return 0;
284 /* no more frames in queue, prepare to flush remaining frames */
285 if (!c
->mc_frame_counter
) {
288 /* still frames in queue so limit lifetime to remaining frames */
289 else c
->mc_lifetime
= c
->mc_frame_counter
;
290 /* still new data available */
292 /* fill up mc_meta_charset with data until lifetime exceeds */
293 if (c
->mc_frame_counter
< c
->mc_lifetime
) {
295 p
->pict_type
= AV_PICTURE_TYPE_I
;
297 to_meta_with_crop(avctx
, p
, meta
+ 32000 * c
->mc_frame_counter
);
298 c
->mc_frame_counter
++;
299 if (c
->next_pts
== AV_NOPTS_VALUE
)
300 c
->next_pts
= pict
->pts
;
301 /* lifetime is not reached so wait for next frame first */
306 /* lifetime reached so now convert X frames at once */
307 if (c
->mc_frame_counter
== c
->mc_lifetime
) {
309 /* any frames to encode? */
310 if (c
->mc_lifetime
) {
311 req_size
= charset_size
+ c
->mc_lifetime
*(screen_size
+ colram_size
);
312 if ((ret
= ff_alloc_packet(pkt
, req_size
)) < 0) {
313 av_log(avctx
, AV_LOG_ERROR
, "Error getting output packet of size %d.\n", req_size
);
318 /* calc optimal new charset + charmaps */
319 ff_init_elbg(meta
, 32, 1000 * c
->mc_lifetime
, best_cb
, CHARSET_CHARS
, 50, charmap
, &c
->randctx
);
320 ff_do_elbg (meta
, 32, 1000 * c
->mc_lifetime
, best_cb
, CHARSET_CHARS
, 50, charmap
, &c
->randctx
);
322 /* create colorram map and a c64 readable charset */
323 render_charset(avctx
, charset
, colram
);
325 /* copy charset to buf */
326 memcpy(buf
, charset
, charset_size
);
328 /* advance pointers */
330 charset
+= charset_size
;
333 /* write x frames to buf */
334 for (frame
= 0; frame
< c
->mc_lifetime
; frame
++) {
335 /* copy charmap to buf. buf is uchar*, charmap is int*, so no memcpy here, sorry */
336 for (y
= 0; y
< b_height
; y
++) {
337 for (x
= 0; x
< b_width
; x
++) {
338 buf
[y
* b_width
+ x
] = charmap
[y
* b_width
+ x
];
341 /* advance pointers */
343 req_size
+= screen_size
;
345 /* compress and copy colram to buf */
346 if (c
->mc_use_5col
) {
347 a64_compress_colram(buf
, charmap
, colram
);
348 /* advance pointers */
350 req_size
+= colram_size
;
353 /* advance to next charmap */
357 AV_WB32(avctx
->extradata
+ 4, c
->mc_frame_counter
);
358 AV_WB32(avctx
->extradata
+ 8, charset_size
);
359 AV_WB32(avctx
->extradata
+ 12, screen_size
+ colram_size
);
362 c
->mc_frame_counter
= 0;
364 pkt
->pts
= pkt
->dts
= c
->next_pts
;
365 c
->next_pts
= AV_NOPTS_VALUE
;
367 pkt
->size
= req_size
;
368 pkt
->flags
|= AV_PKT_FLAG_KEY
;
369 *got_packet
= !!req_size
;
374 AVCodec ff_a64multi_encoder
= {
376 .type
= AVMEDIA_TYPE_VIDEO
,
377 .id
= AV_CODEC_ID_A64_MULTI
,
378 .priv_data_size
= sizeof(A64Context
),
379 .init
= a64multi_init_encoder
,
380 .encode2
= a64multi_encode_frame
,
381 .close
= a64multi_close_encoder
,
382 .pix_fmts
= (const enum AVPixelFormat
[]) {AV_PIX_FMT_GRAY8
, AV_PIX_FMT_NONE
},
383 .long_name
= NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64"),
384 .capabilities
= CODEC_CAP_DELAY
,
387 AVCodec ff_a64multi5_encoder
= {
389 .type
= AVMEDIA_TYPE_VIDEO
,
390 .id
= AV_CODEC_ID_A64_MULTI5
,
391 .priv_data_size
= sizeof(A64Context
),
392 .init
= a64multi_init_encoder
,
393 .encode2
= a64multi_encode_frame
,
394 .close
= a64multi_close_encoder
,
395 .pix_fmts
= (const enum AVPixelFormat
[]) {AV_PIX_FMT_GRAY8
, AV_PIX_FMT_NONE
},
396 .long_name
= NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64, extended with 5th color (colram)"),
397 .capabilities
= CODEC_CAP_DELAY
,