Coverage Report

Created: 2025-08-28 07:12

/src/ffmpeg/libavcodec/hapdec.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Vidvox Hap decoder
3
 * Copyright (C) 2015 Vittorio Giovara <vittorio.giovara@gmail.com>
4
 * Copyright (C) 2015 Tom Butterworth <bangnoise@gmail.com>
5
 *
6
 * HapQA and HAPAlphaOnly added by Jokyo Images
7
 *
8
 * This file is part of FFmpeg.
9
 *
10
 * FFmpeg is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public
12
 * License as published by the Free Software Foundation; either
13
 * version 2.1 of the License, or (at your option) any later version.
14
 *
15
 * FFmpeg is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
 * Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public
21
 * License along with FFmpeg; if not, write to the Free Software
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23
 */
24
25
/**
26
 * @file
27
 * Hap decoder
28
 *
29
 * Fourcc: Hap1, Hap5, HapY, HapA, HapM
30
 *
31
 * https://github.com/Vidvox/hap/blob/master/documentation/HapVideoDRAFT.md
32
 */
33
34
#include <stdint.h>
35
36
#include "libavutil/imgutils.h"
37
#include "libavutil/mem.h"
38
39
#include "avcodec.h"
40
#include "bytestream.h"
41
#include "codec_internal.h"
42
#include "hap.h"
43
#include "snappy.h"
44
#include "texturedsp.h"
45
#include "thread.h"
46
47
static int hap_parse_decode_instructions(HapContext *ctx, int size)
48
3.69k
{
49
3.69k
    GetByteContext *gbc = &ctx->gbc;
50
3.69k
    int section_size;
51
3.69k
    enum HapSectionType section_type;
52
3.69k
    int is_first_table = 1, had_offsets = 0, had_compressors = 0, had_sizes = 0;
53
3.69k
    int i, ret;
54
55
16.8k
    while (size > 0) {
56
14.7k
        int stream_remaining = bytestream2_get_bytes_left(gbc);
57
14.7k
        ret = ff_hap_parse_section_header(gbc, &section_size, &section_type);
58
14.7k
        if (ret != 0)
59
769
            return ret;
60
61
13.9k
        size -= stream_remaining - bytestream2_get_bytes_left(gbc);
62
63
13.9k
        switch (section_type) {
64
2.14k
            case HAP_ST_COMPRESSOR_TABLE:
65
2.14k
                ret = ff_hap_set_chunk_count(ctx, section_size, is_first_table);
66
2.14k
                if (ret != 0)
67
249
                    return ret;
68
898k
                for (i = 0; i < section_size; i++) {
69
896k
                    ctx->chunks[i].compressor = bytestream2_get_byte(gbc) << 4;
70
896k
                }
71
1.89k
                had_compressors = 1;
72
1.89k
                is_first_table = 0;
73
1.89k
                break;
74
2.91k
            case HAP_ST_SIZE_TABLE:
75
2.91k
                ret = ff_hap_set_chunk_count(ctx, section_size / 4, is_first_table);
76
2.91k
                if (ret != 0)
77
416
                    return ret;
78
98.5k
                for (i = 0; i < section_size / 4; i++) {
79
96.0k
                    ctx->chunks[i].compressed_size = bytestream2_get_le32(gbc);
80
96.0k
                }
81
2.50k
                had_sizes = 1;
82
2.50k
                is_first_table = 0;
83
2.50k
                break;
84
1.94k
            case HAP_ST_OFFSET_TABLE:
85
1.94k
                ret = ff_hap_set_chunk_count(ctx, section_size / 4, is_first_table);
86
1.94k
                if (ret != 0)
87
197
                    return ret;
88
423k
                for (i = 0; i < section_size / 4; i++) {
89
421k
                    ctx->chunks[i].compressed_offset = bytestream2_get_le32(gbc);
90
421k
                }
91
1.74k
                had_offsets = 1;
92
1.74k
                is_first_table = 0;
93
1.74k
                break;
94
6.97k
            default:
95
6.97k
                break;
96
13.9k
        }
97
13.1k
        size -= section_size;
98
13.1k
    }
99
100
2.06k
    if (!had_sizes || !had_compressors)
101
708
        return AVERROR_INVALIDDATA;
102
103
    /* The offsets table is optional. If not present than calculate offsets by
104
     * summing the sizes of preceding chunks. */
105
1.35k
    if (!had_offsets) {
106
1.14k
        size_t running_size = 0;
107
7.19k
        for (i = 0; i < ctx->chunk_count; i++) {
108
6.27k
            ctx->chunks[i].compressed_offset = running_size;
109
6.27k
            if (ctx->chunks[i].compressed_size > UINT32_MAX - running_size)
110
229
                return AVERROR_INVALIDDATA;
111
6.04k
            running_size += ctx->chunks[i].compressed_size;
112
6.04k
        }
113
1.14k
    }
114
115
1.12k
    return 0;
116
1.35k
}
117
118
static int hap_can_use_tex_in_place(HapContext *ctx)
119
88.7k
{
120
88.7k
    int i;
121
88.7k
    size_t running_offset = 0;
122
90.0k
    for (i = 0; i < ctx->chunk_count; i++) {
123
88.7k
        if (ctx->chunks[i].compressed_offset != running_offset
124
88.7k
            || ctx->chunks[i].compressor != HAP_COMP_NONE)
125
87.5k
            return 0;
126
1.24k
        running_offset += ctx->chunks[i].compressed_size;
127
1.24k
    }
128
1.24k
    return 1;
129
88.7k
}
130
131
static int hap_parse_frame_header(AVCodecContext *avctx)
132
445k
{
133
445k
    HapContext *ctx = avctx->priv_data;
134
445k
    GetByteContext *gbc = &ctx->gbc;
135
445k
    int section_size;
136
445k
    enum HapSectionType section_type;
137
445k
    const char *compressorstr;
138
445k
    int i, ret;
139
140
445k
    ret = ff_hap_parse_section_header(gbc, &ctx->texture_section_size, &section_type);
141
445k
    if (ret != 0)
142
341k
        return ret;
143
144
103k
    if ((avctx->codec_tag == MKTAG('H','a','p','1') && (section_type & 0x0F) != HAP_FMT_RGBDXT1) ||
145
103k
        (avctx->codec_tag == MKTAG('H','a','p','5') && (section_type & 0x0F) != HAP_FMT_RGBADXT5) ||
146
103k
        (avctx->codec_tag == MKTAG('H','a','p','Y') && (section_type & 0x0F) != HAP_FMT_YCOCGDXT5) ||
147
103k
        (avctx->codec_tag == MKTAG('H','a','p','A') && (section_type & 0x0F) != HAP_FMT_RGTC1) ||
148
103k
        ((avctx->codec_tag == MKTAG('H','a','p','M') && (section_type & 0x0F) != HAP_FMT_RGTC1) &&
149
96.2k
                                                        (section_type & 0x0F) != HAP_FMT_YCOCGDXT5)) {
150
7.65k
        av_log(avctx, AV_LOG_ERROR,
151
7.65k
               "Invalid texture format %#04x.\n", section_type & 0x0F);
152
7.65k
        return AVERROR_INVALIDDATA;
153
7.65k
    }
154
155
96.0k
    switch (section_type & 0xF0) {
156
1.44k
        case HAP_COMP_NONE:
157
91.3k
        case HAP_COMP_SNAPPY:
158
91.3k
            ret = ff_hap_set_chunk_count(ctx, 1, 1);
159
91.3k
            if (ret == 0) {
160
91.3k
                ctx->chunks[0].compressor = section_type & 0xF0;
161
91.3k
                ctx->chunks[0].compressed_offset = 0;
162
91.3k
                ctx->chunks[0].compressed_size = ctx->texture_section_size;
163
91.3k
            }
164
91.3k
            if (ctx->chunks[0].compressor == HAP_COMP_NONE) {
165
1.44k
                compressorstr = "none";
166
89.9k
            } else {
167
89.9k
                compressorstr = "snappy";
168
89.9k
            }
169
91.3k
            break;
170
4.20k
        case HAP_COMP_COMPLEX:
171
4.20k
            ret = ff_hap_parse_section_header(gbc, &section_size, &section_type);
172
4.20k
            if (ret == 0 && section_type != HAP_ST_DECODE_INSTRUCTIONS)
173
239
                ret = AVERROR_INVALIDDATA;
174
4.20k
            if (ret == 0)
175
3.69k
                ret = hap_parse_decode_instructions(ctx, section_size);
176
4.20k
            compressorstr = "complex";
177
4.20k
            break;
178
446
        default:
179
446
            ret = AVERROR_INVALIDDATA;
180
446
            break;
181
96.0k
    }
182
183
96.0k
    if (ret != 0)
184
3.51k
        return ret;
185
186
    /* Check the frame is valid and read the uncompressed chunk sizes */
187
92.4k
    ctx->tex_size = 0;
188
183k
    for (i = 0; i < ctx->chunk_count; i++) {
189
92.5k
        HapChunk *chunk = &ctx->chunks[i];
190
191
        /* Check the compressed buffer is valid */
192
92.5k
        if (chunk->compressed_offset + (uint64_t)chunk->compressed_size > bytestream2_get_bytes_left(gbc))
193
341
            return AVERROR_INVALIDDATA;
194
195
        /* Chunks are unpacked sequentially, ctx->tex_size is the uncompressed
196
         * size thus far */
197
92.2k
        chunk->uncompressed_offset = ctx->tex_size;
198
199
        /* Fill out uncompressed size */
200
92.2k
        if (chunk->compressor == HAP_COMP_SNAPPY) {
201
90.4k
            GetByteContext gbc_tmp;
202
90.4k
            int64_t uncompressed_size;
203
90.4k
            bytestream2_init(&gbc_tmp, gbc->buffer + chunk->compressed_offset,
204
90.4k
                             chunk->compressed_size);
205
90.4k
            uncompressed_size = ff_snappy_peek_uncompressed_length(&gbc_tmp);
206
90.4k
            if (uncompressed_size < 0) {
207
437
                return uncompressed_size;
208
437
            }
209
90.0k
            chunk->uncompressed_size = uncompressed_size;
210
90.0k
        } else if (chunk->compressor == HAP_COMP_NONE) {
211
1.44k
            chunk->uncompressed_size = chunk->compressed_size;
212
1.44k
        } else {
213
355
            return AVERROR_INVALIDDATA;
214
355
        }
215
91.4k
        ctx->tex_size += chunk->uncompressed_size;
216
91.4k
    }
217
218
91.3k
    av_log(avctx, AV_LOG_DEBUG, "%s compressor\n", compressorstr);
219
220
91.3k
    return ret;
221
92.4k
}
222
223
static int decompress_chunks_thread(AVCodecContext *avctx, void *arg,
224
                                    int chunk_nb, int thread_nb)
225
87.5k
{
226
87.5k
    HapContext *ctx = avctx->priv_data;
227
228
87.5k
    HapChunk *chunk = &ctx->chunks[chunk_nb];
229
87.5k
    GetByteContext gbc;
230
87.5k
    uint8_t *dst = ctx->tex_buf + chunk->uncompressed_offset;
231
232
87.5k
    bytestream2_init(&gbc, ctx->gbc.buffer + chunk->compressed_offset, chunk->compressed_size);
233
234
87.5k
    if (chunk->compressor == HAP_COMP_SNAPPY) {
235
87.5k
        int ret;
236
87.5k
        int64_t uncompressed_size = ctx->tex_size;
237
238
        /* Uncompress the frame */
239
87.5k
        ret = ff_snappy_uncompress(&gbc, dst, &uncompressed_size);
240
87.5k
        if (ret < 0) {
241
2.79k
             av_log(avctx, AV_LOG_ERROR, "Snappy uncompress error\n");
242
2.79k
             return ret;
243
2.79k
        }
244
87.5k
    } else if (chunk->compressor == HAP_COMP_NONE) {
245
0
        bytestream2_get_buffer(&gbc, dst, chunk->compressed_size);
246
0
    }
247
248
84.7k
    return 0;
249
87.5k
}
250
251
static int hap_decode(AVCodecContext *avctx, AVFrame *frame,
252
                      int *got_frame, AVPacket *avpkt)
253
454k
{
254
454k
    HapContext *ctx = avctx->priv_data;
255
454k
    int ret, i, t;
256
454k
    int section_size;
257
454k
    enum HapSectionType section_type;
258
454k
    int start_texture_section = 0;
259
260
454k
    bytestream2_init(&ctx->gbc, avpkt->data, avpkt->size);
261
262
    /* check for multi texture header */
263
454k
    if (ctx->texture_count == 2) {
264
11.3k
        ret = ff_hap_parse_section_header(&ctx->gbc, &section_size, &section_type);
265
11.3k
        if (ret != 0)
266
9.40k
            return ret;
267
1.98k
        if ((section_type & 0x0F) != 0x0D) {
268
279
            av_log(avctx, AV_LOG_ERROR, "Invalid section type in 2 textures mode %#04x.\n", section_type);
269
279
            return AVERROR_INVALIDDATA;
270
279
        }
271
1.70k
        start_texture_section = 4;
272
1.70k
    }
273
274
    /* Get the output frame ready to receive data */
275
444k
    ret = ff_thread_get_buffer(avctx, frame, 0);
276
444k
    if (ret < 0)
277
613
        return ret;
278
279
530k
    for (t = 0; t < ctx->texture_count; t++) {
280
445k
        bytestream2_seek(&ctx->gbc, start_texture_section, SEEK_SET);
281
282
        /* Check for section header */
283
445k
        ret = hap_parse_frame_header(avctx);
284
445k
        if (ret < 0)
285
353k
            return ret;
286
287
91.3k
        if (ctx->tex_size != (avctx->coded_width  / TEXTURE_BLOCK_W)
288
91.3k
            *(avctx->coded_height / TEXTURE_BLOCK_H)
289
91.3k
            *ctx->dec[t].tex_ratio) {
290
2.60k
            av_log(avctx, AV_LOG_ERROR, "uncompressed size mismatches\n");
291
2.60k
            return AVERROR_INVALIDDATA;
292
2.60k
        }
293
294
88.7k
        start_texture_section += ctx->texture_section_size + 4;
295
296
        /* Unpack the DXT texture */
297
88.7k
        if (hap_can_use_tex_in_place(ctx)) {
298
1.24k
            int tex_size;
299
            /* Only DXTC texture compression in a contiguous block */
300
1.24k
            ctx->dec[t].tex_data.in = ctx->gbc.buffer;
301
1.24k
            tex_size = FFMIN(ctx->texture_section_size, bytestream2_get_bytes_left(&ctx->gbc));
302
1.24k
            if (tex_size < (avctx->coded_width  / TEXTURE_BLOCK_W)
303
1.24k
                *(avctx->coded_height / TEXTURE_BLOCK_H)
304
1.24k
                *ctx->dec[t].tex_ratio) {
305
0
                av_log(avctx, AV_LOG_ERROR, "Insufficient data\n");
306
0
                return AVERROR_INVALIDDATA;
307
0
            }
308
87.5k
        } else {
309
            /* Perform the second-stage decompression */
310
87.5k
            ret = av_reallocp(&ctx->tex_buf, ctx->tex_size);
311
87.5k
            if (ret < 0)
312
0
                return ret;
313
87.5k
            memset(ctx->tex_buf, 0, ctx->tex_size);
314
315
87.5k
            avctx->execute2(avctx, decompress_chunks_thread, NULL,
316
87.5k
                            ctx->chunk_results, ctx->chunk_count);
317
318
172k
            for (i = 0; i < ctx->chunk_count; i++) {
319
87.5k
                if (ctx->chunk_results[i] < 0)
320
2.79k
                    return ctx->chunk_results[i];
321
87.5k
            }
322
323
84.7k
            ctx->dec[t].tex_data.in = ctx->tex_buf;
324
84.7k
        }
325
326
85.9k
        ctx->dec[t].frame_data.out = frame->data[0];
327
85.9k
        ctx->dec[t].stride = frame->linesize[0];
328
85.9k
        ctx->dec[t].width  = avctx->coded_width;
329
85.9k
        ctx->dec[t].height = avctx->coded_height;
330
85.9k
        ff_texturedsp_exec_decompress_threads(avctx, &ctx->dec[t]);
331
85.9k
    }
332
333
    /* Frame is ready to be output */
334
84.8k
    *got_frame = 1;
335
336
84.8k
    return avpkt->size;
337
444k
}
338
339
static av_cold int hap_init(AVCodecContext *avctx)
340
948
{
341
948
    HapContext *ctx = avctx->priv_data;
342
948
    TextureDSPContext dxtc;
343
948
    const char *texture_name;
344
948
    int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx);
345
346
948
    if (ret < 0) {
347
128
        av_log(avctx, AV_LOG_ERROR, "Invalid video size %dx%d.\n",
348
128
               avctx->width, avctx->height);
349
128
        return ret;
350
128
    }
351
352
    /* Since codec is based on 4x4 blocks, size is aligned to 4 */
353
820
    avctx->coded_width  = FFALIGN(avctx->width,  TEXTURE_BLOCK_W);
354
820
    avctx->coded_height = FFALIGN(avctx->height, TEXTURE_BLOCK_H);
355
356
820
    ff_texturedsp_init(&dxtc);
357
358
820
    ctx->texture_count  = 1;
359
820
    ctx->dec[0].raw_ratio = 16;
360
820
    ctx->dec[0].slice_count = av_clip(avctx->thread_count, 1,
361
820
                                      avctx->coded_height / TEXTURE_BLOCK_H);
362
363
820
    switch (avctx->codec_tag) {
364
110
    case MKTAG('H','a','p','1'):
365
110
        texture_name = "DXT1";
366
110
        ctx->dec[0].tex_ratio = 8;
367
110
        ctx->dec[0].tex_funct = dxtc.dxt1_block;
368
110
        avctx->pix_fmt = AV_PIX_FMT_RGB0;
369
110
        break;
370
88
    case MKTAG('H','a','p','5'):
371
88
        texture_name = "DXT5";
372
88
        ctx->dec[0].tex_ratio = 16;
373
88
        ctx->dec[0].tex_funct = dxtc.dxt5_block;
374
88
        avctx->pix_fmt = AV_PIX_FMT_RGBA;
375
88
        break;
376
486
    case MKTAG('H','a','p','Y'):
377
486
        texture_name = "DXT5-YCoCg-scaled";
378
486
        ctx->dec[0].tex_ratio = 16;
379
486
        ctx->dec[0].tex_funct = dxtc.dxt5ys_block;
380
486
        avctx->pix_fmt = AV_PIX_FMT_RGB0;
381
486
        break;
382
73
    case MKTAG('H','a','p','A'):
383
73
        texture_name = "RGTC1";
384
73
        ctx->dec[0].tex_ratio = 8;
385
73
        ctx->dec[0].tex_funct = dxtc.rgtc1u_gray_block;
386
73
        ctx->dec[0].raw_ratio = 4;
387
73
        avctx->pix_fmt = AV_PIX_FMT_GRAY8;
388
73
        break;
389
63
    case MKTAG('H','a','p','M'):
390
63
        texture_name  = "DXT5-YCoCg-scaled / RGTC1";
391
63
        ctx->dec[0].tex_ratio = 16;
392
63
        ctx->dec[1].tex_ratio = 8;
393
63
        ctx->dec[0].tex_funct = dxtc.dxt5ys_block;
394
63
        ctx->dec[1].tex_funct = dxtc.rgtc1u_alpha_block;
395
63
        ctx->dec[1].raw_ratio = 16;
396
63
        ctx->dec[1].slice_count = ctx->dec[0].slice_count;
397
63
        avctx->pix_fmt = AV_PIX_FMT_RGBA;
398
63
        ctx->texture_count = 2;
399
63
        break;
400
0
    default:
401
0
        return AVERROR_DECODER_NOT_FOUND;
402
820
    }
403
404
820
    av_log(avctx, AV_LOG_DEBUG, "%s texture\n", texture_name);
405
406
820
    return 0;
407
820
}
408
409
static av_cold int hap_close(AVCodecContext *avctx)
410
948
{
411
948
    HapContext *ctx = avctx->priv_data;
412
413
948
    ff_hap_free_context(ctx);
414
415
948
    return 0;
416
948
}
417
418
const FFCodec ff_hap_decoder = {
419
    .p.name         = "hap",
420
    CODEC_LONG_NAME("Vidvox Hap"),
421
    .p.type         = AVMEDIA_TYPE_VIDEO,
422
    .p.id           = AV_CODEC_ID_HAP,
423
    .init           = hap_init,
424
    FF_CODEC_DECODE_CB(hap_decode),
425
    .close          = hap_close,
426
    .priv_data_size = sizeof(HapContext),
427
    .p.capabilities = AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS |
428
                      AV_CODEC_CAP_DR1,
429
    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP,
430
    .codec_tags     = (const uint32_t []){
431
        MKTAG('H','a','p','1'),
432
        MKTAG('H','a','p','5'),
433
        MKTAG('H','a','p','Y'),
434
        MKTAG('H','a','p','A'),
435
        MKTAG('H','a','p','M'),
436
        FF_CODEC_TAGS_END,
437
    },
438
};