1/*
2 * American Laser Games MM Format Demuxer
3 * Copyright (c) 2006 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 Format Demuxer
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 "avformat.h"
36
37#define MM_PREAMBLE_SIZE    6
38
39#define MM_TYPE_HEADER      0x0
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_AUDIO       0x15
47#define MM_TYPE_PALETTE     0x31
48
49#define MM_HEADER_LEN_V     0x16    /* video only */
50#define MM_HEADER_LEN_AV    0x18    /* video + audio */
51
52#define MM_PALETTE_COUNT    128
53#define MM_PALETTE_SIZE     (MM_PALETTE_COUNT*3)
54
55typedef struct {
56  unsigned int audio_pts, video_pts;
57} MmDemuxContext;
58
59static int probe(AVProbeData *p)
60{
61    int len, type, fps, w, h;
62    if (p->buf_size < MM_HEADER_LEN_AV + MM_PREAMBLE_SIZE)
63        return 0;
64    /* the first chunk is always the header */
65    if (AV_RL16(&p->buf[0]) != MM_TYPE_HEADER)
66        return 0;
67    len = AV_RL32(&p->buf[2]);
68    if (len != MM_HEADER_LEN_V && len != MM_HEADER_LEN_AV)
69        return 0;
70    fps = AV_RL16(&p->buf[8]);
71    w = AV_RL16(&p->buf[12]);
72    h = AV_RL16(&p->buf[14]);
73    if (!fps || fps > 60 || !w || w > 2048 || !h || h > 2048)
74        return 0;
75    type = AV_RL16(&p->buf[len]);
76    if (!type || type > 0x31)
77        return 0;
78
79    /* only return half certainty since this check is a bit sketchy */
80    return AVPROBE_SCORE_MAX / 2;
81}
82
83static int read_header(AVFormatContext *s,
84                           AVFormatParameters *ap)
85{
86    MmDemuxContext *mm = s->priv_data;
87    ByteIOContext *pb = s->pb;
88    AVStream *st;
89
90    unsigned int type, length;
91    unsigned int frame_rate, width, height;
92
93    type = get_le16(pb);
94    length = get_le32(pb);
95
96    if (type != MM_TYPE_HEADER)
97        return AVERROR_INVALIDDATA;
98
99    /* read header */
100    get_le16(pb);   /* total number of chunks */
101    frame_rate = get_le16(pb);
102    get_le16(pb);   /* ibm-pc video bios mode */
103    width = get_le16(pb);
104    height = get_le16(pb);
105    url_fseek(pb, length - 10, SEEK_CUR);  /* unknown data */
106
107    /* video stream */
108    st = av_new_stream(s, 0);
109    if (!st)
110        return AVERROR(ENOMEM);
111    st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
112    st->codec->codec_id = CODEC_ID_MMVIDEO;
113    st->codec->codec_tag = 0;  /* no fourcc */
114    st->codec->width = width;
115    st->codec->height = height;
116    av_set_pts_info(st, 64, 1, frame_rate);
117
118    /* audio stream */
119    if (length == MM_HEADER_LEN_AV) {
120        st = av_new_stream(s, 0);
121        if (!st)
122            return AVERROR(ENOMEM);
123        st->codec->codec_type = AVMEDIA_TYPE_AUDIO;
124        st->codec->codec_tag = 0; /* no fourcc */
125        st->codec->codec_id = CODEC_ID_PCM_U8;
126        st->codec->channels = 1;
127        st->codec->sample_rate = 8000;
128        av_set_pts_info(st, 64, 1, 8000); /* 8000 hz */
129    }
130
131    mm->audio_pts = 0;
132    mm->video_pts = 0;
133    return 0;
134}
135
136static int read_packet(AVFormatContext *s,
137                           AVPacket *pkt)
138{
139    MmDemuxContext *mm = s->priv_data;
140    ByteIOContext *pb = s->pb;
141    unsigned char preamble[MM_PREAMBLE_SIZE];
142    unsigned int type, length;
143
144    while(1) {
145
146        if (get_buffer(pb, preamble, MM_PREAMBLE_SIZE) != MM_PREAMBLE_SIZE) {
147            return AVERROR(EIO);
148        }
149
150        type = AV_RL16(&preamble[0]);
151        length = AV_RL16(&preamble[2]);
152
153        switch(type) {
154        case MM_TYPE_PALETTE :
155        case MM_TYPE_INTER :
156        case MM_TYPE_INTRA :
157        case MM_TYPE_INTRA_HH :
158        case MM_TYPE_INTER_HH :
159        case MM_TYPE_INTRA_HHV :
160        case MM_TYPE_INTER_HHV :
161            /* output preamble + data */
162            if (av_new_packet(pkt, length + MM_PREAMBLE_SIZE))
163                return AVERROR(ENOMEM);
164            memcpy(pkt->data, preamble, MM_PREAMBLE_SIZE);
165            if (get_buffer(pb, pkt->data + MM_PREAMBLE_SIZE, length) != length)
166                return AVERROR(EIO);
167            pkt->size = length + MM_PREAMBLE_SIZE;
168            pkt->stream_index = 0;
169            pkt->pts = mm->video_pts;
170            if (type!=MM_TYPE_PALETTE)
171                mm->video_pts++;
172            return 0;
173
174        case MM_TYPE_AUDIO :
175            if (av_get_packet(s->pb, pkt, length)<0)
176                return AVERROR(ENOMEM);
177            pkt->size = length;
178            pkt->stream_index = 1;
179            pkt->pts = mm->audio_pts++;
180            return 0;
181
182        default :
183            av_log(s, AV_LOG_INFO, "unknown chunk type 0x%x\n", type);
184            url_fseek(pb, length, SEEK_CUR);
185        }
186    }
187
188    return 0;
189}
190
191AVInputFormat mm_demuxer = {
192    "mm",
193    NULL_IF_CONFIG_SMALL("American Laser Games MM format"),
194    sizeof(MmDemuxContext),
195    probe,
196    read_header,
197    read_packet,
198};
199