mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-12-18 03:19:31 +02:00
20f9727018
Up until now, codec.h contains both public and private parts of AVCodec. This exposes the internals of AVCodec to users and leads them into the temptation of actually using them and forces us to forward-declare structures and types that users can't use at all. This commit changes this by adding a new structure FFCodec to codec_internal.h that extends AVCodec, i.e. contains the public AVCodec as first member; the private fields of AVCodec are moved to this structure, leaving codec.h clean. Reviewed-by: Anton Khirnov <anton@khirnov.net> Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
375 lines
11 KiB
C
375 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,
|
|
void *data, 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;
|
|
AVFrame *frame = data;
|
|
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,
|
|
.decode = tgv_decode_frame,
|
|
.p.capabilities = AV_CODEC_CAP_DR1,
|
|
.caps_internal = FF_CODEC_CAP_INIT_THREADSAFE,
|
|
};
|