diff options
Diffstat (limited to 'video')
-rw-r--r-- | video/avi_decoder.cpp | 397 | ||||
-rw-r--r-- | video/avi_decoder.h | 25 | ||||
-rw-r--r-- | video/module.mk | 1 | ||||
-rw-r--r-- | video/mpegps_decoder.cpp | 732 | ||||
-rw-r--r-- | video/mpegps_decoder.h | 189 | ||||
-rw-r--r-- | video/qt_decoder.cpp | 270 | ||||
-rw-r--r-- | video/qt_decoder.h | 12 | ||||
-rw-r--r-- | video/theora_decoder.cpp | 4 | ||||
-rw-r--r-- | video/theora_decoder.h | 6 | ||||
-rw-r--r-- | video/video_decoder.cpp | 24 | ||||
-rw-r--r-- | video/video_decoder.h | 32 |
11 files changed, 1436 insertions, 256 deletions
diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp index d9761e1c38..700975d9a2 100644 --- a/video/avi_decoder.cpp +++ b/video/avi_decoder.cpp @@ -149,7 +149,7 @@ bool AVIDecoder::parseNextChunk() { skipChunk(size); break; case ID_IDX1: - readOldIndex(size); + readOldIndex(size); break; default: error("Unknown tag \'%s\' found", tag2str(tag)); @@ -319,8 +319,25 @@ bool AVIDecoder::loadStream(Common::SeekableReadStream *stream) { return false; } - // Seek back to the start of the MOVI list - _fileStream->seek(_movieListStart); + // Create the status entries + uint32 index = 0; + for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++, index++) { + TrackStatus status; + status.track = *it; + status.index = index; + status.chunkSearchOffset = _movieListStart; + + if ((*it)->getTrackType() == Track::kTrackTypeVideo) + _videoTracks.push_back(status); + else + _audioTracks.push_back(status); + } + + if (_videoTracks.size() != 1) { + warning("Unhandled AVI video track count: %d", _videoTracks.size()); + close(); + return false; + } // Check if this is a special Duck Truemotion video checkTruemotion1(); @@ -340,79 +357,138 @@ void AVIDecoder::close() { _indexEntries.clear(); memset(&_header, 0, sizeof(_header)); + + _videoTracks.clear(); + _audioTracks.clear(); } void AVIDecoder::readNextPacket() { - if ((uint32)_fileStream->pos() >= _movieListEnd) { - // Ugh, reached the end premature. - forceVideoEnd(); + // Shouldn't get this unless called on a non-open video + if (_videoTracks.empty()) return; - } - uint32 nextTag = _fileStream->readUint32BE(); - uint32 size = _fileStream->readUint32LE(); + // Get the video frame first + handleNextPacket(_videoTracks[0]); + + // Handle audio tracks next + for (uint32 i = 0; i < _audioTracks.size(); i++) + handleNextPacket(_audioTracks[i]); +} + +void AVIDecoder::handleNextPacket(TrackStatus &status) { + // If there's no more to search, bail out + if (status.chunkSearchOffset + 8 >= _movieListEnd) { + if (status.track->getTrackType() == Track::kTrackTypeVideo) { + // Horrible AVI video has a premature end + // Force the frame to be the last frame + debug(0, "Forcing end of AVI video"); + ((AVIVideoTrack *)status.track)->forceTrackEnd(); + } - if (_fileStream->eos()) { - // Also premature end. - forceVideoEnd(); return; } - if (nextTag == ID_LIST) { - // A list of audio/video chunks - int32 startPos = _fileStream->pos(); + // See if audio needs to be buffered and break out if not + if (status.track->getTrackType() == Track::kTrackTypeAudio && !shouldQueueAudio(status)) + return; - if (_fileStream->readUint32BE() != ID_REC) - error("Expected 'rec ' LIST"); + // Seek to where we shall start searching + _fileStream->seek(status.chunkSearchOffset); + + for (;;) { + // If there's no more to search, bail out + if ((uint32)_fileStream->pos() + 8 >= _movieListEnd) { + if (status.track->getTrackType() == Track::kTrackTypeVideo) { + // Horrible AVI video has a premature end + // Force the frame to be the last frame + debug(0, "Forcing end of AVI video"); + ((AVIVideoTrack *)status.track)->forceTrackEnd(); + } - size -= 4; // subtract list type + break; + } - // Decode chunks in the list - while (_fileStream->pos() < startPos + (int32)size) - readNextPacket(); + uint32 nextTag = _fileStream->readUint32BE(); + uint32 size = _fileStream->readUint32LE(); - return; - } else if (nextTag == ID_JUNK || nextTag == ID_IDX1) { - skipChunk(size); - return; - } + if (nextTag == ID_LIST) { + // A list of audio/video chunks + if (_fileStream->readUint32BE() != ID_REC) + error("Expected 'rec ' LIST"); - Track *track = getTrack(getStreamIndex(nextTag)); + continue; + } else if (nextTag == ID_JUNK || nextTag == ID_IDX1) { + skipChunk(size); + continue; + } - if (!track) - error("Cannot get track from tag '%s'", tag2str(nextTag)); + // Only accept chunks for this stream + uint32 streamIndex = getStreamIndex(nextTag); + if (streamIndex != status.index) { + skipChunk(size); + continue; + } - Common::SeekableReadStream *chunk = 0; + Common::SeekableReadStream *chunk = 0; - if (size != 0) { - chunk = _fileStream->readStream(size); - _fileStream->skip(size & 1); - } + if (size != 0) { + chunk = _fileStream->readStream(size); + _fileStream->skip(size & 1); + } - if (track->getTrackType() == Track::kTrackTypeAudio) { - if (getStreamType(nextTag) != kStreamTypeAudio) - error("Invalid audio track tag '%s'", tag2str(nextTag)); + if (status.track->getTrackType() == Track::kTrackTypeAudio) { + if (getStreamType(nextTag) != kStreamTypeAudio) + error("Invalid audio track tag '%s'", tag2str(nextTag)); - assert(chunk); - ((AVIAudioTrack *)track)->queueSound(chunk); - } else { - AVIVideoTrack *videoTrack = (AVIVideoTrack *)track; + assert(chunk); + ((AVIAudioTrack *)status.track)->queueSound(chunk); - if (getStreamType(nextTag) == kStreamTypePaletteChange) { - // Palette Change - videoTrack->loadPaletteFromChunk(chunk); + // Break out if we have enough audio + if (!shouldQueueAudio(status)) + break; } else { - // Otherwise, assume it's a compressed frame - videoTrack->decodeFrame(chunk); + AVIVideoTrack *videoTrack = (AVIVideoTrack *)status.track; + + if (getStreamType(nextTag) == kStreamTypePaletteChange) { + // Palette Change + videoTrack->loadPaletteFromChunk(chunk); + } else { + // Otherwise, assume it's a compressed frame + videoTrack->decodeFrame(chunk); + break; + } } } + + // Start us off in this position next time + status.chunkSearchOffset = _fileStream->pos(); +} + +bool AVIDecoder::shouldQueueAudio(TrackStatus& status) { + // Sanity check: + if (status.track->getTrackType() != Track::kTrackTypeAudio) + return false; + + // If video is done, make sure that the rest of the audio is queued + // (I guess this is also really a sanity check) + AVIVideoTrack *videoTrack = (AVIVideoTrack *)_videoTracks[0].track; + if (videoTrack->endOfTrack()) + return true; + + // Being three frames ahead should be enough for any video. + return ((AVIAudioTrack *)status.track)->getCurChunk() < (uint32)(videoTrack->getCurFrame() + 3); } bool AVIDecoder::rewind() { if (!VideoDecoder::rewind()) return false; - _fileStream->seek(_movieListStart); + for (uint32 i = 0; i < _videoTracks.size(); i++) + _videoTracks[i].chunkSearchOffset = _movieListStart; + + for (uint32 i = 0; i < _audioTracks.size(); i++) + _audioTracks[i].chunkSearchOffset = _movieListStart; + return true; } @@ -421,29 +497,9 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { if (time > getDuration()) return false; - // Track down our video track. - // We only support seeking with one video track right now. - AVIVideoTrack *videoTrack = 0; - int videoIndex = -1; - uint trackID = 0; - - for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++, trackID++) { - if ((*it)->getTrackType() == Track::kTrackTypeVideo) { - if (videoTrack) { - // Already have one - // -> Not supported - return false; - } - - videoTrack = (AVIVideoTrack *)*it; - videoIndex = trackID; - } - } - - // Need a video track to go forwards - // If there isn't a video track, why would anyone be using AVI then? - if (!videoTrack) - return false; + // Get our video + AVIVideoTrack *videoTrack = (AVIVideoTrack *)_videoTracks[0].track; + uint32 videoIndex = _videoTracks[0].index; // If we seek directly to the end, just mark the tracks as over if (time == getDuration()) { @@ -464,7 +520,6 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { int lastKeyFrame = -1; int frameIndex = -1; - int lastRecord = -1; uint curFrame = 0; // Go through and figure out where we should be @@ -472,40 +527,40 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { for (uint32 i = 0; i < _indexEntries.size(); i++) { const OldIndex &index = _indexEntries[i]; - if (index.id == ID_REC) { - // Keep track of any records we find - lastRecord = i; - } else { - if (getStreamIndex(index.id) != videoIndex) - continue; + // We don't care about RECs + if (index.id == ID_REC) + continue; - uint16 streamType = getStreamType(index.id); + // We're only looking at entries for this track + if (getStreamIndex(index.id) != videoIndex) + continue; - if (streamType == kStreamTypePaletteChange) { - // We need to handle any palette change we see since there's no - // flag to tell if this is a "key" palette. - // Decode the palette - _fileStream->seek(_indexEntries[i].offset + 8); - Common::SeekableReadStream *chunk = 0; + uint16 streamType = getStreamType(index.id); - if (_indexEntries[i].size != 0) - chunk = _fileStream->readStream(_indexEntries[i].size); + if (streamType == kStreamTypePaletteChange) { + // We need to handle any palette change we see since there's no + // flag to tell if this is a "key" palette. + // Decode the palette + _fileStream->seek(_indexEntries[i].offset + 8); + Common::SeekableReadStream *chunk = 0; - videoTrack->loadPaletteFromChunk(chunk); - } else { - // Check to see if this is a keyframe - // The first frame has to be a keyframe - if ((_indexEntries[i].flags & AVIIF_INDEX) || curFrame == 0) - lastKeyFrame = i; - - // Did we find the target frame? - if (frame == curFrame) { - frameIndex = i; - break; - } + if (_indexEntries[i].size != 0) + chunk = _fileStream->readStream(_indexEntries[i].size); - curFrame++; + videoTrack->loadPaletteFromChunk(chunk); + } else { + // Check to see if this is a keyframe + // The first frame has to be a keyframe + if ((_indexEntries[i].flags & AVIIF_INDEX) || curFrame == 0) + lastKeyFrame = i; + + // Did we find the target frame? + if (frame == curFrame) { + frameIndex = i; + break; } + + curFrame++; } } @@ -513,57 +568,36 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { return false; // Update all the audio tracks - uint audioIndex = 0; - - for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++, audioIndex++) { - if ((*it)->getTrackType() != Track::kTrackTypeAudio) - continue; - - AVIAudioTrack *audioTrack = (AVIAudioTrack *)*it; - - // We need to find where the start of audio should be. - // Which is exactly 'initialFrames' audio chunks back from where - // our found frame is. + for (uint32 i = 0; i < _audioTracks.size(); i++) { + AVIAudioTrack *audioTrack = (AVIAudioTrack *)_audioTracks[i].track; // Recreate the audio stream audioTrack->resetStream(); - uint framesNeeded = _header.initialFrames; - if (framesNeeded == 0) - framesNeeded = 1; + // Set the chunk index for the track + audioTrack->setCurChunk(frame); - uint startAudioChunk = 0; - int startAudioSearch = (lastRecord < 0) ? (frameIndex - 1) : (lastRecord - 1); + uint32 chunksFound = 0; + for (uint32 j = 0; j < _indexEntries.size(); j++) { + const OldIndex &index = _indexEntries[j]; - for (int i = startAudioSearch; i >= 0; i--) { - if (getStreamIndex(_indexEntries[i].id) != audioIndex) + // Continue ignoring RECs + if (index.id == ID_REC) continue; - assert(getStreamType(_indexEntries[i].id) == kStreamTypeAudio); - - framesNeeded--; + if (getStreamIndex(index.id) == _audioTracks[i].index) { + if (chunksFound == frame) { + _fileStream->seek(index.offset + 8); + Common::SeekableReadStream *audioChunk = _fileStream->readStream(index.size); + audioTrack->queueSound(audioChunk); + _audioTracks[i].chunkSearchOffset = (j == _indexEntries.size() - 1) ? _movieListEnd : _indexEntries[j + 1].offset; + break; + } - if (framesNeeded == 0) { - startAudioChunk = i; - break; + chunksFound++; } } - // Now go forward and queue them all - for (int i = startAudioChunk; i <= startAudioSearch; i++) { - if (_indexEntries[i].id == ID_REC) - continue; - - if (getStreamIndex(_indexEntries[i].id) != audioIndex) - continue; - - assert(getStreamType(_indexEntries[i].id) == kStreamTypeAudio); - - _fileStream->seek(_indexEntries[i].offset + 8); - Common::SeekableReadStream *chunk = _fileStream->readStream(_indexEntries[i].size); - audioTrack->queueSound(chunk); - } - // Skip any audio to bring us to the right time audioTrack->skipAudio(time, videoTrack->getFrameTime(frame)); } @@ -592,15 +626,11 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { videoTrack->decodeFrame(chunk); } - // Seek to the right spot - // To the beginning of the last record, or frame if that doesn't exist - if (lastRecord >= 0) - _fileStream->seek(_indexEntries[lastRecord].offset); - else - _fileStream->seek(_indexEntries[frameIndex].offset); - + // Set the video track's frame videoTrack->setCurFrame((int)frame - 1); + // Set the video track's search offset to the right spot + _videoTracks[0].chunkSearchOffset = _indexEntries[frameIndex].offset; return true; } @@ -623,7 +653,7 @@ void AVIDecoder::readOldIndex(uint32 size) { OldIndex firstEntry; firstEntry.id = _fileStream->readUint32BE(); firstEntry.flags = _fileStream->readUint32LE(); - firstEntry.offset = _fileStream->readUint32LE(); + firstEntry.offset = _fileStream->readUint32LE(); firstEntry.size = _fileStream->readUint32LE(); // Check if the offset is already absolute @@ -654,45 +684,22 @@ void AVIDecoder::readOldIndex(uint32 size) { } } -void AVIDecoder::forceVideoEnd() { - // Horrible AVI video has a premature end - // Force the frame to be the last frame - debug(0, "Forcing end of AVI video"); - - for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++) - if ((*it)->getTrackType() == Track::kTrackTypeVideo) - ((AVIVideoTrack *)*it)->forceTrackEnd(); -} - void AVIDecoder::checkTruemotion1() { - AVIVideoTrack *track = 0; + // If we got here from loadStream(), we know the track is valid + assert(!_videoTracks.empty()); - for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++) { - if ((*it)->getTrackType() == Track::kTrackTypeVideo) { - if (track) { - // Multiple tracks; isn't going to be truemotion 1 - return; - } - - track = (AVIVideoTrack *)*it; - } - } - - // No track found? - if (!track) - return; + TrackStatus &status = _videoTracks[0]; + AVIVideoTrack *track = (AVIVideoTrack *)status.track; // Ignore non-truemotion tracks if (!track->isTruemotion1()) return; - // Search for a non-empty frame - const Graphics::Surface *frame = 0; - for (int i = 0; i < 10 && !frame; i++) - frame = decodeNextFrame(); + // Read the next video packet + handleNextPacket(status); + const Graphics::Surface *frame = track->decodeNextFrame(); if (!frame) { - // Probably shouldn't happen rewind(); return; } @@ -808,8 +815,32 @@ void AVIDecoder::AVIVideoTrack::forceTrackEnd() { _curFrame = _frameCount - 1; } +const byte *AVIDecoder::AVIVideoTrack::getPalette() const { + if (_videoCodec && _videoCodec->containsPalette()) + return _videoCodec->getPalette(); + + _dirtyPalette = false; + return _palette; +} + +bool AVIDecoder::AVIVideoTrack::hasDirtyPalette() const { + if (_videoCodec && _videoCodec->containsPalette()) + return _videoCodec->hasDirtyPalette(); + + return _dirtyPalette; +} + +bool AVIDecoder::AVIVideoTrack::canDither() const { + return _videoCodec && _videoCodec->canDither(Image::Codec::kDitherTypeVFW); +} + +void AVIDecoder::AVIVideoTrack::setDither(const byte *palette) { + assert(_videoCodec); + _videoCodec->setDither(Image::Codec::kDitherTypeVFW, palette); +} + AVIDecoder::AVIAudioTrack::AVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType) - : _audsHeader(streamHeader), _wvInfo(waveFormat), _soundType(soundType) { + : _audsHeader(streamHeader), _wvInfo(waveFormat), _soundType(soundType), _curChunk(0) { _audStream = createAudioStream(); } @@ -836,12 +867,12 @@ void AVIDecoder::AVIAudioTrack::queueSound(Common::SeekableReadStream *stream) { _audStream->queueAudioStream(Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMMSIma, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign), DisposeAfterUse::YES); } else if (_wvInfo.tag == kWaveFormatDK3) { _audStream->queueAudioStream(Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMDK3, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign), DisposeAfterUse::YES); - } else if (_wvInfo.tag == kWaveFormatMP3) { - warning("AVI: MP3 audio stream is not supported"); } } else { delete stream; } + + _curChunk++; } void AVIDecoder::AVIAudioTrack::skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime) { @@ -862,6 +893,7 @@ void AVIDecoder::AVIAudioTrack::skipAudio(const Audio::Timestamp &time, const Au void AVIDecoder::AVIAudioTrack::resetStream() { delete _audStream; _audStream = createAudioStream(); + _curChunk = 0; } bool AVIDecoder::AVIAudioTrack::rewind() { @@ -874,12 +906,17 @@ Audio::AudioStream *AVIDecoder::AVIAudioTrack::getAudioStream() const { } Audio::QueuingAudioStream *AVIDecoder::AVIAudioTrack::createAudioStream() { - if (_wvInfo.tag == kWaveFormatPCM || _wvInfo.tag == kWaveFormatMSADPCM || _wvInfo.tag == kWaveFormatMSIMAADPCM || _wvInfo.tag == kWaveFormatDK3 || _wvInfo.tag == kWaveFormatMP3) + if (_wvInfo.tag == kWaveFormatPCM || _wvInfo.tag == kWaveFormatMSADPCM || _wvInfo.tag == kWaveFormatMSIMAADPCM || _wvInfo.tag == kWaveFormatDK3) return Audio::makeQueuingAudioStream(_wvInfo.samplesPerSec, _wvInfo.channels == 2); + else if (_wvInfo.tag == kWaveFormatMP3) + warning("Unsupported AVI MP3 tracks"); else if (_wvInfo.tag != kWaveFormatNone) // No sound warning("Unsupported AVI audio format %d", _wvInfo.tag); return 0; } +AVIDecoder::TrackStatus::TrackStatus() : track(0), chunkSearchOffset(0) { +} + } // End of namespace Video diff --git a/video/avi_decoder.h b/video/avi_decoder.h index 4461e537c5..6c1ce1a4b9 100644 --- a/video/avi_decoder.h +++ b/video/avi_decoder.h @@ -179,11 +179,14 @@ protected: int getCurFrame() const { return _curFrame; } int getFrameCount() const { return _frameCount; } const Graphics::Surface *decodeNextFrame() { return _lastFrame; } - const byte *getPalette() const { _dirtyPalette = false; return _palette; } - bool hasDirtyPalette() const { return _dirtyPalette; } + + const byte *getPalette() const; + bool hasDirtyPalette() const; void setCurFrame(int frame) { _curFrame = frame; } void loadPaletteFromChunk(Common::SeekableReadStream *chunk); void useInitialPalette(); + bool canDither() const; + void setDither(const byte *palette); bool isTruemotion1() const; void forceDimensions(uint16 width, uint16 height); @@ -215,7 +218,9 @@ protected: virtual void queueSound(Common::SeekableReadStream *stream); Audio::Mixer::SoundType getSoundType() const { return _soundType; } void skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime); - void resetStream(); + virtual void resetStream(); + uint32 getCurChunk() const { return _curChunk; } + void setCurChunk(uint32 chunk) { _curChunk = chunk; } bool isRewindable() const { return true; } bool rewind(); @@ -238,6 +243,15 @@ protected: Audio::Mixer::SoundType _soundType; Audio::QueuingAudioStream *_audStream; Audio::QueuingAudioStream *createAudioStream(); + uint32 _curChunk; + }; + + struct TrackStatus { + TrackStatus(); + + Track *track; + uint32 index; + uint32 chunkSearchOffset; }; AVIHeader _header; @@ -260,9 +274,12 @@ protected: void handleStreamHeader(uint32 size); uint16 getStreamType(uint32 tag) const { return tag & 0xFFFF; } byte getStreamIndex(uint32 tag) const; - void forceVideoEnd(); void checkTruemotion1(); + void handleNextPacket(TrackStatus& status); + bool shouldQueueAudio(TrackStatus& status); + Common::Array<TrackStatus> _videoTracks, _audioTracks; + public: virtual AVIAudioTrack *createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo); }; 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 diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp index 0a29692948..324bf65294 100644 --- a/video/qt_decoder.cpp +++ b/video/qt_decoder.cpp @@ -286,13 +286,15 @@ QuickTimeDecoder::VideoTrackHandler::VideoTrackHandler(QuickTimeDecoder *decoder _curEdit = 0; enterNewEditList(false); - _holdNextFrameStartTime = false; _curFrame = -1; _durationOverride = -1; _scaledSurface = 0; _curPalette = 0; _dirtyPalette = false; _reversed = false; + _forcedDitherPalette = 0; + _ditherTable = 0; + _ditherFrame = 0; } QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() { @@ -300,6 +302,14 @@ QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() { _scaledSurface->free(); delete _scaledSurface; } + + delete[] _forcedDitherPalette; + delete[] _ditherTable; + + if (_ditherFrame) { + _ditherFrame->free(); + delete _ditherFrame; + } } bool QuickTimeDecoder::VideoTrackHandler::endOfTrack() const { @@ -347,15 +357,12 @@ bool QuickTimeDecoder::VideoTrackHandler::seek(const Audio::Timestamp &requested } } - // All that's left is to figure out what our starting time is going to be - // Compare the starting point for the frame to where we need to be - _holdNextFrameStartTime = getRateAdjustedFrameTime() != (uint32)time.totalNumberOfFrames(); - - // If we went past the time, go back a frame. _curFrame before this point is at the frame - // that should be displayed. This adjustment ensures it is on the frame before the one that - // should be displayed. - if (_holdNextFrameStartTime) + // Check if we went past, then adjust the frame times + if (getRateAdjustedFrameTime() != (uint32)time.totalNumberOfFrames()) { _curFrame--; + _durationOverride = getRateAdjustedFrameTime() - time.totalNumberOfFrames(); + _nextFrameStartTime = time.totalNumberOfFrames(); + } if (_reversed) { // Call setReverse again to update @@ -386,6 +393,9 @@ uint16 QuickTimeDecoder::VideoTrackHandler::getHeight() const { } Graphics::PixelFormat QuickTimeDecoder::VideoTrackHandler::getPixelFormat() const { + if (_forcedDitherPalette) + return Graphics::PixelFormat::createFormatCLUT8(); + return ((VideoSampleDesc *)_parent->sampleDescs[0])->_videoCodec->getPixelFormat(); } @@ -397,8 +407,22 @@ uint32 QuickTimeDecoder::VideoTrackHandler::getNextFrameStartTime() const { if (endOfTrack()) return 0; - // Convert to milliseconds so the tracks can be compared - return getRateAdjustedFrameTime() * 1000 / _parent->timeScale; + Audio::Timestamp frameTime(0, getRateAdjustedFrameTime(), _parent->timeScale); + + // Check if the frame goes beyond the end of the edit. In that case, the next frame + // should really be when we cross the edit boundary. + if (_reversed) { + Audio::Timestamp editStartTime(0, _parent->editList[_curEdit].timeOffset, _decoder->_timeScale); + if (frameTime < editStartTime) + return editStartTime.msecs(); + } else { + Audio::Timestamp nextEditStartTime(0, _parent->editList[_curEdit].timeOffset + _parent->editList[_curEdit].trackDuration, _decoder->_timeScale); + if (frameTime > nextEditStartTime) + return nextEditStartTime.msecs(); + } + + // Not past an edit boundary, so the frame time is what should be used + return frameTime.msecs(); } const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame() { @@ -422,39 +446,41 @@ const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame() bufferNextFrame(); } + // Update the edit list, if applicable + if (endOfCurEdit()) { + _curEdit++; + + if (atLastEdit()) + return 0; + + enterNewEditList(true); + } + const Graphics::Surface *frame = bufferNextFrame(); if (_reversed) { - if (_holdNextFrameStartTime) { - // Don't set the next frame start time here; we just did a seek - _holdNextFrameStartTime = false; + if (_durationOverride >= 0) { + // Use our own duration overridden from a media seek + _nextFrameStartTime -= _durationOverride; + _durationOverride = -1; } else { // Just need to subtract the time _nextFrameStartTime -= getFrameDuration(); } } else { - if (_holdNextFrameStartTime) { - // Don't set the next frame start time here; we just did a seek - _holdNextFrameStartTime = false; - } else if (_durationOverride >= 0) { - // Use our own duration from the edit list calculation - _nextFrameStartTime += _durationOverride; + if (_durationOverride >= 0) { + // Use our own duration overridden from a media seek + _nextFrameStartTime += _durationOverride; _durationOverride = -1; } else { _nextFrameStartTime += getFrameDuration(); } - - // Update the edit list, if applicable - // HACK: We're also accepting the time minus one because edit lists - // aren't as accurate as one would hope. - if (!atLastEdit() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) { - _curEdit++; - - if (!atLastEdit()) - enterNewEditList(true); - } } + // Handle forced dithering + if (frame && _forcedDitherPalette) + frame = forceDither(*frame); + if (frame && (_parent->scaleFactorX != 1 || _parent->scaleFactorY != 1)) { if (!_scaledSurface) { _scaledSurface = new Graphics::Surface(); @@ -468,6 +494,11 @@ const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame() return frame; } +const byte *QuickTimeDecoder::VideoTrackHandler::getPalette() const { + _dirtyPalette = false; + return _forcedDitherPalette ? _forcedDitherPalette : _curPalette; +} + bool QuickTimeDecoder::VideoTrackHandler::setReverse(bool reverse) { _reversed = reverse; @@ -485,11 +516,11 @@ bool QuickTimeDecoder::VideoTrackHandler::setReverse(bool reverse) { _curEdit = _parent->editCount - 1; _curFrame = _parent->frameCount; _nextFrameStartTime = _parent->editList[_curEdit].trackDuration + _parent->editList[_curEdit].timeOffset; - } else if (_holdNextFrameStartTime) { - // We just seeked, so "pivot" around the frame that should be displayed - _curFrame++; - _nextFrameStartTime -= getFrameDuration(); - _curFrame++; + } else if (_durationOverride >= 0) { + // We just had a media seek, so "pivot" around the frame that should + // be displayed. + _curFrame += 2; + _nextFrameStartTime += _durationOverride; } else { // We need to put _curFrame to be the one after the one that should be displayed. // Since we're on the frame that should be displaying right now, add one. @@ -497,20 +528,19 @@ bool QuickTimeDecoder::VideoTrackHandler::setReverse(bool reverse) { } } else { // Update the edit list, if applicable - // HACK: We're also accepting the time minus one because edit lists - // aren't as accurate as one would hope. - if (!atLastEdit() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) { + if (!atLastEdit() && endOfCurEdit()) { _curEdit++; if (atLastEdit()) return true; } - if (_holdNextFrameStartTime) { - // We just seeked, so "pivot" around the frame that should be displayed + if (_durationOverride >= 0) { + // We just had a media seek, so "pivot" around the frame that should + // be displayed. _curFrame--; - _nextFrameStartTime += getFrameDuration(); - } + _nextFrameStartTime -= _durationOverride; + } // We need to put _curFrame to be the one before the one that should be displayed. // Since we're on the frame that should be displaying right now, subtract one. @@ -559,10 +589,8 @@ Common::SeekableReadStream *QuickTimeDecoder::VideoTrackHandler::getNextFramePac } } - if (actualChunk < 0) { - warning("Could not find data for frame %d", _curFrame); - return 0; - } + if (actualChunk < 0) + error("Could not find data for frame %d", _curFrame); // Next seek to that frame Common::SeekableReadStream *stream = _decoder->_fd; @@ -617,30 +645,32 @@ void QuickTimeDecoder::VideoTrackHandler::enterNewEditList(bool bufferFrames) { if (atLastEdit()) return; + uint32 mediaTime = _parent->editList[_curEdit].mediaTime; uint32 frameNum = 0; - bool done = false; uint32 totalDuration = 0; - uint32 prevDuration = 0; + _durationOverride = -1; // Track down where the mediaTime is in the media // This is basically time -> frame mapping // Note that this code uses first frame = 0 - for (int32 i = 0; i < _parent->timeToSampleCount && !done; i++) { - for (int32 j = 0; j < _parent->timeToSample[i].count; j++) { - if (totalDuration == (uint32)_parent->editList[_curEdit].mediaTime) { - done = true; - prevDuration = totalDuration; - break; - } else if (totalDuration > (uint32)_parent->editList[_curEdit].mediaTime) { - done = true; - frameNum--; - break; - } + for (int32 i = 0; i < _parent->timeToSampleCount; i++) { + uint32 duration = _parent->timeToSample[i].count * _parent->timeToSample[i].duration; + + if (totalDuration + duration >= mediaTime) { + uint32 frameInc = (mediaTime - totalDuration) / _parent->timeToSample[i].duration; + frameNum += frameInc; + totalDuration += frameInc * _parent->timeToSample[i].duration; - prevDuration = totalDuration; - totalDuration += _parent->timeToSample[i].duration; - frameNum++; + // If we didn't get to the exact media time, mark an override for + // the time. + if (totalDuration != mediaTime) + _durationOverride = totalDuration + _parent->timeToSample[i].duration - mediaTime; + + break; } + + frameNum += _parent->timeToSample[i].count; + totalDuration += duration; } if (bufferFrames) { @@ -656,12 +686,6 @@ void QuickTimeDecoder::VideoTrackHandler::enterNewEditList(bool bufferFrames) { } _nextFrameStartTime = getCurEditTimeOffset(); - - // Set an override for the duration since we came up in-between two frames - if (prevDuration != totalDuration) - _durationOverride = totalDuration - prevDuration; - else - _durationOverride = -1; } const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::bufferNextFrame() { @@ -709,7 +733,12 @@ const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::bufferNextFrame() uint32 QuickTimeDecoder::VideoTrackHandler::getRateAdjustedFrameTime() const { // Figure out what time the next frame is at taking the edit list rate into account - uint32 convertedTime = (Common::Rational(_nextFrameStartTime - getCurEditTimeOffset()) / _parent->editList[_curEdit].mediaRate).toInt(); + Common::Rational offsetFromEdit = Common::Rational(_nextFrameStartTime - getCurEditTimeOffset()) / _parent->editList[_curEdit].mediaRate; + uint32 convertedTime = offsetFromEdit.toInt(); + + if ((offsetFromEdit.getNumerator() % offsetFromEdit.getDenominator()) > (offsetFromEdit.getDenominator() / 2)) + convertedTime++; + return convertedTime + getCurEditTimeOffset(); } @@ -739,4 +768,107 @@ bool QuickTimeDecoder::VideoTrackHandler::atLastEdit() const { return _curEdit == _parent->editCount; } +bool QuickTimeDecoder::VideoTrackHandler::endOfCurEdit() const { + // We're at the end of the edit once the next frame's time would + // bring us past the end of the edit. + return getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration(); +} + +bool QuickTimeDecoder::VideoTrackHandler::canDither() const { + for (uint i = 0; i < _parent->sampleDescs.size(); i++) { + VideoSampleDesc *desc = (VideoSampleDesc *)_parent->sampleDescs[i]; + + if (!desc || !desc->_videoCodec) + return false; + } + + return true; +} + +void QuickTimeDecoder::VideoTrackHandler::setDither(const byte *palette) { + assert(canDither()); + + for (uint i = 0; i < _parent->sampleDescs.size(); i++) { + VideoSampleDesc *desc = (VideoSampleDesc *)_parent->sampleDescs[i]; + + if (desc->_videoCodec->canDither(Image::Codec::kDitherTypeQT)) { + // Codec dither + desc->_videoCodec->setDither(Image::Codec::kDitherTypeQT, palette); + } else { + // Forced dither + _forcedDitherPalette = new byte[256 * 3]; + memcpy(_forcedDitherPalette, palette, 256 * 3); + _ditherTable = Image::Codec::createQuickTimeDitherTable(_forcedDitherPalette, 256); + _dirtyPalette = true; + } + } +} + +namespace { + +// Return a pixel in RGB554 +uint16 makeDitherColor(byte r, byte g, byte b) { + return ((r & 0xF8) << 6) | ((g & 0xF8) << 1) | (b >> 4); +} + +// Default template to convert a dither color +template<typename PixelInt> +inline uint16 readDitherColor(PixelInt srcColor, const Graphics::PixelFormat& format, const byte *palette) { + byte r, g, b; + format.colorToRGB(srcColor, r, g, b); + return makeDitherColor(r, g, b); +} + +// Specialized version for 8bpp +template<> +inline uint16 readDitherColor(byte srcColor, const Graphics::PixelFormat& format, const byte *palette) { + return makeDitherColor(palette[srcColor * 3], palette[srcColor * 3 + 1], palette[srcColor * 3 + 2]); +} + +template<typename PixelInt> +void ditherFrame(const Graphics::Surface &src, Graphics::Surface &dst, const byte *ditherTable, const byte *palette = 0) { + static const uint16 colorTableOffsets[] = { 0x0000, 0xC000, 0x4000, 0x8000 }; + + for (int y = 0; y < dst.h; y++) { + const PixelInt *srcPtr = (const PixelInt *)src.getBasePtr(0, y); + byte *dstPtr = (byte *)dst.getBasePtr(0, y); + uint16 colorTableOffset = colorTableOffsets[y & 3]; + + for (int x = 0; x < dst.w; x++) { + uint16 color = readDitherColor(*srcPtr++, src.format, palette); + *dstPtr++ = ditherTable[colorTableOffset + color]; + colorTableOffset += 0x4000; + } + } +} + +} // End of anonymous namespace + +const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::forceDither(const Graphics::Surface &frame) { + if (frame.format.bytesPerPixel == 1) { + // This should always be true, but this is for sanity + if (!_curPalette) + return &frame; + + // If the palettes match, bail out + if (memcmp(_forcedDitherPalette, _curPalette, 256 * 3) == 0) + return &frame; + } + + // Need to create a new one + if (!_ditherFrame) { + _ditherFrame = new Graphics::Surface(); + _ditherFrame->create(frame.w, frame.h, Graphics::PixelFormat::createFormatCLUT8()); + } + + if (frame.format.bytesPerPixel == 1) + ditherFrame<byte>(frame, *_ditherFrame, _ditherTable, _curPalette); + else if (frame.format.bytesPerPixel == 2) + ditherFrame<uint16>(frame, *_ditherFrame, _ditherTable); + else if (frame.format.bytesPerPixel == 4) + ditherFrame<uint32>(frame, *_ditherFrame, _ditherTable); + + return _ditherFrame; +} + } // End of namespace Video diff --git a/video/qt_decoder.h b/video/qt_decoder.h index 7e87d21ae7..5a6c5eebec 100644 --- a/video/qt_decoder.h +++ b/video/qt_decoder.h @@ -136,10 +136,12 @@ private: int getFrameCount() const; uint32 getNextFrameStartTime() const; const Graphics::Surface *decodeNextFrame(); - const byte *getPalette() const { _dirtyPalette = false; return _curPalette; } + const byte *getPalette() const; bool hasDirtyPalette() const { return _curPalette; } bool setReverse(bool reverse); bool isReversed() const { return _reversed; } + bool canDither() const; + void setDither(const byte *palette); Common::Rational getScaledWidth() const; Common::Rational getScaledHeight() const; @@ -151,12 +153,17 @@ private: int32 _curFrame; uint32 _nextFrameStartTime; Graphics::Surface *_scaledSurface; - bool _holdNextFrameStartTime; int32 _durationOverride; const byte *_curPalette; mutable bool _dirtyPalette; bool _reversed; + // Forced dithering of frames + byte *_forcedDitherPalette; + byte *_ditherTable; + Graphics::Surface *_ditherFrame; + const Graphics::Surface *forceDither(const Graphics::Surface &frame); + Common::SeekableReadStream *getNextFramePacket(uint32 &descId); uint32 getFrameDuration(); uint32 findKeyFrame(uint32 frame) const; @@ -166,6 +173,7 @@ private: uint32 getCurEditTimeOffset() const; uint32 getCurEditTrackDuration() const; bool atLastEdit() const; + bool endOfCurEdit() const; }; }; diff --git a/video/theora_decoder.cpp b/video/theora_decoder.cpp index cb6289bd60..ba596c6032 100644 --- a/video/theora_decoder.cpp +++ b/video/theora_decoder.cpp @@ -360,7 +360,11 @@ static double rint(double v) { } bool TheoraDecoder::VorbisAudioTrack::decodeSamples() { +#ifdef USE_TREMOR + ogg_int32_t **pcm; +#else float **pcm; +#endif // if there's pending, decoded audio, grab it int ret = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm); diff --git a/video/theora_decoder.h b/video/theora_decoder.h index b85388426b..5b683cf6af 100644 --- a/video/theora_decoder.h +++ b/video/theora_decoder.h @@ -33,7 +33,12 @@ #include "graphics/surface.h" #include <theora/theoradec.h> + +#ifdef USE_TREMOR +#include <tremor/ivorbiscodec.h> +#else #include <vorbis/codec.h> +#endif namespace Common { class SeekableReadStream; @@ -50,6 +55,7 @@ namespace Video { * * Decoder for Theora videos. * Video decoder used in engines: + * - pegasus * - sword25 * - wintermute */ diff --git a/video/video_decoder.cpp b/video/video_decoder.cpp index dce96aae03..217b4c8456 100644 --- a/video/video_decoder.cpp +++ b/video/video_decoder.cpp @@ -47,6 +47,7 @@ VideoDecoder::VideoDecoder() { _endTimeSet = false; _nextVideoTrack = 0; _mainAudioTrack = 0; + _canSetDither = true; // Find the best format for output _defaultHighColorFormat = g_system->getScreenFormat(); @@ -77,6 +78,7 @@ void VideoDecoder::close() { _endTimeSet = false; _nextVideoTrack = 0; _mainAudioTrack = 0; + _canSetDither = true; } bool VideoDecoder::loadFile(const Common::String &filename) { @@ -171,6 +173,7 @@ Graphics::PixelFormat VideoDecoder::getPixelFormat() const { const Graphics::Surface *VideoDecoder::decodeNextFrame() { _needsUpdate = false; + _canSetDither = false; readNextPacket(); @@ -488,6 +491,23 @@ bool VideoDecoder::seekIntern(const Audio::Timestamp &time) { return true; } +bool VideoDecoder::setDitheringPalette(const byte *palette) { + // If a frame was already decoded, we can't set it now. + if (!_canSetDither) + return false; + + bool result = false; + + for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) { + if ((*it)->getTrackType() == Track::kTrackTypeVideo && ((VideoTrack *)*it)->canDither()) { + ((VideoTrack *)*it)->setDither(palette); + result = true; + } + } + + return result; +} + VideoDecoder::Track::Track() { _paused = false; } @@ -530,7 +550,9 @@ Audio::Timestamp VideoDecoder::FixedRateVideoTrack::getFrameTime(uint frame) con // (which Audio::Timestamp doesn't support). Common::Rational frameRate = getFrameRate(); - if (frameRate == frameRate.toInt()) // The nice case (a whole number) + // Try to keep it in terms of the frame rate, if the frame rate is a whole + // number. + if (frameRate.getDenominator() == 1) return Audio::Timestamp(0, frame, frameRate.toInt()); // Convert as best as possible diff --git a/video/video_decoder.h b/video/video_decoder.h index c3879e9144..eca15e7265 100644 --- a/video/video_decoder.h +++ b/video/video_decoder.h @@ -377,6 +377,25 @@ public: */ bool setReverse(bool reverse); + /** + * Tell the video to dither to a palette. + * + * By default, VideoDecoder will return surfaces in native, or in the case + * of YUV-based videos, the format set by setDefaultHighColorFormat(). + * For video formats or codecs that support it, this will start outputting + * its surfaces in 8bpp with this palette. + * + * This should be called after loadStream(), but before a decodeNextFrame() + * call. This is enforced. + * + * The palette will be copied, so you do not need to worry about the pointer + * going out-of-scope. + * + * @param palette The palette to use for dithering + * @return true on success, false otherwise + */ + bool setDitheringPalette(const byte *palette); + ///////////////////////////////////////// // Audio Control ///////////////////////////////////////// @@ -604,6 +623,16 @@ protected: * Is the video track set to play in reverse? */ virtual bool isReversed() const { return false; } + + /** + * Can the video track dither? + */ + virtual bool canDither() const { return false; } + + /** + * Activate dithering mode with a palette + */ + virtual void setDither(const byte *palette) {} }; /** @@ -901,6 +930,9 @@ private: mutable bool _dirtyPalette; const byte *_palette; + // Enforcement of not being able to set dither + bool _canSetDither; + // Default PixelFormat settings Graphics::PixelFormat _defaultHighColorFormat; |