blob: 04de6bb4c84aa661c3f786797825788e527cc837
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 "internal.h" |
38 | |
39 | #define MM_PREAMBLE_SIZE 6 |
40 | |
41 | #define MM_TYPE_INTER 0x5 |
42 | #define MM_TYPE_INTRA 0x8 |
43 | #define MM_TYPE_INTRA_HH 0xc |
44 | #define MM_TYPE_INTER_HH 0xd |
45 | #define MM_TYPE_INTRA_HHV 0xe |
46 | #define MM_TYPE_INTER_HHV 0xf |
47 | #define MM_TYPE_PALETTE 0x31 |
48 | |
49 | typedef struct MmContext { |
50 | AVCodecContext *avctx; |
51 | AVFrame *frame; |
52 | unsigned int palette[AVPALETTE_COUNT]; |
53 | GetByteContext gb; |
54 | } MmContext; |
55 | |
56 | static av_cold int mm_decode_init(AVCodecContext *avctx) |
57 | { |
58 | MmContext *s = avctx->priv_data; |
59 | |
60 | s->avctx = avctx; |
61 | |
62 | avctx->pix_fmt = AV_PIX_FMT_PAL8; |
63 | |
64 | if (!avctx->width || !avctx->height || |
65 | (avctx->width & 1) || (avctx->height & 1)) { |
66 | av_log(avctx, AV_LOG_ERROR, "Invalid video dimensions: %dx%d\n", |
67 | avctx->width, avctx->height); |
68 | return AVERROR(EINVAL); |
69 | } |
70 | |
71 | s->frame = av_frame_alloc(); |
72 | if (!s->frame) |
73 | return AVERROR(ENOMEM); |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static void mm_decode_pal(MmContext *s) |
79 | { |
80 | int i; |
81 | |
82 | bytestream2_skip(&s->gb, 4); |
83 | for (i = 0; i < 128; i++) { |
84 | s->palette[i] = 0xFFU << 24 | bytestream2_get_be24(&s->gb); |
85 | s->palette[i+128] = s->palette[i]<<2; |
86 | } |
87 | } |
88 | |
89 | /** |
90 | * @param half_horiz Half horizontal resolution (0 or 1) |
91 | * @param half_vert Half vertical resolution (0 or 1) |
92 | */ |
93 | static int mm_decode_intra(MmContext * s, int half_horiz, int half_vert) |
94 | { |
95 | int x = 0, y = 0; |
96 | |
97 | while (bytestream2_get_bytes_left(&s->gb) > 0) { |
98 | int run_length, color; |
99 | |
100 | if (y >= s->avctx->height) |
101 | return 0; |
102 | |
103 | color = bytestream2_get_byte(&s->gb); |
104 | if (color & 0x80) { |
105 | run_length = 1; |
106 | }else{ |
107 | run_length = (color & 0x7f) + 2; |
108 | color = bytestream2_get_byte(&s->gb); |
109 | } |
110 | |
111 | if (half_horiz) |
112 | run_length *=2; |
113 | |
114 | if (run_length > s->avctx->width - x) |
115 | return AVERROR_INVALIDDATA; |
116 | |
117 | if (color) { |
118 | memset(s->frame->data[0] + y*s->frame->linesize[0] + x, color, run_length); |
119 | if (half_vert && y + half_vert < s->avctx->height) |
120 | memset(s->frame->data[0] + (y+1)*s->frame->linesize[0] + x, color, run_length); |
121 | } |
122 | x+= run_length; |
123 | |
124 | if (x >= s->avctx->width) { |
125 | x=0; |
126 | y += 1 + half_vert; |
127 | } |
128 | } |
129 | |
130 | return 0; |
131 | } |
132 | |
133 | /** |
134 | * @param half_horiz Half horizontal resolution (0 or 1) |
135 | * @param half_vert Half vertical resolution (0 or 1) |
136 | */ |
137 | static int mm_decode_inter(MmContext * s, int half_horiz, int half_vert) |
138 | { |
139 | int data_off = bytestream2_get_le16(&s->gb); |
140 | int y = 0; |
141 | GetByteContext data_ptr; |
142 | |
143 | if (bytestream2_get_bytes_left(&s->gb) < data_off) |
144 | return AVERROR_INVALIDDATA; |
145 | |
146 | bytestream2_init(&data_ptr, s->gb.buffer + data_off, bytestream2_get_bytes_left(&s->gb) - data_off); |
147 | while (s->gb.buffer < data_ptr.buffer_start) { |
148 | int i, j; |
149 | int length = bytestream2_get_byte(&s->gb); |
150 | int x = bytestream2_get_byte(&s->gb) + ((length & 0x80) << 1); |
151 | length &= 0x7F; |
152 | |
153 | if (length==0) { |
154 | y += x; |
155 | continue; |
156 | } |
157 | |
158 | if (y + half_vert >= s->avctx->height) |
159 | return 0; |
160 | |
161 | for(i=0; i<length; i++) { |
162 | int replace_array = bytestream2_get_byte(&s->gb); |
163 | for(j=0; j<8; j++) { |
164 | int replace = (replace_array >> (7-j)) & 1; |
165 | if (x + half_horiz >= s->avctx->width) |
166 | return AVERROR_INVALIDDATA; |
167 | if (replace) { |
168 | int color = bytestream2_get_byte(&data_ptr); |
169 | s->frame->data[0][y*s->frame->linesize[0] + x] = color; |
170 | if (half_horiz) |
171 | s->frame->data[0][y*s->frame->linesize[0] + x + 1] = color; |
172 | if (half_vert) { |
173 | s->frame->data[0][(y+1)*s->frame->linesize[0] + x] = color; |
174 | if (half_horiz) |
175 | s->frame->data[0][(y+1)*s->frame->linesize[0] + x + 1] = color; |
176 | } |
177 | } |
178 | x += 1 + half_horiz; |
179 | } |
180 | } |
181 | |
182 | y += 1 + half_vert; |
183 | } |
184 | |
185 | return 0; |
186 | } |
187 | |
188 | static int mm_decode_frame(AVCodecContext *avctx, |
189 | void *data, int *got_frame, |
190 | 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) |
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(data, s->frame)) < 0) |
225 | return res; |
226 | |
227 | *got_frame = 1; |
228 | |
229 | return avpkt->size; |
230 | } |
231 | |
232 | static 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 | |
241 | AVCodec ff_mmvideo_decoder = { |
242 | .name = "mmvideo", |
243 | .long_name = NULL_IF_CONFIG_SMALL("American Laser Games MM Video"), |
244 | .type = AVMEDIA_TYPE_VIDEO, |
245 | .id = AV_CODEC_ID_MMVIDEO, |
246 | .priv_data_size = sizeof(MmContext), |
247 | .init = mm_decode_init, |
248 | .close = mm_decode_end, |
249 | .decode = mm_decode_frame, |
250 | .capabilities = AV_CODEC_CAP_DR1, |
251 | }; |
252 |