eacmv.c 7.56 KB
Newer Older
Peter Ross's avatar
Peter Ross committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * Electronic Arts CMV 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
 */

/**
23
 * @file
Peter Ross's avatar
Peter Ross committed
24
 * Electronic Arts CMV Video Decoder
Peter Ross's avatar
Peter Ross committed
25
 * by Peter Ross (pross@xvid.org)
Peter Ross's avatar
Peter Ross committed
26 27 28 29 30
 *
 * Technical details here:
 * http://wiki.multimedia.cx/index.php?title=Electronic_Arts_CMV
 */

31
#include "libavutil/common.h"
32
#include "libavutil/intreadwrite.h"
33
#include "libavutil/imgutils.h"
Peter Ross's avatar
Peter Ross committed
34
#include "avcodec.h"
35
#include "internal.h"
Peter Ross's avatar
Peter Ross committed
36 37 38

typedef struct CmvContext {
    AVCodecContext *avctx;
39 40
    AVFrame *last_frame;   ///< last
    AVFrame *last2_frame;  ///< second-last
Peter Ross's avatar
Peter Ross committed
41 42 43 44 45 46
    int width, height;
    unsigned int palette[AVPALETTE_COUNT];
} CmvContext;

static av_cold int cmv_decode_init(AVCodecContext *avctx){
    CmvContext *s = avctx->priv_data;
47

Peter Ross's avatar
Peter Ross committed
48
    s->avctx = avctx;
49
    avctx->pix_fmt = AV_PIX_FMT_PAL8;
50 51 52 53 54 55 56 57 58

    s->last_frame  = av_frame_alloc();
    s->last2_frame = av_frame_alloc();
    if (!s->last_frame || !s->last2_frame) {
        av_frame_free(&s->last_frame);
        av_frame_free(&s->last2_frame);
        return AVERROR(ENOMEM);
    }

Peter Ross's avatar
Peter Ross committed
59 60 61
    return 0;
}

62 63 64 65
static void cmv_decode_intra(CmvContext * s, AVFrame *frame,
                             const uint8_t *buf, const uint8_t *buf_end)
{
    unsigned char *dst = frame->data[0];
Peter Ross's avatar
Peter Ross committed
66 67
    int i;

68
    for (i=0; i < s->avctx->height && buf_end - buf >= s->avctx->width; i++) {
Peter Ross's avatar
Peter Ross committed
69
        memcpy(dst, buf, s->avctx->width);
70
        dst += frame->linesize[0];
Peter Ross's avatar
Peter Ross committed
71 72 73 74
        buf += s->avctx->width;
    }
}

75 76
static void cmv_motcomp(unsigned char *dst, ptrdiff_t dst_stride,
                        const unsigned char *src, ptrdiff_t src_stride,
Peter Ross's avatar
Peter Ross committed
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
                        int x, int y,
                        int xoffset, int yoffset,
                        int width, int height){
    int i,j;

    for(j=y;j<y+4;j++)
    for(i=x;i<x+4;i++)
    {
        if (i+xoffset>=0 && i+xoffset<width &&
            j+yoffset>=0 && j+yoffset<height) {
            dst[j*dst_stride + i] = src[(j+yoffset)*src_stride + i+xoffset];
        }else{
            dst[j*dst_stride + i] = 0;
        }
    }
}

94 95 96
static void cmv_decode_inter(CmvContext *s, AVFrame *frame, const uint8_t *buf,
                             const uint8_t *buf_end)
{
Peter Ross's avatar
Peter Ross committed
97 98 99 100 101
    const uint8_t *raw = buf + (s->avctx->width*s->avctx->height/16);
    int x,y,i;

    i = 0;
    for(y=0; y<s->avctx->height/4; y++)
102
    for(x=0; x<s->avctx->width/4 && buf_end - buf > i; x++) {
Peter Ross's avatar
Peter Ross committed
103
        if (buf[i]==0xFF) {
104
            unsigned char *dst = frame->data[0] + (y*4)*frame->linesize[0] + x*4;
Peter Ross's avatar
Peter Ross committed
105 106 107
            if (raw+16<buf_end && *raw==0xFF) { /* intra */
                raw++;
                memcpy(dst, raw, 4);
108 109 110
                memcpy(dst +     frame->linesize[0], raw+4, 4);
                memcpy(dst + 2 * frame->linesize[0], raw+8, 4);
                memcpy(dst + 3 * frame->linesize[0], raw+12, 4);
Peter Ross's avatar
Peter Ross committed
111 112 113 114
                raw+=16;
            }else if(raw<buf_end) {  /* inter using second-last frame as reference */
                int xoffset = (*raw & 0xF) - 7;
                int yoffset = ((*raw >> 4)) - 7;
115 116 117
                if (s->last2_frame->data[0])
                    cmv_motcomp(frame->data[0], frame->linesize[0],
                                s->last2_frame->data[0], s->last2_frame->linesize[0],
Peter Ross's avatar
Peter Ross committed
118
                                x*4, y*4, xoffset, yoffset, s->avctx->width, s->avctx->height);
Peter Ross's avatar
Peter Ross committed
119 120 121 122 123
                raw++;
            }
        }else{  /* inter using last frame as reference */
            int xoffset = (buf[i] & 0xF) - 7;
            int yoffset = ((buf[i] >> 4)) - 7;
124 125
            if (s->last_frame->data[0])
                cmv_motcomp(frame->data[0], frame->linesize[0],
126 127
                            s->last_frame->data[0], s->last_frame->linesize[0],
                            x*4, y*4, xoffset, yoffset, s->avctx->width, s->avctx->height);
Peter Ross's avatar
Peter Ross committed
128 129 130 131 132
        }
        i++;
    }
}

133
static int cmv_process_header(CmvContext *s, const uint8_t *buf, const uint8_t *buf_end)
Peter Ross's avatar
Peter Ross committed
134
{
135
    int pal_start, pal_count, i, ret, fps;
Peter Ross's avatar
Peter Ross committed
136

137
    if(buf_end - buf < 16) {
Peter Ross's avatar
Peter Ross committed
138
        av_log(s->avctx, AV_LOG_WARNING, "truncated header\n");
139
        return AVERROR_INVALIDDATA;
Peter Ross's avatar
Peter Ross committed
140 141 142 143
    }

    s->width  = AV_RL16(&buf[4]);
    s->height = AV_RL16(&buf[6]);
144

145 146
    if (s->width  != s->avctx->width ||
        s->height != s->avctx->height) {
147 148
        av_frame_unref(s->last_frame);
        av_frame_unref(s->last2_frame);
149
    }
Peter Ross's avatar
Peter Ross committed
150

151 152 153
    ret = ff_set_dimensions(s->avctx, s->width, s->height);
    if (ret < 0)
        return ret;
Peter Ross's avatar
Peter Ross committed
154

155 156
    fps = AV_RL16(&buf[10]);
    if (fps > 0)
157
        s->avctx->framerate = (AVRational){ fps, 1 };
Peter Ross's avatar
Peter Ross committed
158 159 160 161 162

    pal_start = AV_RL16(&buf[12]);
    pal_count = AV_RL16(&buf[14]);

    buf += 16;
163
    for (i=pal_start; i<pal_start+pal_count && i<AVPALETTE_COUNT && buf_end - buf >= 3; i++) {
164
        s->palette[i] = 0xFFU << 24 | AV_RB24(buf);
Peter Ross's avatar
Peter Ross committed
165 166
        buf += 3;
    }
167 168

    return 0;
Peter Ross's avatar
Peter Ross committed
169 170 171 172 173 174
}

#define EA_PREAMBLE_SIZE 8
#define MVIh_TAG MKTAG('M', 'V', 'I', 'h')

static int cmv_decode_frame(AVCodecContext *avctx,
175
                            void *data, int *got_frame,
176
                            AVPacket *avpkt)
Peter Ross's avatar
Peter Ross committed
177
{
178 179
    const uint8_t *buf = avpkt->data;
    int buf_size = avpkt->size;
Peter Ross's avatar
Peter Ross committed
180 181
    CmvContext *s = avctx->priv_data;
    const uint8_t *buf_end = buf + buf_size;
182 183
    AVFrame *frame = data;
    int ret;
Peter Ross's avatar
Peter Ross committed
184

185 186 187
    if (buf_end - buf < EA_PREAMBLE_SIZE)
        return AVERROR_INVALIDDATA;

Peter Ross's avatar
Peter Ross committed
188
    if (AV_RL32(buf)==MVIh_TAG||AV_RB32(buf)==MVIh_TAG) {
189
        unsigned size = AV_RL32(buf + 4);
190 191 192
        ret = cmv_process_header(s, buf+EA_PREAMBLE_SIZE, buf_end);
        if (ret < 0)
            return ret;
193
        if (size > buf_end - buf - EA_PREAMBLE_SIZE)
194
            return AVERROR_INVALIDDATA;
195
        buf += size;
Peter Ross's avatar
Peter Ross committed
196 197
    }

198 199
    if ((ret = av_image_check_size(s->width, s->height, 0, s->avctx)) < 0)
        return ret;
Peter Ross's avatar
Peter Ross committed
200

201
    if ((ret = ff_get_buffer(avctx, frame, AV_GET_BUFFER_FLAG_REF)) < 0)
202
        return ret;
Peter Ross's avatar
Peter Ross committed
203

204
    memcpy(frame->data[1], s->palette, AVPALETTE_SIZE);
Peter Ross's avatar
Peter Ross committed
205 206 207

    buf += EA_PREAMBLE_SIZE;
    if ((buf[0]&1)) {  // subtype
208 209 210
        cmv_decode_inter(s, frame, buf+2, buf_end);
        frame->key_frame = 0;
        frame->pict_type = AV_PICTURE_TYPE_P;
Peter Ross's avatar
Peter Ross committed
211
    }else{
212 213 214
        frame->key_frame = 1;
        frame->pict_type = AV_PICTURE_TYPE_I;
        cmv_decode_intra(s, frame, buf+2, buf_end);
Peter Ross's avatar
Peter Ross committed
215 216
    }

217 218 219 220 221
    av_frame_unref(s->last2_frame);
    av_frame_move_ref(s->last2_frame, s->last_frame);
    if ((ret = av_frame_ref(s->last_frame, frame)) < 0)
        return ret;

222
    *got_frame = 1;
Peter Ross's avatar
Peter Ross committed
223 224 225 226 227 228

    return buf_size;
}

static av_cold int cmv_decode_end(AVCodecContext *avctx){
    CmvContext *s = avctx->priv_data;
229 230 231

    av_frame_free(&s->last_frame);
    av_frame_free(&s->last2_frame);
Peter Ross's avatar
Peter Ross committed
232 233 234 235

    return 0;
}

236
AVCodec ff_eacmv_decoder = {
237
    .name           = "eacmv",
238
    .long_name      = NULL_IF_CONFIG_SMALL("Electronic Arts CMV video"),
239
    .type           = AVMEDIA_TYPE_VIDEO,
240
    .id             = AV_CODEC_ID_CMV,
241 242 243 244
    .priv_data_size = sizeof(CmvContext),
    .init           = cmv_decode_init,
    .close          = cmv_decode_end,
    .decode         = cmv_decode_frame,
245
    .capabilities   = AV_CODEC_CAP_DR1,
Peter Ross's avatar
Peter Ross committed
246
};