libavcodec/mmvideo.c
4e114829
 /*
  * American Laser Games MM Video Decoder
60f451b6
  * Copyright (c) 2006,2008 Peter Ross
4e114829
  *
b78e7197
  * This file is part of FFmpeg.
  *
  * FFmpeg is free software; you can redistribute it and/or
4e114829
  * 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.
4e114829
  *
b78e7197
  * FFmpeg is distributed in the hope that it will be useful,
4e114829
  * 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
e5a389a1
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
4e114829
  */
 
 /**
ba87f080
  * @file
4e114829
  * American Laser Games MM Video Decoder
8600106a
  * by Peter Ross (pross@xvid.org)
4e114829
  *
  * The MM format was used by IBM-PC ports of ALG's "arcade shooter" games,
  * including Mad Dog McCree and Crime Patrol.
  *
  * Technical details here:
  *  http://wiki.multimedia.cx/index.php?title=American_Laser_Games_MM
  */
 
6a5d31ac
 #include "libavutil/intreadwrite.h"
4e114829
 #include "avcodec.h"
a55d5bdc
 #include "bytestream.h"
759001c5
 #include "internal.h"
4e114829
 
 #define MM_PREAMBLE_SIZE    6
 
 #define MM_TYPE_INTER       0x5
 #define MM_TYPE_INTRA       0x8
 #define MM_TYPE_INTRA_HH    0xc
 #define MM_TYPE_INTER_HH    0xd
 #define MM_TYPE_INTRA_HHV   0xe
 #define MM_TYPE_INTER_HHV   0xf
60f451b6
 #define MM_TYPE_PALETTE     0x31
4e114829
 
 typedef struct MmContext {
     AVCodecContext *avctx;
5c96f029
     AVFrame *frame;
60f451b6
     int palette[AVPALETTE_COUNT];
a55d5bdc
     GetByteContext gb;
4e114829
 } MmContext;
 
98a6fff9
 static av_cold int mm_decode_init(AVCodecContext *avctx)
4e114829
 {
     MmContext *s = avctx->priv_data;
 
     s->avctx = avctx;
 
716d413c
     avctx->pix_fmt = AV_PIX_FMT_PAL8;
4e114829
 
5c96f029
     s->frame = av_frame_alloc();
     if (!s->frame)
         return AVERROR(ENOMEM);
3b199d29
 
4e114829
     return 0;
 }
 
a55d5bdc
 static int mm_decode_pal(MmContext *s)
60f451b6
 {
     int i;
a55d5bdc
 
     bytestream2_skip(&s->gb, 4);
     for (i = 0; i < 128; i++) {
b12d92ef
         s->palette[i] = 0xFFU << 24 | bytestream2_get_be24(&s->gb);
60f451b6
         s->palette[i+128] = s->palette[i]<<2;
     }
a55d5bdc
 
     return 0;
60f451b6
 }
 
091bc6ca
 /**
  * @param half_horiz Half horizontal resolution (0 or 1)
  * @param half_vert Half vertical resolution (0 or 1)
  */
a55d5bdc
 static int mm_decode_intra(MmContext * s, int half_horiz, int half_vert)
4e114829
 {
246b050f
     int x = 0, y = 0;
4e114829
 
a55d5bdc
     while (bytestream2_get_bytes_left(&s->gb) > 0) {
4e114829
         int run_length, color;
 
94e58e57
         if (y >= s->avctx->height)
a55d5bdc
             return 0;
94e58e57
 
a55d5bdc
         color = bytestream2_get_byte(&s->gb);
         if (color & 0x80) {
4e114829
             run_length = 1;
         }else{
a55d5bdc
             run_length = (color & 0x7f) + 2;
             color = bytestream2_get_byte(&s->gb);
4e114829
         }
 
         if (half_horiz)
             run_length *=2;
 
ae2132ac
         if (run_length > s->avctx->width - x)
             return AVERROR_INVALIDDATA;
 
4e114829
         if (color) {
5c96f029
             memset(s->frame->data[0] + y*s->frame->linesize[0] + x, color, run_length);
4e114829
             if (half_vert)
5c96f029
                 memset(s->frame->data[0] + (y+1)*s->frame->linesize[0] + x, color, run_length);
4e114829
         }
         x+= run_length;
 
         if (x >= s->avctx->width) {
             x=0;
091bc6ca
             y += 1 + half_vert;
4e114829
         }
     }
a55d5bdc
 
     return 0;
4e114829
 }
 
9ccc349f
 /**
091bc6ca
  * @param half_horiz Half horizontal resolution (0 or 1)
  * @param half_vert Half vertical resolution (0 or 1)
  */
a55d5bdc
 static int mm_decode_inter(MmContext * s, int half_horiz, int half_vert)
4e114829
 {
fc06ee6e
     int data_off = bytestream2_get_le16(&s->gb);
     int y = 0;
a55d5bdc
     GetByteContext data_ptr;
37fca5da
 
a55d5bdc
     if (bytestream2_get_bytes_left(&s->gb) < data_off)
         return AVERROR_INVALIDDATA;
37fca5da
 
a55d5bdc
     bytestream2_init(&data_ptr, s->gb.buffer + data_off, bytestream2_get_bytes_left(&s->gb) - data_off);
     while (s->gb.buffer < data_ptr.buffer_start) {
4e114829
         int i, j;
a55d5bdc
         int length = bytestream2_get_byte(&s->gb);
         int x = bytestream2_get_byte(&s->gb) + ((length & 0x80) << 1);
         length &= 0x7F;
4e114829
 
         if (length==0) {
             y += x;
             continue;
         }
 
94e58e57
         if (y + half_vert >= s->avctx->height)
a55d5bdc
             return 0;
94e58e57
 
4e114829
         for(i=0; i<length; i++) {
a55d5bdc
             int replace_array = bytestream2_get_byte(&s->gb);
4e114829
             for(j=0; j<8; j++) {
a55d5bdc
                 int replace = (replace_array >> (7-j)) & 1;
8d3c99e8
                 if (x + half_horiz >= s->avctx->width)
                     return AVERROR_INVALIDDATA;
4e114829
                 if (replace) {
a55d5bdc
                     int color = bytestream2_get_byte(&data_ptr);
5c96f029
                     s->frame->data[0][y*s->frame->linesize[0] + x] = color;
4e114829
                     if (half_horiz)
5c96f029
                         s->frame->data[0][y*s->frame->linesize[0] + x + 1] = color;
4e114829
                     if (half_vert) {
5c96f029
                         s->frame->data[0][(y+1)*s->frame->linesize[0] + x] = color;
4e114829
                         if (half_horiz)
5c96f029
                             s->frame->data[0][(y+1)*s->frame->linesize[0] + x + 1] = color;
4e114829
                     }
                 }
091bc6ca
                 x += 1 + half_horiz;
4e114829
             }
         }
 
091bc6ca
         y += 1 + half_vert;
4e114829
     }
a55d5bdc
 
     return 0;
4e114829
 }
 
 static int mm_decode_frame(AVCodecContext *avctx,
df9b9567
                             void *data, int *got_frame,
7a00bbad
                             AVPacket *avpkt)
4e114829
 {
7a00bbad
     const uint8_t *buf = avpkt->data;
     int buf_size = avpkt->size;
4e114829
     MmContext *s = avctx->priv_data;
a55d5bdc
     int type, res;
4e114829
 
a55d5bdc
     if (buf_size < MM_PREAMBLE_SIZE)
         return AVERROR_INVALIDDATA;
fead30d4
     type = AV_RL16(&buf[0]);
4e114829
     buf += MM_PREAMBLE_SIZE;
     buf_size -= MM_PREAMBLE_SIZE;
a55d5bdc
     bytestream2_init(&s->gb, buf, buf_size);
4e114829
 
fe3808ed
     if ((res = ff_reget_buffer(avctx, s->frame)) < 0)
f3bd6fa7
         return res;
6e1f0d5e
 
4e114829
     switch(type) {
3d5dc7d8
     case MM_TYPE_PALETTE   : res = mm_decode_pal(s); return avpkt->size;
a55d5bdc
     case MM_TYPE_INTRA     : res = mm_decode_intra(s, 0, 0); break;
     case MM_TYPE_INTRA_HH  : res = mm_decode_intra(s, 1, 0); break;
     case MM_TYPE_INTRA_HHV : res = mm_decode_intra(s, 1, 1); break;
     case MM_TYPE_INTER     : res = mm_decode_inter(s, 0, 0); break;
     case MM_TYPE_INTER_HH  : res = mm_decode_inter(s, 1, 0); break;
     case MM_TYPE_INTER_HHV : res = mm_decode_inter(s, 1, 1); break;
     default:
         res = AVERROR_INVALIDDATA;
         break;
4e114829
     }
a55d5bdc
     if (res < 0)
         return res;
4e114829
 
5c96f029
     memcpy(s->frame->data[1], s->palette, AVPALETTE_SIZE);
60f451b6
 
5c96f029
     if ((res = av_frame_ref(data, s->frame)) < 0)
759001c5
         return res;
 
df9b9567
     *got_frame      = 1;
4e114829
 
3d5dc7d8
     return avpkt->size;
4e114829
 }
 
98a6fff9
 static av_cold int mm_decode_end(AVCodecContext *avctx)
4e114829
 {
     MmContext *s = avctx->priv_data;
 
5c96f029
     av_frame_free(&s->frame);
4e114829
 
     return 0;
 }
 
e7e2df27
 AVCodec ff_mmvideo_decoder = {
ec6402b7
     .name           = "mmvideo",
b2bed932
     .long_name      = NULL_IF_CONFIG_SMALL("American Laser Games MM Video"),
ec6402b7
     .type           = AVMEDIA_TYPE_VIDEO,
36ef5369
     .id             = AV_CODEC_ID_MMVIDEO,
ec6402b7
     .priv_data_size = sizeof(MmContext),
     .init           = mm_decode_init,
     .close          = mm_decode_end,
     .decode         = mm_decode_frame,
     .capabilities   = CODEC_CAP_DR1,
4e114829
 };