diff options
author | Matthew Hoops | 2012-11-19 11:27:22 -0500 |
---|---|---|
committer | Matthew Hoops | 2015-01-08 00:45:13 -0500 |
commit | 7054bcd9c53f704f0fdd08a67fec00289f075c32 (patch) | |
tree | 491542bd6a2bdd50fb19c71580558bfb1911c746 | |
parent | 913075df90307a5331870009e0b29bfc87103ef6 (diff) | |
download | scummvm-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.mk | 1 | ||||
-rw-r--r-- | video/mpegps_decoder.cpp | 732 | ||||
-rw-r--r-- | video/mpegps_decoder.h | 189 |
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 |