/src/ffmpeg/libavcodec/mmvideo.c
Line | Count | Source |
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 "decode.h" |
39 | | |
40 | 414k | #define MM_PREAMBLE_SIZE 6 |
41 | | |
42 | 745 | #define MM_TYPE_RAW 0x2 |
43 | 7.93k | #define MM_TYPE_INTER 0x5 |
44 | 1.88k | #define MM_TYPE_INTRA 0x8 |
45 | 1.22k | #define MM_TYPE_INTRA_HH 0xc |
46 | 668 | #define MM_TYPE_INTER_HH 0xd |
47 | 1.31k | #define MM_TYPE_INTRA_HHV 0xe |
48 | 966 | #define MM_TYPE_INTER_HHV 0xf |
49 | 118k | #define MM_TYPE_PALETTE 0x31 |
50 | | |
51 | | typedef struct MmContext { |
52 | | AVCodecContext *avctx; |
53 | | AVFrame *frame; |
54 | | unsigned int palette[AVPALETTE_COUNT]; |
55 | | GetByteContext gb; |
56 | | } MmContext; |
57 | | |
58 | | static av_cold int mm_decode_init(AVCodecContext *avctx) |
59 | 732 | { |
60 | 732 | MmContext *s = avctx->priv_data; |
61 | | |
62 | 732 | s->avctx = avctx; |
63 | | |
64 | 732 | avctx->pix_fmt = AV_PIX_FMT_PAL8; |
65 | | |
66 | 732 | if (!avctx->width || !avctx->height || |
67 | 594 | (avctx->width & 1) || (avctx->height & 1)) { |
68 | 149 | av_log(avctx, AV_LOG_ERROR, "Invalid video dimensions: %dx%d\n", |
69 | 149 | avctx->width, avctx->height); |
70 | 149 | return AVERROR(EINVAL); |
71 | 149 | } |
72 | | |
73 | 583 | s->frame = av_frame_alloc(); |
74 | 583 | if (!s->frame) |
75 | 0 | return AVERROR(ENOMEM); |
76 | | |
77 | 583 | return 0; |
78 | 583 | } |
79 | | |
80 | | static int mm_decode_raw(MmContext * s) |
81 | 745 | { |
82 | 745 | if (bytestream2_get_bytes_left(&s->gb) < s->avctx->width * s->avctx->height) |
83 | 266 | return AVERROR_INVALIDDATA; |
84 | 274k | for (int y = 0; y < s->avctx->height; y++) |
85 | 274k | bytestream2_get_buffer(&s->gb, s->frame->data[0] + y*s->frame->linesize[0], s->avctx->width); |
86 | 479 | return 0; |
87 | 745 | } |
88 | | |
89 | | static void mm_decode_pal(MmContext *s) |
90 | 118k | { |
91 | 118k | int start = bytestream2_get_le16(&s->gb); |
92 | 118k | int count = bytestream2_get_le16(&s->gb); |
93 | 68.5M | for (int i = 0; i < count; i++) |
94 | 68.4M | s->palette[(start+i)&0xFF] = 0xFFU << 24 | (bytestream2_get_be24(&s->gb) << 2); |
95 | 118k | } |
96 | | |
97 | | /** |
98 | | * @param half_horiz Half horizontal resolution (0 or 1) |
99 | | * @param half_vert Half vertical resolution (0 or 1) |
100 | | */ |
101 | | static int mm_decode_intra(MmContext * s, int half_horiz, int half_vert) |
102 | 4.42k | { |
103 | 4.42k | int x = 0, y = 0; |
104 | | |
105 | 431k | while (bytestream2_get_bytes_left(&s->gb) > 0) { |
106 | 428k | int run_length, color; |
107 | | |
108 | 428k | if (y >= s->avctx->height) |
109 | 758 | return 0; |
110 | | |
111 | 427k | color = bytestream2_get_byte(&s->gb); |
112 | 427k | if (color & 0x80) { |
113 | 241k | run_length = 1; |
114 | 241k | }else{ |
115 | 186k | run_length = (color & 0x7f) + 2; |
116 | 186k | color = bytestream2_get_byte(&s->gb); |
117 | 186k | } |
118 | | |
119 | 427k | if (half_horiz) |
120 | 139k | run_length *=2; |
121 | | |
122 | 427k | if (run_length > s->avctx->width - x) |
123 | 1.34k | return AVERROR_INVALIDDATA; |
124 | | |
125 | 426k | if (color) { |
126 | 270k | memset(s->frame->data[0] + y*s->frame->linesize[0] + x, color, run_length); |
127 | 270k | if (half_vert && y + half_vert < s->avctx->height) |
128 | 37.7k | memset(s->frame->data[0] + (y+1)*s->frame->linesize[0] + x, color, run_length); |
129 | 270k | } |
130 | 426k | x+= run_length; |
131 | | |
132 | 426k | if (x >= s->avctx->width) { |
133 | 2.89k | x=0; |
134 | 2.89k | y += 1 + half_vert; |
135 | 2.89k | } |
136 | 426k | } |
137 | | |
138 | 2.31k | return 0; |
139 | 4.42k | } |
140 | | |
141 | | /** |
142 | | * @param half_horiz Half horizontal resolution (0 or 1) |
143 | | * @param half_vert Half vertical resolution (0 or 1) |
144 | | */ |
145 | | static int mm_decode_inter(MmContext * s, int half_horiz, int half_vert) |
146 | 9.57k | { |
147 | 9.57k | int data_off = bytestream2_get_le16(&s->gb); |
148 | 9.57k | int y = 0; |
149 | 9.57k | GetByteContext data_ptr; |
150 | | |
151 | 9.57k | if (bytestream2_get_bytes_left(&s->gb) < data_off) |
152 | 1.06k | return AVERROR_INVALIDDATA; |
153 | | |
154 | 8.50k | bytestream2_init(&data_ptr, s->gb.buffer + data_off, bytestream2_get_bytes_left(&s->gb) - data_off); |
155 | 36.4k | while (s->gb.buffer < data_ptr.buffer_start) { |
156 | 28.2k | int i, j; |
157 | 28.2k | int length = bytestream2_get_byte(&s->gb); |
158 | 28.2k | int x = bytestream2_get_byte(&s->gb) + ((length & 0x80) << 1); |
159 | 28.2k | length &= 0x7F; |
160 | | |
161 | 28.2k | if (length==0) { |
162 | 26.3k | y += x; |
163 | 26.3k | continue; |
164 | 26.3k | } |
165 | | |
166 | 1.84k | if (y + half_vert >= s->avctx->height) |
167 | 307 | return 0; |
168 | | |
169 | 59.7k | for(i=0; i<length; i++) { |
170 | 58.1k | int replace_array = bytestream2_get_byte(&s->gb); |
171 | 384k | for(j=0; j<8; j++) { |
172 | 343k | int replace = (replace_array >> (7-j)) & 1; |
173 | 343k | if (x + half_horiz >= s->avctx->width) |
174 | 17.4k | break; |
175 | 326k | if (replace) { |
176 | 39.4k | int color = bytestream2_get_byte(&data_ptr); |
177 | 39.4k | s->frame->data[0][y*s->frame->linesize[0] + x] = color; |
178 | 39.4k | if (half_horiz) |
179 | 13.8k | s->frame->data[0][y*s->frame->linesize[0] + x + 1] = color; |
180 | 39.4k | if (half_vert) { |
181 | 9.11k | s->frame->data[0][(y+1)*s->frame->linesize[0] + x] = color; |
182 | 9.11k | if (half_horiz) |
183 | 9.11k | s->frame->data[0][(y+1)*s->frame->linesize[0] + x + 1] = color; |
184 | 9.11k | } |
185 | 39.4k | } |
186 | 326k | x += 1 + half_horiz; |
187 | 326k | } |
188 | 58.1k | } |
189 | | |
190 | 1.54k | y += 1 + half_vert; |
191 | 1.54k | } |
192 | | |
193 | 8.19k | return 0; |
194 | 8.50k | } |
195 | | |
196 | | static int mm_decode_frame(AVCodecContext *avctx, AVFrame *rframe, |
197 | | int *got_frame, AVPacket *avpkt) |
198 | 140k | { |
199 | 140k | const uint8_t *buf = avpkt->data; |
200 | 140k | int buf_size = avpkt->size; |
201 | 140k | MmContext *s = avctx->priv_data; |
202 | 140k | int type, res; |
203 | | |
204 | 140k | if (buf_size < MM_PREAMBLE_SIZE) |
205 | 3.77k | return AVERROR_INVALIDDATA; |
206 | 137k | type = AV_RL16(&buf[0]); |
207 | 137k | buf += MM_PREAMBLE_SIZE; |
208 | 137k | buf_size -= MM_PREAMBLE_SIZE; |
209 | 137k | bytestream2_init(&s->gb, buf, buf_size); |
210 | | |
211 | 137k | if ((res = ff_reget_buffer(avctx, s->frame, 0)) < 0) |
212 | 60 | return res; |
213 | | |
214 | 137k | switch(type) { |
215 | 745 | case MM_TYPE_RAW : res = mm_decode_raw(s); break; |
216 | 118k | case MM_TYPE_PALETTE : mm_decode_pal(s); return avpkt->size; |
217 | 1.88k | case MM_TYPE_INTRA : res = mm_decode_intra(s, 0, 0); break; |
218 | 1.22k | case MM_TYPE_INTRA_HH : res = mm_decode_intra(s, 1, 0); break; |
219 | 1.31k | case MM_TYPE_INTRA_HHV : res = mm_decode_intra(s, 1, 1); break; |
220 | 7.93k | case MM_TYPE_INTER : res = mm_decode_inter(s, 0, 0); break; |
221 | 668 | case MM_TYPE_INTER_HH : res = mm_decode_inter(s, 1, 0); break; |
222 | 966 | case MM_TYPE_INTER_HHV : res = mm_decode_inter(s, 1, 1); break; |
223 | 3.83k | default: |
224 | 3.83k | res = AVERROR_INVALIDDATA; |
225 | 3.83k | break; |
226 | 137k | } |
227 | 18.5k | if (res < 0) |
228 | 6.51k | return res; |
229 | | |
230 | 12.0k | memcpy(s->frame->data[1], s->palette, AVPALETTE_SIZE); |
231 | | |
232 | 12.0k | if ((res = av_frame_ref(rframe, s->frame)) < 0) |
233 | 0 | return res; |
234 | | |
235 | 12.0k | *got_frame = 1; |
236 | | |
237 | 12.0k | return avpkt->size; |
238 | 12.0k | } |
239 | | |
240 | | static av_cold int mm_decode_end(AVCodecContext *avctx) |
241 | 583 | { |
242 | 583 | MmContext *s = avctx->priv_data; |
243 | | |
244 | 583 | av_frame_free(&s->frame); |
245 | | |
246 | 583 | return 0; |
247 | 583 | } |
248 | | |
249 | | const FFCodec ff_mmvideo_decoder = { |
250 | | .p.name = "mmvideo", |
251 | | CODEC_LONG_NAME("American Laser Games MM Video"), |
252 | | .p.type = AVMEDIA_TYPE_VIDEO, |
253 | | .p.id = AV_CODEC_ID_MMVIDEO, |
254 | | .priv_data_size = sizeof(MmContext), |
255 | | .init = mm_decode_init, |
256 | | .close = mm_decode_end, |
257 | | FF_CODEC_DECODE_CB(mm_decode_frame), |
258 | | .p.capabilities = AV_CODEC_CAP_DR1, |
259 | | }; |