From c45fcf30cfab687004ed1cdc06ebaa21f4262a0b Mon Sep 17 00:00:00 2001 From: Vittorio Giovara Date: Wed, 26 Aug 2015 11:31:41 +0200 Subject: DXV decoder Support all DXDI and DXD3 normal quality videos. --- libavcodec/dxv.c | 461 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 461 insertions(+) create mode 100644 libavcodec/dxv.c (limited to 'libavcodec/dxv.c') diff --git a/libavcodec/dxv.c b/libavcodec/dxv.c new file mode 100644 index 0000000000..dc11f38def --- /dev/null +++ b/libavcodec/dxv.c @@ -0,0 +1,461 @@ +/* + * Resolume DXV decoder + * Copyright (C) 2015 Vittorio Giovara + * + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "libavutil/imgutils.h" + +#include "avcodec.h" +#include "bytestream.h" +#include "internal.h" +#include "lzf.h" +#include "texturedsp.h" +#include "thread.h" + +typedef struct DXVContext { + TextureDSPContext texdsp; + GetByteContext gbc; + + uint8_t *tex_data; // Compressed texture + int tex_rat; // Compression ratio + int tex_step; // Distance between blocks + int64_t tex_size; // Texture size + + /* Optimal number of slices for parallel decoding */ + int slice_count; + + /* Pointer to the selected decompression function */ + int (*tex_funct)(uint8_t *dst, ptrdiff_t stride, const uint8_t *block); +} DXVContext; + +static int decompress_texture_thread(AVCodecContext *avctx, void *arg, + int slice, int thread_nb) +{ + DXVContext *ctx = avctx->priv_data; + AVFrame *frame = arg; + const uint8_t *d = ctx->tex_data; + int w_block = avctx->coded_width / TEXTURE_BLOCK_W; + int h_block = avctx->coded_height / TEXTURE_BLOCK_H; + int x, y; + int start_slice, end_slice; + int base_blocks_per_slice = h_block / ctx->slice_count; + int remainder_blocks = h_block % ctx->slice_count; + + /* When the frame height (in blocks) doesn't divide evenly between the + * number of slices, spread the remaining blocks evenly between the first + * operations */ + start_slice = slice * base_blocks_per_slice; + /* Add any extra blocks (one per slice) that have been added + * before this slice */ + start_slice += FFMIN(slice, remainder_blocks); + + end_slice = start_slice + base_blocks_per_slice; + /* Add an extra block if there are remainder blocks to be accounted for */ + if (slice < remainder_blocks) + end_slice++; + + for (y = start_slice; y < end_slice; y++) { + uint8_t *p = frame->data[0] + y * frame->linesize[0] * TEXTURE_BLOCK_H; + int off = y * w_block; + for (x = 0; x < w_block; x++) { + ctx->tex_funct(p + x * 16, frame->linesize[0], + d + (off + x) * ctx->tex_step); + } + } + + return 0; +} + +/* This scheme addresses already decoded elements depending on 2-bit status: + * 0 -> copy new element + * 1 -> copy one element from position -x + * 2 -> copy one element from position -(get_byte() + 2) * x + * 3 -> copy one element from position -(get_16le() + 0x102) * x + * x is always 2 for dxt1 and 4 for dxt5. */ +#define CHECKPOINT(x) \ + do { \ + if (state == 0) { \ + value = bytestream2_get_le32(gbc); \ + state = 16; \ + } \ + op = value & 0x3; \ + value >>= 2; \ + state--; \ + switch (op) { \ + case 1: \ + idx = x; \ + break; \ + case 2: \ + idx = (bytestream2_get_byte(gbc) + 2) * x; \ + break; \ + case 3: \ + idx = (bytestream2_get_le16(gbc) + 0x102) * x; \ + break; \ + } \ + } while(0) + +static int dxv_decompress_dxt1(AVCodecContext *avctx) +{ + DXVContext *ctx = avctx->priv_data; + GetByteContext *gbc = &ctx->gbc; + uint32_t value, prev, op; + int idx = 0, state = 0; + int pos = 2; + + /* Copy the first two elements */ + AV_WL32(ctx->tex_data, bytestream2_get_le32(gbc)); + AV_WL32(ctx->tex_data + 4, bytestream2_get_le32(gbc)); + + /* Process input until the whole texture has been filled */ + while (pos < ctx->tex_size / 4) { + CHECKPOINT(2); + + /* Copy two elements from a previous offset or from the input buffer */ + if (op) { + prev = AV_RL32(ctx->tex_data + 4 * (pos - idx)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + + prev = AV_RL32(ctx->tex_data + 4 * (pos - idx)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + } else { + CHECKPOINT(2); + + if (op) + prev = AV_RL32(ctx->tex_data + 4 * (pos - idx)); + else + prev = bytestream2_get_le32(gbc); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + + CHECKPOINT(2); + + if (op) + prev = AV_RL32(ctx->tex_data + 4 * (pos - idx)); + else + prev = bytestream2_get_le32(gbc); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + } + } + + return 0; +} + +static int dxv_decompress_dxt5(AVCodecContext *avctx) +{ + DXVContext *ctx = avctx->priv_data; + GetByteContext *gbc = &ctx->gbc; + uint32_t value, op; + int idx, prev, state = 0; + int pos = 4; + int run = 0; + int probe, check; + + /* Copy the first four elements */ + AV_WL32(ctx->tex_data + 0, bytestream2_get_le32(gbc)); + AV_WL32(ctx->tex_data + 4, bytestream2_get_le32(gbc)); + AV_WL32(ctx->tex_data + 8, bytestream2_get_le32(gbc)); + AV_WL32(ctx->tex_data + 12, bytestream2_get_le32(gbc)); + + /* Process input until the whole texture has been filled */ + while (pos < ctx->tex_size / 4) { + if (run) { + run--; + + prev = AV_RL32(ctx->tex_data + 4 * (pos - 4)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + prev = AV_RL32(ctx->tex_data + 4 * (pos - 4)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + } else { + if (state == 0) { + value = bytestream2_get_le32(gbc); + state = 16; + } + op = value & 0x3; + value >>= 2; + state--; + + switch (op) { + case 0: + /* Long copy */ + check = bytestream2_get_byte(gbc) + 1; + if (check == 256) { + do { + probe = bytestream2_get_le16(gbc); + check += probe; + } while (probe == 0xFFFF); + } + while (check && pos < ctx->tex_size / 4) { + prev = AV_RL32(ctx->tex_data + 4 * (pos - 4)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + + prev = AV_RL32(ctx->tex_data + 4 * (pos - 4)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + + prev = AV_RL32(ctx->tex_data + 4 * (pos - 4)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + + prev = AV_RL32(ctx->tex_data + 4 * (pos - 4)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + + check--; + } + + /* Restart (or exit) the loop */ + continue; + break; + case 1: + /* Load new run value */ + run = bytestream2_get_byte(gbc); + if (run == 255) { + do { + probe = bytestream2_get_le16(gbc); + run += probe; + } while (probe == 0xFFFF); + } + + /* Copy two dwords from previous data */ + prev = AV_RL32(ctx->tex_data + 4 * (pos - 4)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + + prev = AV_RL32(ctx->tex_data + 4 * (pos - 4)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + break; + case 2: + /* Copy two dwords from a previous index */ + idx = 8 + bytestream2_get_le16(gbc); + prev = AV_RL32(ctx->tex_data + 4 * (pos - idx)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + + prev = AV_RL32(ctx->tex_data + 4 * (pos - idx)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + break; + case 3: + /* Copy two dwords from input */ + prev = bytestream2_get_le32(gbc); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + + prev = bytestream2_get_le32(gbc); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + break; + } + } + + CHECKPOINT(4); + + /* Copy two elements from a previous offset or from the input buffer */ + if (op) { + prev = AV_RL32(ctx->tex_data + 4 * (pos - idx)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + + prev = AV_RL32(ctx->tex_data + 4 * (pos - idx)); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + } else { + CHECKPOINT(4); + + if (op) + prev = AV_RL32(ctx->tex_data + 4 * (pos - idx)); + else + prev = bytestream2_get_le32(gbc); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + + CHECKPOINT(4); + + if (op) + prev = AV_RL32(ctx->tex_data + 4 * (pos - idx)); + else + prev = bytestream2_get_le32(gbc); + AV_WL32(ctx->tex_data + 4 * pos, prev); + pos++; + } + } + + return 0; +} + +static int dxv_decompress_lzf(AVCodecContext *avctx) +{ + DXVContext *ctx = avctx->priv_data; + return ff_lzf_uncompress(&ctx->gbc, &ctx->tex_data, &ctx->tex_size); +} + +static int dxv_decode(AVCodecContext *avctx, void *data, + int *got_frame, AVPacket *avpkt) +{ + DXVContext *ctx = avctx->priv_data; + ThreadFrame tframe; + GetByteContext *gbc = &ctx->gbc; + int (*decompress_tex)(AVCodecContext *avctx); + uint32_t tag; + int channels, size = 0, old_type = 0; + int ret; + + bytestream2_init(gbc, avpkt->data, avpkt->size); + + tag = bytestream2_get_le32(gbc); + switch (tag) { + case MKBETAG('D', 'X', 'T', '1'): + decompress_tex = dxv_decompress_dxt1; + ctx->tex_funct = ctx->texdsp.dxt1_block; + ctx->tex_rat = 8; + ctx->tex_step = 8; + av_log(avctx, AV_LOG_DEBUG, "DXTR1 compression and DXT1 texture "); + break; + case MKBETAG('D', 'X', 'T', '5'): + decompress_tex = dxv_decompress_dxt5; + ctx->tex_funct = ctx->texdsp.dxt5_block; + ctx->tex_rat = 4; + ctx->tex_step = 16; + av_log(avctx, AV_LOG_DEBUG, "DXTR5 compression and DXT5 texture "); + break; + case MKBETAG('Y', 'C', 'G', '6'): + case MKBETAG('Y', 'G', '1', '0'): + avpriv_report_missing_feature(avctx, "Tag 0x%08X", tag); + return AVERROR_PATCHWELCOME; + default: + /* Old version does not have a real header, just size and type. */ + size = tag & 0x00FFFFFF; + old_type = tag >> 24; + channels = old_type & 0x0F; + if (old_type & 0x40) { + av_log(avctx, AV_LOG_DEBUG, "LZF compression and DXT5 texture "); + ctx->tex_funct = ctx->texdsp.dxt5_block; + ctx->tex_step = 16; + } else if (old_type & 0x20) { + av_log(avctx, AV_LOG_DEBUG, "LZF compression and DXT1 texture "); + ctx->tex_funct = ctx->texdsp.dxt1_block; + ctx->tex_step = 8; + } else { + av_log(avctx, AV_LOG_ERROR, "Unsupported header (0x%08X)\n.", tag); + return AVERROR_INVALIDDATA; + } + decompress_tex = dxv_decompress_lzf; + ctx->tex_rat = 1; + break; + } + + /* New header is 12 bytes long. */ + if (!old_type) { + channels = bytestream2_get_byte(gbc); + bytestream2_skip(gbc, 3); // unknown + size = bytestream2_get_le32(gbc); + } + av_log(avctx, AV_LOG_DEBUG, "(%d channels)\n", channels); + + if (size != bytestream2_get_bytes_left(gbc)) { + av_log(avctx, AV_LOG_ERROR, "Incomplete or invalid file (%u > %u)\n.", + size, bytestream2_get_bytes_left(gbc)); + return AVERROR_INVALIDDATA; + } + + ctx->tex_size = avctx->coded_width * avctx->coded_height * 4 / ctx->tex_rat; + ret = av_reallocp(&ctx->tex_data, ctx->tex_size); + if (ret < 0) + return ret; + + /* Decompress texture out of the intermediate compression. */ + ret = decompress_tex(avctx); + if (ret < 0) + return ret; + + tframe.f = data; + ret = ff_thread_get_buffer(avctx, &tframe, 0); + if (ret < 0) + return ret; + ff_thread_finish_setup(avctx); + + /* Now decompress the texture with the standard functions. */ + avctx->execute2(avctx, decompress_texture_thread, + tframe.f, NULL, ctx->slice_count); + + /* Frame is ready to be output. */ + tframe.f->pict_type = AV_PICTURE_TYPE_I; + tframe.f->key_frame = 1; + *got_frame = 1; + + return avpkt->size; +} + +static int dxv_init(AVCodecContext *avctx) +{ + DXVContext *ctx = avctx->priv_data; + int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx); + + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Invalid image size %dx%d.\n", + avctx->width, avctx->height); + return ret; + } + + /* Codec requires 16x16 alignment. */ + avctx->coded_width = FFALIGN(avctx->width, 16); + avctx->coded_height = FFALIGN(avctx->height, 16); + + ff_texturedsp_init(&ctx->texdsp); + avctx->pix_fmt = AV_PIX_FMT_RGBA; + + ctx->slice_count = av_clip(avctx->thread_count, 1, + avctx->coded_height / TEXTURE_BLOCK_H); + + return 0; +} + +static int dxv_close(AVCodecContext *avctx) +{ + DXVContext *ctx = avctx->priv_data; + + av_freep(&ctx->tex_data); + + return 0; +} + +AVCodec ff_dxv_decoder = { + .name = "dxv", + .long_name = NULL_IF_CONFIG_SMALL("Resolume DXV"), + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_DXV, + .init = dxv_init, + .decode = dxv_decode, + .close = dxv_close, + .priv_data_size = sizeof(DXVContext), + .capabilities = AV_CODEC_CAP_DR1 | + AV_CODEC_CAP_SLICE_THREADS | + AV_CODEC_CAP_FRAME_THREADS, + .caps_internal = FF_CODEC_CAP_INIT_THREADSAFE | + FF_CODEC_CAP_INIT_CLEANUP, +}; -- cgit v1.2.3