1/* 2 * American Laser Games MM Video Decoder 3 * Copyright (c) 2006,2008 Peter Ross 4 * 5 * This file is part of FFmpeg. 6 * 7 * FFmpeg 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 * FFmpeg 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 FFmpeg; 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#include "codec_internal.h" 38#include "internal.h" 39 40#define MM_PREAMBLE_SIZE 6 41 42#define MM_TYPE_INTER 0x5 43#define MM_TYPE_INTRA 0x8 44#define MM_TYPE_INTRA_HH 0xc 45#define MM_TYPE_INTER_HH 0xd 46#define MM_TYPE_INTRA_HHV 0xe 47#define MM_TYPE_INTER_HHV 0xf 48#define MM_TYPE_PALETTE 0x31 49 50typedef struct MmContext { 51 AVCodecContext *avctx; 52 AVFrame *frame; 53 unsigned int palette[AVPALETTE_COUNT]; 54 GetByteContext gb; 55} MmContext; 56 57static av_cold int mm_decode_init(AVCodecContext *avctx) 58{ 59 MmContext *s = avctx->priv_data; 60 61 s->avctx = avctx; 62 63 avctx->pix_fmt = AV_PIX_FMT_PAL8; 64 65 if (!avctx->width || !avctx->height || 66 (avctx->width & 1) || (avctx->height & 1)) { 67 av_log(avctx, AV_LOG_ERROR, "Invalid video dimensions: %dx%d\n", 68 avctx->width, avctx->height); 69 return AVERROR(EINVAL); 70 } 71 72 s->frame = av_frame_alloc(); 73 if (!s->frame) 74 return AVERROR(ENOMEM); 75 76 return 0; 77} 78 79static void mm_decode_pal(MmContext *s) 80{ 81 int i; 82 83 bytestream2_skip(&s->gb, 4); 84 for (i = 0; i < 128; i++) { 85 s->palette[i] = 0xFFU << 24 | bytestream2_get_be24(&s->gb); 86 s->palette[i+128] = s->palette[i]<<2; 87 } 88} 89 90/** 91 * @param half_horiz Half horizontal resolution (0 or 1) 92 * @param half_vert Half vertical resolution (0 or 1) 93 */ 94static int mm_decode_intra(MmContext * s, int half_horiz, int half_vert) 95{ 96 int x = 0, y = 0; 97 98 while (bytestream2_get_bytes_left(&s->gb) > 0) { 99 int run_length, color; 100 101 if (y >= s->avctx->height) 102 return 0; 103 104 color = bytestream2_get_byte(&s->gb); 105 if (color & 0x80) { 106 run_length = 1; 107 }else{ 108 run_length = (color & 0x7f) + 2; 109 color = bytestream2_get_byte(&s->gb); 110 } 111 112 if (half_horiz) 113 run_length *=2; 114 115 if (run_length > s->avctx->width - x) 116 return AVERROR_INVALIDDATA; 117 118 if (color) { 119 memset(s->frame->data[0] + y*s->frame->linesize[0] + x, color, run_length); 120 if (half_vert && y + half_vert < s->avctx->height) 121 memset(s->frame->data[0] + (y+1)*s->frame->linesize[0] + x, color, run_length); 122 } 123 x+= run_length; 124 125 if (x >= s->avctx->width) { 126 x=0; 127 y += 1 + half_vert; 128 } 129 } 130 131 return 0; 132} 133 134/** 135 * @param half_horiz Half horizontal resolution (0 or 1) 136 * @param half_vert Half vertical resolution (0 or 1) 137 */ 138static int mm_decode_inter(MmContext * s, int half_horiz, int half_vert) 139{ 140 int data_off = bytestream2_get_le16(&s->gb); 141 int y = 0; 142 GetByteContext data_ptr; 143 144 if (bytestream2_get_bytes_left(&s->gb) < data_off) 145 return AVERROR_INVALIDDATA; 146 147 bytestream2_init(&data_ptr, s->gb.buffer + data_off, bytestream2_get_bytes_left(&s->gb) - data_off); 148 while (s->gb.buffer < data_ptr.buffer_start) { 149 int i, j; 150 int length = bytestream2_get_byte(&s->gb); 151 int x = bytestream2_get_byte(&s->gb) + ((length & 0x80) << 1); 152 length &= 0x7F; 153 154 if (length==0) { 155 y += x; 156 continue; 157 } 158 159 if (y + half_vert >= s->avctx->height) 160 return 0; 161 162 for(i=0; i<length; i++) { 163 int replace_array = bytestream2_get_byte(&s->gb); 164 for(j=0; j<8; j++) { 165 int replace = (replace_array >> (7-j)) & 1; 166 if (x + half_horiz >= s->avctx->width) 167 return AVERROR_INVALIDDATA; 168 if (replace) { 169 int color = bytestream2_get_byte(&data_ptr); 170 s->frame->data[0][y*s->frame->linesize[0] + x] = color; 171 if (half_horiz) 172 s->frame->data[0][y*s->frame->linesize[0] + x + 1] = color; 173 if (half_vert) { 174 s->frame->data[0][(y+1)*s->frame->linesize[0] + x] = color; 175 if (half_horiz) 176 s->frame->data[0][(y+1)*s->frame->linesize[0] + x + 1] = color; 177 } 178 } 179 x += 1 + half_horiz; 180 } 181 } 182 183 y += 1 + half_vert; 184 } 185 186 return 0; 187} 188 189static int mm_decode_frame(AVCodecContext *avctx, AVFrame *rframe, 190 int *got_frame, AVPacket *avpkt) 191{ 192 const uint8_t *buf = avpkt->data; 193 int buf_size = avpkt->size; 194 MmContext *s = avctx->priv_data; 195 int type, res; 196 197 if (buf_size < MM_PREAMBLE_SIZE) 198 return AVERROR_INVALIDDATA; 199 type = AV_RL16(&buf[0]); 200 buf += MM_PREAMBLE_SIZE; 201 buf_size -= MM_PREAMBLE_SIZE; 202 bytestream2_init(&s->gb, buf, buf_size); 203 204 if ((res = ff_reget_buffer(avctx, s->frame, 0)) < 0) 205 return res; 206 207 switch(type) { 208 case MM_TYPE_PALETTE : mm_decode_pal(s); return avpkt->size; 209 case MM_TYPE_INTRA : res = mm_decode_intra(s, 0, 0); break; 210 case MM_TYPE_INTRA_HH : res = mm_decode_intra(s, 1, 0); break; 211 case MM_TYPE_INTRA_HHV : res = mm_decode_intra(s, 1, 1); break; 212 case MM_TYPE_INTER : res = mm_decode_inter(s, 0, 0); break; 213 case MM_TYPE_INTER_HH : res = mm_decode_inter(s, 1, 0); break; 214 case MM_TYPE_INTER_HHV : res = mm_decode_inter(s, 1, 1); break; 215 default: 216 res = AVERROR_INVALIDDATA; 217 break; 218 } 219 if (res < 0) 220 return res; 221 222 memcpy(s->frame->data[1], s->palette, AVPALETTE_SIZE); 223 224 if ((res = av_frame_ref(rframe, s->frame)) < 0) 225 return res; 226 227 *got_frame = 1; 228 229 return avpkt->size; 230} 231 232static av_cold int mm_decode_end(AVCodecContext *avctx) 233{ 234 MmContext *s = avctx->priv_data; 235 236 av_frame_free(&s->frame); 237 238 return 0; 239} 240 241const FFCodec ff_mmvideo_decoder = { 242 .p.name = "mmvideo", 243 .p.long_name = NULL_IF_CONFIG_SMALL("American Laser Games MM Video"), 244 .p.type = AVMEDIA_TYPE_VIDEO, 245 .p.id = AV_CODEC_ID_MMVIDEO, 246 .priv_data_size = sizeof(MmContext), 247 .init = mm_decode_init, 248 .close = mm_decode_end, 249 FF_CODEC_DECODE_CB(mm_decode_frame), 250 .p.capabilities = AV_CODEC_CAP_DR1, 251 .caps_internal = FF_CODEC_CAP_INIT_THREADSAFE, 252}; 253