/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, §ion_size, §ion_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, §ion_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, §ion_size, §ion_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, §ion_size, §ion_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 | | }; |