qpeg.c 9.53 KB
Newer Older
1 2 3 4
/*
 * QPEG codec
 * Copyright (c) 2004 Konstantin Shishkov
 *
5
 * This file is part of Libav.
6
 *
7
 * Libav is free software; you can redistribute it and/or
8 9
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * Libav is distributed in the hope that it will be useful,
13 14 15 16 17
 * 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
18
 * License along with Libav; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
 */
21

22
/**
23
 * @file
24 25
 * QPEG codec.
 */
26

27 28 29 30 31 32
#include "avcodec.h"

typedef struct QpegContext{
    AVCodecContext *avctx;
    AVFrame pic;
    uint8_t *refdata;
33
    uint32_t pal[256];
34 35
} QpegContext;

Michael Niedermayer's avatar
Michael Niedermayer committed
36
static void qpeg_decode_intra(const uint8_t *src, uint8_t *dst, int size,
37
                            int stride, int width, int height)
38 39 40 41 42 43
{
    int i;
    int code;
    int c0, c1;
    int run, copy;
    int filled = 0;
Kostya Shishkov's avatar
Kostya Shishkov committed
44
    int rows_to_go;
45

Kostya Shishkov's avatar
Kostya Shishkov committed
46
    rows_to_go = height;
47 48
    height--;
    dst = dst + height * stride;
49

Kostya Shishkov's avatar
Kostya Shishkov committed
50
    while((size > 0) && (rows_to_go > 0)) {
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
        code = *src++;
        size--;
        run = copy = 0;
        if(code == 0xFC) /* end-of-picture code */
            break;
        if(code >= 0xF8) { /* very long run */
            c0 = *src++;
            c1 = *src++;
            size -= 2;
            run = ((code & 0x7) << 16) + (c0 << 8) + c1 + 2;
        } else if (code >= 0xF0) { /* long run */
            c0 = *src++;
            size--;
            run = ((code & 0xF) << 8) + c0 + 2;
        } else if (code >= 0xE0) { /* short run */
            run = (code & 0x1F) + 2;
        } else if (code >= 0xC0) { /* very long copy */
            c0 = *src++;
            c1 = *src++;
            size -= 2;
            copy = ((code & 0x3F) << 16) + (c0 << 8) + c1 + 1;
        } else if (code >= 0x80) { /* long copy */
            c0 = *src++;
            size--;
            copy = ((code & 0x7F) << 8) + c0 + 1;
        } else { /* short copy */
            copy = code + 1;
        }
79

80 81 82
        /* perform actual run or copy */
        if(run) {
            int p;
83

84 85 86 87 88 89 90
            p = *src++;
            size--;
            for(i = 0; i < run; i++) {
                dst[filled++] = p;
                if (filled >= width) {
                    filled = 0;
                    dst -= stride;
Kostya Shishkov's avatar
Kostya Shishkov committed
91 92 93
                    rows_to_go--;
                    if(rows_to_go <= 0)
                        break;
94 95 96
                }
            }
        } else {
Kostya Shishkov's avatar
Kostya Shishkov committed
97
            size -= copy;
98 99 100 101 102
            for(i = 0; i < copy; i++) {
                dst[filled++] = *src++;
                if (filled >= width) {
                    filled = 0;
                    dst -= stride;
Kostya Shishkov's avatar
Kostya Shishkov committed
103 104 105
                    rows_to_go--;
                    if(rows_to_go <= 0)
                        break;
106 107 108
                }
            }
        }
109 110 111
    }
}

112
static const int qpeg_table_h[16] =
113
 { 0x00, 0x20, 0x20, 0x20, 0x18, 0x10, 0x10, 0x20, 0x10, 0x08, 0x18, 0x08, 0x08, 0x18, 0x10, 0x04};
114
static const int qpeg_table_w[16] =
115
 { 0x00, 0x20, 0x18, 0x08, 0x18, 0x10, 0x20, 0x10, 0x08, 0x10, 0x20, 0x20, 0x08, 0x10, 0x18, 0x04};
116

117
/* Decodes delta frames */
Michael Niedermayer's avatar
Michael Niedermayer committed
118
static void qpeg_decode_inter(const uint8_t *src, uint8_t *dst, int size,
119
                            int stride, int width, int height,
Michael Niedermayer's avatar
Michael Niedermayer committed
120
                            int delta, const uint8_t *ctable, uint8_t *refdata)
121 122 123 124
{
    int i, j;
    int code;
    int filled = 0;
Kostya Shishkov's avatar
Kostya Shishkov committed
125
    int orig_height;
126

127 128
    /* copy prev frame */
    for(i = 0; i < height; i++)
129
        memcpy(refdata + (i * width), dst + (i * stride), width);
130

Kostya Shishkov's avatar
Kostya Shishkov committed
131
    orig_height = height;
132 133 134
    height--;
    dst = dst + height * stride;

Kostya Shishkov's avatar
Kostya Shishkov committed
135
    while((size > 0) && (height >= 0)) {
136 137
        code = *src++;
        size--;
138

139 140 141 142 143 144 145 146
        if(delta) {
            /* motion compensation */
            while((code & 0xF0) == 0xF0) {
                if(delta == 1) {
                    int me_idx;
                    int me_w, me_h, me_x, me_y;
                    uint8_t *me_plane;
                    int corr, val;
147

148 149 150 151
                    /* get block size by index */
                    me_idx = code & 0xF;
                    me_w = qpeg_table_w[me_idx];
                    me_h = qpeg_table_h[me_idx];
152

153 154 155
                    /* extract motion vector */
                    corr = *src++;
                    size--;
156

157 158 159 160
                    val = corr >> 4;
                    if(val > 7)
                        val -= 16;
                    me_x = val;
161

162 163 164 165
                    val = corr & 0xF;
                    if(val > 7)
                        val -= 16;
                    me_y = val;
166

Kostya Shishkov's avatar
Kostya Shishkov committed
167 168 169 170 171 172 173 174 175 176 177 178 179
                    /* check motion vector */
                    if ((me_x + filled < 0) || (me_x + me_w + filled > width) ||
                       (height - me_y - me_h < 0) || (height - me_y > orig_height) ||
                       (filled + me_w > width) || (height - me_h < 0))
                        av_log(NULL, AV_LOG_ERROR, "Bogus motion vector (%i,%i), block size %ix%i at %i,%i\n",
                               me_x, me_y, me_w, me_h, filled, height);
                    else {
                        /* do motion compensation */
                        me_plane = refdata + (filled + me_x) + (height - me_y) * width;
                        for(j = 0; j < me_h; j++) {
                            for(i = 0; i < me_w; i++)
                                dst[filled + i - (j * stride)] = me_plane[i - (j * width)];
                        }
180 181 182 183 184 185
                    }
                }
                code = *src++;
                size--;
            }
        }
186

187 188 189 190
        if(code == 0xE0) /* end-of-picture code */
            break;
        if(code > 0xE0) { /* run code: 0xE1..0xFF */
            int p;
191

192 193 194 195 196 197 198 199 200 201 202 203 204
            code &= 0x1F;
            p = *src++;
            size--;
            for(i = 0; i <= code; i++) {
                dst[filled++] = p;
                if(filled >= width) {
                    filled = 0;
                    dst -= stride;
                    height--;
                }
            }
        } else if(code >= 0xC0) { /* copy code: 0xC0..0xDF */
            code &= 0x1F;
205

206 207 208 209 210 211 212 213 214 215 216
            for(i = 0; i <= code; i++) {
                dst[filled++] = *src++;
                if(filled >= width) {
                    filled = 0;
                    dst -= stride;
                    height--;
                }
            }
            size -= code + 1;
        } else if(code >= 0x80) { /* skip code: 0x80..0xBF */
            int skip;
217

218 219 220 221 222 223 224 225 226 227 228 229 230 231
            code &= 0x3F;
            /* codes 0x80 and 0x81 are actually escape codes,
               skip value minus constant is in the next byte */
            if(!code)
                skip = (*src++) + 64;
            else if(code == 1)
                skip = (*src++) + 320;
            else
                skip = code;
            filled += skip;
            while( filled >= width) {
                filled -= width;
                dst -= stride;
                height--;
Kostya Shishkov's avatar
Kostya Shishkov committed
232 233
                if(height < 0)
                    break;
234 235 236 237 238 239 240 241 242 243 244 245 246
            }
        } else {
            /* zero code treated as one-pixel skip */
            if(code)
                dst[filled++] = ctable[code & 0x7F];
            else
                filled++;
            if(filled >= width) {
                filled = 0;
                dst -= stride;
                height--;
            }
        }
247 248 249
    }
}

250
static int decode_frame(AVCodecContext *avctx,
251
                        void *data, int *data_size,
252
                        AVPacket *avpkt)
253
{
254 255
    const uint8_t *buf = avpkt->data;
    int buf_size = avpkt->size;
256 257 258 259
    QpegContext * const a = avctx->priv_data;
    AVFrame * const p= (AVFrame*)&a->pic;
    uint8_t* outdata;
    int delta;
260
    const uint8_t *pal = av_packet_get_side_data(avpkt, AV_PKT_DATA_PALETTE, NULL);
261

262 263 264
    p->reference = 3;
    if (avctx->reget_buffer(avctx, p) < 0) {
        av_log(avctx, AV_LOG_ERROR, "reget_buffer() failed\n");
265 266 267 268
        return -1;
    }
    outdata = a->pic.data[0];
    if(buf[0x85] == 0x10) {
269
        qpeg_decode_intra(buf+0x86, outdata, buf_size - 0x86, a->pic.linesize[0], avctx->width, avctx->height);
270
    } else {
271 272
        delta = buf[0x85];
        qpeg_decode_inter(buf+0x86, outdata, buf_size - 0x86, a->pic.linesize[0], avctx->width, avctx->height, delta, buf + 4, a->refdata);
273 274 275
    }

    /* make the palette available on the way out */
276
    if (pal) {
277
        a->pic.palette_has_changed = 1;
278
        memcpy(a->pal, pal, AVPALETTE_SIZE);
279
    }
280
    memcpy(a->pic.data[1], a->pal, AVPALETTE_SIZE);
281 282 283

    *data_size = sizeof(AVFrame);
    *(AVFrame*)data = a->pic;
284

285 286 287
    return buf_size;
}

288
static av_cold int decode_init(AVCodecContext *avctx){
289
    QpegContext * const a = avctx->priv_data;
290

291 292 293 294 295 296 297
    a->avctx = avctx;
    avctx->pix_fmt= PIX_FMT_PAL8;
    a->refdata = av_malloc(avctx->width * avctx->height);

    return 0;
}

298
static av_cold int decode_end(AVCodecContext *avctx){
299
    QpegContext * const a = avctx->priv_data;
Mike Melanson's avatar
Mike Melanson committed
300
    AVFrame * const p= (AVFrame*)&a->pic;
301

Mike Melanson's avatar
Mike Melanson committed
302 303
    if(p->data[0])
        avctx->release_buffer(avctx, p);
304 305 306 307 308

    av_free(a->refdata);
    return 0;
}

309
AVCodec ff_qpeg_decoder = {
310 311 312 313 314 315 316 317
    .name           = "qpeg",
    .type           = AVMEDIA_TYPE_VIDEO,
    .id             = CODEC_ID_QPEG,
    .priv_data_size = sizeof(QpegContext),
    .init           = decode_init,
    .close          = decode_end,
    .decode         = decode_frame,
    .capabilities   = CODEC_CAP_DR1,
318
    .long_name = NULL_IF_CONFIG_SMALL("Q-team QPEG"),
319
};