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