Skip to content
Snippets Groups Projects
dxa.c 11.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • Kostya Shishkov's avatar
    Kostya Shishkov committed
    /*
     * Feeble Files/ScummVM DXA decoder
     * Copyright (c) 2007 Konstantin Shishkov
     *
     * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA
     */
    
    /**
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
     * DXA Video decoder
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    
    #include "libavutil/common.h"
    
    #include "libavutil/intreadwrite.h"
    
    #include "bytestream.h"
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
    #include "avcodec.h"
    
    #include "internal.h"
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
    
    #include <zlib.h>
    
    /*
     * Decoder context
     */
    typedef struct DxaDecContext {
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
    
        int dsize;
        uint8_t *decomp_buf;
        uint32_t pal[256];
    } DxaDecContext;
    
    static const int shift1[6] = { 0, 8, 8, 8, 4, 4 };
    static const int shift2[6] = { 0, 0, 8, 4, 0, 4 };
    
    
    static int decode_13(AVCodecContext *avctx, DxaDecContext *c, uint8_t* dst,
                         int stride, uint8_t *src, uint8_t *ref)
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
    {
        uint8_t *code, *data, *mv, *msk, *tmp, *tmp2;
        int i, j, k;
        int type, x, y, d, d2;
        uint32_t mask;
    
        code = src  + 12;
        data = code + ((avctx->width * avctx->height) >> 4);
        mv   = data + AV_RB32(src + 0);
        msk  = mv   + AV_RB32(src + 4);
    
        for(j = 0; j < avctx->height; j += 4){
            for(i = 0; i < avctx->width; i += 4){
                tmp  = dst + i;
                tmp2 = ref + i;
                type = *code++;
                switch(type){
                case 4: // motion compensation
                    x = (*mv) >> 4;    if(x & 8) x = 8 - x;
                    y = (*mv++) & 0xF; if(y & 8) y = 8 - y;
    
                    if (i < -x || avctx->width  - i - 4 < x ||
                        j < -y || avctx->height - j - 4 < y) {
                        av_log(avctx, AV_LOG_ERROR, "MV %d %d out of bounds\n", x,y);
                        return AVERROR_INVALIDDATA;
                    }
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
                    tmp2 += x + y*stride;
                case 0: // skip
                case 5: // skip in method 12
                    for(y = 0; y < 4; y++){
                        memcpy(tmp, tmp2, 4);
                        tmp  += stride;
                        tmp2 += stride;
                    }
                    break;
                case 1:  // masked change
                case 10: // masked change with only half of pixels changed
                case 11: // cases 10-15 are for method 12 only
                case 12:
                case 13:
                case 14:
                case 15:
                    if(type == 1){
                        mask = AV_RB16(msk);
                        msk += 2;
                    }else{
                        type -= 10;
                        mask = ((msk[0] & 0xF0) << shift1[type]) | ((msk[0] & 0xF) << shift2[type]);
                        msk++;
                    }
                    for(y = 0; y < 4; y++){
                        for(x = 0; x < 4; x++){
                            tmp[x] = (mask & 0x8000) ? *data++ : tmp2[x];
                            mask <<= 1;
                        }
                        tmp  += stride;
                        tmp2 += stride;
                    }
                    break;
                case 2: // fill block
                    for(y = 0; y < 4; y++){
                        memset(tmp, data[0], 4);
                        tmp += stride;
                    }
                    data++;
                    break;
                case 3: // raw block
                    for(y = 0; y < 4; y++){
                        memcpy(tmp, data, 4);
                        data += 4;
                        tmp  += stride;
                    }
                    break;
                case 8: // subblocks - method 13 only
                    mask = *msk++;
                    for(k = 0; k < 4; k++){
                        d  = ((k & 1) << 1) + ((k & 2) * stride);
                        d2 = ((k & 1) << 1) + ((k & 2) * stride);
                        tmp2 = ref + i + d2;
                        switch(mask & 0xC0){
                        case 0x80: // motion compensation
                            x = (*mv) >> 4;    if(x & 8) x = 8 - x;
                            y = (*mv++) & 0xF; if(y & 8) y = 8 - y;
    
                            if (i + 2*(k & 1) < -x || avctx->width  - i - 2*(k & 1) - 2 < x ||
                                j +   (k & 2) < -y || avctx->height - j -   (k & 2) - 2 < y) {
                                av_log(avctx, AV_LOG_ERROR, "MV %d %d out of bounds\n", x,y);
                                return AVERROR_INVALIDDATA;
                            }
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
                            tmp2 += x + y*stride;
                        case 0x00: // skip
                            tmp[d + 0         ] = tmp2[0];
                            tmp[d + 1         ] = tmp2[1];
                            tmp[d + 0 + stride] = tmp2[0 + stride];
                            tmp[d + 1 + stride] = tmp2[1 + stride];
                            break;
                        case 0x40: // fill
                            tmp[d + 0         ] = data[0];
                            tmp[d + 1         ] = data[0];
                            tmp[d + 0 + stride] = data[0];
                            tmp[d + 1 + stride] = data[0];
                            data++;
                            break;
                        case 0xC0: // raw
                            tmp[d + 0         ] = *data++;
                            tmp[d + 1         ] = *data++;
                            tmp[d + 0 + stride] = *data++;
                            tmp[d + 1 + stride] = *data++;
                            break;
                        }
                        mask <<= 2;
                    }
                    break;
                case 32: // vector quantization - 2 colors
                    mask = AV_RB16(msk);
                    msk += 2;
                    for(y = 0; y < 4; y++){
                        for(x = 0; x < 4; x++){
                            tmp[x] = data[mask & 1];
                            mask >>= 1;
                        }
                        tmp  += stride;
                        tmp2 += stride;
                    }
                    data += 2;
                    break;
                case 33: // vector quantization - 3 or 4 colors
                case 34:
                    mask = AV_RB32(msk);
                    msk += 4;
                    for(y = 0; y < 4; y++){
                        for(x = 0; x < 4; x++){
                            tmp[x] = data[mask & 3];
                            mask >>= 2;
                        }
                        tmp  += stride;
                        tmp2 += stride;
                    }
                    data += type - 30;
                    break;
                default:
                    av_log(avctx, AV_LOG_ERROR, "Unknown opcode %d\n", type);
    
                    return AVERROR_INVALIDDATA;
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
                }
            }
            dst += stride * 4;
            ref += stride * 4;
        }
        return 0;
    }
    
    
    static int decode_frame(AVCodecContext *avctx, void *data, int *got_frame, AVPacket *avpkt)
    
        AVFrame *frame = data;
    
        DxaDecContext * const c = avctx->priv_data;
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
        uint8_t *outptr, *srcptr, *tmpptr;
        unsigned long dsize;
    
        int i, j, compr, ret;
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
        int stride;
        int pc = 0;
    
        GetByteContext gb;
    
        bytestream2_init(&gb, avpkt->data, avpkt->size);
    
        /* make the palette available on the way out */
        if (bytestream2_peek_le32(&gb) == MKTAG('C','M','A','P')) {
            bytestream2_skip(&gb, 4);
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
            for(i = 0; i < 256; i++){
    
                c->pal[i] = 0xFFU << 24 | bytestream2_get_be24(&gb);
    
        if ((ret = ff_get_buffer(avctx, frame, AV_GET_BUFFER_FLAG_REF)) < 0)
    
        memcpy(frame->data[1], c->pal, AVPALETTE_SIZE);
        frame->palette_has_changed = pc;
    
        outptr = frame->data[0];
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
        srcptr = c->decomp_buf;
    
        tmpptr = c->prev->data[0];
    
        stride = frame->linesize[0];
    
        if (bytestream2_get_le32(&gb) == MKTAG('N','U','L','L'))
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
            compr = -1;
        else
    
            compr = bytestream2_get_byte(&gb);
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
    
        dsize = c->dsize;
    
        if (compr != 4 && compr != -1) {
            bytestream2_skip(&gb, 4);
            if (uncompress(c->decomp_buf, &dsize, avpkt->data + bytestream2_tell(&gb),
                           bytestream2_get_bytes_left(&gb)) != Z_OK) {
                av_log(avctx, AV_LOG_ERROR, "Uncompress failed!\n");
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
        }
        switch(compr){
        case -1:
    
            frame->key_frame = 0;
            frame->pict_type = AV_PICTURE_TYPE_P;
    
            if (c->prev->data[0])
                memcpy(frame->data[0], c->prev->data[0], frame->linesize[0] * avctx->height);
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
            else{ // Should happen only when first frame is 'NULL'
    
                memset(frame->data[0], 0, frame->linesize[0] * avctx->height);
                frame->key_frame = 1;
                frame->pict_type = AV_PICTURE_TYPE_I;
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
            }
            break;
        case 2:
        case 3:
        case 4:
        case 5:
    
            frame->key_frame = !(compr & 1);
            frame->pict_type = (compr & 1) ? AV_PICTURE_TYPE_P : AV_PICTURE_TYPE_I;
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
            for(j = 0; j < avctx->height; j++){
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
                    for(i = 0; i < avctx->width; i++)
                        outptr[i] = srcptr[i] ^ tmpptr[i];
                    tmpptr += stride;
                }else
                    memcpy(outptr, srcptr, avctx->width);
                outptr += stride;
                srcptr += avctx->width;
            }
            break;
        case 12: // ScummVM coding
        case 13:
    
            frame->key_frame = 0;
            frame->pict_type = AV_PICTURE_TYPE_P;
    
            if (!c->prev->data[0]) {
    
                av_log(avctx, AV_LOG_ERROR, "Missing reference frame\n");
                return AVERROR_INVALIDDATA;
            }
    
            decode_13(avctx, c, frame->data[0], frame->linesize[0], srcptr, c->prev->data[0]);
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
            break;
        default:
    
            av_log(avctx, AV_LOG_ERROR, "Unknown/unsupported compression type %d\n", compr);
    
            return AVERROR_INVALIDDATA;
    
        av_frame_unref(c->prev);
        if ((ret = av_frame_ref(c->prev, frame)) < 0)
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
    
        /* always report that the buffer was completely consumed */
    
        return avpkt->size;
    
    static av_cold int decode_init(AVCodecContext *avctx)
    
        DxaDecContext * const c = avctx->priv_data;
    
        avctx->pix_fmt = AV_PIX_FMT_PAL8;
    
        c->prev = av_frame_alloc();
        if (!c->prev)
            return AVERROR(ENOMEM);
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
        c->dsize = avctx->width * avctx->height * 2;
    
        c->decomp_buf = av_malloc(c->dsize);
        if (!c->decomp_buf) {
    
            av_frame_free(&c->prev);
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
            av_log(avctx, AV_LOG_ERROR, "Can't allocate decompression buffer.\n");
    
            return AVERROR(ENOMEM);
    
    static av_cold int decode_end(AVCodecContext *avctx)
    
        DxaDecContext * const c = avctx->priv_data;
    
    Kostya Shishkov's avatar
    Kostya Shishkov committed
    
        av_freep(&c->decomp_buf);
    
        av_frame_free(&c->prev);
    
        .name           = "dxa",
        .type           = AVMEDIA_TYPE_VIDEO,
    
        .id             = AV_CODEC_ID_DXA,
    
        .priv_data_size = sizeof(DxaDecContext),
        .init           = decode_init,
        .close          = decode_end,
        .decode         = decode_frame,
        .capabilities   = CODEC_CAP_DR1,
    
        .long_name      = NULL_IF_CONFIG_SMALL("Feeble Files/ScummVM DXA"),