From 4e114829b1d03ce4714c45f5d8628d46cc873cc0 Mon Sep 17 00:00:00 2001 From: Mike Melanson Date: Sun, 12 Feb 2006 06:49:40 +0000 Subject: [PATCH] complete American Laser Games MM playback system, courtesy of Peter Ross (suxen_drol at hotmail dot com) Originally committed as revision 4999 to svn://svn.ffmpeg.org/ffmpeg/trunk --- CREDITS | 1 + Changelog | 1 + doc/ffmpeg-doc.texi | 3 + libavcodec/Makefile | 3 + libavcodec/allcodecs.c | 4 + libavcodec/avcodec.h | 2 + libavcodec/mmvideo.c | 204 ++++++++++++++++++++++++++++++++++++ libavformat/Makefile | 2 +- libavformat/allformats.c | 1 + libavformat/avformat.h | 3 + libavformat/mm.c | 216 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 libavcodec/mmvideo.c create mode 100644 libavformat/mm.c diff --git a/CREDITS b/CREDITS index d7cadf1c03..83833ad0c1 100644 --- a/CREDITS +++ b/CREDITS @@ -30,6 +30,7 @@ Loren Merritt Jeff Muizelaar Michael Niedermayer François Revol +Peter Ross Måns Rullgård Roman Shaposhnik Dieter Shirley diff --git a/Changelog b/Changelog index ca0f716f2e..c0ae149558 100644 --- a/Changelog +++ b/Changelog @@ -36,6 +36,7 @@ version - AIFF/AIFF-C audio format, encoding and decoding - ADTS AAC file reading and writing - Creative VOC file reading and writing +- American Laser Games multimedia (*.mm) playback system version 0.4.9-pre1: diff --git a/doc/ffmpeg-doc.texi b/doc/ffmpeg-doc.texi index 5a451c1bc6..617a6ea276 100644 --- a/doc/ffmpeg-doc.texi +++ b/doc/ffmpeg-doc.texi @@ -698,6 +698,8 @@ library: @item Nullsoft Video (NSV) format @tab @tab X @item ADTS AAC audio @tab X @tab X @item Creative VOC @tab X @tab X @tab Created for the Sound Blaster Pro. +@item American Laser Games MM @tab @tab X +@tab Multimedia format used in games like Mad Dog McCree @end multitable @code{X} means that encoding (resp. decoding) is supported. @@ -788,6 +790,7 @@ following image formats are supported: @item Autodesk Animator Studio Codec @tab @tab X @tab fourcc: AASC @item Fraps FPS1 @tab @tab X @tab @item CamStudio @tab @tab X @tab fourcc: CSCD +@item American Laser Games Video @tab @tab X @tab Used in games like Mad Dog McCree @end multitable @code{X} means that encoding (resp. decoding) is supported. diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 9437f3e101..aa8fbe50b5 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -218,6 +218,9 @@ endif ifeq ($(CONFIG_BMP_DECODER),yes) OBJS+= bmp.o endif +ifeq ($(CONFIG_MMVIDEO_DECODER),yes) + OBJS+= mmvideo.o +endif AMROBJS= ifeq ($(AMR_NB),yes) diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index e78312d24d..89ff990c55 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -539,6 +539,10 @@ void avcodec_register_all(void) register_avcodec(&bmp_decoder); #endif +#if CONFIG_MMVIDEO_DECODER + register_avcodec(&mmvideo_decoder); +#endif //CONFIG_MMVIDEO_DECODER + /* pcm codecs */ #if defined (CONFIG_ENCODERS) && defined (CONFIG_DECODERS) #define PCM_CODEC(id, name) \ diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h index 513c88df32..d29d258d45 100644 --- a/libavcodec/avcodec.h +++ b/libavcodec/avcodec.h @@ -115,6 +115,7 @@ enum CodecID { CODEC_ID_TRUEMOTION2, CODEC_ID_BMP, CODEC_ID_CSCD, + CODEC_ID_MMVIDEO, /* various pcm "codecs" */ CODEC_ID_PCM_S16LE= 0x10000, @@ -2226,6 +2227,7 @@ extern AVCodec fraps_decoder; extern AVCodec libgsm_encoder; extern AVCodec libgsm_decoder; extern AVCodec bmp_decoder; +extern AVCodec mmvideo_decoder; /* pcm codecs */ #define PCM_CODEC(id, name) \ diff --git a/libavcodec/mmvideo.c b/libavcodec/mmvideo.c new file mode 100644 index 0000000000..b2e146839d --- /dev/null +++ b/libavcodec/mmvideo.c @@ -0,0 +1,204 @@ +/* + * American Laser Games MM Video Decoder + * Copyright (c) 2006 Peter Ross + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** + * @file mm.c + * American Laser Games MM Video Decoder + * by Peter Ross (suxen_drol at hotmail dot com) + * + * The MM format was used by IBM-PC ports of ALG's "arcade shooter" games, + * including Mad Dog McCree and Crime Patrol. + * + * Technical details here: + * http://wiki.multimedia.cx/index.php?title=American_Laser_Games_MM + */ + +#include "avcodec.h" + +#define MM_PREAMBLE_SIZE 6 + +#define MM_TYPE_INTER 0x5 +#define MM_TYPE_INTRA 0x8 +#define MM_TYPE_INTRA_HH 0xc +#define MM_TYPE_INTER_HH 0xd +#define MM_TYPE_INTRA_HHV 0xe +#define MM_TYPE_INTER_HHV 0xf + +typedef struct MmContext { + AVCodecContext *avctx; + AVFrame frame; +} MmContext; + +static int mm_decode_init(AVCodecContext *avctx) +{ + MmContext *s = avctx->priv_data; + + s->avctx = avctx; + + if (s->avctx->palctrl == NULL) { + av_log(avctx, AV_LOG_ERROR, "mmvideo: palette expected.\n"); + return -1; + } + + avctx->pix_fmt = PIX_FMT_PAL8; + avctx->has_b_frames = 0; + + if (avcodec_check_dimensions(avctx, avctx->width, avctx->height)) + return -1; + + s->frame.reference = 1; + if (avctx->get_buffer(avctx, &s->frame)) { + av_log(s->avctx, AV_LOG_ERROR, "mmvideo: get_buffer() failed\n"); + return -1; + } + + return 0; +} + +static void mm_decode_intra(MmContext * s, int half_horiz, int half_vert, const uint8_t *buf, int buf_size) +{ + int i, x, y; + i=0; x=0; y=0; + + while(iframe.data[0] + y*s->frame.linesize[0] + x, color, run_length); + if (half_vert) + memset(s->frame.data[0] + (y+1)*s->frame.linesize[0] + x, color, run_length); + } + x+= run_length; + + if (x >= s->avctx->width) { + x=0; + y += half_vert ? 2 : 1; + } + } +} + +static void mm_decode_inter(MmContext * s, int half_horiz, int half_vert, const uint8_t *buf, int buf_size) +{ + const int data_ptr = 2 + LE_16(&buf[0]); + int d, r, y; + d = data_ptr; r = 2; y = 0; + + while(r < data_ptr) { + int i, j; + int length = buf[r] & 0x7f; + int x = buf[r+1] + ((buf[r] & 0x80) << 1); + r += 2; + + if (length==0) { + y += x; + continue; + } + + for(i=0; i> (7-j)) & 1; + if (replace) { + int color = buf[d]; + s->frame.data[0][y*s->frame.linesize[0] + x] = color; + if (half_horiz) + s->frame.data[0][y*s->frame.linesize[0] + x + 1] = color; + if (half_vert) { + s->frame.data[0][(y+1)*s->frame.linesize[0] + x] = color; + if (half_horiz) + s->frame.data[0][(y+1)*s->frame.linesize[0] + x + 1] = color; + } + d++; + } + x += half_horiz ? 2 : 1; + } + } + + r += length; + y += half_vert ? 2 : 1; + } +} + +static int mm_decode_frame(AVCodecContext *avctx, + void *data, int *data_size, + uint8_t *buf, int buf_size) +{ + MmContext *s = avctx->priv_data; + AVPaletteControl *palette_control = avctx->palctrl; + int type; + + if (palette_control->palette_changed) { + memcpy(s->frame.data[1], palette_control->palette, AVPALETTE_SIZE); + palette_control->palette_changed = 0; + } + + type = LE_16(&buf[0]); + buf += MM_PREAMBLE_SIZE; + buf_size -= MM_PREAMBLE_SIZE; + + switch(type) { + case MM_TYPE_INTRA : mm_decode_intra(s, 0, 0, buf, buf_size); break; + case MM_TYPE_INTRA_HH : mm_decode_intra(s, 1, 0, buf, buf_size); break; + case MM_TYPE_INTRA_HHV : mm_decode_intra(s, 1, 1, buf, buf_size); break; + case MM_TYPE_INTER : mm_decode_inter(s, 0, 0, buf, buf_size); break; + case MM_TYPE_INTER_HH : mm_decode_inter(s, 1, 0, buf, buf_size); break; + case MM_TYPE_INTER_HHV : mm_decode_inter(s, 1, 1, buf, buf_size); break; + default : + return -1; + } + + *data_size = sizeof(AVFrame); + *(AVFrame*)data = s->frame; + + return buf_size; +} + +static int mm_decode_end(AVCodecContext *avctx) +{ + MmContext *s = avctx->priv_data; + + if(s->frame.data[0]) + avctx->release_buffer(avctx, &s->frame); + + return 0; +} + +AVCodec mmvideo_decoder = { + "mmvideo", + CODEC_TYPE_VIDEO, + CODEC_ID_MMVIDEO, + sizeof(MmContext), + mm_decode_init, + NULL, + mm_decode_end, + mm_decode_frame, + CODEC_CAP_DR1, +}; diff --git a/libavformat/Makefile b/libavformat/Makefile index 2621f90e88..381b04642a 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -23,7 +23,7 @@ OBJS+=mpeg.o mpegts.o mpegtsenc.o ffm.o crc.o img.o img2.o raw.o rm.o \ nut.o wc3movie.o mp3.o westwood.o segafilm.o idcin.o flic.o \ sierravmd.o matroska.o sol.o electronicarts.o nsvdec.o asf.o \ ogg2.o oggparsevorbis.o oggparsetheora.o oggparseflac.o daud.o aiff.o \ - voc.o tta.o + voc.o tta.o mm.o # muxers ifeq ($(CONFIG_MUXERS),yes) diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 2fea324836..1341621fe3 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -79,6 +79,7 @@ void av_register_all(void) idcin_init(); flic_init(); vmd_init(); + mm_init(); #if defined(AMR_NB) || defined(AMR_NB_FIXED) || defined(AMR_WB) amr_init(); diff --git a/libavformat/avformat.h b/libavformat/avformat.h index f04fbb128b..c01f6efa75 100644 --- a/libavformat/avformat.h +++ b/libavformat/avformat.h @@ -561,6 +561,9 @@ int tta_init(void); /* adts.c */ int ff_adts_init(void); +/* mm.c */ +int mm_init(void); + #include "rtp.h" #include "rtsp.h" diff --git a/libavformat/mm.c b/libavformat/mm.c new file mode 100644 index 0000000000..aa857d4c79 --- /dev/null +++ b/libavformat/mm.c @@ -0,0 +1,216 @@ +/* + * American Laser Games MM Format Demuxer + * Copyright (c) 2006 Peter Ross + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** + * @file mm.c + * American Laser Games MM Format Demuxer + * by Peter Ross (suxen_drol at hotmail dot com) + * + * The MM format was used by IBM-PC ports of ALG's "arcade shooter" games, + * including Mad Dog McCree and Crime Patrol. + * + * Technical details here: + * http://wiki.multimedia.cx/index.php?title=American_Laser_Games_MM + */ + +#include "avformat.h" + +#define MM_PREAMBLE_SIZE 6 + +#define MM_TYPE_HEADER 0x0 +#define MM_TYPE_INTER 0x5 +#define MM_TYPE_INTRA 0x8 +#define MM_TYPE_INTRA_HH 0xc +#define MM_TYPE_INTER_HH 0xd +#define MM_TYPE_INTRA_HHV 0xe +#define MM_TYPE_INTER_HHV 0xf +#define MM_TYPE_AUDIO 0x15 +#define MM_TYPE_PALETTE 0x31 + +#define MM_HEADER_LEN_V 0x16 /* video only */ +#define MM_HEADER_LEN_AV 0x18 /* video + audio */ + +#define MM_PALETTE_COUNT 128 +#define MM_PALETTE_SIZE (MM_PALETTE_COUNT*3) + +typedef struct { + AVPaletteControl palette_control; + unsigned int audio_pts, video_pts; +} MmDemuxContext; + +static int mm_probe(AVProbeData *p) +{ + /* the first chunk is always the header */ + if (p->buf_size < MM_PREAMBLE_SIZE) + return 0; + if (LE_16(&p->buf[0]) != MM_TYPE_HEADER) + return 0; + if (LE_32(&p->buf[2]) != MM_HEADER_LEN_V && LE_32(&p->buf[2]) != MM_HEADER_LEN_AV) + return 0; + + /* only return half certainty since this check is a bit sketchy */ + return AVPROBE_SCORE_MAX / 2; +} + +static int mm_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + MmDemuxContext *mm = (MmDemuxContext *)s->priv_data; + ByteIOContext *pb = &s->pb; + AVStream *st; + + unsigned int type, length; + unsigned int frame_rate, width, height; + + type = get_le16(pb); + length = get_le32(pb); + + if (type != MM_TYPE_HEADER) + return AVERROR_INVALIDDATA; + + /* read header */ + get_le16(pb); /* total number of chunks */ + frame_rate = get_le16(pb); + get_le16(pb); /* ibm-pc video bios mode */ + width = get_le16(pb); + height = get_le16(pb); + url_fseek(pb, length - 10, SEEK_CUR); /* unknown data */ + + /* video stream */ + st = av_new_stream(s, 0); + if (!st) + return AVERROR_NOMEM; + st->codec->codec_type = CODEC_TYPE_VIDEO; + st->codec->codec_id = CODEC_ID_MMVIDEO; + st->codec->codec_tag = 0; /* no fourcc */ + st->codec->width = width; + st->codec->height = height; + st->codec->palctrl = &mm->palette_control; + av_set_pts_info(st, 64, 1, frame_rate); + + /* audio stream */ + if (length == MM_HEADER_LEN_AV) { + st = av_new_stream(s, 0); + if (!st) + return AVERROR_NOMEM; + st->codec->codec_type = CODEC_TYPE_AUDIO; + st->codec->codec_tag = 0; /* no fourcc */ + st->codec->codec_id = CODEC_ID_PCM_U8; + st->codec->channels = 1; + st->codec->sample_rate = 8000; + av_set_pts_info(st, 64, 1, 8000); /* 8000 hz */ + } + + mm->palette_control.palette_changed = 0; + mm->audio_pts = 0; + mm->video_pts = 0; + return 0; +} + +static int mm_read_packet(AVFormatContext *s, + AVPacket *pkt) +{ + MmDemuxContext *mm = (MmDemuxContext *)s->priv_data; + ByteIOContext *pb = &s->pb; + unsigned char preamble[MM_PREAMBLE_SIZE]; + unsigned char pal[MM_PALETTE_SIZE]; + unsigned int type, length; + int i; + + while(1) { + + if (get_buffer(pb, preamble, MM_PREAMBLE_SIZE) != MM_PREAMBLE_SIZE) { + return AVERROR_IO; + } + + type = LE_16(&preamble[0]); + length = LE_16(&preamble[2]); + + switch(type) { + case MM_TYPE_PALETTE : + url_fseek(pb, 4, SEEK_CUR); /* unknown data */ + if (get_buffer(pb, pal, MM_PALETTE_SIZE) != MM_PALETTE_SIZE) + return AVERROR_IO; + url_fseek(pb, length - (4 + MM_PALETTE_SIZE), SEEK_CUR); + + for (i=0; ipalette_control.palette[i] = (r << 16) | (g << 8) | (b); + /* repeat palette, where each components is multiplied by four */ + mm->palette_control.palette[i+128] = (r << 18) | (g << 10) | (b<<2); + } + mm->palette_control.palette_changed = 1; + break; + + case MM_TYPE_INTER : + case MM_TYPE_INTRA : + case MM_TYPE_INTRA_HH : + case MM_TYPE_INTER_HH : + case MM_TYPE_INTRA_HHV : + case MM_TYPE_INTER_HHV : + /* output preamble + data */ + if (av_new_packet(pkt, length + MM_PREAMBLE_SIZE)) + return AVERROR_NOMEM; + memcpy(pkt->data, preamble, MM_PREAMBLE_SIZE); + if (get_buffer(pb, pkt->data + MM_PREAMBLE_SIZE, length) != length) + return AVERROR_IO; + pkt->size = length + MM_PREAMBLE_SIZE; + pkt->stream_index = 0; + pkt->pts = mm->video_pts++; + return 0; + + case MM_TYPE_AUDIO : + if (av_get_packet(&s->pb, pkt, length)<0) + return AVERROR_NOMEM; + pkt->size = length; + pkt->stream_index = 1; + pkt->pts = mm->audio_pts++; + return 0; + + default : + av_log(NULL, AV_LOG_INFO, "mm: unknown chunk type 0x%x\n", type); + url_fseek(pb, length, SEEK_CUR); + } + } + + return 0; +} + +static int mm_read_close(AVFormatContext *s) +{ + return 0; +} + +static AVInputFormat mm_iformat = { + "mm", + "American Laser Games MM format", + sizeof(MmDemuxContext), + mm_probe, + mm_read_header, + mm_read_packet, + mm_read_close, +}; + +int mm_init(void) +{ + av_register_input_format(&mm_iformat); + return 0; +}