2fdf638b |
/*
* Cinepak Video Decoder
* Copyright (C) 2003 the ffmpeg project
* |
b78e7197 |
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or |
2fdf638b |
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either |
b78e7197 |
* version 2.1 of the License, or (at your option) any later version. |
2fdf638b |
* |
b78e7197 |
* FFmpeg is distributed in the hope that it will be useful, |
2fdf638b |
* 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 |
b78e7197 |
* License along with FFmpeg; if not, write to the Free Software |
5509bffa |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
2fdf638b |
*/
/** |
ba87f080 |
* @file |
2fdf638b |
* Cinepak video decoder |
e873c03a |
* @author Ewald Snel <ewald@rambo.its.tudelft.nl>
*
* @see For more information on the Cinepak algorithm, visit: |
2fdf638b |
* http://www.csse.monash.edu.au/~timf/ |
e873c03a |
* @see For more information on the quirky data inside Sega FILM/CPK files, visit: |
10f865c9 |
* http://wiki.multimedia.cx/index.php?title=Sega_FILM |
4e635e10 |
*
* Cinepak colorspace support (c) 2013 Rl, Aetey Global Technologies AB
* @author Cinepak colorspace, Rl, Aetey Global Technologies AB |
2fdf638b |
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
|
1d9c2dc8 |
#include "libavutil/common.h" |
6a5d31ac |
#include "libavutil/intreadwrite.h" |
2fdf638b |
#include "avcodec.h" |
759001c5 |
#include "internal.h" |
2fdf638b |
|
4e635e10 |
typedef uint8_t cvid_codebook[12]; |
2fdf638b |
#define MAX_STRIPS 32
typedef struct {
uint16_t id;
uint16_t x1, y1;
uint16_t x2, y2; |
02fb2546 |
cvid_codebook v4_codebook[256];
cvid_codebook v1_codebook[256];
} cvid_strip; |
2fdf638b |
typedef struct CinepakContext {
AVCodecContext *avctx; |
80e9e63c |
AVFrame *frame; |
2fdf638b |
|
d31522b8 |
const unsigned char *data; |
2fdf638b |
int size;
|
94fd9201 |
int width, height;
|
2fdf638b |
int palette_video; |
02fb2546 |
cvid_strip strips[MAX_STRIPS]; |
2fdf638b |
|
10f865c9 |
int sega_film_skip_bytes;
|
2d8591c2 |
uint32_t pal[256]; |
2fdf638b |
} CinepakContext;
|
02fb2546 |
static void cinepak_decode_codebook (cvid_codebook *codebook, |
d31522b8 |
int chunk_id, int size, const uint8_t *data) |
2fdf638b |
{ |
d31522b8 |
const uint8_t *eod = (data + size); |
2fdf638b |
uint32_t flag, mask;
int i, n; |
4e635e10 |
uint8_t *p; |
2fdf638b |
/* check if this chunk contains 4- or 6-element vectors */ |
d94b1f12 |
n = (chunk_id & 0x04) ? 4 : 6; |
2fdf638b |
flag = 0;
mask = 0;
|
4e635e10 |
p = codebook[0]; |
2fdf638b |
for (i=0; i < 256; i++) { |
d94b1f12 |
if ((chunk_id & 0x01) && !(mask >>= 1)) { |
2fdf638b |
if ((data + 4) > eod)
break;
|
fead30d4 |
flag = AV_RB32 (data); |
2fdf638b |
data += 4;
mask = 0x80000000;
}
|
d94b1f12 |
if (!(chunk_id & 0x01) || (flag & mask)) { |
4e635e10 |
int k, kk;
|
2fdf638b |
if ((data + n) > eod)
break;
|
4e635e10 |
for (k = 0; k < 4; ++k) {
int r = *data++;
for (kk = 0; kk < 3; ++kk)
*p++ = r;
} |
2fdf638b |
if (n == 6) { |
4e635e10 |
int r, g, b, u, v;
u = *(int8_t *)data++;
v = *(int8_t *)data++;
p -= 12;
for(k=0; k<4; ++k) {
r = *p++ + v*2;
g = *p++ - (u/2) - v;
b = *p + u*2;
p -= 2;
*p++ = av_clip_uint8(r);
*p++ = av_clip_uint8(g);
*p++ = av_clip_uint8(b);
} |
2fdf638b |
} |
4e635e10 |
} else {
p += 12; |
2fdf638b |
}
}
}
|
02fb2546 |
static int cinepak_decode_vectors (CinepakContext *s, cvid_strip *strip, |
d31522b8 |
int chunk_id, int size, const uint8_t *data) |
2fdf638b |
{ |
d31522b8 |
const uint8_t *eod = (data + size); |
2fdf638b |
uint32_t flag, mask; |
4e635e10 |
uint8_t *cb0, *cb1, *cb2, *cb3; |
2a0dbb1e |
unsigned int x, y; |
4e635e10 |
char *ip0, *ip1, *ip2, *ip3; |
2fdf638b |
flag = 0;
mask = 0;
for (y=strip->y1; y < strip->y2; y+=4) {
|
4e635e10 |
/* take care of y dimension not being multiple of 4, such streams exist */ |
80e9e63c |
ip0 = ip1 = ip2 = ip3 = s->frame->data[0] +
(s->palette_video?strip->x1:strip->x1*3) + (y * s->frame->linesize[0]); |
4e635e10 |
if(s->avctx->height - y > 1) { |
80e9e63c |
ip1 = ip0 + s->frame->linesize[0]; |
4e635e10 |
if(s->avctx->height - y > 2) { |
80e9e63c |
ip2 = ip1 + s->frame->linesize[0]; |
4e635e10 |
if(s->avctx->height - y > 3) { |
80e9e63c |
ip3 = ip2 + s->frame->linesize[0]; |
4e635e10 |
}
}
}
/* to get the correct picture for not-multiple-of-4 cases let us fill
* each block from the bottom up, thus possibly overwriting the top line
* more than once but ending with the correct data in place
* (instead of in-loop checking) */ |
2fdf638b |
for (x=strip->x1; x < strip->x2; x+=4) { |
d94b1f12 |
if ((chunk_id & 0x01) && !(mask >>= 1)) { |
2fdf638b |
if ((data + 4) > eod) |
b7d939d9 |
return AVERROR_INVALIDDATA; |
2fdf638b |
|
fead30d4 |
flag = AV_RB32 (data); |
2fdf638b |
data += 4;
mask = 0x80000000;
}
|
d94b1f12 |
if (!(chunk_id & 0x01) || (flag & mask)) {
if (!(chunk_id & 0x02) && !(mask >>= 1)) { |
2fdf638b |
if ((data + 4) > eod) |
b7d939d9 |
return AVERROR_INVALIDDATA; |
2fdf638b |
|
fead30d4 |
flag = AV_RB32 (data); |
2fdf638b |
data += 4;
mask = 0x80000000;
}
|
d94b1f12 |
if ((chunk_id & 0x02) || (~flag & mask)) { |
4e635e10 |
uint8_t *p; |
2fdf638b |
if (data >= eod) |
b7d939d9 |
return AVERROR_INVALIDDATA; |
2fdf638b |
|
a3adbedf |
p = strip->v1_codebook[*data++]; |
4e635e10 |
if (s->palette_video) { |
a3adbedf |
ip3[0] = ip3[1] = ip2[0] = ip2[1] = p[6];
ip3[2] = ip3[3] = ip2[2] = ip2[3] = p[9];
ip1[0] = ip1[1] = ip0[0] = ip0[1] = p[0];
ip1[2] = ip1[3] = ip0[2] = ip0[3] = p[3]; |
4e635e10 |
} else { |
a3adbedf |
p += 6; |
4e635e10 |
memcpy(ip3 + 0, p, 3); memcpy(ip3 + 3, p, 3);
memcpy(ip2 + 0, p, 3); memcpy(ip2 + 3, p, 3);
p += 3; /* ... + 9 */
memcpy(ip3 + 6, p, 3); memcpy(ip3 + 9, p, 3);
memcpy(ip2 + 6, p, 3); memcpy(ip2 + 9, p, 3);
p -= 9; /* ... + 0 */
memcpy(ip1 + 0, p, 3); memcpy(ip1 + 3, p, 3);
memcpy(ip0 + 0, p, 3); memcpy(ip0 + 3, p, 3);
p += 3; /* ... + 3 */
memcpy(ip1 + 6, p, 3); memcpy(ip1 + 9, p, 3);
memcpy(ip0 + 6, p, 3); memcpy(ip0 + 9, p, 3); |
2fdf638b |
}
} else if (flag & mask) {
if ((data + 4) > eod) |
b7d939d9 |
return AVERROR_INVALIDDATA; |
2fdf638b |
|
4e635e10 |
cb0 = strip->v4_codebook[*data++];
cb1 = strip->v4_codebook[*data++];
cb2 = strip->v4_codebook[*data++];
cb3 = strip->v4_codebook[*data++];
if (s->palette_video) {
uint8_t *p;
p = ip3;
*p++ = cb2[6];
*p++ = cb2[9];
*p++ = cb3[6];
*p = cb3[9];
p = ip2;
*p++ = cb2[0];
*p++ = cb2[3];
*p++ = cb3[0];
*p = cb3[3];
p = ip1;
*p++ = cb0[6];
*p++ = cb0[9];
*p++ = cb1[6];
*p = cb1[9];
p = ip0;
*p++ = cb0[0];
*p++ = cb0[3];
*p++ = cb1[0];
*p = cb1[3];
} else {
memcpy(ip3 + 0, cb2 + 6, 6);
memcpy(ip3 + 6, cb3 + 6, 6);
memcpy(ip2 + 0, cb2 + 0, 6);
memcpy(ip2 + 6, cb3 + 0, 6);
memcpy(ip1 + 0, cb0 + 6, 6);
memcpy(ip1 + 6, cb1 + 6, 6);
memcpy(ip0 + 0, cb0 + 0, 6);
memcpy(ip0 + 6, cb1 + 0, 6); |
2fdf638b |
}
}
}
|
4e635e10 |
if (s->palette_video) {
ip0 += 4; ip1 += 4;
ip2 += 4; ip3 += 4;
} else {
ip0 += 12; ip1 += 12;
ip2 += 12; ip3 += 12;
} |
2fdf638b |
}
}
return 0;
}
static int cinepak_decode_strip (CinepakContext *s, |
02fb2546 |
cvid_strip *strip, const uint8_t *data, int size) |
2fdf638b |
{ |
d31522b8 |
const uint8_t *eod = (data + size); |
2fdf638b |
int chunk_id, chunk_size;
/* coordinate sanity checks */ |
a4009c6a |
if (strip->x2 > s->width ||
strip->y2 > s->height || |
94fd9201 |
strip->x1 >= strip->x2 || strip->y1 >= strip->y2) |
b7d939d9 |
return AVERROR_INVALIDDATA; |
2fdf638b |
while ((data + 4) <= eod) { |
d94b1f12 |
chunk_id = data[0];
chunk_size = AV_RB24 (&data[1]) - 4; |
472ea128 |
if(chunk_size < 0) |
b7d939d9 |
return AVERROR_INVALIDDATA; |
472ea128 |
|
2fdf638b |
data += 4;
chunk_size = ((data + chunk_size) > eod) ? (eod - data) : chunk_size;
switch (chunk_id) {
|
d94b1f12 |
case 0x20:
case 0x21:
case 0x24:
case 0x25: |
115329f1 |
cinepak_decode_codebook (strip->v4_codebook, chunk_id, |
2fdf638b |
chunk_size, data);
break;
|
d94b1f12 |
case 0x22:
case 0x23:
case 0x26:
case 0x27: |
115329f1 |
cinepak_decode_codebook (strip->v1_codebook, chunk_id, |
2fdf638b |
chunk_size, data);
break;
|
d94b1f12 |
case 0x30:
case 0x31:
case 0x32: |
115329f1 |
return cinepak_decode_vectors (s, strip, chunk_id, |
2fdf638b |
chunk_size, data);
}
data += chunk_size;
}
|
b7d939d9 |
return AVERROR_INVALIDDATA; |
2fdf638b |
}
static int cinepak_decode (CinepakContext *s)
{ |
d31522b8 |
const uint8_t *eod = (s->data + s->size); |
2fdf638b |
int i, result, strip_size, frame_flags, num_strips;
int y0 = 0; |
af9da83b |
int encoded_buf_size; |
2fdf638b |
if (s->size < 10) |
b7d939d9 |
return AVERROR_INVALIDDATA; |
2fdf638b |
frame_flags = s->data[0]; |
fead30d4 |
num_strips = AV_RB16 (&s->data[8]); |
f35f50b3 |
encoded_buf_size = AV_RB24(&s->data[1]); |
10f865c9 |
/* if this is the first frame, check for deviant Sega FILM data */
if (s->sega_film_skip_bytes == -1) { |
b55aa7df |
if (!encoded_buf_size) { |
6d97484d |
avpriv_request_sample(s->avctx, "encoded_buf_size 0"); |
f3298f12 |
return AVERROR_PATCHWELCOME; |
4e7b3ef3 |
} |
12d9a364 |
if (encoded_buf_size != s->size && (s->size % encoded_buf_size) != 0) { |
10f865c9 |
/* If the encoded frame size differs from the frame size as indicated
* by the container file, this data likely comes from a Sega FILM/CPK file.
* If the frame header is followed by the bytes FE 00 00 06 00 00 then
* this is probably one of the two known files that have 6 extra bytes |
747283a0 |
* after the frame header. Else, assume 2 extra bytes. The container |
12d9a364 |
* size also cannot be a multiple of the encoded size. */ |
dc255275 |
if (s->size >= 16 &&
(s->data[10] == 0xFE) && |
10f865c9 |
(s->data[11] == 0x00) &&
(s->data[12] == 0x00) &&
(s->data[13] == 0x06) &&
(s->data[14] == 0x00) &&
(s->data[15] == 0x00))
s->sega_film_skip_bytes = 6;
else
s->sega_film_skip_bytes = 2;
} else
s->sega_film_skip_bytes = 0;
}
s->data += 10 + s->sega_film_skip_bytes; |
2fdf638b |
|
111ffa55 |
num_strips = FFMIN(num_strips, MAX_STRIPS); |
2fdf638b |
|
80e9e63c |
s->frame->key_frame = 0; |
dd968a2e |
|
2fdf638b |
for (i=0; i < num_strips; i++) {
if ((s->data + 12) > eod) |
b7d939d9 |
return AVERROR_INVALIDDATA; |
2fdf638b |
|
34f3f6d1 |
s->strips[i].id = s->data[0]; |
8d1dd5bd |
/* zero y1 means "relative to the previous stripe" */
if (!(s->strips[i].y1 = AV_RB16 (&s->data[4])))
s->strips[i].y2 = (s->strips[i].y1 = y0) + AV_RB16 (&s->data[8]);
else
s->strips[i].y2 = AV_RB16 (&s->data[8]);
s->strips[i].x1 = AV_RB16 (&s->data[6]);
s->strips[i].x2 = AV_RB16 (&s->data[10]); |
2fdf638b |
|
dd968a2e |
if (s->strips[i].id == 0x10) |
80e9e63c |
s->frame->key_frame = 1; |
dd968a2e |
|
d94b1f12 |
strip_size = AV_RB24 (&s->data[1]) - 12; |
867b4966 |
if (strip_size < 0) |
b7d939d9 |
return AVERROR_INVALIDDATA; |
2fdf638b |
s->data += 12;
strip_size = ((s->data + strip_size) > eod) ? (eod - s->data) : strip_size;
if ((i > 0) && !(frame_flags & 0x01)) {
memcpy (s->strips[i].v4_codebook, s->strips[i-1].v4_codebook,
sizeof(s->strips[i].v4_codebook));
memcpy (s->strips[i].v1_codebook, s->strips[i-1].v1_codebook,
sizeof(s->strips[i].v1_codebook));
}
result = cinepak_decode_strip (s, &s->strips[i], s->data, strip_size);
if (result != 0)
return result;
s->data += strip_size;
y0 = s->strips[i].y2;
}
return 0;
}
|
98a6fff9 |
static av_cold int cinepak_decode_init(AVCodecContext *avctx) |
2fdf638b |
{ |
e4141433 |
CinepakContext *s = avctx->priv_data; |
2fdf638b |
s->avctx = avctx; |
94fd9201 |
s->width = (avctx->width + 3) & ~3;
s->height = (avctx->height + 3) & ~3; |
a3adbedf |
|
10f865c9 |
s->sega_film_skip_bytes = -1; /* uninitialized state */ |
2fdf638b |
|
dab1c4c6 |
// check for paletted data |
2d8591c2 |
if (avctx->bits_per_coded_sample != 8) { |
dab1c4c6 |
s->palette_video = 0; |
4e635e10 |
avctx->pix_fmt = AV_PIX_FMT_RGB24; |
dab1c4c6 |
} else {
s->palette_video = 1; |
716d413c |
avctx->pix_fmt = AV_PIX_FMT_PAL8; |
dab1c4c6 |
} |
2fdf638b |
|
80e9e63c |
s->frame = av_frame_alloc();
if (!s->frame)
return AVERROR(ENOMEM); |
2fdf638b |
return 0;
}
static int cinepak_decode_frame(AVCodecContext *avctx, |
df9b9567 |
void *data, int *got_frame, |
7a00bbad |
AVPacket *avpkt) |
2fdf638b |
{ |
7a00bbad |
const uint8_t *buf = avpkt->data; |
b7d939d9 |
int ret = 0, buf_size = avpkt->size; |
e4141433 |
CinepakContext *s = avctx->priv_data; |
2fdf638b |
s->data = buf;
s->size = buf_size;
|
1ec94b0f |
if ((ret = ff_reget_buffer(avctx, s->frame)) < 0) |
b7d939d9 |
return ret; |
2fdf638b |
|
dab1c4c6 |
if (s->palette_video) { |
2d8591c2 |
const uint8_t *pal = av_packet_get_side_data(avpkt, AV_PKT_DATA_PALETTE, NULL);
if (pal) { |
80e9e63c |
s->frame->palette_has_changed = 1; |
2d8591c2 |
memcpy(s->pal, pal, AVPALETTE_SIZE);
} |
dab1c4c6 |
}
|
0a707da3 |
if ((ret = cinepak_decode(s)) < 0) {
av_log(avctx, AV_LOG_ERROR, "cinepak_decode failed\n");
} |
2d8591c2 |
if (s->palette_video) |
80e9e63c |
memcpy (s->frame->data[1], s->pal, AVPALETTE_SIZE); |
2d8591c2 |
|
80e9e63c |
if ((ret = av_frame_ref(data, s->frame)) < 0) |
759001c5 |
return ret; |
2d8591c2 |
|
df9b9567 |
*got_frame = 1; |
2fdf638b |
/* report that the buffer was completely consumed */
return buf_size;
}
|
98a6fff9 |
static av_cold int cinepak_decode_end(AVCodecContext *avctx) |
2fdf638b |
{ |
e4141433 |
CinepakContext *s = avctx->priv_data; |
2fdf638b |
|
80e9e63c |
av_frame_free(&s->frame); |
2fdf638b |
return 0;
}
|
e7e2df27 |
AVCodec ff_cinepak_decoder = { |
ec6402b7 |
.name = "cinepak", |
b2bed932 |
.long_name = NULL_IF_CONFIG_SMALL("Cinepak"), |
ec6402b7 |
.type = AVMEDIA_TYPE_VIDEO, |
36ef5369 |
.id = AV_CODEC_ID_CINEPAK, |
ec6402b7 |
.priv_data_size = sizeof(CinepakContext),
.init = cinepak_decode_init,
.close = cinepak_decode_end,
.decode = cinepak_decode_frame,
.capabilities = CODEC_CAP_DR1, |
2fdf638b |
}; |