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