Skip to content
Snippets Groups Projects
Commit c7e64434 authored by Tom Butterworth's avatar Tom Butterworth Committed by Michael Niedermayer
Browse files

Support the Hap chunked frame format

parent 9837d3b0
No related branches found
No related tags found
No related merge requests found
...@@ -291,8 +291,8 @@ OBJS-$(CONFIG_H264_MMAL_DECODER) += mmaldec.o ...@@ -291,8 +291,8 @@ OBJS-$(CONFIG_H264_MMAL_DECODER) += mmaldec.o
OBJS-$(CONFIG_H264_VDA_DECODER) += vda_h264_dec.o OBJS-$(CONFIG_H264_VDA_DECODER) += vda_h264_dec.o
OBJS-$(CONFIG_H264_QSV_DECODER) += qsvdec_h264.o OBJS-$(CONFIG_H264_QSV_DECODER) += qsvdec_h264.o
OBJS-$(CONFIG_H264_QSV_ENCODER) += qsvenc_h264.o OBJS-$(CONFIG_H264_QSV_ENCODER) += qsvenc_h264.o
OBJS-$(CONFIG_HAP_DECODER) += hapdec.o OBJS-$(CONFIG_HAP_DECODER) += hapdec.o hap.o
OBJS-$(CONFIG_HAP_ENCODER) += hapenc.o OBJS-$(CONFIG_HAP_ENCODER) += hapenc.o hap.o
OBJS-$(CONFIG_HEVC_DECODER) += hevc.o hevc_mvs.o hevc_ps.o hevc_sei.o \ OBJS-$(CONFIG_HEVC_DECODER) += hevc.o hevc_mvs.o hevc_ps.o hevc_sei.o \
hevc_cabac.o hevc_refs.o hevcpred.o \ hevc_cabac.o hevc_refs.o hevcpred.o \
hevcdsp.o hevc_filter.o hevc_parse.o hevc_data.o hevcdsp.o hevc_filter.o hevc_parse.o hevc_data.o
......
/*
* Vidvox Hap utility functions
* Copyright (C) 2015 Tom Butterworth <bangnoise@gmail.com>
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file
* Hap utilities
*/
#include "hap.h"
int ff_hap_set_chunk_count(HapContext *ctx, int count, int first_in_frame)
{
int ret = 0;
if (first_in_frame == 1 && ctx->chunk_count != count) {
int ret = av_reallocp_array(&ctx->chunks, count, sizeof(HapChunk));
if (ret == 0)
ret = av_reallocp_array(&ctx->chunk_results, count, sizeof(int));
if (ret < 0) {
ctx->chunk_count = 0;
} else {
ctx->chunk_count = count;
}
} else if (ctx->chunk_count != count) {
/* If this is not the first chunk count calculated for a frame and a
* different count has already been encountered, then reject the frame:
* each table in the Decode Instructions Container must describe the
* same number of chunks. */
ret = AVERROR_INVALIDDATA;
}
return ret;
}
av_cold void ff_hap_free_context(HapContext *ctx)
{
av_freep(&ctx->tex_buf);
av_freep(&ctx->chunks);
av_freep(&ctx->chunk_results);
}
/* /*
* Vidvox Hap * Vidvox Hap
* Copyright (C) 2015 Vittorio Giovara <vittorio.giovara@gmail.com> * Copyright (C) 2015 Vittorio Giovara <vittorio.giovara@gmail.com>
* Copyright (C) 2015 Tom Butterworth <bangnoise@gmail.com>
* *
* This file is part of FFmpeg. * This file is part of FFmpeg.
* *
...@@ -41,20 +42,39 @@ enum HapCompressor { ...@@ -41,20 +42,39 @@ enum HapCompressor {
HAP_COMP_COMPLEX = 0xC0, HAP_COMP_COMPLEX = 0xC0,
}; };
enum HapSectionType {
HAP_ST_DECODE_INSTRUCTIONS = 0x01,
HAP_ST_COMPRESSOR_TABLE = 0x02,
HAP_ST_SIZE_TABLE = 0x03,
HAP_ST_OFFSET_TABLE = 0x04,
};
typedef struct HapChunk {
enum HapCompressor compressor;
int compressed_offset;
size_t compressed_size;
int uncompressed_offset;
size_t uncompressed_size;
} HapChunk;
typedef struct HapContext { typedef struct HapContext {
AVClass *class; AVClass *class;
TextureDSPContext dxtc; TextureDSPContext dxtc;
GetByteContext gbc; GetByteContext gbc;
int section_type; /* Header type */ enum HapTextureFormat opt_tex_fmt; /* Texture type (encoder only) */
int opt_chunk_count; /* User-requested chunk count (encoder only) */
int chunk_count;
HapChunk *chunks;
int *chunk_results; /* Results from threaded operations */
int tex_rat; /* Compression ratio */ int tex_rat; /* Compression ratio */
const uint8_t *tex_data; /* Compressed texture */ const uint8_t *tex_data; /* Compressed texture */
uint8_t *tex_buf; /* Uncompressed texture */ uint8_t *tex_buf; /* Buffer for compressed texture */
size_t tex_size; /* Size of the compressed texture */ size_t tex_size; /* Size of the compressed texture */
uint8_t *snappied; /* Buffer interacting with snappy */
size_t max_snappy; /* Maximum compressed size for snappy buffer */ size_t max_snappy; /* Maximum compressed size for snappy buffer */
int slice_size; /* Optimal slice size */ int slice_size; /* Optimal slice size */
...@@ -63,4 +83,15 @@ typedef struct HapContext { ...@@ -63,4 +83,15 @@ typedef struct HapContext {
int (*tex_fun)(uint8_t *dst, ptrdiff_t stride, const uint8_t *block); int (*tex_fun)(uint8_t *dst, ptrdiff_t stride, const uint8_t *block);
} HapContext; } HapContext;
/*
* Set the number of chunks in the frame. Returns 0 on success or an error if:
* - first_in_frame is 0 and the number of chunks has changed
* - any other error occurs
*/
int ff_hap_set_chunk_count(HapContext *ctx, int count, int first_in_frame);
/*
* Free resources associated with the context
*/
av_cold void ff_hap_free_context(HapContext *ctx);
#endif /* AVCODEC_HAP_H */ #endif /* AVCODEC_HAP_H */
/* /*
* Vidvox Hap decoder * Vidvox Hap decoder
* Copyright (C) 2015 Vittorio Giovara <vittorio.giovara@gmail.com> * Copyright (C) 2015 Vittorio Giovara <vittorio.giovara@gmail.com>
* Copyright (C) 2015 Tom Butterworth <bangnoise@gmail.com>
* *
* This file is part of FFmpeg. * This file is part of FFmpeg.
* *
...@@ -39,88 +40,227 @@ ...@@ -39,88 +40,227 @@
#include "snappy.h" #include "snappy.h"
#include "texturedsp.h" #include "texturedsp.h"
#include "thread.h" #include "thread.h"
#include "memory.h"
/* The first three bytes are the size of the section past the header, or zero /* The first three bytes are the size of the section past the header, or zero
* if the length is stored in the next long word. The fourth byte in the first * if the length is stored in the next long word. The fourth byte in the first
* long word indicates the type of the current section. */ * long word indicates the type of the current section. */
static int parse_section_header(AVCodecContext *avctx) static int parse_section_header(GetByteContext *gbc, int *section_size,
enum HapSectionType *section_type)
{ {
HapContext *ctx = avctx->priv_data;
GetByteContext *gbc = &ctx->gbc;
int length;
if (bytestream2_get_bytes_left(gbc) < 4) if (bytestream2_get_bytes_left(gbc) < 4)
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
length = bytestream2_get_le24(gbc); *section_size = bytestream2_get_le24(gbc);
*section_type = bytestream2_get_byte(gbc);
ctx->section_type = bytestream2_get_byte(gbc);
if (length == 0) { if (*section_size == 0) {
if (bytestream2_get_bytes_left(gbc) < 4) if (bytestream2_get_bytes_left(gbc) < 4)
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
length = bytestream2_get_le32(gbc);
*section_size = bytestream2_get_le32(gbc);
}
if (*section_size > bytestream2_get_bytes_left(gbc))
return AVERROR_INVALIDDATA;
else
return 0;
}
static int hap_parse_decode_instructions(HapContext *ctx, int size)
{
GetByteContext *gbc = &ctx->gbc;
int section_size;
enum HapSectionType section_type;
int is_first_table = 1, had_offsets = 0, had_compressors = 0, had_sizes = 0;
int i, ret;
while (size > 0) {
int stream_remaining = bytestream2_get_bytes_left(gbc);
ret = parse_section_header(gbc, &section_size, &section_type);
if (ret != 0)
return ret;
size -= stream_remaining - bytestream2_get_bytes_left(gbc);
switch (section_type) {
case HAP_ST_COMPRESSOR_TABLE:
ret = ff_hap_set_chunk_count(ctx, section_size, is_first_table);
if (ret != 0)
return ret;
for (i = 0; i < section_size; i++) {
ctx->chunks[i].compressor = bytestream2_get_byte(gbc) << 4;
}
had_compressors = 1;
is_first_table = 0;
break;
case HAP_ST_SIZE_TABLE:
ret = ff_hap_set_chunk_count(ctx, section_size / 4, is_first_table);
if (ret != 0)
return ret;
for (i = 0; i < section_size / 4; i++) {
ctx->chunks[i].compressed_size = bytestream2_get_le32(gbc);
}
had_sizes = 1;
is_first_table = 0;
break;
case HAP_ST_OFFSET_TABLE:
ret = ff_hap_set_chunk_count(ctx, section_size / 4, is_first_table);
if (ret != 0)
return ret;
for (i = 0; i < section_size / 4; i++) {
ctx->chunks[i].compressed_offset = bytestream2_get_le32(gbc);
}
had_offsets = 1;
is_first_table = 0;
break;
default:
break;
}
size -= section_size;
} }
if (length > bytestream2_get_bytes_left(gbc) || length == 0) if (!had_sizes || !had_compressors)
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
return length; /* The offsets table is optional. If not present than calculate offsets by
* summing the sizes of preceding chunks. */
if (!had_offsets) {
size_t running_size = 0;
for (i = 0; i < ctx->chunk_count; i++) {
ctx->chunks[i].compressed_offset = running_size;
running_size += ctx->chunks[i].compressed_size;
}
}
return 0;
}
static int hap_can_use_tex_in_place(HapContext *ctx)
{
int i;
size_t running_offset = 0;
for (i = 0; i < ctx->chunk_count; i++) {
if (ctx->chunks[i].compressed_offset != running_offset
|| ctx->chunks[i].compressor != HAP_COMP_NONE)
return 0;
running_offset += ctx->chunks[i].compressed_size;
}
return 1;
} }
/* Prepare the texture to be decompressed */ static int hap_parse_frame_header(AVCodecContext *avctx)
static int setup_texture(AVCodecContext *avctx, size_t length)
{ {
HapContext *ctx = avctx->priv_data; HapContext *ctx = avctx->priv_data;
GetByteContext *gbc = &ctx->gbc; GetByteContext *gbc = &ctx->gbc;
int64_t snappy_size; int section_size;
enum HapSectionType section_type;
const char *compressorstr; const char *compressorstr;
int ret; int i, ret;
ret = parse_section_header(gbc, &section_size, &section_type);
if (ret != 0)
return ret;
if ((avctx->codec_tag == MKTAG('H','a','p','1') && (ctx->section_type & 0x0F) != HAP_FMT_RGBDXT1) if ((avctx->codec_tag == MKTAG('H','a','p','1') && (section_type & 0x0F) != HAP_FMT_RGBDXT1)
|| (avctx->codec_tag == MKTAG('H','a','p','5') && (ctx->section_type & 0x0F) != HAP_FMT_RGBADXT5) || (avctx->codec_tag == MKTAG('H','a','p','5') && (section_type & 0x0F) != HAP_FMT_RGBADXT5)
|| (avctx->codec_tag == MKTAG('H','a','p','Y') && (ctx->section_type & 0x0F) != HAP_FMT_YCOCGDXT5)) { || (avctx->codec_tag == MKTAG('H','a','p','Y') && (section_type & 0x0F) != HAP_FMT_YCOCGDXT5)) {
av_log(avctx, AV_LOG_ERROR, "Invalid texture format %#04x.\n", ctx->section_type & 0x0F); av_log(avctx, AV_LOG_ERROR, "Invalid texture format %#04x.\n", section_type & 0x0F);
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
} }
switch (ctx->section_type & 0xF0) { switch (section_type & 0xF0) {
case HAP_COMP_NONE: case HAP_COMP_NONE:
/* Only DXTC texture compression */ case HAP_COMP_SNAPPY:
ctx->tex_data = gbc->buffer; ret = ff_hap_set_chunk_count(ctx, 1, 1);
ctx->tex_size = length; if (ret == 0) {
compressorstr = "none"; ctx->chunks[0].compressor = section_type & 0xF0;
break; ctx->chunks[0].compressed_offset = 0;
case HAP_COMP_SNAPPY: ctx->chunks[0].compressed_size = section_size;
snappy_size = ff_snappy_peek_uncompressed_length(gbc); }
ret = av_reallocp(&ctx->snappied, snappy_size); if (ctx->chunks[0].compressor == HAP_COMP_NONE) {
if (ret < 0) { compressorstr = "none";
return ret; } else {
compressorstr = "snappy";
}
break;
case HAP_COMP_COMPLEX:
ret = parse_section_header(gbc, &section_size, &section_type);
if (ret == 0 && section_type != HAP_ST_DECODE_INSTRUCTIONS)
ret = AVERROR_INVALIDDATA;
if (ret == 0)
ret = hap_parse_decode_instructions(ctx, section_size);
compressorstr = "complex";
break;
default:
ret = AVERROR_INVALIDDATA;
break;
}
if (ret != 0)
return ret;
/* Check the frame is valid and read the uncompressed chunk sizes */
ctx->tex_size = 0;
for (i = 0; i < ctx->chunk_count; i++) {
HapChunk *chunk = &ctx->chunks[i];
/* Check the compressed buffer is valid */
if (chunk->compressed_offset + chunk->compressed_size > bytestream2_get_bytes_left(gbc))
return AVERROR_INVALIDDATA;
/* Chunks are unpacked sequentially, ctx->tex_size is the uncompressed
* size thus far */
chunk->uncompressed_offset = ctx->tex_size;
/* Fill out uncompressed size */
if (chunk->compressor == HAP_COMP_SNAPPY) {
GetByteContext gbc_tmp;
int64_t uncompressed_size;
bytestream2_init(&gbc_tmp, gbc->buffer + chunk->compressed_offset,
chunk->compressed_size);
uncompressed_size = ff_snappy_peek_uncompressed_length(&gbc_tmp);
if (uncompressed_size < 0) {
return uncompressed_size;
}
chunk->uncompressed_size = uncompressed_size;
} else if (chunk->compressor == HAP_COMP_NONE) {
chunk->uncompressed_size = chunk->compressed_size;
} else {
return AVERROR_INVALIDDATA;
} }
ctx->tex_size += chunk->uncompressed_size;
}
av_log(avctx, AV_LOG_DEBUG, "%s compressor\n", compressorstr);
return ret;
}
static int decompress_chunks_thread(AVCodecContext *avctx, void *arg,
int chunk_nb, int thread_nb)
{
HapContext *ctx = avctx->priv_data;
HapChunk *chunk = &ctx->chunks[chunk_nb];
GetByteContext gbc;
uint8_t *dst = ctx->tex_buf + chunk->uncompressed_offset;
bytestream2_init(&gbc, ctx->gbc.buffer + chunk->compressed_offset, chunk->compressed_size);
if (chunk->compressor == HAP_COMP_SNAPPY) {
int ret;
int64_t uncompressed_size = ctx->tex_size;
/* Uncompress the frame */ /* Uncompress the frame */
ret = ff_snappy_uncompress(gbc, ctx->snappied, &snappy_size); ret = ff_snappy_uncompress(&gbc, dst, &uncompressed_size);
if (ret < 0) { if (ret < 0) {
av_log(avctx, AV_LOG_ERROR, "Snappy uncompress error\n"); av_log(avctx, AV_LOG_ERROR, "Snappy uncompress error\n");
return ret; return ret;
} }
} else if (chunk->compressor == HAP_COMP_NONE) {
ctx->tex_data = ctx->snappied; bytestream2_get_buffer(&gbc, dst, chunk->compressed_size);
ctx->tex_size = snappy_size;
compressorstr = "snappy";
break;
case HAP_COMP_COMPLEX:
compressorstr = "complex";
avpriv_request_sample(avctx, "Complex Hap compressor");
return AVERROR_PATCHWELCOME;
break;
default:
av_log(avctx, AV_LOG_ERROR,
"Invalid compressor mode %02X.\n", ctx->section_type);
return AVERROR_INVALIDDATA;
} }
av_log(avctx, AV_LOG_DEBUG, "%s compressor\n", compressorstr);
return 0; return 0;
} }
...@@ -157,7 +297,7 @@ static int hap_decode(AVCodecContext *avctx, void *data, ...@@ -157,7 +297,7 @@ static int hap_decode(AVCodecContext *avctx, void *data,
{ {
HapContext *ctx = avctx->priv_data; HapContext *ctx = avctx->priv_data;
ThreadFrame tframe; ThreadFrame tframe;
int ret, length; int ret, i;
int slices = FFMIN(avctx->thread_count, int slices = FFMIN(avctx->thread_count,
avctx->coded_height / TEXTURE_BLOCK_H); avctx->coded_height / TEXTURE_BLOCK_H);
...@@ -166,14 +306,7 @@ static int hap_decode(AVCodecContext *avctx, void *data, ...@@ -166,14 +306,7 @@ static int hap_decode(AVCodecContext *avctx, void *data,
bytestream2_init(&ctx->gbc, avpkt->data, avpkt->size); bytestream2_init(&ctx->gbc, avpkt->data, avpkt->size);
/* Check for section header */ /* Check for section header */
length = parse_section_header(avctx); ret = hap_parse_frame_header(avctx);
if (length < 0) {
av_log(avctx, AV_LOG_ERROR, "Frame is too small.\n");
return length;
}
/* Prepare the texture buffer and decompress function */
ret = setup_texture(avctx, length);
if (ret < 0) if (ret < 0)
return ret; return ret;
...@@ -185,6 +318,27 @@ static int hap_decode(AVCodecContext *avctx, void *data, ...@@ -185,6 +318,27 @@ static int hap_decode(AVCodecContext *avctx, void *data,
if (avctx->codec->update_thread_context) if (avctx->codec->update_thread_context)
ff_thread_finish_setup(avctx); ff_thread_finish_setup(avctx);
/* Unpack the DXT texture */
if (hap_can_use_tex_in_place(ctx)) {
/* Only DXTC texture compression in a contiguous block */
ctx->tex_data = ctx->gbc.buffer;
} else {
/* Perform the second-stage decompression */
ret = av_reallocp(&ctx->tex_buf, ctx->tex_size);
if (ret < 0)
return ret;
avctx->execute2(avctx, decompress_chunks_thread, NULL,
ctx->chunk_results, ctx->chunk_count);
for (i = 0; i < ctx->chunk_count; i++) {
if (ctx->chunk_results[i] < 0)
return ctx->chunk_results[i];
}
ctx->tex_data = ctx->tex_buf;
}
/* Use the decompress function on the texture, one block per thread */ /* Use the decompress function on the texture, one block per thread */
avctx->execute2(avctx, decompress_texture_thread, tframe.f, NULL, slices); avctx->execute2(avctx, decompress_texture_thread, tframe.f, NULL, slices);
...@@ -246,7 +400,7 @@ static av_cold int hap_close(AVCodecContext *avctx) ...@@ -246,7 +400,7 @@ static av_cold int hap_close(AVCodecContext *avctx)
{ {
HapContext *ctx = avctx->priv_data; HapContext *ctx = avctx->priv_data;
av_freep(&ctx->snappied); ff_hap_free_context(ctx);
return 0; return 0;
} }
......
/* /*
* Vidvox Hap encoder * Vidvox Hap encoder
* Copyright (C) 2015 Vittorio Giovara <vittorio.giovara@gmail.com> * Copyright (C) 2015 Vittorio Giovara <vittorio.giovara@gmail.com>
* Copyright (C) 2015 Tom Butterworth <bangnoise@gmail.com>
* *
* This file is part of FFmpeg. * This file is part of FFmpeg.
* *
...@@ -42,8 +43,12 @@ ...@@ -42,8 +43,12 @@
#include "internal.h" #include "internal.h"
#include "texturedsp.h" #include "texturedsp.h"
/* A fixed header size allows to skip a memcpy */ #define HAP_MAX_CHUNKS 64
#define HEADER_SIZE 8
enum HapHeaderLength {
HAP_HDR_4_BYTE = 4,
HAP_HDR_8_BYTE = 8,
};
static void compress_texture(AVCodecContext *avctx, const AVFrame *f) static void compress_texture(AVCodecContext *avctx, const AVFrame *f)
{ {
...@@ -60,13 +65,131 @@ static void compress_texture(AVCodecContext *avctx, const AVFrame *f) ...@@ -60,13 +65,131 @@ static void compress_texture(AVCodecContext *avctx, const AVFrame *f)
} }
} }
/* section_length does not include the header */
static void hap_write_section_header(PutByteContext *pbc,
enum HapHeaderLength header_length,
int section_length,
enum HapSectionType section_type)
{
/* The first three bytes are the length of the section (not including the
* header) or zero if using an eight-byte header.
* For an eight-byte header, the length is in the last four bytes.
* The fourth byte stores the section type. */
bytestream2_put_le24(pbc, header_length == HAP_HDR_8_BYTE ? 0 : section_length);
bytestream2_put_byte(pbc, section_type);
if (header_length == HAP_HDR_8_BYTE) {
bytestream2_put_le32(pbc, section_length);
}
}
static int hap_compress_frame(AVCodecContext *avctx, uint8_t *dst)
{
HapContext *ctx = avctx->priv_data;
int i, final_size = 0;
for (i = 0; i < ctx->chunk_count; i++) {
HapChunk *chunk = &ctx->chunks[i];
uint8_t *chunk_src, *chunk_dst;
int ret;
if (i == 0) {
chunk->compressed_offset = 0;
} else {
chunk->compressed_offset = ctx->chunks[i-1].compressed_offset
+ ctx->chunks[i-1].compressed_size;
}
chunk->uncompressed_size = ctx->tex_size / ctx->chunk_count;
chunk->uncompressed_offset = i * chunk->uncompressed_size;
chunk->compressed_size = ctx->max_snappy;
chunk_src = ctx->tex_buf + chunk->uncompressed_offset;
chunk_dst = dst + chunk->compressed_offset;
/* Compress with snappy too, write directly on packet buffer. */
ret = snappy_compress(chunk_src, chunk->uncompressed_size,
chunk_dst, &chunk->compressed_size);
if (ret != SNAPPY_OK) {
av_log(avctx, AV_LOG_ERROR, "Snappy compress error.\n");
return AVERROR_BUG;
}
/* If there is no gain from snappy, just use the raw texture. */
if (chunk->compressed_size >= chunk->uncompressed_size) {
av_log(avctx, AV_LOG_VERBOSE,
"Snappy buffer bigger than uncompressed (%lu >= %lu bytes).\n",
chunk->compressed_size, chunk->uncompressed_size);
memcpy(chunk_dst, chunk_src, chunk->uncompressed_size);
chunk->compressor = HAP_COMP_NONE;
chunk->compressed_size = chunk->uncompressed_size;
} else {
chunk->compressor = HAP_COMP_SNAPPY;
}
final_size += chunk->compressed_size;
}
return final_size;
}
static int hap_decode_instructions_length(HapContext *ctx)
{
/* = Second-Stage Compressor Table + Chunk Size Table + headers for both sections
* = chunk_count + (4 * chunk_count) + 4 + 4 */
return (5 * ctx->chunk_count) + 8;
}
static int hap_header_length(HapContext *ctx)
{
/* Top section header (long version) */
int length = HAP_HDR_8_BYTE;
if (ctx->chunk_count > 1) {
/* Decode Instructions header (short) + Decode Instructions Container */
length += HAP_HDR_4_BYTE + hap_decode_instructions_length(ctx);
}
return length;
}
static void hap_write_frame_header(HapContext *ctx, uint8_t *dst, int frame_length)
{
PutByteContext pbc;
int i;
bytestream2_init_writer(&pbc, dst, frame_length);
if (ctx->chunk_count == 1) {
/* Write a simple header */
hap_write_section_header(&pbc, HAP_HDR_8_BYTE, frame_length - 8,
ctx->chunks[0].compressor | ctx->opt_tex_fmt);
} else {
/* Write a complex header with Decode Instructions Container */
hap_write_section_header(&pbc, HAP_HDR_8_BYTE, frame_length - 8,
HAP_COMP_COMPLEX | ctx->opt_tex_fmt);
hap_write_section_header(&pbc, HAP_HDR_4_BYTE, hap_decode_instructions_length(ctx),
HAP_ST_DECODE_INSTRUCTIONS);
hap_write_section_header(&pbc, HAP_HDR_4_BYTE, ctx->chunk_count,
HAP_ST_COMPRESSOR_TABLE);
for (i = 0; i < ctx->chunk_count; i++) {
bytestream2_put_byte(&pbc, ctx->chunks[i].compressor >> 4);
}
hap_write_section_header(&pbc, HAP_HDR_4_BYTE, ctx->chunk_count * 4,
HAP_ST_SIZE_TABLE);
for (i = 0; i < ctx->chunk_count; i++) {
bytestream2_put_le32(&pbc, ctx->chunks[i].compressed_size);
}
}
}
static int hap_encode(AVCodecContext *avctx, AVPacket *pkt, static int hap_encode(AVCodecContext *avctx, AVPacket *pkt,
const AVFrame *frame, int *got_packet) const AVFrame *frame, int *got_packet)
{ {
HapContext *ctx = avctx->priv_data; HapContext *ctx = avctx->priv_data;
size_t final_size = ctx->max_snappy; int header_length = hap_header_length(ctx);
int ret, comp = HAP_COMP_SNAPPY; int final_data_size, ret;
int pktsize = FFMAX(ctx->tex_size, ctx->max_snappy) + HEADER_SIZE; int pktsize = FFMAX(ctx->tex_size, ctx->max_snappy * ctx->chunk_count) + header_length;
/* Allocate maximum size packet, shrink later. */ /* Allocate maximum size packet, shrink later. */
ret = ff_alloc_packet(pkt, pktsize); ret = ff_alloc_packet(pkt, pktsize);
...@@ -76,30 +199,15 @@ static int hap_encode(AVCodecContext *avctx, AVPacket *pkt, ...@@ -76,30 +199,15 @@ static int hap_encode(AVCodecContext *avctx, AVPacket *pkt,
/* DXTC compression. */ /* DXTC compression. */
compress_texture(avctx, frame); compress_texture(avctx, frame);
/* Compress with snappy too, write directly on packet buffer. */ /* Compress (using Snappy) the frame */
ret = snappy_compress(ctx->tex_buf, ctx->tex_size, final_data_size = hap_compress_frame(avctx, pkt->data + header_length);
pkt->data + HEADER_SIZE, &final_size); if (final_data_size < 0)
if (ret != SNAPPY_OK) { return final_data_size;
av_log(avctx, AV_LOG_ERROR, "Snappy compress error.\n");
return AVERROR_BUG;
}
/* If there is no gain from snappy, just use the raw texture. */
if (final_size > ctx->tex_size) {
comp = HAP_COMP_NONE;
av_log(avctx, AV_LOG_VERBOSE,
"Snappy buffer bigger than uncompressed (%lu > %lu bytes).\n",
final_size, ctx->tex_size);
memcpy(pkt->data + HEADER_SIZE, ctx->tex_buf, ctx->tex_size);
final_size = ctx->tex_size;
}
/* Write header at the start. */ /* Write header at the start. */
AV_WL24(pkt->data, 0); hap_write_frame_header(ctx, pkt->data, final_data_size + header_length);
AV_WL32(pkt->data + 4, final_size);
pkt->data[3] = comp | ctx->section_type;
av_shrink_packet(pkt, final_size + HEADER_SIZE); av_shrink_packet(pkt, final_data_size + header_length);
pkt->flags |= AV_PKT_FLAG_KEY; pkt->flags |= AV_PKT_FLAG_KEY;
*got_packet = 1; *got_packet = 1;
return 0; return 0;
...@@ -109,6 +217,7 @@ static av_cold int hap_init(AVCodecContext *avctx) ...@@ -109,6 +217,7 @@ static av_cold int hap_init(AVCodecContext *avctx)
{ {
HapContext *ctx = avctx->priv_data; HapContext *ctx = avctx->priv_data;
int ratio; int ratio;
int corrected_chunk_count;
int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx); int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx);
if (ret < 0) { if (ret < 0) {
...@@ -125,7 +234,7 @@ static av_cold int hap_init(AVCodecContext *avctx) ...@@ -125,7 +234,7 @@ static av_cold int hap_init(AVCodecContext *avctx)
ff_texturedspenc_init(&ctx->dxtc); ff_texturedspenc_init(&ctx->dxtc);
switch (ctx->section_type & 0x0F) { switch (ctx->opt_tex_fmt) {
case HAP_FMT_RGBDXT1: case HAP_FMT_RGBDXT1:
ratio = 8; ratio = 8;
avctx->codec_tag = MKTAG('H', 'a', 'p', '1'); avctx->codec_tag = MKTAG('H', 'a', 'p', '1');
...@@ -142,7 +251,7 @@ static av_cold int hap_init(AVCodecContext *avctx) ...@@ -142,7 +251,7 @@ static av_cold int hap_init(AVCodecContext *avctx)
ctx->tex_fun = ctx->dxtc.dxt5ys_block; ctx->tex_fun = ctx->dxtc.dxt5ys_block;
break; break;
default: default:
av_log(avctx, AV_LOG_ERROR, "Invalid format %02X\n", ctx->section_type); av_log(avctx, AV_LOG_ERROR, "Invalid format %02X\n", ctx->opt_tex_fmt);
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
} }
...@@ -150,7 +259,21 @@ static av_cold int hap_init(AVCodecContext *avctx) ...@@ -150,7 +259,21 @@ static av_cold int hap_init(AVCodecContext *avctx)
* beforehand the final size of the uncompressed buffer. */ * beforehand the final size of the uncompressed buffer. */
ctx->tex_size = FFALIGN(avctx->width, TEXTURE_BLOCK_W) * ctx->tex_size = FFALIGN(avctx->width, TEXTURE_BLOCK_W) *
FFALIGN(avctx->height, TEXTURE_BLOCK_H) * 4 / ratio; FFALIGN(avctx->height, TEXTURE_BLOCK_H) * 4 / ratio;
ctx->max_snappy = snappy_max_compressed_length(ctx->tex_size);
/* Round the chunk count to divide evenly on DXT block edges */
corrected_chunk_count = av_clip(ctx->opt_chunk_count, 1, HAP_MAX_CHUNKS);
while ((ctx->tex_size / (64 / ratio)) % corrected_chunk_count != 0) {
corrected_chunk_count--;
}
if (corrected_chunk_count != ctx->opt_chunk_count) {
av_log(avctx, AV_LOG_INFO, "%d chunks requested but %d used.\n",
ctx->opt_chunk_count, corrected_chunk_count);
}
ret = ff_hap_set_chunk_count(ctx, corrected_chunk_count, 1);
if (ret != 0)
return ret;
ctx->max_snappy = snappy_max_compressed_length(ctx->tex_size / corrected_chunk_count);
ctx->tex_buf = av_malloc(ctx->tex_size); ctx->tex_buf = av_malloc(ctx->tex_size);
if (!ctx->tex_buf) if (!ctx->tex_buf)
...@@ -163,7 +286,7 @@ static av_cold int hap_close(AVCodecContext *avctx) ...@@ -163,7 +286,7 @@ static av_cold int hap_close(AVCodecContext *avctx)
{ {
HapContext *ctx = avctx->priv_data; HapContext *ctx = avctx->priv_data;
av_freep(&ctx->tex_buf); ff_hap_free_context(ctx);
return 0; return 0;
} }
...@@ -171,11 +294,11 @@ static av_cold int hap_close(AVCodecContext *avctx) ...@@ -171,11 +294,11 @@ static av_cold int hap_close(AVCodecContext *avctx)
#define OFFSET(x) offsetof(HapContext, x) #define OFFSET(x) offsetof(HapContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = { static const AVOption options[] = {
{ "format", NULL, OFFSET(section_type), AV_OPT_TYPE_INT, { .i64 = HAP_FMT_RGBDXT1 }, HAP_FMT_RGBDXT1, HAP_FMT_YCOCGDXT5, FLAGS, "format" }, { "format", NULL, OFFSET(opt_tex_fmt), AV_OPT_TYPE_INT, { .i64 = HAP_FMT_RGBDXT1 }, HAP_FMT_RGBDXT1, HAP_FMT_YCOCGDXT5, FLAGS, "format" },
{ "hap", "Hap 1 (DXT1 textures)", 0, AV_OPT_TYPE_CONST, { .i64 = HAP_FMT_RGBDXT1 }, 0, 0, FLAGS, "format" }, { "hap", "Hap 1 (DXT1 textures)", 0, AV_OPT_TYPE_CONST, { .i64 = HAP_FMT_RGBDXT1 }, 0, 0, FLAGS, "format" },
{ "hap_alpha", "Hap Alpha (DXT5 textures)", 0, AV_OPT_TYPE_CONST, { .i64 = HAP_FMT_RGBADXT5 }, 0, 0, FLAGS, "format" }, { "hap_alpha", "Hap Alpha (DXT5 textures)", 0, AV_OPT_TYPE_CONST, { .i64 = HAP_FMT_RGBADXT5 }, 0, 0, FLAGS, "format" },
{ "hap_q", "Hap Q (DXT5-YCoCg textures)", 0, AV_OPT_TYPE_CONST, { .i64 = HAP_FMT_YCOCGDXT5 }, 0, 0, FLAGS, "format" }, { "hap_q", "Hap Q (DXT5-YCoCg textures)", 0, AV_OPT_TYPE_CONST, { .i64 = HAP_FMT_YCOCGDXT5 }, 0, 0, FLAGS, "format" },
{ "chunks", "chunk count", OFFSET(opt_chunk_count), AV_OPT_TYPE_INT, {.i64 = 1 }, 1, HAP_MAX_CHUNKS, FLAGS, },
{ NULL }, { NULL },
}; };
......
...@@ -177,6 +177,9 @@ fate-hap5: CMD = framecrc -i $(TARGET_SAMPLES)/hap/hap5.mov ...@@ -177,6 +177,9 @@ fate-hap5: CMD = framecrc -i $(TARGET_SAMPLES)/hap/hap5.mov
FATE_HAP += fate-hapy FATE_HAP += fate-hapy
fate-hapy: CMD = framecrc -i $(TARGET_SAMPLES)/hap/hapy.mov fate-hapy: CMD = framecrc -i $(TARGET_SAMPLES)/hap/hapy.mov
FATE_HAP += fate-hap-chunk
fate-hap-chunk: CMD = framecrc -i $(TARGET_SAMPLES)/hap/hapy-12-chunks.mov
FATE_SAMPLES_AVCONV-$(call DEMDEC, MOV, HAP) += $(FATE_HAP) FATE_SAMPLES_AVCONV-$(call DEMDEC, MOV, HAP) += $(FATE_HAP)
fate-hap: $(FATE_HAP) fate-hap: $(FATE_HAP)
......
#tb 0: 1/30
0, 0, 0, 1, 16384, 0x096d409e
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment