/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.51k | { |
49 | 3.51k | GetByteContext *gbc = &ctx->gbc; |
50 | 3.51k | int section_size; |
51 | 3.51k | enum HapSectionType section_type; |
52 | 3.51k | int is_first_table = 1, had_offsets = 0, had_compressors = 0, had_sizes = 0; |
53 | 3.51k | int i, ret; |
54 | | |
55 | 16.0k | while (size > 0) { |
56 | 14.1k | int stream_remaining = bytestream2_get_bytes_left(gbc); |
57 | 14.1k | ret = ff_hap_parse_section_header(gbc, §ion_size, §ion_type); |
58 | 14.1k | if (ret != 0) |
59 | 799 | return ret; |
60 | | |
61 | 13.3k | size -= stream_remaining - bytestream2_get_bytes_left(gbc); |
62 | | |
63 | 13.3k | switch (section_type) { |
64 | 2.06k | case HAP_ST_COMPRESSOR_TABLE: |
65 | 2.06k | ret = ff_hap_set_chunk_count(ctx, section_size, is_first_table); |
66 | 2.06k | if (ret != 0) |
67 | 260 | return ret; |
68 | 916k | for (i = 0; i < section_size; i++) { |
69 | 914k | ctx->chunks[i].compressor = bytestream2_get_byte(gbc) << 4; |
70 | 914k | } |
71 | 1.80k | had_compressors = 1; |
72 | 1.80k | is_first_table = 0; |
73 | 1.80k | break; |
74 | 2.75k | case HAP_ST_SIZE_TABLE: |
75 | 2.75k | ret = ff_hap_set_chunk_count(ctx, section_size / 4, is_first_table); |
76 | 2.75k | if (ret != 0) |
77 | 324 | return ret; |
78 | 60.9k | for (i = 0; i < section_size / 4; i++) { |
79 | 58.5k | ctx->chunks[i].compressed_size = bytestream2_get_le32(gbc); |
80 | 58.5k | } |
81 | 2.43k | had_sizes = 1; |
82 | 2.43k | is_first_table = 0; |
83 | 2.43k | break; |
84 | 1.90k | case HAP_ST_OFFSET_TABLE: |
85 | 1.90k | ret = ff_hap_set_chunk_count(ctx, section_size / 4, is_first_table); |
86 | 1.90k | if (ret != 0) |
87 | 203 | return ret; |
88 | 641k | for (i = 0; i < section_size / 4; i++) { |
89 | 640k | ctx->chunks[i].compressed_offset = bytestream2_get_le32(gbc); |
90 | 640k | } |
91 | 1.70k | had_offsets = 1; |
92 | 1.70k | is_first_table = 0; |
93 | 1.70k | break; |
94 | 6.57k | default: |
95 | 6.57k | break; |
96 | 13.3k | } |
97 | 12.5k | size -= section_size; |
98 | 12.5k | } |
99 | | |
100 | 1.92k | if (!had_sizes || !had_compressors) |
101 | 667 | 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.25k | if (!had_offsets) { |
106 | 1.04k | size_t running_size = 0; |
107 | 6.37k | for (i = 0; i < ctx->chunk_count; i++) { |
108 | 5.56k | ctx->chunks[i].compressed_offset = running_size; |
109 | 5.56k | if (ctx->chunks[i].compressed_size > UINT32_MAX - running_size) |
110 | 238 | return AVERROR_INVALIDDATA; |
111 | 5.32k | running_size += ctx->chunks[i].compressed_size; |
112 | 5.32k | } |
113 | 1.04k | } |
114 | | |
115 | 1.02k | return 0; |
116 | 1.25k | } |
117 | | |
118 | | static int hap_can_use_tex_in_place(HapContext *ctx) |
119 | 93.9k | { |
120 | 93.9k | int i; |
121 | 93.9k | size_t running_offset = 0; |
122 | 95.3k | for (i = 0; i < ctx->chunk_count; i++) { |
123 | 93.9k | if (ctx->chunks[i].compressed_offset != running_offset |
124 | 93.9k | || ctx->chunks[i].compressor != HAP_COMP_NONE) |
125 | 92.5k | return 0; |
126 | 1.41k | running_offset += ctx->chunks[i].compressed_size; |
127 | 1.41k | } |
128 | 1.41k | return 1; |
129 | 93.9k | } |
130 | | |
131 | | static int hap_parse_frame_header(AVCodecContext *avctx) |
132 | 349k | { |
133 | 349k | HapContext *ctx = avctx->priv_data; |
134 | 349k | GetByteContext *gbc = &ctx->gbc; |
135 | 349k | int section_size; |
136 | 349k | enum HapSectionType section_type; |
137 | 349k | const char *compressorstr; |
138 | 349k | int i, ret; |
139 | | |
140 | 349k | ret = ff_hap_parse_section_header(gbc, &ctx->texture_section_size, §ion_type); |
141 | 349k | if (ret != 0) |
142 | 222k | return ret; |
143 | | |
144 | 126k | if ((avctx->codec_tag == MKTAG('H','a','p','1') && (section_type & 0x0F) != HAP_FMT_RGBDXT1) || |
145 | 126k | (avctx->codec_tag == MKTAG('H','a','p','5') && (section_type & 0x0F) != HAP_FMT_RGBADXT5) || |
146 | 126k | (avctx->codec_tag == MKTAG('H','a','p','Y') && (section_type & 0x0F) != HAP_FMT_YCOCGDXT5) || |
147 | 126k | (avctx->codec_tag == MKTAG('H','a','p','A') && (section_type & 0x0F) != HAP_FMT_RGTC1) || |
148 | 126k | ((avctx->codec_tag == MKTAG('H','a','p','M') && (section_type & 0x0F) != HAP_FMT_RGTC1) && |
149 | 100k | (section_type & 0x0F) != HAP_FMT_YCOCGDXT5)) { |
150 | 26.2k | av_log(avctx, AV_LOG_ERROR, |
151 | 26.2k | "Invalid texture format %#04x.\n", section_type & 0x0F); |
152 | 26.2k | return AVERROR_INVALIDDATA; |
153 | 26.2k | } |
154 | | |
155 | 100k | switch (section_type & 0xF0) { |
156 | 1.61k | case HAP_COMP_NONE: |
157 | 96.2k | case HAP_COMP_SNAPPY: |
158 | 96.2k | ret = ff_hap_set_chunk_count(ctx, 1, 1); |
159 | 96.2k | if (ret == 0) { |
160 | 96.2k | ctx->chunks[0].compressor = section_type & 0xF0; |
161 | 96.2k | ctx->chunks[0].compressed_offset = 0; |
162 | 96.2k | ctx->chunks[0].compressed_size = ctx->texture_section_size; |
163 | 96.2k | } |
164 | 96.2k | if (ctx->chunks[0].compressor == HAP_COMP_NONE) { |
165 | 1.61k | compressorstr = "none"; |
166 | 94.6k | } else { |
167 | 94.6k | compressorstr = "snappy"; |
168 | 94.6k | } |
169 | 96.2k | break; |
170 | 4.01k | case HAP_COMP_COMPLEX: |
171 | 4.01k | ret = ff_hap_parse_section_header(gbc, §ion_size, §ion_type); |
172 | 4.01k | if (ret == 0 && section_type != HAP_ST_DECODE_INSTRUCTIONS) |
173 | 219 | ret = AVERROR_INVALIDDATA; |
174 | 4.01k | if (ret == 0) |
175 | 3.51k | ret = hap_parse_decode_instructions(ctx, section_size); |
176 | 4.01k | compressorstr = "complex"; |
177 | 4.01k | break; |
178 | 462 | default: |
179 | 462 | ret = AVERROR_INVALIDDATA; |
180 | 462 | break; |
181 | 100k | } |
182 | | |
183 | 100k | if (ret != 0) |
184 | 3.46k | return ret; |
185 | | |
186 | | /* Check the frame is valid and read the uncompressed chunk sizes */ |
187 | 97.2k | ctx->tex_size = 0; |
188 | 193k | for (i = 0; i < ctx->chunk_count; i++) { |
189 | 97.2k | HapChunk *chunk = &ctx->chunks[i]; |
190 | | |
191 | | /* Check the compressed buffer is valid */ |
192 | 97.2k | if (chunk->compressed_offset + (uint64_t)chunk->compressed_size > bytestream2_get_bytes_left(gbc)) |
193 | 343 | return AVERROR_INVALIDDATA; |
194 | | |
195 | | /* Chunks are unpacked sequentially, ctx->tex_size is the uncompressed |
196 | | * size thus far */ |
197 | 96.9k | chunk->uncompressed_offset = ctx->tex_size; |
198 | | |
199 | | /* Fill out uncompressed size */ |
200 | 96.9k | if (chunk->compressor == HAP_COMP_SNAPPY) { |
201 | 95.0k | GetByteContext gbc_tmp; |
202 | 95.0k | int64_t uncompressed_size; |
203 | 95.0k | bytestream2_init(&gbc_tmp, gbc->buffer + chunk->compressed_offset, |
204 | 95.0k | chunk->compressed_size); |
205 | 95.0k | uncompressed_size = ff_snappy_peek_uncompressed_length(&gbc_tmp); |
206 | 95.0k | if (uncompressed_size < 0) { |
207 | 467 | return uncompressed_size; |
208 | 467 | } |
209 | 94.5k | chunk->uncompressed_size = uncompressed_size; |
210 | 94.5k | } else if (chunk->compressor == HAP_COMP_NONE) { |
211 | 1.61k | chunk->uncompressed_size = chunk->compressed_size; |
212 | 1.61k | } else { |
213 | 255 | return AVERROR_INVALIDDATA; |
214 | 255 | } |
215 | 96.2k | ctx->tex_size += chunk->uncompressed_size; |
216 | 96.2k | } |
217 | | |
218 | 96.2k | av_log(avctx, AV_LOG_DEBUG, "%s compressor\n", compressorstr); |
219 | | |
220 | 96.2k | return ret; |
221 | 97.2k | } |
222 | | |
223 | | static int decompress_chunks_thread(AVCodecContext *avctx, void *arg, |
224 | | int chunk_nb, int thread_nb) |
225 | 92.5k | { |
226 | 92.5k | HapContext *ctx = avctx->priv_data; |
227 | | |
228 | 92.5k | HapChunk *chunk = &ctx->chunks[chunk_nb]; |
229 | 92.5k | GetByteContext gbc; |
230 | 92.5k | uint8_t *dst = ctx->tex_buf + chunk->uncompressed_offset; |
231 | | |
232 | 92.5k | bytestream2_init(&gbc, ctx->gbc.buffer + chunk->compressed_offset, chunk->compressed_size); |
233 | | |
234 | 92.5k | if (chunk->compressor == HAP_COMP_SNAPPY) { |
235 | 92.5k | int ret; |
236 | 92.5k | int64_t uncompressed_size = ctx->tex_size; |
237 | | |
238 | | /* Uncompress the frame */ |
239 | 92.5k | ret = ff_snappy_uncompress(&gbc, dst, &uncompressed_size); |
240 | 92.5k | if (ret < 0) { |
241 | 2.82k | av_log(avctx, AV_LOG_ERROR, "Snappy uncompress error\n"); |
242 | 2.82k | return ret; |
243 | 2.82k | } |
244 | 92.5k | } else if (chunk->compressor == HAP_COMP_NONE) { |
245 | 0 | bytestream2_get_buffer(&gbc, dst, chunk->compressed_size); |
246 | 0 | } |
247 | | |
248 | 89.6k | return 0; |
249 | 92.5k | } |
250 | | |
251 | | static int hap_decode(AVCodecContext *avctx, AVFrame *frame, |
252 | | int *got_frame, AVPacket *avpkt) |
253 | 361k | { |
254 | 361k | HapContext *ctx = avctx->priv_data; |
255 | 361k | int ret, i, t; |
256 | 361k | int section_size; |
257 | 361k | enum HapSectionType section_type; |
258 | 361k | int start_texture_section = 0; |
259 | | |
260 | 361k | bytestream2_init(&ctx->gbc, avpkt->data, avpkt->size); |
261 | | |
262 | | /* check for multi texture header */ |
263 | 361k | if (ctx->texture_count == 2) { |
264 | 14.2k | ret = ff_hap_parse_section_header(&ctx->gbc, §ion_size, §ion_type); |
265 | 14.2k | if (ret != 0) |
266 | 12.1k | return ret; |
267 | 2.16k | if ((section_type & 0x0F) != 0x0D) { |
268 | 352 | av_log(avctx, AV_LOG_ERROR, "Invalid section type in 2 textures mode %#04x.\n", section_type); |
269 | 352 | return AVERROR_INVALIDDATA; |
270 | 352 | } |
271 | 1.81k | start_texture_section = 4; |
272 | 1.81k | } |
273 | | |
274 | | /* Get the output frame ready to receive data */ |
275 | 348k | ret = ff_thread_get_buffer(avctx, frame, 0); |
276 | 348k | if (ret < 0) |
277 | 380 | return ret; |
278 | | |
279 | 439k | for (t = 0; t < ctx->texture_count; t++) { |
280 | 349k | bytestream2_seek(&ctx->gbc, start_texture_section, SEEK_SET); |
281 | | |
282 | | /* Check for section header */ |
283 | 349k | ret = hap_parse_frame_header(avctx); |
284 | 349k | if (ret < 0) |
285 | 253k | return ret; |
286 | | |
287 | 96.2k | if (ctx->tex_size != (avctx->coded_width / TEXTURE_BLOCK_W) |
288 | 96.2k | *(avctx->coded_height / TEXTURE_BLOCK_H) |
289 | 96.2k | *ctx->dec[t].tex_ratio) { |
290 | 2.27k | av_log(avctx, AV_LOG_ERROR, "uncompressed size mismatches\n"); |
291 | 2.27k | return AVERROR_INVALIDDATA; |
292 | 2.27k | } |
293 | | |
294 | 93.9k | start_texture_section += ctx->texture_section_size + 4; |
295 | | |
296 | | /* Unpack the DXT texture */ |
297 | 93.9k | if (hap_can_use_tex_in_place(ctx)) { |
298 | 1.41k | int tex_size; |
299 | | /* Only DXTC texture compression in a contiguous block */ |
300 | 1.41k | ctx->dec[t].tex_data.in = ctx->gbc.buffer; |
301 | 1.41k | tex_size = FFMIN(ctx->texture_section_size, bytestream2_get_bytes_left(&ctx->gbc)); |
302 | 1.41k | if (tex_size < (avctx->coded_width / TEXTURE_BLOCK_W) |
303 | 1.41k | *(avctx->coded_height / TEXTURE_BLOCK_H) |
304 | 1.41k | *ctx->dec[t].tex_ratio) { |
305 | 0 | av_log(avctx, AV_LOG_ERROR, "Insufficient data\n"); |
306 | 0 | return AVERROR_INVALIDDATA; |
307 | 0 | } |
308 | 92.5k | } else { |
309 | | /* Perform the second-stage decompression */ |
310 | 92.5k | ret = av_reallocp(&ctx->tex_buf, ctx->tex_size); |
311 | 92.5k | if (ret < 0) |
312 | 0 | return ret; |
313 | | |
314 | 92.5k | avctx->execute2(avctx, decompress_chunks_thread, NULL, |
315 | 92.5k | ctx->chunk_results, ctx->chunk_count); |
316 | | |
317 | 182k | for (i = 0; i < ctx->chunk_count; i++) { |
318 | 92.5k | if (ctx->chunk_results[i] < 0) |
319 | 2.82k | return ctx->chunk_results[i]; |
320 | 92.5k | } |
321 | | |
322 | 89.6k | ctx->dec[t].tex_data.in = ctx->tex_buf; |
323 | 89.6k | } |
324 | | |
325 | 91.1k | ctx->dec[t].frame_data.out = frame->data[0]; |
326 | 91.1k | ctx->dec[t].stride = frame->linesize[0]; |
327 | 91.1k | ctx->dec[t].width = avctx->coded_width; |
328 | 91.1k | ctx->dec[t].height = avctx->coded_height; |
329 | 91.1k | ff_texturedsp_exec_decompress_threads(avctx, &ctx->dec[t]); |
330 | 91.1k | } |
331 | | |
332 | | /* Frame is ready to be output */ |
333 | 89.9k | *got_frame = 1; |
334 | | |
335 | 89.9k | return avpkt->size; |
336 | 348k | } |
337 | | |
338 | | static av_cold int hap_init(AVCodecContext *avctx) |
339 | 950 | { |
340 | 950 | HapContext *ctx = avctx->priv_data; |
341 | 950 | TextureDSPContext dxtc; |
342 | 950 | const char *texture_name; |
343 | 950 | int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx); |
344 | | |
345 | 950 | if (ret < 0) { |
346 | 147 | av_log(avctx, AV_LOG_ERROR, "Invalid video size %dx%d.\n", |
347 | 147 | avctx->width, avctx->height); |
348 | 147 | return ret; |
349 | 147 | } |
350 | | |
351 | | /* Since codec is based on 4x4 blocks, size is aligned to 4 */ |
352 | 803 | avctx->coded_width = FFALIGN(avctx->width, TEXTURE_BLOCK_W); |
353 | 803 | avctx->coded_height = FFALIGN(avctx->height, TEXTURE_BLOCK_H); |
354 | | |
355 | 803 | ff_texturedsp_init(&dxtc); |
356 | | |
357 | 803 | ctx->texture_count = 1; |
358 | 803 | ctx->dec[0].raw_ratio = 16; |
359 | 803 | ctx->dec[0].slice_count = av_clip(avctx->thread_count, 1, |
360 | 803 | avctx->coded_height / TEXTURE_BLOCK_H); |
361 | | |
362 | 803 | switch (avctx->codec_tag) { |
363 | 110 | case MKTAG('H','a','p','1'): |
364 | 110 | texture_name = "DXT1"; |
365 | 110 | ctx->dec[0].tex_ratio = 8; |
366 | 110 | ctx->dec[0].tex_funct = dxtc.dxt1_block; |
367 | 110 | avctx->pix_fmt = AV_PIX_FMT_RGB0; |
368 | 110 | break; |
369 | 78 | case MKTAG('H','a','p','5'): |
370 | 78 | texture_name = "DXT5"; |
371 | 78 | ctx->dec[0].tex_ratio = 16; |
372 | 78 | ctx->dec[0].tex_funct = dxtc.dxt5_block; |
373 | 78 | avctx->pix_fmt = AV_PIX_FMT_RGBA; |
374 | 78 | break; |
375 | 480 | case MKTAG('H','a','p','Y'): |
376 | 480 | texture_name = "DXT5-YCoCg-scaled"; |
377 | 480 | ctx->dec[0].tex_ratio = 16; |
378 | 480 | ctx->dec[0].tex_funct = dxtc.dxt5ys_block; |
379 | 480 | avctx->pix_fmt = AV_PIX_FMT_RGB0; |
380 | 480 | break; |
381 | 66 | case MKTAG('H','a','p','A'): |
382 | 66 | texture_name = "RGTC1"; |
383 | 66 | ctx->dec[0].tex_ratio = 8; |
384 | 66 | ctx->dec[0].tex_funct = dxtc.rgtc1u_gray_block; |
385 | 66 | ctx->dec[0].raw_ratio = 4; |
386 | 66 | avctx->pix_fmt = AV_PIX_FMT_GRAY8; |
387 | 66 | break; |
388 | 69 | case MKTAG('H','a','p','M'): |
389 | 69 | texture_name = "DXT5-YCoCg-scaled / RGTC1"; |
390 | 69 | ctx->dec[0].tex_ratio = 16; |
391 | 69 | ctx->dec[1].tex_ratio = 8; |
392 | 69 | ctx->dec[0].tex_funct = dxtc.dxt5ys_block; |
393 | 69 | ctx->dec[1].tex_funct = dxtc.rgtc1u_alpha_block; |
394 | 69 | ctx->dec[1].raw_ratio = 16; |
395 | 69 | ctx->dec[1].slice_count = ctx->dec[0].slice_count; |
396 | 69 | avctx->pix_fmt = AV_PIX_FMT_RGBA; |
397 | 69 | ctx->texture_count = 2; |
398 | 69 | break; |
399 | 0 | default: |
400 | 0 | return AVERROR_DECODER_NOT_FOUND; |
401 | 803 | } |
402 | | |
403 | 803 | av_log(avctx, AV_LOG_DEBUG, "%s texture\n", texture_name); |
404 | | |
405 | 803 | return 0; |
406 | 803 | } |
407 | | |
408 | | static av_cold int hap_close(AVCodecContext *avctx) |
409 | 950 | { |
410 | 950 | HapContext *ctx = avctx->priv_data; |
411 | | |
412 | 950 | ff_hap_free_context(ctx); |
413 | | |
414 | 950 | return 0; |
415 | 950 | } |
416 | | |
417 | | const FFCodec ff_hap_decoder = { |
418 | | .p.name = "hap", |
419 | | CODEC_LONG_NAME("Vidvox Hap"), |
420 | | .p.type = AVMEDIA_TYPE_VIDEO, |
421 | | .p.id = AV_CODEC_ID_HAP, |
422 | | .init = hap_init, |
423 | | FF_CODEC_DECODE_CB(hap_decode), |
424 | | .close = hap_close, |
425 | | .priv_data_size = sizeof(HapContext), |
426 | | .p.capabilities = AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS | |
427 | | AV_CODEC_CAP_DR1, |
428 | | .caps_internal = FF_CODEC_CAP_INIT_CLEANUP, |
429 | | .codec_tags = (const uint32_t []){ |
430 | | MKTAG('H','a','p','1'), |
431 | | MKTAG('H','a','p','5'), |
432 | | MKTAG('H','a','p','Y'), |
433 | | MKTAG('H','a','p','A'), |
434 | | MKTAG('H','a','p','M'), |
435 | | FF_CODEC_TAGS_END, |
436 | | }, |
437 | | }; |