FFmpeg  4.0
hapenc.c
Go to the documentation of this file.
1 /*
2  * Vidvox Hap encoder
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 encoder
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 #include "snappy-c.h"
34 
35 #include "libavutil/frame.h"
36 #include "libavutil/imgutils.h"
37 #include "libavutil/intreadwrite.h"
38 #include "libavutil/opt.h"
39 
40 #include "avcodec.h"
41 #include "bytestream.h"
42 #include "hap.h"
43 #include "internal.h"
44 #include "texturedsp.h"
45 
46 #define HAP_MAX_CHUNKS 64
47 
49  /* Short header: four bytes with a 24 bit size value */
51  /* Long header: eight bytes with a 32 bit size value */
53 };
54 
55 static int compress_texture(AVCodecContext *avctx, uint8_t *out, int out_length, const AVFrame *f)
56 {
57  HapContext *ctx = avctx->priv_data;
58  int i, j;
59 
60  if (ctx->tex_size > out_length)
62 
63  for (j = 0; j < avctx->height; j += 4) {
64  for (i = 0; i < avctx->width; i += 4) {
65  uint8_t *p = f->data[0] + i * 4 + j * f->linesize[0];
66  const int step = ctx->tex_fun(out, f->linesize[0], p);
67  out += step;
68  }
69  }
70 
71  return 0;
72 }
73 
74 /* section_length does not include the header */
76  enum HapHeaderLength header_length,
77  int section_length,
78  enum HapSectionType section_type)
79 {
80  /* The first three bytes are the length of the section (not including the
81  * header) or zero if using an eight-byte header.
82  * For an eight-byte header, the length is in the last four bytes.
83  * The fourth byte stores the section type. */
84  bytestream2_put_le24(pbc, header_length == HAP_HDR_LONG ? 0 : section_length);
85  bytestream2_put_byte(pbc, section_type);
86 
87  if (header_length == HAP_HDR_LONG) {
88  bytestream2_put_le32(pbc, section_length);
89  }
90 }
91 
92 static int hap_compress_frame(AVCodecContext *avctx, uint8_t *dst)
93 {
94  HapContext *ctx = avctx->priv_data;
95  int i, final_size = 0;
96 
97  for (i = 0; i < ctx->chunk_count; i++) {
98  HapChunk *chunk = &ctx->chunks[i];
99  uint8_t *chunk_src, *chunk_dst;
100  int ret;
101 
102  if (i == 0) {
103  chunk->compressed_offset = 0;
104  } else {
105  chunk->compressed_offset = ctx->chunks[i-1].compressed_offset
106  + ctx->chunks[i-1].compressed_size;
107  }
108  chunk->uncompressed_size = ctx->tex_size / ctx->chunk_count;
109  chunk->uncompressed_offset = i * chunk->uncompressed_size;
110  chunk->compressed_size = ctx->max_snappy;
111  chunk_src = ctx->tex_buf + chunk->uncompressed_offset;
112  chunk_dst = dst + chunk->compressed_offset;
113 
114  /* Compress with snappy too, write directly on packet buffer. */
115  ret = snappy_compress(chunk_src, chunk->uncompressed_size,
116  chunk_dst, &chunk->compressed_size);
117  if (ret != SNAPPY_OK) {
118  av_log(avctx, AV_LOG_ERROR, "Snappy compress error.\n");
119  return AVERROR_BUG;
120  }
121 
122  /* If there is no gain from snappy, just use the raw texture. */
123  if (chunk->compressed_size >= chunk->uncompressed_size) {
124  av_log(avctx, AV_LOG_VERBOSE,
125  "Snappy buffer bigger than uncompressed (%"SIZE_SPECIFIER" >= %"SIZE_SPECIFIER" bytes).\n",
126  chunk->compressed_size, chunk->uncompressed_size);
127  memcpy(chunk_dst, chunk_src, chunk->uncompressed_size);
128  chunk->compressor = HAP_COMP_NONE;
129  chunk->compressed_size = chunk->uncompressed_size;
130  } else {
131  chunk->compressor = HAP_COMP_SNAPPY;
132  }
133 
134  final_size += chunk->compressed_size;
135  }
136 
137  return final_size;
138 }
139 
141 {
142  /* Second-Stage Compressor Table (one byte per entry)
143  * + Chunk Size Table (four bytes per entry)
144  * + headers for both sections (short versions)
145  * = chunk_count + (4 * chunk_count) + 4 + 4 */
146  return (5 * ctx->chunk_count) + 8;
147 }
148 
150 {
151  /* Top section header (long version) */
152  int length = HAP_HDR_LONG;
153 
154  if (ctx->chunk_count > 1) {
155  /* Decode Instructions header (short) + Decode Instructions Container */
157  }
158 
159  return length;
160 }
161 
162 static void hap_write_frame_header(HapContext *ctx, uint8_t *dst, int frame_length)
163 {
164  PutByteContext pbc;
165  int i;
166 
167  bytestream2_init_writer(&pbc, dst, frame_length);
168  if (ctx->chunk_count == 1) {
169  /* Write a simple header */
170  hap_write_section_header(&pbc, HAP_HDR_LONG, frame_length - 8,
171  ctx->chunks[0].compressor | ctx->opt_tex_fmt);
172  } else {
173  /* Write a complex header with Decode Instructions Container */
174  hap_write_section_header(&pbc, HAP_HDR_LONG, frame_length - 8,
180 
181  for (i = 0; i < ctx->chunk_count; i++) {
182  bytestream2_put_byte(&pbc, ctx->chunks[i].compressor >> 4);
183  }
184 
187 
188  for (i = 0; i < ctx->chunk_count; i++) {
189  bytestream2_put_le32(&pbc, ctx->chunks[i].compressed_size);
190  }
191  }
192 }
193 
194 static int hap_encode(AVCodecContext *avctx, AVPacket *pkt,
195  const AVFrame *frame, int *got_packet)
196 {
197  HapContext *ctx = avctx->priv_data;
198  int header_length = hap_header_length(ctx);
199  int final_data_size, ret;
200  int pktsize = FFMAX(ctx->tex_size, ctx->max_snappy * ctx->chunk_count) + header_length;
201 
202  /* Allocate maximum size packet, shrink later. */
203  ret = ff_alloc_packet2(avctx, pkt, pktsize, header_length);
204  if (ret < 0)
205  return ret;
206 
207  if (ctx->opt_compressor == HAP_COMP_NONE) {
208  /* DXTC compression directly to the packet buffer. */
209  ret = compress_texture(avctx, pkt->data + header_length, pkt->size - header_length, frame);
210  if (ret < 0)
211  return ret;
212 
213  ctx->chunks[0].compressor = HAP_COMP_NONE;
214  final_data_size = ctx->tex_size;
215  } else {
216  /* DXTC compression. */
217  ret = compress_texture(avctx, ctx->tex_buf, ctx->tex_size, frame);
218  if (ret < 0)
219  return ret;
220 
221  /* Compress (using Snappy) the frame */
222  final_data_size = hap_compress_frame(avctx, pkt->data + header_length);
223  if (final_data_size < 0)
224  return final_data_size;
225  }
226 
227  /* Write header at the start. */
228  hap_write_frame_header(ctx, pkt->data, final_data_size + header_length);
229 
230  av_shrink_packet(pkt, final_data_size + header_length);
231  pkt->flags |= AV_PKT_FLAG_KEY;
232  *got_packet = 1;
233  return 0;
234 }
235 
236 static av_cold int hap_init(AVCodecContext *avctx)
237 {
238  HapContext *ctx = avctx->priv_data;
239  int ratio;
240  int corrected_chunk_count;
241  int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx);
242 
243  if (ret < 0) {
244  av_log(avctx, AV_LOG_ERROR, "Invalid video size %dx%d.\n",
245  avctx->width, avctx->height);
246  return ret;
247  }
248 
249  if (avctx->width % 4 || avctx->height % 4) {
250  av_log(avctx, AV_LOG_ERROR, "Video size %dx%d is not multiple of 4.\n",
251  avctx->width, avctx->height);
252  return AVERROR_INVALIDDATA;
253  }
254 
256 
257  switch (ctx->opt_tex_fmt) {
258  case HAP_FMT_RGBDXT1:
259  ratio = 8;
260  avctx->codec_tag = MKTAG('H', 'a', 'p', '1');
261  avctx->bits_per_coded_sample = 24;
262  ctx->tex_fun = ctx->dxtc.dxt1_block;
263  break;
264  case HAP_FMT_RGBADXT5:
265  ratio = 4;
266  avctx->codec_tag = MKTAG('H', 'a', 'p', '5');
267  avctx->bits_per_coded_sample = 32;
268  ctx->tex_fun = ctx->dxtc.dxt5_block;
269  break;
270  case HAP_FMT_YCOCGDXT5:
271  ratio = 4;
272  avctx->codec_tag = MKTAG('H', 'a', 'p', 'Y');
273  avctx->bits_per_coded_sample = 24;
274  ctx->tex_fun = ctx->dxtc.dxt5ys_block;
275  break;
276  default:
277  av_log(avctx, AV_LOG_ERROR, "Invalid format %02X\n", ctx->opt_tex_fmt);
278  return AVERROR_INVALIDDATA;
279  }
280 
281  /* Texture compression ratio is constant, so can we computer
282  * beforehand the final size of the uncompressed buffer. */
283  ctx->tex_size = FFALIGN(avctx->width, TEXTURE_BLOCK_W) *
284  FFALIGN(avctx->height, TEXTURE_BLOCK_H) * 4 / ratio;
285 
286  switch (ctx->opt_compressor) {
287  case HAP_COMP_NONE:
288  /* No benefit chunking uncompressed data */
289  corrected_chunk_count = 1;
290 
291  ctx->max_snappy = ctx->tex_size;
292  ctx->tex_buf = NULL;
293  break;
294  case HAP_COMP_SNAPPY:
295  /* Round the chunk count to divide evenly on DXT block edges */
296  corrected_chunk_count = av_clip(ctx->opt_chunk_count, 1, HAP_MAX_CHUNKS);
297  while ((ctx->tex_size / (64 / ratio)) % corrected_chunk_count != 0) {
298  corrected_chunk_count--;
299  }
300 
301  ctx->max_snappy = snappy_max_compressed_length(ctx->tex_size / corrected_chunk_count);
302  ctx->tex_buf = av_malloc(ctx->tex_size);
303  if (!ctx->tex_buf) {
304  return AVERROR(ENOMEM);
305  }
306  break;
307  default:
308  av_log(avctx, AV_LOG_ERROR, "Invalid compresor %02X\n", ctx->opt_compressor);
309  return AVERROR_INVALIDDATA;
310  }
311  if (corrected_chunk_count != ctx->opt_chunk_count) {
312  av_log(avctx, AV_LOG_INFO, "%d chunks requested but %d used.\n",
313  ctx->opt_chunk_count, corrected_chunk_count);
314  }
315  ret = ff_hap_set_chunk_count(ctx, corrected_chunk_count, 1);
316  if (ret != 0)
317  return ret;
318 
319  return 0;
320 }
321 
322 static av_cold int hap_close(AVCodecContext *avctx)
323 {
324  HapContext *ctx = avctx->priv_data;
325 
326  ff_hap_free_context(ctx);
327 
328  return 0;
329 }
330 
331 #define OFFSET(x) offsetof(HapContext, x)
332 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
333 static const AVOption options[] = {
334  { "format", NULL, OFFSET(opt_tex_fmt), AV_OPT_TYPE_INT, { .i64 = HAP_FMT_RGBDXT1 }, HAP_FMT_RGBDXT1, HAP_FMT_YCOCGDXT5, FLAGS, "format" },
335  { "hap", "Hap 1 (DXT1 textures)", 0, AV_OPT_TYPE_CONST, { .i64 = HAP_FMT_RGBDXT1 }, 0, 0, FLAGS, "format" },
336  { "hap_alpha", "Hap Alpha (DXT5 textures)", 0, AV_OPT_TYPE_CONST, { .i64 = HAP_FMT_RGBADXT5 }, 0, 0, FLAGS, "format" },
337  { "hap_q", "Hap Q (DXT5-YCoCg textures)", 0, AV_OPT_TYPE_CONST, { .i64 = HAP_FMT_YCOCGDXT5 }, 0, 0, FLAGS, "format" },
338  { "chunks", "chunk count", OFFSET(opt_chunk_count), AV_OPT_TYPE_INT, {.i64 = 1 }, 1, HAP_MAX_CHUNKS, FLAGS, },
339  { "compressor", "second-stage compressor", OFFSET(opt_compressor), AV_OPT_TYPE_INT, { .i64 = HAP_COMP_SNAPPY }, HAP_COMP_NONE, HAP_COMP_SNAPPY, FLAGS, "compressor" },
340  { "none", "None", 0, AV_OPT_TYPE_CONST, { .i64 = HAP_COMP_NONE }, 0, 0, FLAGS, "compressor" },
341  { "snappy", "Snappy", 0, AV_OPT_TYPE_CONST, { .i64 = HAP_COMP_SNAPPY }, 0, 0, FLAGS, "compressor" },
342  { NULL },
343 };
344 
345 static const AVClass hapenc_class = {
346  .class_name = "Hap encoder",
347  .item_name = av_default_item_name,
348  .option = options,
349  .version = LIBAVUTIL_VERSION_INT,
350 };
351 
353  .name = "hap",
354  .long_name = NULL_IF_CONFIG_SMALL("Vidvox Hap"),
355  .type = AVMEDIA_TYPE_VIDEO,
356  .id = AV_CODEC_ID_HAP,
357  .priv_data_size = sizeof(HapContext),
358  .priv_class = &hapenc_class,
359  .init = hap_init,
360  .encode2 = hap_encode,
361  .close = hap_close,
362  .pix_fmts = (const enum AVPixelFormat[]) {
364  },
365  .caps_internal = FF_CODEC_CAP_INIT_THREADSAFE |
367 };
#define FF_CODEC_CAP_INIT_CLEANUP
The codec allows calling the close function for deallocation even if the init function returned a fai...
Definition: internal.h:48
Definition: hap.h:61
#define NULL
Definition: coverity.c:32
#define AVERROR_INVALIDDATA
Invalid data found when processing input.
Definition: error.h:59
#define HAP_MAX_CHUNKS
Definition: hapenc.c:46
int(* dxt5_block)(uint8_t *dst, ptrdiff_t stride, const uint8_t *block)
Definition: texturedsp.h:51
This structure describes decoded (raw) audio or video data.
Definition: frame.h:218
HapChunk * chunks
Definition: hap.h:72
AVOption.
Definition: opt.h:246
HapSectionType
Definition: hap.h:46
misc image utilities
#define LIBAVUTIL_VERSION_INT
Definition: version.h:85
void av_shrink_packet(AVPacket *pkt, int size)
Reduce packet size, correctly zeroing padding.
Definition: avpacket.c:101
static av_cold int init(AVCodecContext *avctx)
Definition: avrndec.c:35
Texture block (4x4) module.
int size
Definition: avcodec.h:1431
static av_always_inline void bytestream2_init_writer(PutByteContext *p, uint8_t *buf, int buf_size)
Definition: bytestream.h:143
const char * av_default_item_name(void *ptr)
Return the context name.
Definition: log.c:191
static av_cold int hap_init(AVCodecContext *avctx)
Definition: hapenc.c:236
static AVPacket pkt
size_t compressed_size
Definition: hap.h:56
int compressed_offset
Definition: hap.h:55
AVCodec.
Definition: avcodec.h:3408
size_t uncompressed_size
Definition: hap.h:58
const char * class_name
The name of the class; usually it is the same name as the context structure type to which the AVClass...
Definition: log.h:72
int ff_alloc_packet2(AVCodecContext *avctx, AVPacket *avpkt, int64_t size, int64_t min_size)
Check AVPacket size and/or allocate data.
Definition: encode.c:32
#define FF_CODEC_CAP_INIT_THREADSAFE
The codec does not modify any global variables in the init function, allowing to call the init functi...
Definition: internal.h:40
uint8_t
#define av_cold
Definition: attributes.h:82
#define av_malloc(s)
HapHeaderLength
Definition: hapenc.c:48
static void hap_write_section_header(PutByteContext *pbc, enum HapHeaderLength header_length, int section_length, enum HapSectionType section_type)
Definition: hapenc.c:75
AVOptions.
static int compress_texture(AVCodecContext *avctx, uint8_t *out, int out_length, const AVFrame *f)
Definition: hapenc.c:55
static const AVClass hapenc_class
Definition: hapenc.c:345
int opt_compressor
Definition: hap.h:69
int ff_hap_set_chunk_count(HapContext *ctx, int count, int first_in_frame)
Definition: hap.c:28
static AVFrame * frame
#define TEXTURE_BLOCK_H
Definition: texturedsp.h:43
TextureDSPContext dxtc
Definition: hap.h:64
uint8_t * data
Definition: avcodec.h:1430
#define AV_LOG_VERBOSE
Detailed information.
Definition: log.h:192
int bits_per_coded_sample
bits per sample/pixel from the demuxer (needed for huffyuv).
Definition: avcodec.h:2734
#define FFALIGN(x, a)
Definition: macros.h:48
#define av_log(a,...)
#define AV_PKT_FLAG_KEY
The packet contains a keyframe.
Definition: avcodec.h:1462
int(* dxt5ys_block)(uint8_t *dst, ptrdiff_t stride, const uint8_t *block)
Definition: texturedsp.h:53
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:176
#define AVERROR(e)
Definition: error.h:43
#define NULL_IF_CONFIG_SMALL(x)
Return NULL if CONFIG_SMALL is true, otherwise the argument without modification. ...
Definition: internal.h:186
const char * name
Name of the codec implementation.
Definition: avcodec.h:3415
static av_cold int hap_close(AVCodecContext *avctx)
Definition: hapenc.c:322
int opt_chunk_count
Definition: hap.h:68
#define FFMAX(a, b)
Definition: common.h:94
packed RGBA 8:8:8:8, 32bpp, RGBARGBA...
Definition: pixfmt.h:89
int flags
A combination of AV_PKT_FLAG values.
Definition: avcodec.h:1436
reference-counted frame API
int av_image_check_size(unsigned int w, unsigned int h, int log_offset, void *log_ctx)
Check if the given dimension of an image is valid, meaning that all bytes of the image can be address...
Definition: imgutils.c:282
#define FLAGS
Definition: hapenc.c:332
int uncompressed_offset
Definition: hap.h:57
#define AVERROR_BUFFER_TOO_SMALL
Buffer too small.
Definition: error.h:51
static void hap_write_frame_header(HapContext *ctx, uint8_t *dst, int frame_length)
Definition: hapenc.c:162
int width
picture width / height.
Definition: avcodec.h:1690
static const AVOption options[]
Definition: hapenc.c:333
AVFormatContext * ctx
Definition: movenc.c:48
size_t max_snappy
Definition: hap.h:81
int(* tex_fun)(uint8_t *dst, ptrdiff_t stride, const uint8_t *block)
Definition: hap.h:90
#define AV_LOG_INFO
Standard information.
Definition: log.h:187
Libavcodec external API header.
int linesize[AV_NUM_DATA_POINTERS]
For video, size in bytes of each picture line.
Definition: frame.h:249
av_cold void ff_hap_free_context(HapContext *ctx)
Definition: hap.c:50
main external API structure.
Definition: avcodec.h:1518
unsigned int codec_tag
fourcc (LSB first, so "ABCD" -> (&#39;D&#39;<<24) + (&#39;C&#39;<<16) + (&#39;B&#39;<<8) + &#39;A&#39;).
Definition: avcodec.h:1543
int chunk_count
Definition: hap.h:71
int(* dxt1_block)(uint8_t *dst, ptrdiff_t stride, const uint8_t *block)
Definition: texturedsp.h:46
#define AVERROR_BUG
Internal bug, also see AVERROR_BUG2.
Definition: error.h:50
Describe the class of an AVClass context structure.
Definition: log.h:67
AVCodec ff_hap_encoder
Definition: hapenc.c:352
enum HapTextureFormat opt_tex_fmt
Definition: hap.h:67
void ff_texturedspenc_init(TextureDSPContext *c)
static enum AVPixelFormat pix_fmts[]
Definition: libkvazaar.c:266
#define SIZE_SPECIFIER
Definition: internal.h:262
uint8_t * tex_buf
Definition: hap.h:78
uint8_t * data[AV_NUM_DATA_POINTERS]
pointer to the picture/channel planes.
Definition: frame.h:232
static int hap_encode(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_packet)
Definition: hapenc.c:194
common internal api header.
#define OFFSET(x)
Definition: hapenc.c:331
size_t tex_size
Definition: hap.h:79
Definition: hap.h:53
static int hap_decode_instructions_length(HapContext *ctx)
Definition: hapenc.c:140
void * priv_data
Definition: avcodec.h:1545
FILE * out
Definition: movenc.c:54
static int hap_compress_frame(AVCodecContext *avctx, uint8_t *dst)
Definition: hapenc.c:92
const char int length
Definition: avisynth_c.h:768
enum HapCompressor compressor
Definition: hap.h:54
#define MKTAG(a, b, c, d)
Definition: common.h:366
#define TEXTURE_BLOCK_W
Definition: texturedsp.h:42
AVPixelFormat
Pixel format.
Definition: pixfmt.h:60
This structure stores compressed data.
Definition: avcodec.h:1407
static int hap_header_length(HapContext *ctx)
Definition: hapenc.c:149