1/* 2 * American Laser Games MM Video Decoder 3 * Copyright (c) 2006,2008 Peter Ross 4 * 5 * This file is part of Libav. 6 * 7 * Libav is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * 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 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * 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 * American Laser Games MM Video Decoder 25 * by Peter Ross (pross@xvid.org) 26 * 27 * The MM format was used by IBM-PC ports of ALG's "arcade shooter" games, 28 * including Mad Dog McCree and Crime Patrol. 29 * 30 * Technical details here: 31 * http://wiki.multimedia.cx/index.php?title=American_Laser_Games_MM 32 */ 33 34#include "libavutil/intreadwrite.h" 35#include "avcodec.h" 36#include "bytestream.h" 37 38#define MM_PREAMBLE_SIZE 6 39 40#define MM_TYPE_INTER 0x5 41#define MM_TYPE_INTRA 0x8 42#define MM_TYPE_INTRA_HH 0xc 43#define MM_TYPE_INTER_HH 0xd 44#define MM_TYPE_INTRA_HHV 0xe 45#define MM_TYPE_INTER_HHV 0xf 46#define MM_TYPE_PALETTE 0x31 47 48typedef struct MmContext { 49 AVCodecContext *avctx; 50 AVFrame frame; 51 int palette[AVPALETTE_COUNT]; 52 GetByteContext gb; 53} MmContext; 54 55static av_cold int mm_decode_init(AVCodecContext *avctx) 56{ 57 MmContext *s = avctx->priv_data; 58 59 s->avctx = avctx; 60 61 avctx->pix_fmt = PIX_FMT_PAL8; 62 63 s->frame.reference = 1; 64 65 return 0; 66} 67 68static int mm_decode_pal(MmContext *s) 69{ 70 int i; 71 72 bytestream2_skip(&s->gb, 4); 73 for (i = 0; i < 128; i++) { 74 s->palette[i] = bytestream2_get_be24(&s->gb); 75 s->palette[i+128] = s->palette[i]<<2; 76 } 77 78 return 0; 79} 80 81/** 82 * @param half_horiz Half horizontal resolution (0 or 1) 83 * @param half_vert Half vertical resolution (0 or 1) 84 */ 85static int mm_decode_intra(MmContext * s, int half_horiz, int half_vert) 86{ 87 int i, x, y; 88 i=0; x=0; y=0; 89 90 while (bytestream2_get_bytes_left(&s->gb) > 0) { 91 int run_length, color; 92 93 if (y >= s->avctx->height) 94 return 0; 95 96 color = bytestream2_get_byte(&s->gb); 97 if (color & 0x80) { 98 run_length = 1; 99 }else{ 100 run_length = (color & 0x7f) + 2; 101 color = bytestream2_get_byte(&s->gb); 102 } 103 104 if (half_horiz) 105 run_length *=2; 106 107 if (color) { 108 memset(s->frame.data[0] + y*s->frame.linesize[0] + x, color, run_length); 109 if (half_vert) 110 memset(s->frame.data[0] + (y+1)*s->frame.linesize[0] + x, color, run_length); 111 } 112 x+= run_length; 113 114 if (x >= s->avctx->width) { 115 x=0; 116 y += 1 + half_vert; 117 } 118 } 119 120 return 0; 121} 122 123/* 124 * @param half_horiz Half horizontal resolution (0 or 1) 125 * @param half_vert Half vertical resolution (0 or 1) 126 */ 127static int mm_decode_inter(MmContext * s, int half_horiz, int half_vert) 128{ 129 int data_off = bytestream2_get_le16(&s->gb), y; 130 GetByteContext data_ptr; 131 132 if (bytestream2_get_bytes_left(&s->gb) < data_off) 133 return AVERROR_INVALIDDATA; 134 135 bytestream2_init(&data_ptr, s->gb.buffer + data_off, bytestream2_get_bytes_left(&s->gb) - data_off); 136 while (s->gb.buffer < data_ptr.buffer_start) { 137 int i, j; 138 int length = bytestream2_get_byte(&s->gb); 139 int x = bytestream2_get_byte(&s->gb) + ((length & 0x80) << 1); 140 length &= 0x7F; 141 142 if (length==0) { 143 y += x; 144 continue; 145 } 146 147 if (y + half_vert >= s->avctx->height) 148 return 0; 149 150 for(i=0; i<length; i++) { 151 int replace_array = bytestream2_get_byte(&s->gb); 152 for(j=0; j<8; j++) { 153 int replace = (replace_array >> (7-j)) & 1; 154 if (replace) { 155 int color = bytestream2_get_byte(&data_ptr); 156 s->frame.data[0][y*s->frame.linesize[0] + x] = color; 157 if (half_horiz) 158 s->frame.data[0][y*s->frame.linesize[0] + x + 1] = color; 159 if (half_vert) { 160 s->frame.data[0][(y+1)*s->frame.linesize[0] + x] = color; 161 if (half_horiz) 162 s->frame.data[0][(y+1)*s->frame.linesize[0] + x + 1] = color; 163 } 164 } 165 x += 1 + half_horiz; 166 } 167 } 168 169 y += 1 + half_vert; 170 } 171 172 return 0; 173} 174 175static int mm_decode_frame(AVCodecContext *avctx, 176 void *data, int *data_size, 177 AVPacket *avpkt) 178{ 179 const uint8_t *buf = avpkt->data; 180 int buf_size = avpkt->size; 181 MmContext *s = avctx->priv_data; 182 int type, res; 183 184 if (buf_size < MM_PREAMBLE_SIZE) 185 return AVERROR_INVALIDDATA; 186 type = AV_RL16(&buf[0]); 187 buf += MM_PREAMBLE_SIZE; 188 buf_size -= MM_PREAMBLE_SIZE; 189 bytestream2_init(&s->gb, buf, buf_size); 190 191 if (avctx->reget_buffer(avctx, &s->frame) < 0) { 192 av_log(avctx, AV_LOG_ERROR, "reget_buffer() failed\n"); 193 return -1; 194 } 195 196 switch(type) { 197 case MM_TYPE_PALETTE : res = mm_decode_pal(s); return buf_size; 198 case MM_TYPE_INTRA : res = mm_decode_intra(s, 0, 0); break; 199 case MM_TYPE_INTRA_HH : res = mm_decode_intra(s, 1, 0); break; 200 case MM_TYPE_INTRA_HHV : res = mm_decode_intra(s, 1, 1); break; 201 case MM_TYPE_INTER : res = mm_decode_inter(s, 0, 0); break; 202 case MM_TYPE_INTER_HH : res = mm_decode_inter(s, 1, 0); break; 203 case MM_TYPE_INTER_HHV : res = mm_decode_inter(s, 1, 1); break; 204 default: 205 res = AVERROR_INVALIDDATA; 206 break; 207 } 208 if (res < 0) 209 return res; 210 211 memcpy(s->frame.data[1], s->palette, AVPALETTE_SIZE); 212 213 *data_size = sizeof(AVFrame); 214 *(AVFrame*)data = s->frame; 215 216 return buf_size; 217} 218 219static av_cold int mm_decode_end(AVCodecContext *avctx) 220{ 221 MmContext *s = avctx->priv_data; 222 223 if(s->frame.data[0]) 224 avctx->release_buffer(avctx, &s->frame); 225 226 return 0; 227} 228 229AVCodec ff_mmvideo_decoder = { 230 .name = "mmvideo", 231 .type = AVMEDIA_TYPE_VIDEO, 232 .id = CODEC_ID_MMVIDEO, 233 .priv_data_size = sizeof(MmContext), 234 .init = mm_decode_init, 235 .close = mm_decode_end, 236 .decode = mm_decode_frame, 237 .capabilities = CODEC_CAP_DR1, 238 .long_name = NULL_IF_CONFIG_SMALL("American Laser Games MM Video"), 239}; 240