Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
6147 | serge | 1 | /* |
2 | * Cryo Interactive Entertainment HNM4 video decoder |
||
3 | * |
||
4 | * Copyright (c) 2012 David Kment |
||
5 | * |
||
6 | * This file is part of FFmpeg. |
||
7 | * |
||
8 | * FFmpeg is free software; you can redistribute it and/or |
||
9 | * modify it under the terms of the GNU Lesser General Public |
||
10 | * License as published by the Free Software Foundation; either |
||
11 | * version 2.1 of the License, or (at your option) any later version. |
||
12 | * |
||
13 | * FFmpeg is distributed in the hope that it will be useful, |
||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
16 | * Lesser General Public License for more details. |
||
17 | * |
||
18 | * You should have received a copy of the GNU Lesser General Public |
||
19 | * License along with FFmpeg; if not, write to the Free Software |
||
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||
21 | */ |
||
22 | |||
23 | #include |
||
24 | |||
25 | #include "libavutil/imgutils.h" |
||
26 | #include "libavutil/internal.h" |
||
27 | #include "libavutil/intreadwrite.h" |
||
28 | #include "libavutil/mem.h" |
||
29 | #include "avcodec.h" |
||
30 | #include "bytestream.h" |
||
31 | #include "internal.h" |
||
32 | |||
33 | #define HNM4_CHUNK_ID_PL 19536 |
||
34 | #define HNM4_CHUNK_ID_IZ 23113 |
||
35 | #define HNM4_CHUNK_ID_IU 21833 |
||
36 | #define HNM4_CHUNK_ID_SD 17491 |
||
37 | |||
38 | typedef struct Hnm4VideoContext { |
||
39 | uint8_t version; |
||
40 | int width; |
||
41 | int height; |
||
42 | uint8_t *current; |
||
43 | uint8_t *previous; |
||
44 | uint8_t *buffer1; |
||
45 | uint8_t *buffer2; |
||
46 | uint8_t *processed; |
||
47 | uint32_t palette[256]; |
||
48 | } Hnm4VideoContext; |
||
49 | |||
50 | static int getbit(GetByteContext *gb, uint32_t *bitbuf, int *bits) |
||
51 | { |
||
52 | int ret; |
||
53 | |||
54 | if (!*bits) { |
||
55 | *bitbuf = bytestream2_get_le32(gb); |
||
56 | *bits = 32; |
||
57 | } |
||
58 | |||
59 | ret = *bitbuf >> 31; |
||
60 | *bitbuf <<= 1; |
||
61 | (*bits)--; |
||
62 | |||
63 | return ret; |
||
64 | } |
||
65 | |||
66 | static void unpack_intraframe(AVCodecContext *avctx, uint8_t *src, |
||
67 | uint32_t size) |
||
68 | { |
||
69 | Hnm4VideoContext *hnm = avctx->priv_data; |
||
70 | GetByteContext gb; |
||
71 | uint32_t bitbuf = 0, writeoffset = 0, count = 0; |
||
72 | uint16_t word; |
||
73 | int32_t offset; |
||
74 | int bits = 0; |
||
75 | |||
76 | bytestream2_init(&gb, src, size); |
||
77 | |||
78 | while (bytestream2_tell(&gb) < size) { |
||
79 | if (getbit(&gb, &bitbuf, &bits)) { |
||
80 | if (writeoffset >= hnm->width * hnm->height) { |
||
81 | av_log(avctx, AV_LOG_ERROR, |
||
82 | "Attempting to write out of bounds\n"); |
||
83 | break; |
||
84 | } |
||
85 | hnm->current[writeoffset++] = bytestream2_get_byte(&gb); |
||
86 | } else { |
||
87 | if (getbit(&gb, &bitbuf, &bits)) { |
||
88 | word = bytestream2_get_le16(&gb); |
||
89 | count = word & 0x07; |
||
90 | offset = (word >> 3) - 0x2000; |
||
91 | if (!count) |
||
92 | count = bytestream2_get_byte(&gb); |
||
93 | if (!count) |
||
94 | return; |
||
95 | } else { |
||
96 | count = getbit(&gb, &bitbuf, &bits) * 2; |
||
97 | count += getbit(&gb, &bitbuf, &bits); |
||
98 | offset = bytestream2_get_byte(&gb) - 0x0100; |
||
99 | } |
||
100 | count += 2; |
||
101 | offset += writeoffset; |
||
102 | if (offset < 0 || offset + count >= hnm->width * hnm->height) { |
||
103 | av_log(avctx, AV_LOG_ERROR, "Attempting to read out of bounds\n"); |
||
104 | break; |
||
105 | } else if (writeoffset + count >= hnm->width * hnm->height) { |
||
106 | av_log(avctx, AV_LOG_ERROR, |
||
107 | "Attempting to write out of bounds\n"); |
||
108 | break; |
||
109 | } |
||
110 | while (count--) { |
||
111 | hnm->current[writeoffset++] = hnm->current[offset++]; |
||
112 | } |
||
113 | } |
||
114 | } |
||
115 | } |
||
116 | |||
117 | static void postprocess_current_frame(AVCodecContext *avctx) |
||
118 | { |
||
119 | Hnm4VideoContext *hnm = avctx->priv_data; |
||
120 | uint32_t x, y, src_x, src_y; |
||
121 | |||
122 | for (y = 0; y < hnm->height; y++) { |
||
123 | src_y = y - (y % 2); |
||
124 | src_x = src_y * hnm->width + (y % 2); |
||
125 | for (x = 0; x < hnm->width; x++) { |
||
126 | hnm->processed[(y * hnm->width) + x] = hnm->current[src_x]; |
||
127 | src_x += 2; |
||
128 | } |
||
129 | } |
||
130 | } |
||
131 | |||
132 | static void copy_processed_frame(AVCodecContext *avctx, AVFrame *frame) |
||
133 | { |
||
134 | Hnm4VideoContext *hnm = avctx->priv_data; |
||
135 | uint8_t *src = hnm->processed; |
||
136 | uint8_t *dst = frame->data[0]; |
||
137 | int y; |
||
138 | |||
139 | for (y = 0; y < hnm->height; y++) { |
||
140 | memcpy(dst, src, hnm->width); |
||
141 | src += hnm->width; |
||
142 | dst += frame->linesize[0]; |
||
143 | } |
||
144 | } |
||
145 | |||
146 | static void decode_interframe_v4(AVCodecContext *avctx, uint8_t *src, uint32_t size) |
||
147 | { |
||
148 | Hnm4VideoContext *hnm = avctx->priv_data; |
||
149 | GetByteContext gb; |
||
150 | uint32_t writeoffset = 0; |
||
151 | int count, left, offset; |
||
152 | uint8_t tag, previous, backline, backward, swap; |
||
153 | |||
154 | bytestream2_init(&gb, src, size); |
||
155 | |||
156 | while (bytestream2_tell(&gb) < size) { |
||
157 | count = bytestream2_peek_byte(&gb) & 0x1F; |
||
158 | if (count == 0) { |
||
159 | tag = bytestream2_get_byte(&gb) & 0xE0; |
||
160 | tag = tag >> 5; |
||
161 | |||
162 | if (tag == 0) { |
||
163 | if (writeoffset + 2 > hnm->width * hnm->height) { |
||
164 | av_log(avctx, AV_LOG_ERROR, "writeoffset out of bounds\n"); |
||
165 | break; |
||
166 | } |
||
167 | hnm->current[writeoffset++] = bytestream2_get_byte(&gb); |
||
168 | hnm->current[writeoffset++] = bytestream2_get_byte(&gb); |
||
169 | } else if (tag == 1) { |
||
170 | writeoffset += bytestream2_get_byte(&gb) * 2; |
||
171 | } else if (tag == 2) { |
||
172 | count = bytestream2_get_le16(&gb); |
||
173 | count *= 2; |
||
174 | writeoffset += count; |
||
175 | } else if (tag == 3) { |
||
176 | count = bytestream2_get_byte(&gb) * 2; |
||
177 | if (writeoffset + count > hnm->width * hnm->height) { |
||
178 | av_log(avctx, AV_LOG_ERROR, "writeoffset out of bounds\n"); |
||
179 | break; |
||
180 | } |
||
181 | while (count > 0) { |
||
182 | hnm->current[writeoffset++] = bytestream2_peek_byte(&gb); |
||
183 | count--; |
||
184 | } |
||
185 | bytestream2_skip(&gb, 1); |
||
186 | } else { |
||
187 | break; |
||
188 | } |
||
189 | if (writeoffset > hnm->width * hnm->height) { |
||
190 | av_log(avctx, AV_LOG_ERROR, "writeoffset out of bounds\n"); |
||
191 | break; |
||
192 | } |
||
193 | } else { |
||
194 | previous = bytestream2_peek_byte(&gb) & 0x20; |
||
195 | backline = bytestream2_peek_byte(&gb) & 0x40; |
||
196 | backward = bytestream2_peek_byte(&gb) & 0x80; |
||
197 | bytestream2_skip(&gb, 1); |
||
198 | swap = bytestream2_peek_byte(&gb) & 0x01; |
||
199 | offset = bytestream2_get_le16(&gb); |
||
200 | offset = (offset >> 1) & 0x7FFF; |
||
201 | offset = writeoffset + (offset * 2) - 0x8000; |
||
202 | |||
203 | left = count; |
||
204 | |||
205 | if (!backward && offset + 2*count > hnm->width * hnm->height) { |
||
206 | av_log(avctx, AV_LOG_ERROR, "Attempting to read out of bounds\n"); |
||
207 | break; |
||
208 | } else if (backward && offset + 1 >= hnm->width * hnm->height) { |
||
209 | av_log(avctx, AV_LOG_ERROR, "Attempting to read out of bounds\n"); |
||
210 | break; |
||
211 | } else if (writeoffset + 2*count > hnm->width * hnm->height) { |
||
212 | av_log(avctx, AV_LOG_ERROR, |
||
213 | "Attempting to write out of bounds\n"); |
||
214 | break; |
||
215 | } |
||
216 | if(backward) { |
||
217 | if (offset < (!!backline)*(2 * hnm->width - 1) + 2*(left-1)) { |
||
218 | av_log(avctx, AV_LOG_ERROR, "Attempting to read out of bounds\n"); |
||
219 | break; |
||
220 | } |
||
221 | } else { |
||
222 | if (offset < (!!backline)*(2 * hnm->width - 1)) { |
||
223 | av_log(avctx, AV_LOG_ERROR, "Attempting to read out of bounds\n"); |
||
224 | break; |
||
225 | } |
||
226 | } |
||
227 | |||
228 | if (previous) { |
||
229 | while (left > 0) { |
||
230 | if (backline) { |
||
231 | hnm->current[writeoffset++] = hnm->previous[offset - (2 * hnm->width) + 1]; |
||
232 | hnm->current[writeoffset++] = hnm->previous[offset++]; |
||
233 | offset++; |
||
234 | } else { |
||
235 | hnm->current[writeoffset++] = hnm->previous[offset++]; |
||
236 | hnm->current[writeoffset++] = hnm->previous[offset++]; |
||
237 | } |
||
238 | if (backward) |
||
239 | offset -= 4; |
||
240 | left--; |
||
241 | } |
||
242 | } else { |
||
243 | while (left > 0) { |
||
244 | if (backline) { |
||
245 | hnm->current[writeoffset++] = hnm->current[offset - (2 * hnm->width) + 1]; |
||
246 | hnm->current[writeoffset++] = hnm->current[offset++]; |
||
247 | offset++; |
||
248 | } else { |
||
249 | hnm->current[writeoffset++] = hnm->current[offset++]; |
||
250 | hnm->current[writeoffset++] = hnm->current[offset++]; |
||
251 | } |
||
252 | if (backward) |
||
253 | offset -= 4; |
||
254 | left--; |
||
255 | } |
||
256 | } |
||
257 | |||
258 | if (swap) { |
||
259 | left = count; |
||
260 | writeoffset -= count * 2; |
||
261 | while (left > 0) { |
||
262 | swap = hnm->current[writeoffset]; |
||
263 | hnm->current[writeoffset] = hnm->current[writeoffset + 1]; |
||
264 | hnm->current[writeoffset + 1] = swap; |
||
265 | left--; |
||
266 | writeoffset += 2; |
||
267 | } |
||
268 | } |
||
269 | } |
||
270 | } |
||
271 | } |
||
272 | |||
273 | static void decode_interframe_v4a(AVCodecContext *avctx, uint8_t *src, |
||
274 | uint32_t size) |
||
275 | { |
||
276 | Hnm4VideoContext *hnm = avctx->priv_data; |
||
277 | GetByteContext gb; |
||
278 | uint32_t writeoffset = 0, offset; |
||
279 | uint8_t tag, count, previous, delta; |
||
280 | |||
281 | bytestream2_init(&gb, src, size); |
||
282 | |||
283 | while (bytestream2_tell(&gb) < size) { |
||
284 | count = bytestream2_peek_byte(&gb) & 0x3F; |
||
285 | if (count == 0) { |
||
286 | tag = bytestream2_get_byte(&gb) & 0xC0; |
||
287 | tag = tag >> 6; |
||
288 | if (tag == 0) { |
||
289 | writeoffset += bytestream2_get_byte(&gb); |
||
290 | } else if (tag == 1) { |
||
291 | if (writeoffset + hnm->width >= hnm->width * hnm->height) { |
||
292 | av_log(avctx, AV_LOG_ERROR, "writeoffset out of bounds\n"); |
||
293 | break; |
||
294 | } |
||
295 | hnm->current[writeoffset] = bytestream2_get_byte(&gb); |
||
296 | hnm->current[writeoffset + hnm->width] = bytestream2_get_byte(&gb); |
||
297 | writeoffset++; |
||
298 | } else if (tag == 2) { |
||
299 | writeoffset += hnm->width; |
||
300 | } else if (tag == 3) { |
||
301 | break; |
||
302 | } |
||
303 | if (writeoffset > hnm->width * hnm->height) { |
||
304 | av_log(avctx, AV_LOG_ERROR, "writeoffset out of bounds\n"); |
||
305 | break; |
||
306 | } |
||
307 | } else { |
||
308 | delta = bytestream2_peek_byte(&gb) & 0x80; |
||
309 | previous = bytestream2_peek_byte(&gb) & 0x40; |
||
310 | bytestream2_skip(&gb, 1); |
||
311 | |||
312 | offset = writeoffset; |
||
313 | offset += bytestream2_get_le16(&gb); |
||
314 | |||
315 | if (delta) { |
||
316 | if (offset < 0x10000) { |
||
317 | av_log(avctx, AV_LOG_ERROR, "Attempting to read out of bounds\n"); |
||
318 | break; |
||
319 | } |
||
320 | offset -= 0x10000; |
||
321 | } |
||
322 | |||
323 | if (offset + hnm->width + count >= hnm->width * hnm->height) { |
||
324 | av_log(avctx, AV_LOG_ERROR, "Attempting to read out of bounds\n"); |
||
325 | break; |
||
326 | } else if (writeoffset + hnm->width + count >= hnm->width * hnm->height) { |
||
327 | av_log(avctx, AV_LOG_ERROR, "Attempting to write out of bounds\n"); |
||
328 | break; |
||
329 | } |
||
330 | |||
331 | if (previous) { |
||
332 | while (count > 0) { |
||
333 | hnm->current[writeoffset] = hnm->previous[offset]; |
||
334 | hnm->current[writeoffset + hnm->width] = hnm->previous[offset + hnm->width]; |
||
335 | writeoffset++; |
||
336 | offset++; |
||
337 | count--; |
||
338 | } |
||
339 | } else { |
||
340 | while (count > 0) { |
||
341 | hnm->current[writeoffset] = hnm->current[offset]; |
||
342 | hnm->current[writeoffset + hnm->width] = hnm->current[offset + hnm->width]; |
||
343 | writeoffset++; |
||
344 | offset++; |
||
345 | count--; |
||
346 | } |
||
347 | } |
||
348 | } |
||
349 | } |
||
350 | } |
||
351 | |||
352 | static void hnm_update_palette(AVCodecContext *avctx, uint8_t *src, |
||
353 | uint32_t size) |
||
354 | { |
||
355 | Hnm4VideoContext *hnm = avctx->priv_data; |
||
356 | GetByteContext gb; |
||
357 | uint8_t start, writeoffset; |
||
358 | uint16_t count; |
||
359 | int eight_bit_colors; |
||
360 | |||
361 | eight_bit_colors = src[7] & 0x80 && hnm->version == 0x4a; |
||
362 | |||
363 | // skip first 8 bytes |
||
364 | bytestream2_init(&gb, src + 8, size - 8); |
||
365 | |||
366 | while (bytestream2_tell(&gb) < size - 8) { |
||
367 | start = bytestream2_get_byte(&gb); |
||
368 | count = bytestream2_get_byte(&gb); |
||
369 | if (start == 255 && count == 255) |
||
370 | break; |
||
371 | if (count == 0) |
||
372 | count = 256; |
||
373 | writeoffset = start; |
||
374 | while (count > 0) { |
||
375 | hnm->palette[writeoffset] = bytestream2_get_be24(&gb); |
||
376 | if (!eight_bit_colors) |
||
377 | hnm->palette[writeoffset] <<= 2; |
||
378 | count--; |
||
379 | writeoffset++; |
||
380 | } |
||
381 | } |
||
382 | } |
||
383 | |||
384 | static void hnm_flip_buffers(Hnm4VideoContext *hnm) |
||
385 | { |
||
386 | uint8_t *temp; |
||
387 | |||
388 | temp = hnm->current; |
||
389 | hnm->current = hnm->previous; |
||
390 | hnm->previous = temp; |
||
391 | } |
||
392 | |||
393 | static int hnm_decode_frame(AVCodecContext *avctx, void *data, |
||
394 | int *got_frame, AVPacket *avpkt) |
||
395 | { |
||
396 | AVFrame *frame = data; |
||
397 | Hnm4VideoContext *hnm = avctx->priv_data; |
||
398 | int ret; |
||
399 | uint16_t chunk_id; |
||
400 | |||
401 | if (avpkt->size < 8) { |
||
402 | av_log(avctx, AV_LOG_ERROR, "packet too small\n"); |
||
403 | return AVERROR_INVALIDDATA; |
||
404 | } |
||
405 | |||
406 | chunk_id = AV_RL16(avpkt->data + 4); |
||
407 | |||
408 | if (chunk_id == HNM4_CHUNK_ID_PL) { |
||
409 | hnm_update_palette(avctx, avpkt->data, avpkt->size); |
||
410 | } else if (chunk_id == HNM4_CHUNK_ID_IZ) { |
||
411 | if (avpkt->size < 12) { |
||
412 | av_log(avctx, AV_LOG_ERROR, "packet too small\n"); |
||
413 | return AVERROR_INVALIDDATA; |
||
414 | } |
||
415 | if ((ret = ff_get_buffer(avctx, frame, 0)) < 0) |
||
416 | return ret; |
||
417 | |||
418 | unpack_intraframe(avctx, avpkt->data + 12, avpkt->size - 12); |
||
419 | memcpy(hnm->previous, hnm->current, hnm->width * hnm->height); |
||
420 | if (hnm->version == 0x4a) |
||
421 | memcpy(hnm->processed, hnm->current, hnm->width * hnm->height); |
||
422 | else |
||
423 | postprocess_current_frame(avctx); |
||
424 | copy_processed_frame(avctx, frame); |
||
425 | frame->pict_type = AV_PICTURE_TYPE_I; |
||
426 | frame->key_frame = 1; |
||
427 | memcpy(frame->data[1], hnm->palette, 256 * 4); |
||
428 | *got_frame = 1; |
||
429 | } else if (chunk_id == HNM4_CHUNK_ID_IU) { |
||
430 | if ((ret = ff_get_buffer(avctx, frame, 0)) < 0) |
||
431 | return ret; |
||
432 | |||
433 | if (hnm->version == 0x4a) { |
||
434 | decode_interframe_v4a(avctx, avpkt->data + 8, avpkt->size - 8); |
||
435 | memcpy(hnm->processed, hnm->current, hnm->width * hnm->height); |
||
436 | } else { |
||
437 | decode_interframe_v4(avctx, avpkt->data + 8, avpkt->size - 8); |
||
438 | postprocess_current_frame(avctx); |
||
439 | } |
||
440 | copy_processed_frame(avctx, frame); |
||
441 | frame->pict_type = AV_PICTURE_TYPE_P; |
||
442 | frame->key_frame = 0; |
||
443 | memcpy(frame->data[1], hnm->palette, 256 * 4); |
||
444 | *got_frame = 1; |
||
445 | hnm_flip_buffers(hnm); |
||
446 | } else { |
||
447 | av_log(avctx, AV_LOG_ERROR, "invalid chunk id: %d\n", chunk_id); |
||
448 | return AVERROR_INVALIDDATA; |
||
449 | } |
||
450 | |||
451 | return avpkt->size; |
||
452 | } |
||
453 | |||
454 | static av_cold int hnm_decode_init(AVCodecContext *avctx) |
||
455 | { |
||
456 | Hnm4VideoContext *hnm = avctx->priv_data; |
||
457 | int ret; |
||
458 | |||
459 | if (avctx->extradata_size < 1) { |
||
460 | av_log(avctx, AV_LOG_ERROR, |
||
461 | "Extradata missing, decoder requires version number\n"); |
||
462 | return AVERROR_INVALIDDATA; |
||
463 | } |
||
464 | |||
465 | ret = av_image_check_size(avctx->width, avctx->height, 0, avctx); |
||
466 | if (ret < 0) |
||
467 | return ret; |
||
468 | |||
469 | hnm->version = avctx->extradata[0]; |
||
470 | avctx->pix_fmt = AV_PIX_FMT_PAL8; |
||
471 | hnm->width = avctx->width; |
||
472 | hnm->height = avctx->height; |
||
473 | hnm->buffer1 = av_mallocz(avctx->width * avctx->height); |
||
474 | hnm->buffer2 = av_mallocz(avctx->width * avctx->height); |
||
475 | hnm->processed = av_mallocz(avctx->width * avctx->height); |
||
476 | |||
477 | if ( !hnm->buffer1 || !hnm->buffer2 || !hnm->processed |
||
478 | || avctx->width * avctx->height == 0 |
||
479 | || avctx->height % 2) { |
||
480 | av_log(avctx, AV_LOG_ERROR, "av_mallocz() failed\n"); |
||
481 | av_freep(&hnm->buffer1); |
||
482 | av_freep(&hnm->buffer2); |
||
483 | av_freep(&hnm->processed); |
||
484 | return AVERROR(ENOMEM); |
||
485 | } |
||
486 | |||
487 | hnm->current = hnm->buffer1; |
||
488 | hnm->previous = hnm->buffer2; |
||
489 | |||
490 | return 0; |
||
491 | } |
||
492 | |||
493 | static av_cold int hnm_decode_end(AVCodecContext *avctx) |
||
494 | { |
||
495 | Hnm4VideoContext *hnm = avctx->priv_data; |
||
496 | |||
497 | av_freep(&hnm->buffer1); |
||
498 | av_freep(&hnm->buffer2); |
||
499 | av_freep(&hnm->processed); |
||
500 | |||
501 | return 0; |
||
502 | } |
||
503 | |||
504 | AVCodec ff_hnm4_video_decoder = { |
||
505 | .name = "hnm4video", |
||
506 | .long_name = NULL_IF_CONFIG_SMALL("HNM 4 video"), |
||
507 | .type = AVMEDIA_TYPE_VIDEO, |
||
508 | .id = AV_CODEC_ID_HNM4_VIDEO, |
||
509 | .priv_data_size = sizeof(Hnm4VideoContext), |
||
510 | .init = hnm_decode_init, |
||
511 | .close = hnm_decode_end, |
||
512 | .decode = hnm_decode_frame, |
||
513 | .capabilities = AV_CODEC_CAP_DR1, |
||
514 | };>>>>>>=><=>>>>>>>>>>>>=><=> |