Coverage Report

Created: 2026-04-01 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ffmpeg/libavcodec/hapdec.c
Line
Count
Source
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.63k
{
49
3.63k
    GetByteContext *gbc = &ctx->gbc;
50
3.63k
    int section_size;
51
3.63k
    enum HapSectionType section_type;
52
3.63k
    int is_first_table = 1, had_offsets = 0, had_compressors = 0, had_sizes = 0;
53
3.63k
    int i, ret;
54
55
16.3k
    while (size > 0) {
56
14.3k
        int stream_remaining = bytestream2_get_bytes_left(gbc);
57
14.3k
        ret = ff_hap_parse_section_header(gbc, &section_size, &section_type);
58
14.3k
        if (ret != 0)
59
802
            return ret;
60
61
13.5k
        size -= stream_remaining - bytestream2_get_bytes_left(gbc);
62
63
13.5k
        switch (section_type) {
64
2.28k
            case HAP_ST_COMPRESSOR_TABLE:
65
2.28k
                ret = ff_hap_set_chunk_count(ctx, section_size, is_first_table);
66
2.28k
                if (ret != 0)
67
297
                    return ret;
68
930k
                for (i = 0; i < section_size; i++) {
69
928k
                    ctx->chunks[i].compressor = bytestream2_get_byte(gbc) << 4;
70
928k
                }
71
1.99k
                had_compressors = 1;
72
1.99k
                is_first_table = 0;
73
1.99k
                break;
74
2.80k
            case HAP_ST_SIZE_TABLE:
75
2.80k
                ret = ff_hap_set_chunk_count(ctx, section_size / 4, is_first_table);
76
2.80k
                if (ret != 0)
77
329
                    return ret;
78
122k
                for (i = 0; i < section_size / 4; i++) {
79
120k
                    ctx->chunks[i].compressed_size = bytestream2_get_le32(gbc);
80
120k
                }
81
2.47k
                had_sizes = 1;
82
2.47k
                is_first_table = 0;
83
2.47k
                break;
84
1.86k
            case HAP_ST_OFFSET_TABLE:
85
1.86k
                ret = ff_hap_set_chunk_count(ctx, section_size / 4, is_first_table);
86
1.86k
                if (ret != 0)
87
203
                    return ret;
88
331k
                for (i = 0; i < section_size / 4; i++) {
89
329k
                    ctx->chunks[i].compressed_offset = bytestream2_get_le32(gbc);
90
329k
                }
91
1.66k
                had_offsets = 1;
92
1.66k
                is_first_table = 0;
93
1.66k
                break;
94
6.57k
            default:
95
6.57k
                break;
96
13.5k
        }
97
12.7k
        size -= section_size;
98
12.7k
    }
99
100
2.00k
    if (!had_sizes || !had_compressors)
101
635
        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.37k
    if (!had_offsets) {
106
1.13k
        size_t running_size = 0;
107
7.22k
        for (i = 0; i < ctx->chunk_count; i++) {
108
6.32k
            ctx->chunks[i].compressed_offset = running_size;
109
6.32k
            if (ctx->chunks[i].compressed_size > UINT32_MAX - running_size)
110
245
                return AVERROR_INVALIDDATA;
111
6.08k
            running_size += ctx->chunks[i].compressed_size;
112
6.08k
        }
113
1.13k
    }
114
115
1.12k
    return 0;
116
1.37k
}
117
118
static int hap_can_use_tex_in_place(HapContext *ctx)
119
124k
{
120
124k
    int i;
121
124k
    size_t running_offset = 0;
122
125k
    for (i = 0; i < ctx->chunk_count; i++) {
123
124k
        if (ctx->chunks[i].compressed_offset != running_offset
124
124k
            || ctx->chunks[i].compressor != HAP_COMP_NONE)
125
122k
            return 0;
126
1.15k
        running_offset += ctx->chunks[i].compressed_size;
127
1.15k
    }
128
1.15k
    return 1;
129
124k
}
130
131
static int hap_parse_frame_header(AVCodecContext *avctx)
132
181k
{
133
181k
    HapContext *ctx = avctx->priv_data;
134
181k
    GetByteContext *gbc = &ctx->gbc;
135
181k
    int section_size;
136
181k
    enum HapSectionType section_type;
137
181k
    const char *compressorstr;
138
181k
    int i, ret;
139
140
181k
    ret = ff_hap_parse_section_header(gbc, &ctx->texture_section_size, &section_type);
141
181k
    if (ret != 0)
142
42.6k
        return ret;
143
144
138k
    if ((avctx->codec_tag == MKTAG('H','a','p','1') && (section_type & 0x0F) != HAP_FMT_RGBDXT1) ||
145
136k
        (avctx->codec_tag == MKTAG('H','a','p','5') && (section_type & 0x0F) != HAP_FMT_RGBADXT5) ||
146
136k
        (avctx->codec_tag == MKTAG('H','a','p','Y') && (section_type & 0x0F) != HAP_FMT_YCOCGDXT5) ||
147
132k
        (avctx->codec_tag == MKTAG('H','a','p','A') && (section_type & 0x0F) != HAP_FMT_RGTC1) ||
148
131k
        ((avctx->codec_tag == MKTAG('H','a','p','M') && (section_type & 0x0F) != HAP_FMT_RGTC1) &&
149
6.80k
                                                        (section_type & 0x0F) != HAP_FMT_YCOCGDXT5)) {
150
6.80k
        av_log(avctx, AV_LOG_ERROR,
151
6.80k
               "Invalid texture format %#04x.\n", section_type & 0x0F);
152
6.80k
        return AVERROR_INVALIDDATA;
153
6.80k
    }
154
155
131k
    switch (section_type & 0xF0) {
156
1.17k
        case HAP_COMP_NONE:
157
127k
        case HAP_COMP_SNAPPY:
158
127k
            ret = ff_hap_set_chunk_count(ctx, 1, 1);
159
127k
            if (ret == 0) {
160
127k
                ctx->chunks[0].compressor = section_type & 0xF0;
161
127k
                ctx->chunks[0].compressed_offset = 0;
162
127k
                ctx->chunks[0].compressed_size = ctx->texture_section_size;
163
127k
            }
164
127k
            if (ctx->chunks[0].compressor == HAP_COMP_NONE) {
165
1.17k
                compressorstr = "none";
166
125k
            } else {
167
125k
                compressorstr = "snappy";
168
125k
            }
169
127k
            break;
170
4.12k
        case HAP_COMP_COMPLEX:
171
4.12k
            ret = ff_hap_parse_section_header(gbc, &section_size, &section_type);
172
4.12k
            if (ret == 0 && section_type != HAP_ST_DECODE_INSTRUCTIONS)
173
214
                ret = AVERROR_INVALIDDATA;
174
4.12k
            if (ret == 0)
175
3.63k
                ret = hap_parse_decode_instructions(ctx, section_size);
176
4.12k
            compressorstr = "complex";
177
4.12k
            break;
178
453
        default:
179
453
            ret = AVERROR_INVALIDDATA;
180
453
            break;
181
131k
    }
182
183
131k
    if (ret != 0)
184
3.45k
        return ret;
185
186
    /* Check the frame is valid and read the uncompressed chunk sizes */
187
128k
    ctx->tex_size = 0;
188
255k
    for (i = 0; i < ctx->chunk_count; i++) {
189
128k
        HapChunk *chunk = &ctx->chunks[i];
190
191
        /* Check the compressed buffer is valid */
192
128k
        if (chunk->compressed_offset + (uint64_t)chunk->compressed_size > bytestream2_get_bytes_left(gbc))
193
406
            return AVERROR_INVALIDDATA;
194
195
        /* Chunks are unpacked sequentially, ctx->tex_size is the uncompressed
196
         * size thus far */
197
127k
        chunk->uncompressed_offset = ctx->tex_size;
198
199
        /* Fill out uncompressed size */
200
127k
        if (chunk->compressor == HAP_COMP_SNAPPY) {
201
126k
            GetByteContext gbc_tmp;
202
126k
            int64_t uncompressed_size;
203
126k
            bytestream2_init(&gbc_tmp, gbc->buffer + chunk->compressed_offset,
204
126k
                             chunk->compressed_size);
205
126k
            uncompressed_size = ff_snappy_peek_uncompressed_length(&gbc_tmp);
206
126k
            if (uncompressed_size < 0) {
207
431
                return uncompressed_size;
208
431
            }
209
126k
            chunk->uncompressed_size = uncompressed_size;
210
126k
        } else if (chunk->compressor == HAP_COMP_NONE) {
211
1.17k
            chunk->uncompressed_size = chunk->compressed_size;
212
1.17k
        } else {
213
288
            return AVERROR_INVALIDDATA;
214
288
        }
215
127k
        ctx->tex_size += chunk->uncompressed_size;
216
127k
    }
217
218
127k
    av_log(avctx, AV_LOG_DEBUG, "%s compressor\n", compressorstr);
219
220
127k
    return ret;
221
128k
}
222
223
static int decompress_chunks_thread(AVCodecContext *avctx, void *arg,
224
                                    int chunk_nb, int thread_nb)
225
122k
{
226
122k
    HapContext *ctx = avctx->priv_data;
227
228
122k
    HapChunk *chunk = &ctx->chunks[chunk_nb];
229
122k
    GetByteContext gbc;
230
122k
    uint8_t *dst = ctx->tex_buf + chunk->uncompressed_offset;
231
232
122k
    bytestream2_init(&gbc, ctx->gbc.buffer + chunk->compressed_offset, chunk->compressed_size);
233
234
122k
    if (chunk->compressor == HAP_COMP_SNAPPY) {
235
122k
        int ret;
236
122k
        int64_t uncompressed_size = ctx->tex_size;
237
238
        /* Uncompress the frame */
239
122k
        ret = ff_snappy_uncompress(&gbc, dst, &uncompressed_size);
240
122k
        if (ret < 0) {
241
2.25k
             av_log(avctx, AV_LOG_ERROR, "Snappy uncompress error\n");
242
2.25k
             return ret;
243
2.25k
        }
244
122k
    } else if (chunk->compressor == HAP_COMP_NONE) {
245
0
        bytestream2_get_buffer(&gbc, dst, chunk->compressed_size);
246
0
    }
247
248
120k
    return 0;
249
122k
}
250
251
static int hap_decode(AVCodecContext *avctx, AVFrame *frame,
252
                      int *got_frame, AVPacket *avpkt)
253
218k
{
254
218k
    HapContext *ctx = avctx->priv_data;
255
218k
    int ret, i, t;
256
218k
    int section_size;
257
218k
    enum HapSectionType section_type;
258
218k
    int start_texture_section = 0;
259
260
218k
    bytestream2_init(&ctx->gbc, avpkt->data, avpkt->size);
261
262
    /* check for multi texture header */
263
218k
    if (ctx->texture_count == 2) {
264
39.9k
        ret = ff_hap_parse_section_header(&ctx->gbc, &section_size, &section_type);
265
39.9k
        if (ret != 0)
266
36.9k
            return ret;
267
3.00k
        if ((section_type & 0x0F) != 0x0D) {
268
1.28k
            av_log(avctx, AV_LOG_ERROR, "Invalid section type in 2 textures mode %#04x.\n", section_type);
269
1.28k
            return AVERROR_INVALIDDATA;
270
1.28k
        }
271
1.72k
        start_texture_section = 4;
272
1.72k
    }
273
274
    /* Get the output frame ready to receive data */
275
180k
    ret = ff_thread_get_buffer(avctx, frame, 0);
276
180k
    if (ret < 0)
277
149
        return ret;
278
279
301k
    for (t = 0; t < ctx->texture_count; t++) {
280
181k
        bytestream2_seek(&ctx->gbc, start_texture_section, SEEK_SET);
281
282
        /* Check for section header */
283
181k
        ret = hap_parse_frame_header(avctx);
284
181k
        if (ret < 0)
285
53.9k
            return ret;
286
287
127k
        if (ctx->tex_size != (avctx->coded_width  / TEXTURE_BLOCK_W)
288
127k
            *(avctx->coded_height / TEXTURE_BLOCK_H)
289
127k
            *ctx->dec[t].tex_ratio) {
290
2.94k
            av_log(avctx, AV_LOG_ERROR, "uncompressed size mismatches\n");
291
2.94k
            return AVERROR_INVALIDDATA;
292
2.94k
        }
293
294
124k
        start_texture_section += ctx->texture_section_size + 4;
295
296
        /* Unpack the DXT texture */
297
124k
        if (hap_can_use_tex_in_place(ctx)) {
298
1.15k
            int tex_size;
299
            /* Only DXTC texture compression in a contiguous block */
300
1.15k
            ctx->dec[t].tex_data.in = ctx->gbc.buffer;
301
1.15k
            tex_size = FFMIN(ctx->texture_section_size, bytestream2_get_bytes_left(&ctx->gbc));
302
1.15k
            if (tex_size < (avctx->coded_width  / TEXTURE_BLOCK_W)
303
1.15k
                *(avctx->coded_height / TEXTURE_BLOCK_H)
304
1.15k
                *ctx->dec[t].tex_ratio) {
305
0
                av_log(avctx, AV_LOG_ERROR, "Insufficient data\n");
306
0
                return AVERROR_INVALIDDATA;
307
0
            }
308
122k
        } else {
309
            /* Perform the second-stage decompression */
310
122k
            ret = av_reallocp(&ctx->tex_buf, ctx->tex_size);
311
122k
            if (ret < 0)
312
0
                return ret;
313
122k
            memset(ctx->tex_buf, 0, ctx->tex_size);
314
315
122k
            avctx->execute2(avctx, decompress_chunks_thread, NULL,
316
122k
                            ctx->chunk_results, ctx->chunk_count);
317
318
243k
            for (i = 0; i < ctx->chunk_count; i++) {
319
122k
                if (ctx->chunk_results[i] < 0)
320
2.25k
                    return ctx->chunk_results[i];
321
122k
            }
322
323
120k
            ctx->dec[t].tex_data.in = ctx->tex_buf;
324
120k
        }
325
326
121k
        ctx->dec[t].frame_data.out = frame->data[0];
327
121k
        ctx->dec[t].stride = frame->linesize[0];
328
121k
        ctx->dec[t].width  = avctx->coded_width;
329
121k
        ctx->dec[t].height = avctx->coded_height;
330
121k
        ff_texturedsp_exec_decompress_threads(avctx, &ctx->dec[t]);
331
121k
    }
332
333
    /* Frame is ready to be output */
334
120k
    *got_frame = 1;
335
336
120k
    return avpkt->size;
337
179k
}
338
339
static av_cold int hap_init(AVCodecContext *avctx)
340
999
{
341
999
    HapContext *ctx = avctx->priv_data;
342
999
    TextureDSPContext dxtc;
343
999
    const char *texture_name;
344
999
    int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx);
345
346
999
    if (ret < 0) {
347
131
        av_log(avctx, AV_LOG_ERROR, "Invalid video size %dx%d.\n",
348
131
               avctx->width, avctx->height);
349
131
        return ret;
350
131
    }
351
352
    /* Since codec is based on 4x4 blocks, size is aligned to 4 */
353
868
    avctx->coded_width  = FFALIGN(avctx->width,  TEXTURE_BLOCK_W);
354
868
    avctx->coded_height = FFALIGN(avctx->height, TEXTURE_BLOCK_H);
355
356
868
    ff_texturedsp_init(&dxtc);
357
358
868
    ctx->texture_count  = 1;
359
868
    ctx->dec[0].raw_ratio = 16;
360
868
    ctx->dec[0].slice_count = av_clip(avctx->thread_count, 1,
361
868
                                      avctx->coded_height / TEXTURE_BLOCK_H);
362
363
868
    switch (avctx->codec_tag) {
364
121
    case MKTAG('H','a','p','1'):
365
121
        texture_name = "DXT1";
366
121
        ctx->dec[0].tex_ratio = 8;
367
121
        ctx->dec[0].tex_funct = dxtc.dxt1_block;
368
121
        avctx->pix_fmt = AV_PIX_FMT_RGB0;
369
121
        break;
370
102
    case MKTAG('H','a','p','5'):
371
102
        texture_name = "DXT5";
372
102
        ctx->dec[0].tex_ratio = 16;
373
102
        ctx->dec[0].tex_funct = dxtc.dxt5_block;
374
102
        avctx->pix_fmt = AV_PIX_FMT_RGBA;
375
102
        break;
376
511
    case MKTAG('H','a','p','Y'):
377
511
        texture_name = "DXT5-YCoCg-scaled";
378
511
        ctx->dec[0].tex_ratio = 16;
379
511
        ctx->dec[0].tex_funct = dxtc.dxt5ys_block;
380
511
        avctx->pix_fmt = AV_PIX_FMT_RGB0;
381
511
        break;
382
67
    case MKTAG('H','a','p','A'):
383
67
        texture_name = "RGTC1";
384
67
        ctx->dec[0].tex_ratio = 8;
385
67
        ctx->dec[0].tex_funct = dxtc.rgtc1u_gray_block;
386
67
        ctx->dec[0].raw_ratio = 4;
387
67
        avctx->pix_fmt = AV_PIX_FMT_GRAY8;
388
67
        break;
389
67
    case MKTAG('H','a','p','M'):
390
67
        texture_name  = "DXT5-YCoCg-scaled / RGTC1";
391
67
        ctx->dec[0].tex_ratio = 16;
392
67
        ctx->dec[1].tex_ratio = 8;
393
67
        ctx->dec[0].tex_funct = dxtc.dxt5ys_block;
394
67
        ctx->dec[1].tex_funct = dxtc.rgtc1u_alpha_block;
395
67
        ctx->dec[1].raw_ratio = 16;
396
67
        ctx->dec[1].slice_count = ctx->dec[0].slice_count;
397
67
        avctx->pix_fmt = AV_PIX_FMT_RGBA;
398
67
        ctx->texture_count = 2;
399
67
        break;
400
0
    default:
401
0
        return AVERROR_DECODER_NOT_FOUND;
402
868
    }
403
404
868
    av_log(avctx, AV_LOG_DEBUG, "%s texture\n", texture_name);
405
406
868
    return 0;
407
868
}
408
409
static av_cold int hap_close(AVCodecContext *avctx)
410
999
{
411
999
    HapContext *ctx = avctx->priv_data;
412
413
999
    ff_hap_free_context(ctx);
414
415
999
    return 0;
416
999
}
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
};