aboutsummaryrefslogtreecommitdiff
path: root/video
diff options
context:
space:
mode:
Diffstat (limited to 'video')
-rw-r--r--video/avi_decoder.cpp397
-rw-r--r--video/avi_decoder.h25
-rw-r--r--video/module.mk1
-rw-r--r--video/mpegps_decoder.cpp732
-rw-r--r--video/mpegps_decoder.h189
-rw-r--r--video/qt_decoder.cpp270
-rw-r--r--video/qt_decoder.h12
-rw-r--r--video/theora_decoder.cpp4
-rw-r--r--video/theora_decoder.h6
-rw-r--r--video/video_decoder.cpp24
-rw-r--r--video/video_decoder.h32
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;