mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-11-21 10:55:51 +02:00
4243da4ff4
This is possible, because every given FFCodec has to implement exactly one of these. Doing so decreases sizeof(FFCodec) and therefore decreases the size of the binary. Notice that in case of position-independent code the decrease is in .data.rel.ro, so that this translates to decreased memory consumption. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
373 lines
11 KiB
C
373 lines
11 KiB
C
/*
|
|
* Electronic Arts TGV Video Decoder
|
|
* Copyright (c) 2007-2008 Peter Ross
|
|
*
|
|
* 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 St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* Electronic Arts TGV Video Decoder
|
|
* by Peter Ross (pross@xvid.org)
|
|
*
|
|
* Technical details here:
|
|
* http://wiki.multimedia.cx/index.php?title=Electronic_Arts_TGV
|
|
*/
|
|
|
|
#include "libavutil/imgutils.h"
|
|
#include "libavutil/mem.h"
|
|
|
|
#define BITSTREAM_READER_LE
|
|
#include "avcodec.h"
|
|
#include "get_bits.h"
|
|
#include "codec_internal.h"
|
|
#include "internal.h"
|
|
|
|
#define EA_PREAMBLE_SIZE 8
|
|
#define kVGT_TAG MKTAG('k', 'V', 'G', 'T')
|
|
|
|
typedef struct TgvContext {
|
|
AVCodecContext *avctx;
|
|
AVFrame *last_frame;
|
|
uint8_t *frame_buffer;
|
|
int width,height;
|
|
uint32_t palette[AVPALETTE_COUNT];
|
|
|
|
int (*mv_codebook)[2];
|
|
uint8_t (*block_codebook)[16];
|
|
int num_mvs; ///< current length of mv_codebook
|
|
int num_blocks_packed; ///< current length of block_codebook
|
|
} TgvContext;
|
|
|
|
static av_cold int tgv_decode_init(AVCodecContext *avctx)
|
|
{
|
|
TgvContext *s = avctx->priv_data;
|
|
s->avctx = avctx;
|
|
avctx->framerate = (AVRational){ 15, 1 };
|
|
avctx->pix_fmt = AV_PIX_FMT_PAL8;
|
|
|
|
s->last_frame = av_frame_alloc();
|
|
if (!s->last_frame)
|
|
return AVERROR(ENOMEM);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Unpack buffer
|
|
* @return 0 on success, -1 on critical buffer underflow
|
|
*/
|
|
static int unpack(const uint8_t *src, const uint8_t *src_end,
|
|
uint8_t *dst, int width, int height)
|
|
{
|
|
uint8_t *dst_end = dst + width*height;
|
|
int size, size1, size2, offset, run;
|
|
uint8_t *dst_start = dst;
|
|
|
|
if (src[0] & 0x01)
|
|
src += 5;
|
|
else
|
|
src += 2;
|
|
|
|
if (src_end - src < 3)
|
|
return AVERROR_INVALIDDATA;
|
|
size = AV_RB24(src);
|
|
src += 3;
|
|
|
|
while (size > 0 && src < src_end) {
|
|
|
|
/* determine size1 and size2 */
|
|
size1 = (src[0] & 3);
|
|
if (src[0] & 0x80) { // 1
|
|
if (src[0] & 0x40 ) { // 11
|
|
if (src[0] & 0x20) { // 111
|
|
if (src[0] < 0xFC) // !(111111)
|
|
size1 = (((src[0] & 31) + 1) << 2);
|
|
src++;
|
|
size2 = 0;
|
|
} else { // 110
|
|
offset = ((src[0] & 0x10) << 12) + AV_RB16(&src[1]) + 1;
|
|
size2 = ((src[0] & 0xC) << 6) + src[3] + 5;
|
|
src += 4;
|
|
}
|
|
} else { // 10
|
|
size1 = ((src[1] & 0xC0) >> 6);
|
|
offset = (AV_RB16(&src[1]) & 0x3FFF) + 1;
|
|
size2 = (src[0] & 0x3F) + 4;
|
|
src += 3;
|
|
}
|
|
} else { // 0
|
|
offset = ((src[0] & 0x60) << 3) + src[1] + 1;
|
|
size2 = ((src[0] & 0x1C) >> 2) + 3;
|
|
src += 2;
|
|
}
|
|
|
|
|
|
/* fetch strip from src */
|
|
if (size1 > src_end - src)
|
|
break;
|
|
|
|
if (size1 > 0) {
|
|
size -= size1;
|
|
run = FFMIN(size1, dst_end - dst);
|
|
memcpy(dst, src, run);
|
|
dst += run;
|
|
src += run;
|
|
}
|
|
|
|
if (size2 > 0) {
|
|
if (dst - dst_start < offset)
|
|
return 0;
|
|
size -= size2;
|
|
run = FFMIN(size2, dst_end - dst);
|
|
av_memcpy_backptr(dst, offset, run);
|
|
dst += run;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Decode inter-frame
|
|
* @return 0 on success, -1 on critical buffer underflow
|
|
*/
|
|
static int tgv_decode_inter(TgvContext *s, AVFrame *frame,
|
|
const uint8_t *buf, const uint8_t *buf_end)
|
|
{
|
|
int num_mvs;
|
|
int num_blocks_raw;
|
|
int num_blocks_packed;
|
|
int vector_bits;
|
|
int i,j,x,y;
|
|
GetBitContext gb;
|
|
int mvbits;
|
|
const uint8_t *blocks_raw;
|
|
|
|
if(buf_end - buf < 12)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
num_mvs = AV_RL16(&buf[0]);
|
|
num_blocks_raw = AV_RL16(&buf[2]);
|
|
num_blocks_packed = AV_RL16(&buf[4]);
|
|
vector_bits = AV_RL16(&buf[6]);
|
|
buf += 12;
|
|
|
|
if (vector_bits > MIN_CACHE_BITS || !vector_bits) {
|
|
av_log(s->avctx, AV_LOG_ERROR,
|
|
"Invalid value for motion vector bits: %d\n", vector_bits);
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
|
|
/* allocate codebook buffers as necessary */
|
|
if (num_mvs > s->num_mvs) {
|
|
int err = av_reallocp_array(&s->mv_codebook, num_mvs, sizeof(*s->mv_codebook));
|
|
if (err < 0) {
|
|
s->num_mvs = 0;
|
|
return err;
|
|
}
|
|
s->num_mvs = num_mvs;
|
|
}
|
|
|
|
if (num_blocks_packed > s->num_blocks_packed) {
|
|
int err;
|
|
if ((err = av_reallocp(&s->block_codebook, num_blocks_packed * 16)) < 0) {
|
|
s->num_blocks_packed = 0;
|
|
return err;
|
|
}
|
|
s->num_blocks_packed = num_blocks_packed;
|
|
}
|
|
|
|
/* read motion vectors */
|
|
mvbits = (num_mvs * 2 * 10 + 31) & ~31;
|
|
|
|
if (buf_end - buf < (mvbits>>3) + 16*num_blocks_raw + 8*num_blocks_packed)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
init_get_bits(&gb, buf, mvbits);
|
|
for (i = 0; i < num_mvs; i++) {
|
|
s->mv_codebook[i][0] = get_sbits(&gb, 10);
|
|
s->mv_codebook[i][1] = get_sbits(&gb, 10);
|
|
}
|
|
buf += mvbits >> 3;
|
|
|
|
/* note ptr to uncompressed blocks */
|
|
blocks_raw = buf;
|
|
buf += num_blocks_raw * 16;
|
|
|
|
/* read compressed blocks */
|
|
init_get_bits(&gb, buf, (buf_end - buf) << 3);
|
|
for (i = 0; i < num_blocks_packed; i++) {
|
|
int tmp[4];
|
|
for (j = 0; j < 4; j++)
|
|
tmp[j] = get_bits(&gb, 8);
|
|
for (j = 0; j < 16; j++)
|
|
s->block_codebook[i][15-j] = tmp[get_bits(&gb, 2)];
|
|
}
|
|
|
|
if (get_bits_left(&gb) < vector_bits *
|
|
(s->avctx->height / 4) * (s->avctx->width / 4))
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
/* read vectors and build frame */
|
|
for (y = 0; y < s->avctx->height / 4; y++)
|
|
for (x = 0; x < s->avctx->width / 4; x++) {
|
|
unsigned int vector = get_bits(&gb, vector_bits);
|
|
const uint8_t *src;
|
|
ptrdiff_t src_stride;
|
|
|
|
if (vector < num_mvs) {
|
|
int mx = x * 4 + s->mv_codebook[vector][0];
|
|
int my = y * 4 + s->mv_codebook[vector][1];
|
|
|
|
if (mx < 0 || mx + 4 > s->avctx->width ||
|
|
my < 0 || my + 4 > s->avctx->height) {
|
|
av_log(s->avctx, AV_LOG_ERROR, "MV %d %d out of picture\n", mx, my);
|
|
continue;
|
|
}
|
|
|
|
src = s->last_frame->data[0] + mx + my * s->last_frame->linesize[0];
|
|
src_stride = s->last_frame->linesize[0];
|
|
} else {
|
|
int offset = vector - num_mvs;
|
|
if (offset < num_blocks_raw)
|
|
src = blocks_raw + 16*offset;
|
|
else if (offset - num_blocks_raw < num_blocks_packed)
|
|
src = s->block_codebook[offset - num_blocks_raw];
|
|
else
|
|
continue;
|
|
src_stride = 4;
|
|
}
|
|
|
|
for (j = 0; j < 4; j++)
|
|
for (i = 0; i < 4; i++)
|
|
frame->data[0][(y * 4 + j) * frame->linesize[0] + (x * 4 + i)] =
|
|
src[j * src_stride + i];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tgv_decode_frame(AVCodecContext *avctx, AVFrame *frame,
|
|
int *got_frame, AVPacket *avpkt)
|
|
{
|
|
const uint8_t *buf = avpkt->data;
|
|
int buf_size = avpkt->size;
|
|
TgvContext *s = avctx->priv_data;
|
|
const uint8_t *buf_end = buf + buf_size;
|
|
int chunk_type, ret;
|
|
|
|
if (buf_end - buf < EA_PREAMBLE_SIZE)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
chunk_type = AV_RL32(&buf[0]);
|
|
buf += EA_PREAMBLE_SIZE;
|
|
|
|
if (chunk_type == kVGT_TAG) {
|
|
int pal_count, i;
|
|
if(buf_end - buf < 12) {
|
|
av_log(avctx, AV_LOG_WARNING, "truncated header\n");
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
|
|
s->width = AV_RL16(&buf[0]);
|
|
s->height = AV_RL16(&buf[2]);
|
|
if (s->avctx->width != s->width || s->avctx->height != s->height) {
|
|
av_freep(&s->frame_buffer);
|
|
av_frame_unref(s->last_frame);
|
|
if ((ret = ff_set_dimensions(s->avctx, s->width, s->height)) < 0)
|
|
return ret;
|
|
}
|
|
|
|
pal_count = AV_RL16(&buf[6]);
|
|
buf += 12;
|
|
for(i = 0; i < pal_count && i < AVPALETTE_COUNT && buf_end - buf >= 3; i++) {
|
|
s->palette[i] = 0xFFU << 24 | AV_RB24(buf);
|
|
buf += 3;
|
|
}
|
|
if (buf_end - buf < 5) {
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
}
|
|
|
|
if ((ret = ff_get_buffer(avctx, frame, AV_GET_BUFFER_FLAG_REF)) < 0)
|
|
return ret;
|
|
|
|
memcpy(frame->data[1], s->palette, AVPALETTE_SIZE);
|
|
|
|
if (chunk_type == kVGT_TAG) {
|
|
int y;
|
|
frame->key_frame = 1;
|
|
frame->pict_type = AV_PICTURE_TYPE_I;
|
|
|
|
if (!s->frame_buffer &&
|
|
!(s->frame_buffer = av_mallocz(s->width * s->height)))
|
|
return AVERROR(ENOMEM);
|
|
|
|
if (unpack(buf, buf_end, s->frame_buffer, s->avctx->width, s->avctx->height) < 0) {
|
|
av_log(avctx, AV_LOG_WARNING, "truncated intra frame\n");
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
for (y = 0; y < s->height; y++)
|
|
memcpy(frame->data[0] + y * frame->linesize[0],
|
|
s->frame_buffer + y * s->width,
|
|
s->width);
|
|
} else {
|
|
if (!s->last_frame->data[0]) {
|
|
av_log(avctx, AV_LOG_WARNING, "inter frame without corresponding intra frame\n");
|
|
return buf_size;
|
|
}
|
|
frame->key_frame = 0;
|
|
frame->pict_type = AV_PICTURE_TYPE_P;
|
|
if (tgv_decode_inter(s, frame, buf, buf_end) < 0) {
|
|
av_log(avctx, AV_LOG_WARNING, "truncated inter frame\n");
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
}
|
|
|
|
av_frame_unref(s->last_frame);
|
|
if ((ret = av_frame_ref(s->last_frame, frame)) < 0)
|
|
return ret;
|
|
|
|
*got_frame = 1;
|
|
|
|
return buf_size;
|
|
}
|
|
|
|
static av_cold int tgv_decode_end(AVCodecContext *avctx)
|
|
{
|
|
TgvContext *s = avctx->priv_data;
|
|
av_frame_free(&s->last_frame);
|
|
av_freep(&s->frame_buffer);
|
|
av_freep(&s->mv_codebook);
|
|
av_freep(&s->block_codebook);
|
|
return 0;
|
|
}
|
|
|
|
const FFCodec ff_eatgv_decoder = {
|
|
.p.name = "eatgv",
|
|
.p.long_name = NULL_IF_CONFIG_SMALL("Electronic Arts TGV video"),
|
|
.p.type = AVMEDIA_TYPE_VIDEO,
|
|
.p.id = AV_CODEC_ID_TGV,
|
|
.priv_data_size = sizeof(TgvContext),
|
|
.init = tgv_decode_init,
|
|
.close = tgv_decode_end,
|
|
FF_CODEC_DECODE_CB(tgv_decode_frame),
|
|
.p.capabilities = AV_CODEC_CAP_DR1,
|
|
.caps_internal = FF_CODEC_CAP_INIT_THREADSAFE,
|
|
};
|