aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Hoops2012-11-19 11:27:22 -0500
committerMatthew Hoops2015-01-08 00:45:13 -0500
commit7054bcd9c53f704f0fdd08a67fec00289f075c32 (patch)
tree491542bd6a2bdd50fb19c71580558bfb1911c746
parent913075df90307a5331870009e0b29bfc87103ef6 (diff)
downloadscummvm-rg350-7054bcd9c53f704f0fdd08a67fec00289f075c32.tar.gz
scummvm-rg350-7054bcd9c53f704f0fdd08a67fec00289f075c32.tar.bz2
scummvm-rg350-7054bcd9c53f704f0fdd08a67fec00289f075c32.zip
VIDEO: Add MPEG-PS demuxer
For use with ZVision and later MADE
-rw-r--r--video/module.mk1
-rw-r--r--video/mpegps_decoder.cpp732
-rw-r--r--video/mpegps_decoder.h189
3 files changed, 922 insertions, 0 deletions
diff --git a/video/module.mk b/video/module.mk
index 5754350e42..be014598f2 100644
--- a/video/module.mk
+++ b/video/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS := \
coktel_decoder.o \
dxa_decoder.o \
flic_decoder.o \
+ mpegps_decoder.o \
psx_decoder.o \
qt_decoder.o \
smk_decoder.o \
diff --git a/video/mpegps_decoder.cpp b/video/mpegps_decoder.cpp
new file mode 100644
index 0000000000..d8f7f5a68c
--- /dev/null
+++ b/video/mpegps_decoder.cpp
@@ -0,0 +1,732 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+#include "common/debug.h"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+#include "video/mpegps_decoder.h"
+#include "image/codecs/mpeg.h"
+
+// The demuxing code is based on libav's demuxing code
+
+namespace Video {
+
+enum {
+ kStartCodePack = 0x1BA,
+ kStartCodeSystemHeader = 0x1BB,
+ kStartCodeProgramStreamMap = 0x1BC,
+ kStartCodePrivateStream1 = 0x1BD,
+ kStartCodePaddingStream = 0x1BE,
+ kStartCodePrivateStream2 = 0x1BF
+};
+
+MPEGPSDecoder::MPEGPSDecoder() {
+ _stream = 0;
+ memset(_psmESType, 0, 256);
+}
+
+MPEGPSDecoder::~MPEGPSDecoder() {
+ close();
+}
+
+bool MPEGPSDecoder::loadStream(Common::SeekableReadStream *stream) {
+ close();
+
+ _stream = stream;
+
+ if (!addFirstVideoTrack()) {
+ close();
+ return false;
+ }
+
+ _stream->seek(0);
+ return true;
+}
+
+void MPEGPSDecoder::close() {
+ VideoDecoder::close();
+
+ delete _stream;
+ _stream = 0;
+
+ _streamMap.clear();
+
+ memset(_psmESType, 0, 256);
+}
+
+void MPEGPSDecoder::readNextPacket() {
+ if (_stream->eos())
+ return;
+
+ for (;;) {
+ int32 startCode;
+ uint32 pts, dts;
+ int size = readNextPacketHeader(startCode, pts, dts);
+
+ if (size < 0) {
+ // End of stream
+ for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++)
+ if ((*it)->getTrackType() == Track::kTrackTypeVideo)
+ ((MPEGVideoTrack *)*it)->setEndOfTrack();
+ return;
+ }
+
+ MPEGStream *stream = 0;
+ Common::SeekableReadStream *packet = _stream->readStream(size);
+
+ if (_streamMap.contains(startCode)) {
+ // We already found the stream
+ stream = _streamMap[startCode];
+ } else {
+ // We haven't seen this before
+
+ if (startCode == kStartCodePrivateStream1) {
+ PrivateStreamType streamType = detectPrivateStreamType(packet);
+ packet->seek(0);
+
+ // TODO: Handling of these types (as needed)
+
+ const char *typeName;
+
+ switch (streamType) {
+ case kPrivateStreamAC3:
+ typeName = "AC-3";
+ break;
+ case kPrivateStreamDTS:
+ typeName = "DTS";
+ break;
+ case kPrivateStreamDVDPCM:
+ typeName = "DVD PCM";
+ break;
+ case kPrivateStreamPS2Audio:
+ typeName = "PS2 Audio";
+ break;
+ default:
+ typeName = "Unknown";
+ break;
+ }
+
+ warning("Unhandled DVD private stream: %s", typeName);
+
+ // Make it 0 so we don't get the warning twice
+ _streamMap[startCode] = 0;
+ } else if (startCode >= 0x1E0 && startCode <= 0x1EF) {
+ // Video stream
+ // TODO: Multiple video streams
+ warning("Found extra video stream 0x%04X", startCode);
+ _streamMap[startCode] = 0;
+ } else if (startCode >= 0x1C0 && startCode <= 0x1DF) {
+#ifdef USE_MAD
+ // MPEG Audio stream
+ MPEGAudioTrack *audioTrack = new MPEGAudioTrack(packet);
+ stream = audioTrack;
+ _streamMap[startCode] = audioTrack;
+ addTrack(audioTrack);
+#else
+ warning("Found audio stream 0x%04X, but no MAD support compiled in", startCode);
+ _streamMap[startCode] = 0;
+#endif
+ } else {
+ // Probably not relevant
+ debug(0, "Found unhandled MPEG-PS stream type 0x%04x", startCode);
+ _streamMap[startCode] = 0;
+ }
+ }
+
+ if (stream) {
+ bool done = stream->sendPacket(packet, pts, dts);
+
+ if (done && stream->getStreamType() == MPEGStream::kStreamTypeVideo)
+ return;
+ } else {
+ delete packet;
+ }
+ }
+}
+
+#define MAX_SYNC_SIZE 100000
+
+int MPEGPSDecoder::findNextStartCode(uint32 &size) {
+ size = MAX_SYNC_SIZE;
+ int32 state = 0xFF;
+
+ while (size > 0) {
+ byte v = _stream->readByte();
+
+ if (_stream->eos())
+ return -1;
+
+ size--;
+
+ if (state == 0x1)
+ return ((state << 8) | v) & 0xFFFFFF;
+
+ state = ((state << 8) | v) & 0xFFFFFF;
+ }
+
+ return -1;
+}
+
+int MPEGPSDecoder::readNextPacketHeader(int32 &startCode, uint32 &pts, uint32 &dts) {
+ for (;;) {
+ uint32 size;
+ startCode = findNextStartCode(size);
+
+ if (_stream->eos())
+ return -1;
+
+ if (startCode < 0)
+ continue;
+
+ uint32 lastSync = _stream->pos();
+
+ if (startCode == kStartCodePack || startCode == kStartCodeSystemHeader)
+ continue;
+
+ int length = _stream->readUint16BE();
+
+ if (startCode == kStartCodePaddingStream || startCode == kStartCodePrivateStream2) {
+ _stream->skip(length);
+ continue;
+ }
+
+ if (startCode == kStartCodeProgramStreamMap) {
+ parseProgramStreamMap(length);
+ continue;
+ }
+
+ // Find matching stream
+ if (!((startCode >= 0x1C0 && startCode <= 0x1DF) ||
+ (startCode >= 0x1E0 && startCode <= 0x1EF) ||
+ startCode == kStartCodePrivateStream1 || startCode == 0x1FD))
+ continue;
+
+ // Stuffing
+ byte c;
+ for (;;) {
+ if (length < 1) {
+ _stream->seek(lastSync);
+ continue;
+ }
+
+ c = _stream->readByte();
+ length--;
+
+ // XXX: for mpeg1, should test only bit 7
+ if (c != 0xFF)
+ break;
+ }
+
+ if ((c & 0xC0) == 0x40) {
+ // Buffer scale and size
+ _stream->readByte();
+ c = _stream->readByte();
+ length -= 2;
+ }
+
+ pts = 0xFFFFFFFF;
+ dts = 0xFFFFFFFF;
+
+ if ((c & 0xE0) == 0x20) {
+ dts = pts = readPTS(c);
+ length -= 4;
+
+ if (c & 0x10) {
+ dts = readPTS(-1);
+ length -= 5;
+ }
+ } else if ((c & 0xC0) == 0x80) {
+ // MPEG-2 PES
+ byte flags = _stream->readByte();
+ int headerLength = _stream->readByte();
+ length -= 2;
+
+ if (headerLength > length) {
+ _stream->seek(lastSync);
+ continue;
+ }
+
+ length -= headerLength;
+
+ if (flags & 0x80) {
+ dts = pts = readPTS(-1);
+ headerLength -= 5;
+
+ if (flags & 0x40) {
+ dts = readPTS(-1);
+ headerLength -= 5;
+ }
+ }
+
+ if (flags & 0x3F && headerLength == 0) {
+ flags &= 0xC0;
+ warning("Further flags set but no bytes left");
+ }
+
+ if (flags & 0x01) { // PES extension
+ byte pesExt =_stream->readByte();
+ headerLength--;
+
+ // Skip PES private data, program packet sequence
+ int skip = (pesExt >> 4) & 0xB;
+ skip += skip & 0x9;
+
+ if (pesExt & 0x40 || skip > headerLength) {
+ warning("pesExt %x is invalid", pesExt);
+ pesExt = skip = 0;
+ } else {
+ _stream->skip(skip);
+ headerLength -= skip;
+ }
+
+ if (pesExt & 0x01) { // PES extension 2
+ byte ext2Length = _stream->readByte();
+ headerLength--;
+
+ if ((ext2Length & 0x7F) != 0) {
+ byte idExt = _stream->readByte();
+
+ if ((idExt & 0x80) == 0)
+ startCode = (startCode & 0xFF) << 8;
+
+ headerLength--;
+ }
+ }
+ }
+
+ if (headerLength < 0) {
+ _stream->seek(lastSync);
+ continue;
+ }
+
+ _stream->skip(headerLength);
+ } else if (c != 0xF) {
+ continue;
+ }
+
+ if (length < 0) {
+ _stream->seek(lastSync);
+ continue;
+ }
+
+ return length;
+ }
+}
+
+uint32 MPEGPSDecoder::readPTS(int c) {
+ byte buf[5];
+
+ buf[0] = (c < 0) ? _stream->readByte() : c;
+ _stream->read(buf + 1, 4);
+
+ return ((buf[0] & 0x0E) << 29) | ((READ_BE_UINT16(buf + 1) >> 1) << 15) | (READ_BE_UINT16(buf + 3) >> 1);
+}
+
+void MPEGPSDecoder::parseProgramStreamMap(int length) {
+ _stream->readByte();
+ _stream->readByte();
+
+ // skip program stream info
+ _stream->skip(_stream->readUint16BE());
+
+ int esMapLength = _stream->readUint16BE();
+
+ while (esMapLength >= 4) {
+ byte type = _stream->readByte();
+ byte esID = _stream->readByte();
+ uint16 esInfoLength = _stream->readUint16BE();
+
+ // Remember mapping from stream id to stream type
+ _psmESType[esID] = type;
+
+ // Skip program stream info
+ _stream->skip(esInfoLength);
+
+ esMapLength -= 4 + esInfoLength;
+ }
+
+ _stream->readUint32BE(); // CRC32
+}
+
+bool MPEGPSDecoder::addFirstVideoTrack() {
+ for (;;) {
+ int32 startCode;
+ uint32 pts, dts;
+ int size = readNextPacketHeader(startCode, pts, dts);
+
+ // End of stream? We failed
+ if (size < 0)
+ return false;
+
+ if (startCode >= 0x1E0 && startCode <= 0x1EF) {
+ // Video stream
+ // Can be MPEG-1/2 or MPEG-4/h.264. We'll assume the former and
+ // I hope we never need the latter.
+ Common::SeekableReadStream *firstPacket = _stream->readStream(size);
+ MPEGVideoTrack *track = new MPEGVideoTrack(firstPacket, getDefaultHighColorFormat());
+ addTrack(track);
+ _streamMap[startCode] = track;
+ delete firstPacket;
+ break;
+ }
+
+ _stream->skip(size);
+ }
+
+ return true;
+}
+
+MPEGPSDecoder::PrivateStreamType MPEGPSDecoder::detectPrivateStreamType(Common::SeekableReadStream *packet) {
+ uint32 dvdCode = packet->readUint32LE();
+ if (packet->eos())
+ return kPrivateStreamUnknown;
+
+ uint32 ps2Header = packet->readUint32BE();
+ if (!packet->eos() && ps2Header == MKTAG('S', 'S', 'h', 'd'))
+ return kPrivateStreamPS2Audio;
+
+ switch (dvdCode & 0xE0) {
+ case 0x80:
+ if ((dvdCode & 0xF8) == 0x88)
+ return kPrivateStreamDTS;
+
+ return kPrivateStreamAC3;
+ case 0xA0:
+ return kPrivateStreamDVDPCM;
+ }
+
+ return kPrivateStreamUnknown;
+}
+
+MPEGPSDecoder::MPEGVideoTrack::MPEGVideoTrack(Common::SeekableReadStream *firstPacket, const Graphics::PixelFormat &format) {
+ _surface = 0;
+ _endOfTrack = false;
+ _curFrame = -1;
+ _nextFrameStartTime = Audio::Timestamp(0, 27000000); // 27 MHz timer
+
+ findDimensions(firstPacket, format);
+
+#ifdef USE_MPEG2
+ _mpegDecoder = new Image::MPEGDecoder();
+#endif
+}
+
+MPEGPSDecoder::MPEGVideoTrack::~MPEGVideoTrack() {
+#ifdef USE_MPEG2
+ delete _mpegDecoder;
+#endif
+
+ if (_surface) {
+ _surface->free();
+ delete _surface;
+ }
+}
+
+uint16 MPEGPSDecoder::MPEGVideoTrack::getWidth() const {
+ return _surface ? _surface->w : 0;
+}
+
+uint16 MPEGPSDecoder::MPEGVideoTrack::getHeight() const {
+ return _surface ? _surface->h : 0;
+}
+
+Graphics::PixelFormat MPEGPSDecoder::MPEGVideoTrack::getPixelFormat() const {
+ if (!_surface)
+ return Graphics::PixelFormat();
+
+ return _surface->format;
+}
+
+const Graphics::Surface *MPEGPSDecoder::MPEGVideoTrack::decodeNextFrame() {
+ return _surface;
+}
+
+bool MPEGPSDecoder::MPEGVideoTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
+#ifdef USE_MPEG2
+ uint32 framePeriod;
+ bool foundFrame = _mpegDecoder->decodePacket(*packet, framePeriod, _surface);
+
+ if (foundFrame) {
+ _curFrame++;
+ _nextFrameStartTime = _nextFrameStartTime.addFrames(framePeriod);
+ }
+#endif
+
+ delete packet;
+
+#ifdef USE_MPEG2
+ return foundFrame;
+#else
+ return true;
+#endif
+}
+
+void MPEGPSDecoder::MPEGVideoTrack::findDimensions(Common::SeekableReadStream *firstPacket, const Graphics::PixelFormat &format) {
+ // First, check for the picture start code
+ if (firstPacket->readUint32BE() != 0x1B3)
+ error("Failed to detect MPEG sequence start");
+
+ // This is part of the bitstream, but there's really no purpose
+ // to use Common::BitStream just for this: 12 bits width, 12 bits
+ // height
+ uint16 width = firstPacket->readByte() << 4;
+ uint16 height = firstPacket->readByte();
+ width |= (height & 0xF0) >> 4;
+ height = ((height & 0x0F) << 8) | firstPacket->readByte();
+
+ debug(0, "MPEG dimensions: %dx%d", width, height);
+
+ _surface = new Graphics::Surface();
+ _surface->create(width, height, format);
+
+ firstPacket->seek(0);
+}
+
+#ifdef USE_MAD
+
+// The audio code here is almost entirely based on what we do in mp3.cpp
+
+MPEGPSDecoder::MPEGAudioTrack::MPEGAudioTrack(Common::SeekableReadStream *firstPacket) {
+ // The MAD_BUFFER_GUARD must always contain zeros (the reason
+ // for this is that the Layer III Huffman decoder of libMAD
+ // may read a few bytes beyond the end of the input buffer).
+ memset(_buf + BUFFER_SIZE, 0, MAD_BUFFER_GUARD);
+
+ _state = MP3_STATE_INIT;
+ _audStream = 0;
+
+ // Find out our audio parameters
+ initStream(firstPacket);
+
+ while (_state != MP3_STATE_EOS)
+ readHeader(firstPacket);
+
+ _audStream = Audio::makeQueuingAudioStream(_frame.header.samplerate, MAD_NCHANNELS(&_frame.header) == 2);
+
+ deinitStream();
+
+ firstPacket->seek(0);
+ _state = MP3_STATE_INIT;
+}
+
+MPEGPSDecoder::MPEGAudioTrack::~MPEGAudioTrack() {
+ deinitStream();
+ delete _audStream;
+}
+
+static inline int scaleSample(mad_fixed_t sample) {
+ // round
+ sample += (1L << (MAD_F_FRACBITS - 16));
+
+ // clip
+ if (sample > MAD_F_ONE - 1)
+ sample = MAD_F_ONE - 1;
+ else if (sample < -MAD_F_ONE)
+ sample = -MAD_F_ONE;
+
+ // quantize and scale to not saturate when mixing a lot of channels
+ return sample >> (MAD_F_FRACBITS + 1 - 16);
+}
+
+bool MPEGPSDecoder::MPEGAudioTrack::sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts) {
+ while (_state != MP3_STATE_EOS)
+ decodeMP3Data(packet);
+
+ _state = MP3_STATE_READY;
+ delete packet;
+ return true;
+}
+
+Audio::AudioStream *MPEGPSDecoder::MPEGAudioTrack::getAudioStream() const {
+ return _audStream;
+}
+
+void MPEGPSDecoder::MPEGAudioTrack::initStream(Common::SeekableReadStream *packet) {
+ if (_state != MP3_STATE_INIT)
+ deinitStream();
+
+ // Init MAD
+ mad_stream_init(&_stream);
+ mad_frame_init(&_frame);
+ mad_synth_init(&_synth);
+
+ // Reset the stream data
+ packet->seek(0, SEEK_SET);
+
+ // Update state
+ _state = MP3_STATE_READY;
+
+ // Read the first few sample bytes
+ readMP3Data(packet);
+}
+
+void MPEGPSDecoder::MPEGAudioTrack::deinitStream() {
+ if (_state == MP3_STATE_INIT)
+ return;
+
+ // Deinit MAD
+ mad_synth_finish(&_synth);
+ mad_frame_finish(&_frame);
+ mad_stream_finish(&_stream);
+
+ _state = MP3_STATE_EOS;
+}
+
+void MPEGPSDecoder::MPEGAudioTrack::readMP3Data(Common::SeekableReadStream *packet) {
+ uint32 remaining = 0;
+
+ // Give up immediately if we already used up all data in the stream
+ if (packet->eos()) {
+ _state = MP3_STATE_EOS;
+ return;
+ }
+
+ if (_stream.next_frame) {
+ // If there is still data in the MAD stream, we need to preserve it.
+ // Note that we use memmove, as we are reusing the same buffer,
+ // and hence the data regions we copy from and to may overlap.
+ remaining = _stream.bufend - _stream.next_frame;
+ assert(remaining < BUFFER_SIZE); // Paranoia check
+ memmove(_buf, _stream.next_frame, remaining);
+ }
+
+ memset(_buf + remaining, 0, BUFFER_SIZE - remaining);
+
+ // Try to read the next block
+ uint32 size = packet->read(_buf + remaining, BUFFER_SIZE - remaining);
+ if (size == 0) {
+ _state = MP3_STATE_EOS;
+ return;
+ }
+
+ // Feed the data we just read into the stream decoder
+ _stream.error = MAD_ERROR_NONE;
+ mad_stream_buffer(&_stream, _buf, size + remaining);
+}
+
+void MPEGPSDecoder::MPEGAudioTrack::readHeader(Common::SeekableReadStream *packet) {
+ if (_state != MP3_STATE_READY)
+ return;
+
+ // If necessary, load more data into the stream decoder
+ if (_stream.error == MAD_ERROR_BUFLEN)
+ readMP3Data(packet);
+
+ while (_state != MP3_STATE_EOS) {
+ _stream.error = MAD_ERROR_NONE;
+
+ // Decode the next header. Note: mad_frame_decode would do this for us, too.
+ // However, for seeking we don't want to decode the full frame (else it would
+ // be far too slow). Hence we perform this explicitly in a separate step.
+ if (mad_header_decode(&_frame.header, &_stream) == -1) {
+ if (_stream.error == MAD_ERROR_BUFLEN) {
+ readMP3Data(packet); // Read more data
+ continue;
+ } else if (MAD_RECOVERABLE(_stream.error)) {
+ debug(6, "MPEGAudioTrack::readHeader(): Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ continue;
+ } else {
+ warning("MPEGAudioTrack::readHeader(): Unrecoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ break;
+ }
+ }
+
+ break;
+ }
+
+ if (_stream.error != MAD_ERROR_NONE)
+ _state = MP3_STATE_EOS;
+}
+
+void MPEGPSDecoder::MPEGAudioTrack::decodeMP3Data(Common::SeekableReadStream *packet) {
+ if (_state == MP3_STATE_INIT)
+ initStream(packet);
+
+ if (_state == MP3_STATE_EOS)
+ return;
+
+ do {
+ // If necessary, load more data into the stream decoder
+ if (_stream.error == MAD_ERROR_BUFLEN)
+ readMP3Data(packet);
+
+ while (_state == MP3_STATE_READY) {
+ _stream.error = MAD_ERROR_NONE;
+
+ // Decode the next frame
+ if (mad_frame_decode(&_frame, &_stream) == -1) {
+ if (_stream.error == MAD_ERROR_BUFLEN) {
+ break; // Read more data
+ } else if (MAD_RECOVERABLE(_stream.error)) {
+ // Note: we will occasionally see MAD_ERROR_BADDATAPTR errors here.
+ // These are normal and expected (caused by our frame skipping (i.e. "seeking")
+ // code above).
+ debug(6, "MPEGAudioTrack::decodeMP3Data(): Recoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
+ continue;
+ } else {
+ warning("MPEGAudioTrack::decodeMP3Data(): Unrecoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
+ break;
+ }
+ }
+
+ // Synthesize PCM data
+ mad_synth_frame(&_synth, &_frame);
+
+ // Output it to our queue
+ if (_synth.pcm.length != 0) {
+ byte *buffer = (byte *)malloc(_synth.pcm.length * 2 * MAD_NCHANNELS(&_frame.header));
+ int16 *ptr = (int16 *)buffer;
+
+ for (int i = 0; i < _synth.pcm.length; i++) {
+ *ptr++ = (int16)scaleSample(_synth.pcm.samples[0][i]);
+
+ if (MAD_NCHANNELS(&_frame.header) == 2)
+ *ptr++ = (int16)scaleSample(_synth.pcm.samples[1][i]);
+ }
+
+ int flags = Audio::FLAG_16BITS;
+
+ if (_audStream->isStereo())
+ flags |= Audio::FLAG_STEREO;
+
+#ifdef SCUMM_LITTLE_ENDIAN
+ flags |= Audio::FLAG_LITTLE_ENDIAN;
+#endif
+
+ _audStream->queueBuffer(buffer, _synth.pcm.length * 2 * MAD_NCHANNELS(&_frame.header), DisposeAfterUse::YES, flags);
+ }
+ break;
+ }
+ } while (_state != MP3_STATE_EOS && _stream.error == MAD_ERROR_BUFLEN);
+
+ if (_stream.error != MAD_ERROR_NONE)
+ _state = MP3_STATE_EOS;
+}
+
+#endif
+
+} // End of namespace Video
diff --git a/video/mpegps_decoder.h b/video/mpegps_decoder.h
new file mode 100644
index 0000000000..0184d6f9ba
--- /dev/null
+++ b/video/mpegps_decoder.h
@@ -0,0 +1,189 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef VIDEO_MPEGPS_DECODER_H
+#define VIDEO_MPEGPS_DECODER_H
+
+#include "common/hashmap.h"
+#include "graphics/surface.h"
+#include "video/video_decoder.h"
+
+#ifdef USE_MAD
+#include <mad.h>
+#endif
+
+namespace Audio {
+class QueuingAudioStream;
+}
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct PixelFormat;
+}
+
+namespace Image {
+class MPEGDecoder;
+}
+
+namespace Video {
+
+/**
+ * Decoder for MPEG Program Stream videos.
+ * Video decoder used in engines:
+ * - zvision
+ */
+class MPEGPSDecoder : public VideoDecoder {
+public:
+ MPEGPSDecoder();
+ virtual ~MPEGPSDecoder();
+
+ bool loadStream(Common::SeekableReadStream *stream);
+ void close();
+
+protected:
+ void readNextPacket();
+ bool useAudioSync() const { return false; }
+
+private:
+ // Base class for handling MPEG streams
+ class MPEGStream {
+ public:
+ virtual ~MPEGStream() {}
+
+ enum StreamType {
+ kStreamTypeVideo,
+ kStreamTypeAudio
+ };
+
+ virtual bool sendPacket(Common::SeekableReadStream *firstPacket, uint32 pts, uint32 dts) = 0;
+ virtual StreamType getStreamType() const = 0;
+ };
+
+ // An MPEG 1/2 video track
+ class MPEGVideoTrack : public VideoTrack, public MPEGStream {
+ public:
+ MPEGVideoTrack(Common::SeekableReadStream *firstPacket, const Graphics::PixelFormat &format);
+ ~MPEGVideoTrack();
+
+ bool endOfTrack() const { return _endOfTrack; }
+ uint16 getWidth() const;
+ uint16 getHeight() const;
+ Graphics::PixelFormat getPixelFormat() const;
+ int getCurFrame() const { return _curFrame; }
+ uint32 getNextFrameStartTime() const { return _nextFrameStartTime.msecs(); }
+ const Graphics::Surface *decodeNextFrame();
+
+ bool sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts);
+ StreamType getStreamType() const { return kStreamTypeVideo; }
+
+ void setEndOfTrack() { _endOfTrack = true; }
+
+ private:
+ bool _endOfTrack;
+ int _curFrame;
+ Audio::Timestamp _nextFrameStartTime;
+ Graphics::Surface *_surface;
+
+ void findDimensions(Common::SeekableReadStream *firstPacket, const Graphics::PixelFormat &format);
+
+#ifdef USE_MPEG2
+ Image::MPEGDecoder *_mpegDecoder;
+#endif
+ };
+
+#ifdef USE_MAD
+ // An MPEG audio track
+ // TODO: Merge this with the normal MP3Stream somehow
+ class MPEGAudioTrack : public AudioTrack, public MPEGStream {
+ public:
+ MPEGAudioTrack(Common::SeekableReadStream *firstPacket);
+ ~MPEGAudioTrack();
+
+ bool sendPacket(Common::SeekableReadStream *packet, uint32 pts, uint32 dts);
+ StreamType getStreamType() const { return kStreamTypeAudio; }
+
+ protected:
+ Audio::AudioStream *getAudioStream() const;
+
+ private:
+ Audio::QueuingAudioStream *_audStream;
+
+ enum State {
+ MP3_STATE_INIT, // Need to init the decoder
+ MP3_STATE_READY, // ready for processing data
+ MP3_STATE_EOS // end of data reached (may need to loop)
+ };
+
+ State _state;
+
+ mad_stream _stream;
+ mad_frame _frame;
+ mad_synth _synth;
+
+ enum {
+ BUFFER_SIZE = 5 * 8192
+ };
+
+ // This buffer contains a slab of input data
+ byte _buf[BUFFER_SIZE + MAD_BUFFER_GUARD];
+
+ void initStream(Common::SeekableReadStream *packet);
+ void deinitStream();
+ void readMP3Data(Common::SeekableReadStream *packet);
+ void readHeader(Common::SeekableReadStream *packet);
+ void decodeMP3Data(Common::SeekableReadStream *packet);
+ };
+#endif
+
+ // The different types of private streams we can detect at the moment
+ enum PrivateStreamType {
+ kPrivateStreamUnknown,
+ kPrivateStreamAC3,
+ kPrivateStreamDTS,
+ kPrivateStreamDVDPCM,
+ kPrivateStreamPS2Audio
+ };
+
+ PrivateStreamType detectPrivateStreamType(Common::SeekableReadStream *packet);
+
+ bool addFirstVideoTrack();
+
+ int readNextPacketHeader(int32 &startCode, uint32 &pts, uint32 &dts);
+ int findNextStartCode(uint32 &size);
+ uint32 readPTS(int c);
+
+ void parseProgramStreamMap(int length);
+ byte _psmESType[256];
+
+ // A map from stream types to stream handlers
+ typedef Common::HashMap<int, MPEGStream *> StreamMap;
+ StreamMap _streamMap;
+
+ Common::SeekableReadStream *_stream;
+};
+
+} // End of namespace Video
+
+#endif