blob: a1cb0c76aaa91e06e47ea3df6e21f9334b72a1a6
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 | * 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 | /** |
24 | * @file |
25 | * Hap decoder |
26 | * |
27 | * Fourcc: Hap1, Hap5, HapY |
28 | * |
29 | * https://github.com/Vidvox/hap/blob/master/documentation/HapVideoDRAFT.md |
30 | */ |
31 | |
32 | #include <stdint.h> |
33 | |
34 | #include "libavutil/imgutils.h" |
35 | |
36 | #include "avcodec.h" |
37 | #include "bytestream.h" |
38 | #include "hap.h" |
39 | #include "internal.h" |
40 | #include "snappy.h" |
41 | #include "texturedsp.h" |
42 | #include "thread.h" |
43 | |
44 | /* The first three bytes are the size of the section past the header, or zero |
45 | * if the length is stored in the next long word. The fourth byte in the first |
46 | * long word indicates the type of the current section. */ |
47 | static int parse_section_header(GetByteContext *gbc, int *section_size, |
48 | enum HapSectionType *section_type) |
49 | { |
50 | if (bytestream2_get_bytes_left(gbc) < 4) |
51 | return AVERROR_INVALIDDATA; |
52 | |
53 | *section_size = bytestream2_get_le24(gbc); |
54 | *section_type = bytestream2_get_byte(gbc); |
55 | |
56 | if (*section_size == 0) { |
57 | if (bytestream2_get_bytes_left(gbc) < 4) |
58 | return AVERROR_INVALIDDATA; |
59 | |
60 | *section_size = bytestream2_get_le32(gbc); |
61 | } |
62 | |
63 | if (*section_size > bytestream2_get_bytes_left(gbc) || *section_size < 0) |
64 | return AVERROR_INVALIDDATA; |
65 | else |
66 | return 0; |
67 | } |
68 | |
69 | static int hap_parse_decode_instructions(HapContext *ctx, int size) |
70 | { |
71 | GetByteContext *gbc = &ctx->gbc; |
72 | int section_size; |
73 | enum HapSectionType section_type; |
74 | int is_first_table = 1, had_offsets = 0, had_compressors = 0, had_sizes = 0; |
75 | int i, ret; |
76 | |
77 | while (size > 0) { |
78 | int stream_remaining = bytestream2_get_bytes_left(gbc); |
79 | ret = parse_section_header(gbc, §ion_size, §ion_type); |
80 | if (ret != 0) |
81 | return ret; |
82 | |
83 | size -= stream_remaining - bytestream2_get_bytes_left(gbc); |
84 | |
85 | switch (section_type) { |
86 | case HAP_ST_COMPRESSOR_TABLE: |
87 | ret = ff_hap_set_chunk_count(ctx, section_size, is_first_table); |
88 | if (ret != 0) |
89 | return ret; |
90 | for (i = 0; i < section_size; i++) { |
91 | ctx->chunks[i].compressor = bytestream2_get_byte(gbc) << 4; |
92 | } |
93 | had_compressors = 1; |
94 | is_first_table = 0; |
95 | break; |
96 | case HAP_ST_SIZE_TABLE: |
97 | ret = ff_hap_set_chunk_count(ctx, section_size / 4, is_first_table); |
98 | if (ret != 0) |
99 | return ret; |
100 | for (i = 0; i < section_size / 4; i++) { |
101 | ctx->chunks[i].compressed_size = bytestream2_get_le32(gbc); |
102 | } |
103 | had_sizes = 1; |
104 | is_first_table = 0; |
105 | break; |
106 | case HAP_ST_OFFSET_TABLE: |
107 | ret = ff_hap_set_chunk_count(ctx, section_size / 4, is_first_table); |
108 | if (ret != 0) |
109 | return ret; |
110 | for (i = 0; i < section_size / 4; i++) { |
111 | ctx->chunks[i].compressed_offset = bytestream2_get_le32(gbc); |
112 | } |
113 | had_offsets = 1; |
114 | is_first_table = 0; |
115 | break; |
116 | default: |
117 | break; |
118 | } |
119 | size -= section_size; |
120 | } |
121 | |
122 | if (!had_sizes || !had_compressors) |
123 | return AVERROR_INVALIDDATA; |
124 | |
125 | /* The offsets table is optional. If not present than calculate offsets by |
126 | * summing the sizes of preceding chunks. */ |
127 | if (!had_offsets) { |
128 | size_t running_size = 0; |
129 | for (i = 0; i < ctx->chunk_count; i++) { |
130 | ctx->chunks[i].compressed_offset = running_size; |
131 | running_size += ctx->chunks[i].compressed_size; |
132 | } |
133 | } |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | static int hap_can_use_tex_in_place(HapContext *ctx) |
139 | { |
140 | int i; |
141 | size_t running_offset = 0; |
142 | for (i = 0; i < ctx->chunk_count; i++) { |
143 | if (ctx->chunks[i].compressed_offset != running_offset |
144 | || ctx->chunks[i].compressor != HAP_COMP_NONE) |
145 | return 0; |
146 | running_offset += ctx->chunks[i].compressed_size; |
147 | } |
148 | return 1; |
149 | } |
150 | |
151 | static int hap_parse_frame_header(AVCodecContext *avctx) |
152 | { |
153 | HapContext *ctx = avctx->priv_data; |
154 | GetByteContext *gbc = &ctx->gbc; |
155 | int section_size; |
156 | enum HapSectionType section_type; |
157 | const char *compressorstr; |
158 | int i, ret; |
159 | |
160 | ret = parse_section_header(gbc, §ion_size, §ion_type); |
161 | if (ret != 0) |
162 | return ret; |
163 | |
164 | if ((avctx->codec_tag == MKTAG('H','a','p','1') && (section_type & 0x0F) != HAP_FMT_RGBDXT1) || |
165 | (avctx->codec_tag == MKTAG('H','a','p','5') && (section_type & 0x0F) != HAP_FMT_RGBADXT5) || |
166 | (avctx->codec_tag == MKTAG('H','a','p','Y') && (section_type & 0x0F) != HAP_FMT_YCOCGDXT5)) { |
167 | av_log(avctx, AV_LOG_ERROR, |
168 | "Invalid texture format %#04x.\n", section_type & 0x0F); |
169 | return AVERROR_INVALIDDATA; |
170 | } |
171 | |
172 | switch (section_type & 0xF0) { |
173 | case HAP_COMP_NONE: |
174 | case HAP_COMP_SNAPPY: |
175 | ret = ff_hap_set_chunk_count(ctx, 1, 1); |
176 | if (ret == 0) { |
177 | ctx->chunks[0].compressor = section_type & 0xF0; |
178 | ctx->chunks[0].compressed_offset = 0; |
179 | ctx->chunks[0].compressed_size = section_size; |
180 | } |
181 | if (ctx->chunks[0].compressor == HAP_COMP_NONE) { |
182 | compressorstr = "none"; |
183 | } else { |
184 | compressorstr = "snappy"; |
185 | } |
186 | break; |
187 | case HAP_COMP_COMPLEX: |
188 | ret = parse_section_header(gbc, §ion_size, §ion_type); |
189 | if (ret == 0 && section_type != HAP_ST_DECODE_INSTRUCTIONS) |
190 | ret = AVERROR_INVALIDDATA; |
191 | if (ret == 0) |
192 | ret = hap_parse_decode_instructions(ctx, section_size); |
193 | compressorstr = "complex"; |
194 | break; |
195 | default: |
196 | ret = AVERROR_INVALIDDATA; |
197 | break; |
198 | } |
199 | |
200 | if (ret != 0) |
201 | return ret; |
202 | |
203 | /* Check the frame is valid and read the uncompressed chunk sizes */ |
204 | ctx->tex_size = 0; |
205 | for (i = 0; i < ctx->chunk_count; i++) { |
206 | HapChunk *chunk = &ctx->chunks[i]; |
207 | |
208 | /* Check the compressed buffer is valid */ |
209 | if (chunk->compressed_offset + chunk->compressed_size > bytestream2_get_bytes_left(gbc)) |
210 | return AVERROR_INVALIDDATA; |
211 | |
212 | /* Chunks are unpacked sequentially, ctx->tex_size is the uncompressed |
213 | * size thus far */ |
214 | chunk->uncompressed_offset = ctx->tex_size; |
215 | |
216 | /* Fill out uncompressed size */ |
217 | if (chunk->compressor == HAP_COMP_SNAPPY) { |
218 | GetByteContext gbc_tmp; |
219 | int64_t uncompressed_size; |
220 | bytestream2_init(&gbc_tmp, gbc->buffer + chunk->compressed_offset, |
221 | chunk->compressed_size); |
222 | uncompressed_size = ff_snappy_peek_uncompressed_length(&gbc_tmp); |
223 | if (uncompressed_size < 0) { |
224 | return uncompressed_size; |
225 | } |
226 | chunk->uncompressed_size = uncompressed_size; |
227 | } else if (chunk->compressor == HAP_COMP_NONE) { |
228 | chunk->uncompressed_size = chunk->compressed_size; |
229 | } else { |
230 | return AVERROR_INVALIDDATA; |
231 | } |
232 | ctx->tex_size += chunk->uncompressed_size; |
233 | } |
234 | |
235 | av_log(avctx, AV_LOG_DEBUG, "%s compressor\n", compressorstr); |
236 | |
237 | return ret; |
238 | } |
239 | |
240 | static int decompress_chunks_thread(AVCodecContext *avctx, void *arg, |
241 | int chunk_nb, int thread_nb) |
242 | { |
243 | HapContext *ctx = avctx->priv_data; |
244 | |
245 | HapChunk *chunk = &ctx->chunks[chunk_nb]; |
246 | GetByteContext gbc; |
247 | uint8_t *dst = ctx->tex_buf + chunk->uncompressed_offset; |
248 | |
249 | bytestream2_init(&gbc, ctx->gbc.buffer + chunk->compressed_offset, chunk->compressed_size); |
250 | |
251 | if (chunk->compressor == HAP_COMP_SNAPPY) { |
252 | int ret; |
253 | int64_t uncompressed_size = ctx->tex_size; |
254 | |
255 | /* Uncompress the frame */ |
256 | ret = ff_snappy_uncompress(&gbc, dst, &uncompressed_size); |
257 | if (ret < 0) { |
258 | av_log(avctx, AV_LOG_ERROR, "Snappy uncompress error\n"); |
259 | return ret; |
260 | } |
261 | } else if (chunk->compressor == HAP_COMP_NONE) { |
262 | bytestream2_get_buffer(&gbc, dst, chunk->compressed_size); |
263 | } |
264 | |
265 | return 0; |
266 | } |
267 | |
268 | static int decompress_texture_thread(AVCodecContext *avctx, void *arg, |
269 | int slice, int thread_nb) |
270 | { |
271 | HapContext *ctx = avctx->priv_data; |
272 | AVFrame *frame = arg; |
273 | const uint8_t *d = ctx->tex_data; |
274 | int w_block = avctx->coded_width / TEXTURE_BLOCK_W; |
275 | int h_block = avctx->coded_height / TEXTURE_BLOCK_H; |
276 | int x, y; |
277 | int start_slice, end_slice; |
278 | int base_blocks_per_slice = h_block / ctx->slice_count; |
279 | int remainder_blocks = h_block % ctx->slice_count; |
280 | |
281 | /* When the frame height (in blocks) doesn't divide evenly between the |
282 | * number of slices, spread the remaining blocks evenly between the first |
283 | * operations */ |
284 | start_slice = slice * base_blocks_per_slice; |
285 | /* Add any extra blocks (one per slice) that have been added before this slice */ |
286 | start_slice += FFMIN(slice, remainder_blocks); |
287 | |
288 | end_slice = start_slice + base_blocks_per_slice; |
289 | /* Add an extra block if there are still remainder blocks to be accounted for */ |
290 | if (slice < remainder_blocks) |
291 | end_slice++; |
292 | |
293 | for (y = start_slice; y < end_slice; y++) { |
294 | uint8_t *p = frame->data[0] + y * frame->linesize[0] * TEXTURE_BLOCK_H; |
295 | int off = y * w_block; |
296 | for (x = 0; x < w_block; x++) { |
297 | ctx->tex_fun(p + x * 16, frame->linesize[0], |
298 | d + (off + x) * ctx->tex_rat); |
299 | } |
300 | } |
301 | |
302 | return 0; |
303 | } |
304 | |
305 | static int hap_decode(AVCodecContext *avctx, void *data, |
306 | int *got_frame, AVPacket *avpkt) |
307 | { |
308 | HapContext *ctx = avctx->priv_data; |
309 | ThreadFrame tframe; |
310 | int ret, i; |
311 | int tex_size; |
312 | |
313 | bytestream2_init(&ctx->gbc, avpkt->data, avpkt->size); |
314 | |
315 | /* Check for section header */ |
316 | ret = hap_parse_frame_header(avctx); |
317 | if (ret < 0) |
318 | return ret; |
319 | |
320 | /* Get the output frame ready to receive data */ |
321 | tframe.f = data; |
322 | ret = ff_thread_get_buffer(avctx, &tframe, 0); |
323 | if (ret < 0) |
324 | return ret; |
325 | if (avctx->codec->update_thread_context) |
326 | ff_thread_finish_setup(avctx); |
327 | |
328 | /* Unpack the DXT texture */ |
329 | if (hap_can_use_tex_in_place(ctx)) { |
330 | /* Only DXTC texture compression in a contiguous block */ |
331 | ctx->tex_data = ctx->gbc.buffer; |
332 | tex_size = bytestream2_get_bytes_left(&ctx->gbc); |
333 | } else { |
334 | /* Perform the second-stage decompression */ |
335 | ret = av_reallocp(&ctx->tex_buf, ctx->tex_size); |
336 | if (ret < 0) |
337 | return ret; |
338 | |
339 | avctx->execute2(avctx, decompress_chunks_thread, NULL, |
340 | ctx->chunk_results, ctx->chunk_count); |
341 | |
342 | for (i = 0; i < ctx->chunk_count; i++) { |
343 | if (ctx->chunk_results[i] < 0) |
344 | return ctx->chunk_results[i]; |
345 | } |
346 | |
347 | ctx->tex_data = ctx->tex_buf; |
348 | tex_size = ctx->tex_size; |
349 | } |
350 | |
351 | if (tex_size < (avctx->coded_width / TEXTURE_BLOCK_W) |
352 | *(avctx->coded_height / TEXTURE_BLOCK_H) |
353 | *ctx->tex_rat) { |
354 | av_log(avctx, AV_LOG_ERROR, "Insufficient data\n"); |
355 | return AVERROR_INVALIDDATA; |
356 | } |
357 | |
358 | /* Use the decompress function on the texture, one block per thread */ |
359 | avctx->execute2(avctx, decompress_texture_thread, tframe.f, NULL, ctx->slice_count); |
360 | |
361 | /* Frame is ready to be output */ |
362 | tframe.f->pict_type = AV_PICTURE_TYPE_I; |
363 | tframe.f->key_frame = 1; |
364 | *got_frame = 1; |
365 | |
366 | return avpkt->size; |
367 | } |
368 | |
369 | static av_cold int hap_init(AVCodecContext *avctx) |
370 | { |
371 | HapContext *ctx = avctx->priv_data; |
372 | const char *texture_name; |
373 | int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx); |
374 | |
375 | if (ret < 0) { |
376 | av_log(avctx, AV_LOG_ERROR, "Invalid video size %dx%d.\n", |
377 | avctx->width, avctx->height); |
378 | return ret; |
379 | } |
380 | |
381 | /* Since codec is based on 4x4 blocks, size is aligned to 4 */ |
382 | avctx->coded_width = FFALIGN(avctx->width, TEXTURE_BLOCK_W); |
383 | avctx->coded_height = FFALIGN(avctx->height, TEXTURE_BLOCK_H); |
384 | |
385 | ff_texturedsp_init(&ctx->dxtc); |
386 | |
387 | switch (avctx->codec_tag) { |
388 | case MKTAG('H','a','p','1'): |
389 | texture_name = "DXT1"; |
390 | ctx->tex_rat = 8; |
391 | ctx->tex_fun = ctx->dxtc.dxt1_block; |
392 | avctx->pix_fmt = AV_PIX_FMT_RGB0; |
393 | break; |
394 | case MKTAG('H','a','p','5'): |
395 | texture_name = "DXT5"; |
396 | ctx->tex_rat = 16; |
397 | ctx->tex_fun = ctx->dxtc.dxt5_block; |
398 | avctx->pix_fmt = AV_PIX_FMT_RGBA; |
399 | break; |
400 | case MKTAG('H','a','p','Y'): |
401 | texture_name = "DXT5-YCoCg-scaled"; |
402 | ctx->tex_rat = 16; |
403 | ctx->tex_fun = ctx->dxtc.dxt5ys_block; |
404 | avctx->pix_fmt = AV_PIX_FMT_RGB0; |
405 | break; |
406 | default: |
407 | return AVERROR_DECODER_NOT_FOUND; |
408 | } |
409 | |
410 | av_log(avctx, AV_LOG_DEBUG, "%s texture\n", texture_name); |
411 | |
412 | ctx->slice_count = av_clip(avctx->thread_count, 1, |
413 | avctx->coded_height / TEXTURE_BLOCK_H); |
414 | |
415 | return 0; |
416 | } |
417 | |
418 | static av_cold int hap_close(AVCodecContext *avctx) |
419 | { |
420 | HapContext *ctx = avctx->priv_data; |
421 | |
422 | ff_hap_free_context(ctx); |
423 | |
424 | return 0; |
425 | } |
426 | |
427 | AVCodec ff_hap_decoder = { |
428 | .name = "hap", |
429 | .long_name = NULL_IF_CONFIG_SMALL("Vidvox Hap"), |
430 | .type = AVMEDIA_TYPE_VIDEO, |
431 | .id = AV_CODEC_ID_HAP, |
432 | .init = hap_init, |
433 | .decode = hap_decode, |
434 | .close = hap_close, |
435 | .priv_data_size = sizeof(HapContext), |
436 | .capabilities = AV_CODEC_CAP_FRAME_THREADS | AV_CODEC_CAP_SLICE_THREADS | |
437 | AV_CODEC_CAP_DR1, |
438 | .caps_internal = FF_CODEC_CAP_INIT_THREADSAFE | |
439 | FF_CODEC_CAP_INIT_CLEANUP, |
440 | }; |
441 |