aboutsummaryrefslogtreecommitdiff
path: root/video
diff options
context:
space:
mode:
Diffstat (limited to 'video')
-rw-r--r--video/avi_decoder.h4
-rw-r--r--video/bink_decoder.cpp64
-rw-r--r--video/bink_decoder.h15
-rw-r--r--video/codecs/cinepak.h4
-rw-r--r--video/codecs/mjpeg.h1
-rw-r--r--video/coktel_decoder.cpp287
-rw-r--r--video/coktel_decoder.h16
-rw-r--r--video/dxa_decoder.cpp12
-rw-r--r--video/dxa_decoder.h4
-rw-r--r--video/module.mk1
-rw-r--r--video/psx_decoder.cpp697
-rw-r--r--video/psx_decoder.h128
-rw-r--r--video/qt_decoder.cpp806
-rw-r--r--video/qt_decoder.h114
14 files changed, 1619 insertions, 534 deletions
diff --git a/video/avi_decoder.h b/video/avi_decoder.h
index 540a76855d..508760ec89 100644
--- a/video/avi_decoder.h
+++ b/video/avi_decoder.h
@@ -43,10 +43,6 @@ namespace Graphics {
struct PixelFormat;
}
-namespace Graphics {
-struct Surface;
-}
-
namespace Video {
class Codec;
diff --git a/video/bink_decoder.cpp b/video/bink_decoder.cpp
index 46ac8ac386..884ca69f17 100644
--- a/video/bink_decoder.cpp
+++ b/video/bink_decoder.cpp
@@ -113,23 +113,33 @@ BinkDecoder::BinkDecoder() {
}
_audioStream = 0;
- _audioStarted = false;
}
-BinkDecoder::~BinkDecoder() {
- close();
-}
+void BinkDecoder::startAudio() {
+ if (_audioTrack < _audioTracks.size()) {
+ const AudioTrack &audio = _audioTracks[_audioTrack];
-void BinkDecoder::close() {
- reset();
+ _audioStream = Audio::makeQueuingAudioStream(audio.outSampleRate, audio.outChannels == 2);
+ g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_audioHandle, _audioStream);
+ } // else no audio
+}
+void BinkDecoder::stopAudio() {
if (_audioStream) {
- // Stop audio
g_system->getMixer()->stopHandle(_audioHandle);
_audioStream = 0;
}
+}
+
+BinkDecoder::~BinkDecoder() {
+ close();
+}
- _audioStarted = false;
+void BinkDecoder::close() {
+ reset();
+
+ // Stop audio
+ stopAudio();
for (int i = 0; i < 4; i++) {
delete[] _curPlanes[i]; _curPlanes[i] = 0;
@@ -173,7 +183,7 @@ void BinkDecoder::close() {
uint32 BinkDecoder::getElapsedTime() const {
if (_audioStream && g_system->getMixer()->isSoundHandleActive(_audioHandle))
- return g_system->getMixer()->getSoundElapsedTime(_audioHandle);
+ return g_system->getMixer()->getSoundElapsedTime(_audioHandle) + _audioStartOffset;
return g_system->getMillis() - _startTime;
}
@@ -241,11 +251,6 @@ const Graphics::Surface *BinkDecoder::decodeNextFrame() {
if (_curFrame == 0)
_startTime = g_system->getMillis();
- if (!_audioStarted && _audioStream) {
- _audioStarted = true;
- g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_audioHandle, _audioStream);
- }
-
return &_surface;
}
@@ -510,6 +515,11 @@ void BinkDecoder::mergeHuffmanSymbols(VideoFrame &video, byte *dst, const byte *
}
bool BinkDecoder::loadStream(Common::SeekableReadStream *stream) {
+ Graphics::PixelFormat format = g_system->getScreenFormat();
+ return loadStream(stream, format);
+}
+
+bool BinkDecoder::loadStream(Common::SeekableReadStream *stream, const Graphics::PixelFormat &format) {
close();
_id = stream->readUint32BE();
@@ -589,7 +599,6 @@ bool BinkDecoder::loadStream(Common::SeekableReadStream *stream) {
_hasAlpha = _videoFlags & kVideoFlagAlpha;
_swapPlanes = (_id == kBIKhID) || (_id == kBIKiID); // BIKh and BIKi swap the chroma planes
- Graphics::PixelFormat format = g_system->getScreenFormat();
_surface.create(width, height, format);
// Give the planes a bit extra space
@@ -618,11 +627,8 @@ bool BinkDecoder::loadStream(Common::SeekableReadStream *stream) {
initBundles();
initHuffman();
- if (_audioTrack < _audioTracks.size()) {
- const AudioTrack &audio = _audioTracks[_audioTrack];
-
- _audioStream = Audio::makeQueuingAudioStream(audio.outSampleRate, audio.outChannels == 2);
- }
+ startAudio();
+ _audioStartOffset = 0;
return true;
}
@@ -711,15 +717,15 @@ void BinkDecoder::initBundles() {
for (int i = 0; i < 2; i++) {
int width = MAX<uint32>(cw[i], 8);
- _bundles[kSourceBlockTypes ].countLengths[i] = Common::intLog2((width >> 3) + 511) + 1;
- _bundles[kSourceSubBlockTypes].countLengths[i] = Common::intLog2((width >> 4) + 511) + 1;
- _bundles[kSourceColors ].countLengths[i] = Common::intLog2((cbw[i] )*64 + 511) + 1;
- _bundles[kSourceIntraDC ].countLengths[i] = Common::intLog2((width >> 3) + 511) + 1;
- _bundles[kSourceInterDC ].countLengths[i] = Common::intLog2((width >> 3) + 511) + 1;
- _bundles[kSourceXOff ].countLengths[i] = Common::intLog2((width >> 3) + 511) + 1;
- _bundles[kSourceYOff ].countLengths[i] = Common::intLog2((width >> 3) + 511) + 1;
- _bundles[kSourcePattern ].countLengths[i] = Common::intLog2((cbw[i] << 3) + 511) + 1;
- _bundles[kSourceRun ].countLengths[i] = Common::intLog2((cbw[i] )*48 + 511) + 1;
+ _bundles[kSourceBlockTypes ].countLengths[i] = Common::intLog2((width >> 3) + 511) + 1;
+ _bundles[kSourceSubBlockTypes].countLengths[i] = Common::intLog2(((width + 7) >> 4) + 511) + 1;
+ _bundles[kSourceColors ].countLengths[i] = Common::intLog2((cbw[i]) * 64 + 511) + 1;
+ _bundles[kSourceIntraDC ].countLengths[i] = Common::intLog2((width >> 3) + 511) + 1;
+ _bundles[kSourceInterDC ].countLengths[i] = Common::intLog2((width >> 3) + 511) + 1;
+ _bundles[kSourceXOff ].countLengths[i] = Common::intLog2((width >> 3) + 511) + 1;
+ _bundles[kSourceYOff ].countLengths[i] = Common::intLog2((width >> 3) + 511) + 1;
+ _bundles[kSourcePattern ].countLengths[i] = Common::intLog2((cbw[i] << 3) + 511) + 1;
+ _bundles[kSourceRun ].countLengths[i] = Common::intLog2((cbw[i]) * 48 + 511) + 1;
}
}
diff --git a/video/bink_decoder.h b/video/bink_decoder.h
index dd1b7ca67d..3d5e882dd7 100644
--- a/video/bink_decoder.h
+++ b/video/bink_decoder.h
@@ -76,7 +76,9 @@ public:
// FixedRateVideoDecoder
Common::Rational getFrameRate() const { return _frameRate; }
-private:
+ // Bink specific
+ bool loadStream(Common::SeekableReadStream *stream, const Graphics::PixelFormat &format);
+protected:
static const int kAudioChannelsMax = 2;
static const int kAudioBlockSizeMax = (kAudioChannelsMax << 11);
@@ -221,15 +223,13 @@ private:
Audio::SoundHandle _audioHandle;
Audio::QueuingAudioStream *_audioStream;
- bool _audioStarted;
+ int32 _audioStartOffset;
uint32 _videoFlags; ///< Video frame features.
bool _hasAlpha; ///< Do video frames have alpha?
bool _swapPlanes; ///< Are the planes ordered (A)YVU instead of (A)YUV?
- uint32 _audioFrame;
-
Common::Array<AudioTrack> _audioTracks; ///< All audio tracks.
Common::Array<VideoFrame> _frames; ///< All video frames.
@@ -259,7 +259,7 @@ private:
/** Decode an audio packet. */
void audioPacket(AudioTrack &audio);
/** Decode a video packet. */
- void videoPacket(VideoFrame &video);
+ virtual void videoPacket(VideoFrame &video);
/** Decode a plane. */
void decodePlane(VideoFrame &video, int planeIdx, bool isChroma);
@@ -327,6 +327,11 @@ private:
void IDCT(int16 *block);
void IDCTPut(DecodeContext &ctx, int16 *block);
void IDCTAdd(DecodeContext &ctx, int16 *block);
+
+ /** Start playing the audio track */
+ void startAudio();
+ /** Stop playing the audio track */
+ void stopAudio();
};
} // End of namespace Video
diff --git a/video/codecs/cinepak.h b/video/codecs/cinepak.h
index 67000bf58a..ca4552fae6 100644
--- a/video/codecs/cinepak.h
+++ b/video/codecs/cinepak.h
@@ -33,10 +33,6 @@ namespace Common {
class SeekableReadStream;
}
-namespace Graphics {
-struct Surface;
-}
-
namespace Video {
struct CinepakCodebook {
diff --git a/video/codecs/mjpeg.h b/video/codecs/mjpeg.h
index 8a446ee005..45cb57dea2 100644
--- a/video/codecs/mjpeg.h
+++ b/video/codecs/mjpeg.h
@@ -32,7 +32,6 @@ class SeekableReadStream;
namespace Graphics {
class JPEG;
-struct Surface;
}
namespace Video {
diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp
index 8ea2d08acb..ae0c35cd76 100644
--- a/video/coktel_decoder.cpp
+++ b/video/coktel_decoder.cpp
@@ -1505,23 +1505,6 @@ VMDDecoder::Frame::~Frame() {
delete[] parts;
}
-
-const uint16 VMDDecoder::_tableDPCM[128] = {
- 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080,
- 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120,
- 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0,
- 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230,
- 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280,
- 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0,
- 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320,
- 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370,
- 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0,
- 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480,
- 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700,
- 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00,
- 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
-};
-
VMDDecoder::VMDDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) : CoktelDecoder(mixer, soundType),
_stream(0), _version(0), _flags(0), _frameInfoOffset(0), _partsPerFrame(0), _frames(0),
_soundFlags(0), _soundFreq(0), _soundSliceSize(0), _soundSlicesCount(0),
@@ -2402,33 +2385,36 @@ void VMDDecoder::blit24(const Graphics::Surface &srcSurf, Common::Rect &rect) {
}
void VMDDecoder::emptySoundSlice(uint32 size) {
- byte *sound = soundEmpty(size);
+ byte *soundBuf = (byte *)malloc(size);
- if (sound) {
+ if (soundBuf) {
uint32 flags = 0;
+ memset(soundBuf, 0, size);
flags |= (_soundBytesPerSample == 2) ? Audio::FLAG_16BITS : 0;
flags |= (_soundStereo > 0) ? Audio::FLAG_STEREO : 0;
- _audioStream->queueBuffer(sound, size, DisposeAfterUse::YES, flags);
+ _audioStream->queueBuffer(soundBuf, size, DisposeAfterUse::YES, flags);
}
}
void VMDDecoder::filledSoundSlice(uint32 size) {
- byte *sound = 0;
+ if (!_audioStream) {
+ _stream->skip(size);
+ return;
+ }
+
+ Common::SeekableReadStream *data = _stream->readStream(size);
+ Audio::AudioStream *sliceStream = 0;
+
if (_audioFormat == kAudioFormat8bitRaw)
- sound = sound8bitRaw(size);
+ sliceStream = create8bitRaw(data);
else if (_audioFormat == kAudioFormat16bitDPCM)
- sound = sound16bitDPCM(size);
+ sliceStream = create16bitDPCM(data);
else if (_audioFormat == kAudioFormat16bitADPCM)
- sound = sound16bitADPCM(size);
+ sliceStream = create16bitADPCM(data);
- if (sound) {
- uint32 flags = 0;
- flags |= (_soundBytesPerSample == 2) ? Audio::FLAG_16BITS : 0;
- flags |= (_soundStereo > 0) ? Audio::FLAG_STEREO : 0;
-
- _audioStream->queueBuffer(sound, size, DisposeAfterUse::YES, flags);
- }
+ if (sliceStream)
+ _audioStream->queueAudioStream(sliceStream);
}
void VMDDecoder::filledSoundSlices(uint32 size, uint32 mask) {
@@ -2475,173 +2461,120 @@ uint8 VMDDecoder::evaluateMask(uint32 mask, bool *fillInfo, uint8 &max) {
return n;
}
-byte *VMDDecoder::soundEmpty(uint32 &size) {
- if (!_audioStream)
- return 0;
+Audio::AudioStream *VMDDecoder::create8bitRaw(Common::SeekableReadStream *stream) {
+ int flags = Audio::FLAG_UNSIGNED;
- byte *soundBuf = (byte *)malloc(size);
- memset(soundBuf, 0, size);
+ if (_soundStereo != 0)
+ flags |= Audio::FLAG_STEREO;
- return soundBuf;
+ return Audio::makeRawStream(stream, _soundFreq, flags, DisposeAfterUse::YES);
}
-byte *VMDDecoder::sound8bitRaw(uint32 &size) {
- if (!_audioStream) {
- _stream->skip(size);
- return 0;
+class DPCMStream : public Audio::AudioStream {
+public:
+ DPCMStream(Common::SeekableReadStream *stream, int rate, int channels) {
+ _stream = stream;
+ _rate = rate;
+ _channels = channels;
}
- byte *soundBuf = (byte *)malloc(size);
- _stream->read(soundBuf, size);
- unsignedToSigned(soundBuf, size);
-
- return soundBuf;
-}
-
-byte *VMDDecoder::sound16bitDPCM(uint32 &size) {
- if (!_audioStream) {
- _stream->skip(size);
- return 0;
+ ~DPCMStream() {
+ delete _stream;
}
- int32 init[2];
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return _channels == 2; }
+ int getRate() const { return _rate; }
+ bool endOfData() const { return _stream->pos() >= _stream->size() || _stream->eos() || _stream->err(); }
- init[0] = _stream->readSint16LE();
- size -= 2;
-
- if (_soundStereo > 0) {
- init[1] = _stream->readSint16LE();
- size -= 2;
- }
-
- byte *data = new byte[size];
- byte *sound = 0;
-
- if (_stream->read(data, size) == size)
- sound = deDPCM(data, size, init);
-
- delete[] data;
+private:
+ Common::SeekableReadStream *_stream;
+ int _channels;
+ int _rate;
+ int _buffer[2];
+};
- return sound;
-}
+int DPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ static const uint16 tableDPCM[128] = {
+ 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080,
+ 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120,
+ 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0,
+ 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230,
+ 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280,
+ 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0,
+ 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320,
+ 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370,
+ 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0,
+ 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480,
+ 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700,
+ 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00,
+ 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
+ };
+
+ assert((numSamples % _channels) == 0);
+
+ int samples = 0;
+
+ // Our starting position
+ if (_stream->pos() == 0) {
+ for (int i = 0; i < _channels; i++)
+ *buffer++ = _buffer[i] = _stream->readSint16LE();
+
+ samples += _channels;
+ }
+
+ while (!endOfData() && samples < numSamples) {
+ for (int i = 0; i < _channels; i++) {
+ byte data = _stream->readByte();
+
+ if (data & 0x80)
+ _buffer[i] -= tableDPCM[data & 0x7f];
+ else
+ _buffer[i] += tableDPCM[data];
+
+ *buffer++ = _buffer[i] = CLIP<int32>(_buffer[i], -32768, 32767);
+ }
-byte *VMDDecoder::sound16bitADPCM(uint32 &size) {
- if (!_audioStream) {
- _stream->skip(size);
- return 0;
+ samples += _channels;
}
- int32 init = _stream->readSint16LE();
- size -= 2;
-
- int32 index = _stream->readByte();
- size--;
-
- byte *data = new byte[size];
- byte *sound = 0;
-
- if (_stream->read(data, size) == size)
- sound = deADPCM(data, size, init, index);
-
- delete[] data;
-
- return sound;
+ return samples;
}
-byte *VMDDecoder::deDPCM(const byte *data, uint32 &size, int32 init[2]) {
- if (!data || (size == 0))
- return 0;
-
- int channels = (_soundStereo > 0) ? 2 : 1;
-
- uint32 inSize = size;
- uint32 outSize = size + channels;
-
- int16 *out = (int16 *)malloc(outSize * 2);
- byte *sound = (byte *)out;
-
- if (!out)
- return 0;
-
- int channel = 0;
-
- for (int i = 0; i < channels; i++) {
- *out++ = TO_BE_16(init[channel]);
+Audio::AudioStream *VMDDecoder::create16bitDPCM(Common::SeekableReadStream *stream) {
+ return new DPCMStream(stream, _soundFreq, (_soundStereo == 0) ? 1 : 2);
+}
- channel = (channel + 1) % channels;
+class VMD_ADPCMStream : public Audio::DVI_ADPCMStream {
+public:
+ VMD_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse,
+ int rate, int channels) : Audio::DVI_ADPCMStream(stream, disposeAfterUse, stream->size(), rate, channels, 0) {
+ // FIXME: Using the same predictor/index for two channels probably won't work
+ // properly However, we have no samples of this, so an assert is here for now.
+ // Also, since the DPCM stereo has a second predictor, I'm lead to believe
+ // all VMD with ADPCM are mono unless they changed the code in a later
+ // revision.
+ assert(channels == 1);
+ _startPredictorValue = stream->readSint16LE();
+ _startIndexValue = stream->readByte();
+ _startpos = 3;
+ reset();
}
- while (inSize-- > 0) {
- if (*data & 0x80)
- init[channel] -= _tableDPCM[*data++ & 0x7F];
- else
- init[channel] += _tableDPCM[*data++];
-
- init[channel] = CLIP<int32>(init[channel], -32768, 32767);
- *out++ = TO_BE_16(init[channel]);
-
- channel = (channel + 1) % channels;
+protected:
+ virtual void reset() {
+ Audio::DVI_ADPCMStream::reset();
+ _status.ima_ch[0].last = _startPredictorValue;
+ _status.ima_ch[0].stepIndex = _startIndexValue;
}
- size = outSize * 2;
- return sound;
-}
-
-// Yet another IMA ADPCM variant
-byte *VMDDecoder::deADPCM(const byte *data, uint32 &size, int32 init, int32 index) {
- if (!data || (size == 0))
- return 0;
-
- uint32 outSize = size * 2;
-
- int16 *out = (int16 *)malloc(outSize * 2);
- byte *sound = (byte *) out;
-
- index = CLIP<int32>(index, 0, 88);
-
- int32 predictor = Audio::Ima_ADPCMStream::_imaTable[index];
-
- uint32 dataByte = 0;
- bool newByte = true;
-
- size *= 2;
- while (size -- > 0) {
- byte code = 0;
-
- if (newByte) {
- dataByte = *data++;
- code = (dataByte >> 4) & 0xF;
- } else
- code = dataByte & 0xF;
-
- newByte = !newByte;
-
- index += Audio::ADPCMStream::_stepAdjustTable[code];
- index = CLIP<int32>(index, 0, 88);
-
- int32 value = predictor / 8;
-
- if (code & 4)
- value += predictor;
- if (code & 2)
- value += predictor / 2;
- if (code & 1)
- value += predictor / 4;
-
- if (code & 8)
- init -= value;
- else
- init += value;
-
- init = CLIP<int32>(init, -32768, 32767);
-
- predictor = Audio::Ima_ADPCMStream::_imaTable[index];
-
- *out++ = TO_BE_16(init);
- }
+private:
+ int32 _startPredictorValue;
+ int32 _startIndexValue;
+};
- size = outSize * 2;
- return sound;
+Audio::AudioStream *VMDDecoder::create16bitADPCM(Common::SeekableReadStream *stream) {
+ return new VMD_ADPCMStream(stream, DisposeAfterUse::YES, _soundFreq, (_soundStereo == 0) ? 1 : 2);
}
Graphics::PixelFormat VMDDecoder::getPixelFormat() const {
diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h
index 8ad1456037..b99a44f332 100644
--- a/video/coktel_decoder.h
+++ b/video/coktel_decoder.h
@@ -437,9 +437,6 @@ private:
~Frame();
};
- // Tables for the audio decompressors
- static const uint16 _tableDPCM[128];
-
Common::SeekableReadStream *_stream;
byte _version;
@@ -508,15 +505,10 @@ private:
uint8 evaluateMask(uint32 mask, bool *fillInfo, uint8 &max);
- // Generating sound slices
- byte *soundEmpty (uint32 &size);
- byte *sound8bitRaw (uint32 &size);
- byte *sound16bitDPCM (uint32 &size);
- byte *sound16bitADPCM(uint32 &size);
-
- // Sound decompression
- byte *deDPCM (const byte *data, uint32 &size, int32 init[2]);
- byte *deADPCM(const byte *data, uint32 &size, int32 init, int32 index);
+ // Generating audio streams
+ Audio::AudioStream *create8bitRaw (Common::SeekableReadStream *stream);
+ Audio::AudioStream *create16bitDPCM (Common::SeekableReadStream *stream);
+ Audio::AudioStream *create16bitADPCM(Common::SeekableReadStream *stream);
bool getPartCoords(int16 frame, PartType type, int16 &x, int16 &y, int16 &width, int16 &height);
};
diff --git a/video/dxa_decoder.cpp b/video/dxa_decoder.cpp
index 561719a27d..7d1112a59c 100644
--- a/video/dxa_decoder.cpp
+++ b/video/dxa_decoder.cpp
@@ -115,9 +115,9 @@ bool DXADecoder::loadStream(Common::SeekableReadStream *stream) {
_scaledBuffer = 0;
if (_scaleMode != S_NONE) {
_scaledBuffer = (uint8 *)malloc(_frameSize);
- memset(_scaledBuffer, 0, _frameSize);
if (!_scaledBuffer)
error("Error allocating scale buffer (size %u)", _frameSize);
+ memset(_scaledBuffer, 0, _frameSize);
}
#ifdef DXA_EXPERIMENT_MAXD
@@ -318,7 +318,7 @@ void DXADecoder::decode13(int size) {
for (uint32 by = 0; by < _curHeight; by += BLOCKH) {
for (uint32 bx = 0; bx < _width; bx += BLOCKW) {
uint8 type = *codeBuf++;
- uint8 *b2 = (uint8*)_frameBuffer1 + bx + by * _width;
+ uint8 *b2 = (uint8 *)_frameBuffer1 + bx + by * _width;
switch (type) {
case 0:
@@ -369,7 +369,7 @@ void DXADecoder::decode13(int size) {
if (mbyte & 0x08)
my = -my;
- uint8 *b1 = (uint8*)_frameBuffer2 + (bx+mx) + (by+my) * _width;
+ uint8 *b1 = (uint8 *)_frameBuffer2 + (bx+mx) + (by+my) * _width;
for (int yc = 0; yc < BLOCKH; yc++) {
memcpy(b2, b1, BLOCKW);
b1 += _width;
@@ -385,7 +385,7 @@ void DXADecoder::decode13(int size) {
for (int subBlock = 0; subBlock < 4; subBlock++) {
int sx = bx + subX[subBlock], sy = by + subY[subBlock];
- b2 = (uint8*)_frameBuffer1 + sx + sy * _width;
+ b2 = (uint8 *)_frameBuffer1 + sx + sy * _width;
switch (subMask & 0xC0) {
// 00: skip
case 0x00:
@@ -413,7 +413,7 @@ void DXADecoder::decode13(int size) {
if (mbyte & 0x08)
my = -my;
- uint8 *b1 = (uint8*)_frameBuffer2 + (sx+mx) + (sy+my) * _width;
+ uint8 *b1 = (uint8 *)_frameBuffer2 + (sx+mx) + (sy+my) * _width;
for (int yc = 0; yc < BLOCKH / 2; yc++) {
memcpy(b2, b1, BLOCKW / 2);
b1 += _width;
@@ -489,9 +489,9 @@ const Graphics::Surface *DXADecoder::decodeNextFrame() {
if ((_inBuffer == NULL) || (_inBufferSize < size)) {
free(_inBuffer);
_inBuffer = (byte *)malloc(size);
- memset(_inBuffer, 0, size);
if (_inBuffer == NULL)
error("Error allocating input buffer (size %u)", size);
+ memset(_inBuffer, 0, size);
_inBufferSize = size;
}
diff --git a/video/dxa_decoder.h b/video/dxa_decoder.h
index 4eb4a8958d..d13cd3076c 100644
--- a/video/dxa_decoder.h
+++ b/video/dxa_decoder.h
@@ -31,10 +31,6 @@ namespace Common {
class SeekableReadStream;
}
-namespace Graphics {
-struct Surface;
-}
-
namespace Video {
/**
diff --git a/video/module.mk b/video/module.mk
index ceeac94384..900a781d0a 100644
--- a/video/module.mk
+++ b/video/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS := \
coktel_decoder.o \
dxa_decoder.o \
flic_decoder.o \
+ psx_decoder.o \
qt_decoder.o \
smk_decoder.o \
video_decoder.o \
diff --git a/video/psx_decoder.cpp b/video/psx_decoder.cpp
new file mode 100644
index 0000000000..7c04b7f041
--- /dev/null
+++ b/video/psx_decoder.cpp
@@ -0,0 +1,697 @@
+/* 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.
+ *
+ */
+
+// PlayStation Stream demuxer and XA audio decoder based on FFmpeg/libav
+// MDEC video emulation based on http://kenai.com/downloads/jpsxdec/Old/PlayStation1_STR_format1-00.txt
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/decoders/raw.h"
+#include "common/bitstream.h"
+#include "common/huffman.h"
+#include "common/memstream.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "graphics/yuv_to_rgb.h"
+
+#include "video/psx_decoder.h"
+
+namespace Video {
+
+// Here are the codes/lengths/symbols that are used for decoding
+// DC coefficients (version 3 frames only)
+
+#define DC_CODE_COUNT 9
+#define DC_HUFF_VAL(b, n, p) (((b) << 16) | ((n) << 8) | (p))
+#define GET_DC_BITS(x) ((x) >> 16)
+#define GET_DC_NEG(x) ((int)(((x) >> 8) & 0xff))
+#define GET_DC_POS(x) ((int)((x) & 0xff))
+
+static const uint32 s_huffmanDCChromaCodes[DC_CODE_COUNT] = {
+ 254, 126, 62, 30, 14, 6, 2, 1, 0
+};
+
+static const byte s_huffmanDCChromaLengths[DC_CODE_COUNT] = {
+ 8, 7, 6, 5, 4, 3, 2, 2, 2
+};
+
+static const uint32 s_huffmanDCLumaCodes[DC_CODE_COUNT] = {
+ 126, 62, 30, 14, 6, 5, 1, 0, 4
+};
+
+static const byte s_huffmanDCLumaLengths[DC_CODE_COUNT] = {
+ 7, 6, 5, 4, 3, 3, 2, 2, 3
+};
+
+static const uint32 s_huffmanDCSymbols[DC_CODE_COUNT] = {
+ DC_HUFF_VAL(8, 255, 128), DC_HUFF_VAL(7, 127, 64), DC_HUFF_VAL(6, 63, 32),
+ DC_HUFF_VAL(5, 31, 16), DC_HUFF_VAL(4, 15, 8), DC_HUFF_VAL(3, 7, 4),
+ DC_HUFF_VAL(2, 3, 2), DC_HUFF_VAL(1, 1, 1), DC_HUFF_VAL(0, 0, 0)
+};
+
+// Here are the codes/lengths/symbols that are used for decoding
+// DC coefficients (version 2 and 3 frames)
+
+#define AC_CODE_COUNT 113
+#define AC_HUFF_VAL(z, a) ((z << 8) | a)
+#define ESCAPE_CODE ((uint32)-1) // arbitrary, just so we can tell what code it is
+#define END_OF_BLOCK ((uint32)-2) // arbitrary, just so we can tell what code it is
+#define GET_AC_ZERO_RUN(code) (code >> 8)
+#define GET_AC_COEFFICIENT(code) ((int)(code & 0xff))
+
+static const uint32 s_huffmanACCodes[AC_CODE_COUNT] = {
+ // Regular codes
+ 3, 3, 4, 5, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7,
+ 32, 33, 34, 35, 36, 37, 38, 39, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
+ 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31,
+
+ // Escape code
+ 1,
+ // End of block code
+ 2
+};
+
+static const byte s_huffmanACLengths[AC_CODE_COUNT] = {
+ // Regular codes
+ 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+ 8, 8, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10,
+ 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16,
+
+ // Escape code
+ 6,
+ // End of block code
+ 2
+};
+
+static const uint32 s_huffmanACSymbols[AC_CODE_COUNT] = {
+ // Regular codes
+ AC_HUFF_VAL(0, 1), AC_HUFF_VAL(1, 1), AC_HUFF_VAL(0, 2), AC_HUFF_VAL(2, 1), AC_HUFF_VAL(0, 3),
+ AC_HUFF_VAL(4, 1), AC_HUFF_VAL(3, 1), AC_HUFF_VAL(7, 1), AC_HUFF_VAL(6, 1), AC_HUFF_VAL(1, 2),
+ AC_HUFF_VAL(5, 1), AC_HUFF_VAL(2, 2), AC_HUFF_VAL(9, 1), AC_HUFF_VAL(0, 4), AC_HUFF_VAL(8, 1),
+ AC_HUFF_VAL(13, 1), AC_HUFF_VAL(0, 6), AC_HUFF_VAL(12, 1), AC_HUFF_VAL(11, 1), AC_HUFF_VAL(3, 2),
+ AC_HUFF_VAL(1, 3), AC_HUFF_VAL(0, 5), AC_HUFF_VAL(10, 1), AC_HUFF_VAL(16, 1), AC_HUFF_VAL(5, 2),
+ AC_HUFF_VAL(0, 7), AC_HUFF_VAL(2, 3), AC_HUFF_VAL(1, 4), AC_HUFF_VAL(15, 1), AC_HUFF_VAL(14, 1),
+ AC_HUFF_VAL(4, 2), AC_HUFF_VAL(0, 11), AC_HUFF_VAL(8, 2), AC_HUFF_VAL(4, 3), AC_HUFF_VAL(0, 10),
+ AC_HUFF_VAL(2, 4), AC_HUFF_VAL(7, 2), AC_HUFF_VAL(21, 1), AC_HUFF_VAL(20, 1), AC_HUFF_VAL(0, 9),
+ AC_HUFF_VAL(19, 1), AC_HUFF_VAL(18, 1), AC_HUFF_VAL(1, 5), AC_HUFF_VAL(3, 3), AC_HUFF_VAL(0, 8),
+ AC_HUFF_VAL(6, 2), AC_HUFF_VAL(17, 1), AC_HUFF_VAL(10, 2), AC_HUFF_VAL(9, 2), AC_HUFF_VAL(5, 3),
+ AC_HUFF_VAL(3, 4), AC_HUFF_VAL(2, 5), AC_HUFF_VAL(1, 7), AC_HUFF_VAL(1, 6), AC_HUFF_VAL(0, 15),
+ AC_HUFF_VAL(0, 14), AC_HUFF_VAL(0, 13), AC_HUFF_VAL(0, 12), AC_HUFF_VAL(26, 1), AC_HUFF_VAL(25, 1),
+ AC_HUFF_VAL(24, 1), AC_HUFF_VAL(23, 1), AC_HUFF_VAL(22, 1), AC_HUFF_VAL(0, 31), AC_HUFF_VAL(0, 30),
+ AC_HUFF_VAL(0, 29), AC_HUFF_VAL(0, 28), AC_HUFF_VAL(0, 27), AC_HUFF_VAL(0, 26), AC_HUFF_VAL(0, 25),
+ AC_HUFF_VAL(0, 24), AC_HUFF_VAL(0, 23), AC_HUFF_VAL(0, 22), AC_HUFF_VAL(0, 21), AC_HUFF_VAL(0, 20),
+ AC_HUFF_VAL(0, 19), AC_HUFF_VAL(0, 18), AC_HUFF_VAL(0, 17), AC_HUFF_VAL(0, 16), AC_HUFF_VAL(0, 40),
+ AC_HUFF_VAL(0, 39), AC_HUFF_VAL(0, 38), AC_HUFF_VAL(0, 37), AC_HUFF_VAL(0, 36), AC_HUFF_VAL(0, 35),
+ AC_HUFF_VAL(0, 34), AC_HUFF_VAL(0, 33), AC_HUFF_VAL(0, 32), AC_HUFF_VAL(1, 14), AC_HUFF_VAL(1, 13),
+ AC_HUFF_VAL(1, 12), AC_HUFF_VAL(1, 11), AC_HUFF_VAL(1, 10), AC_HUFF_VAL(1, 9), AC_HUFF_VAL(1, 8),
+ AC_HUFF_VAL(1, 18), AC_HUFF_VAL(1, 17), AC_HUFF_VAL(1, 16), AC_HUFF_VAL(1, 15), AC_HUFF_VAL(6, 3),
+ AC_HUFF_VAL(16, 2), AC_HUFF_VAL(15, 2), AC_HUFF_VAL(14, 2), AC_HUFF_VAL(13, 2), AC_HUFF_VAL(12, 2),
+ AC_HUFF_VAL(11, 2), AC_HUFF_VAL(31, 1), AC_HUFF_VAL(30, 1), AC_HUFF_VAL(29, 1), AC_HUFF_VAL(28, 1),
+ AC_HUFF_VAL(27, 1),
+
+ // Escape code
+ ESCAPE_CODE,
+ // End of block code
+ END_OF_BLOCK
+};
+
+PSXStreamDecoder::PSXStreamDecoder(CDSpeed speed, uint32 frameCount) : _nextFrameStartTime(0, speed), _frameCount(frameCount) {
+ _stream = 0;
+ _audStream = 0;
+ _surface = new Graphics::Surface();
+ _yBuffer = _cbBuffer = _crBuffer = 0;
+ _acHuffman = new Common::Huffman(0, AC_CODE_COUNT, s_huffmanACCodes, s_huffmanACLengths, s_huffmanACSymbols);
+ _dcHuffmanChroma = new Common::Huffman(0, DC_CODE_COUNT, s_huffmanDCChromaCodes, s_huffmanDCChromaLengths, s_huffmanDCSymbols);
+ _dcHuffmanLuma = new Common::Huffman(0, DC_CODE_COUNT, s_huffmanDCLumaCodes, s_huffmanDCLumaLengths, s_huffmanDCSymbols);
+}
+
+PSXStreamDecoder::~PSXStreamDecoder() {
+ close();
+ delete _surface;
+ delete _acHuffman;
+ delete _dcHuffmanLuma;
+ delete _dcHuffmanChroma;
+}
+
+#define RAW_CD_SECTOR_SIZE 2352
+
+#define CDXA_TYPE_MASK 0x0E
+#define CDXA_TYPE_DATA 0x08
+#define CDXA_TYPE_AUDIO 0x04
+#define CDXA_TYPE_VIDEO 0x02
+
+bool PSXStreamDecoder::loadStream(Common::SeekableReadStream *stream) {
+ close();
+
+ _stream = stream;
+
+ Common::SeekableReadStream *sector = readSector();
+
+ if (!sector) {
+ close();
+ return false;
+ }
+
+ // Rip out video info from the first frame
+ sector->seek(18);
+ byte sectorType = sector->readByte() & CDXA_TYPE_MASK;
+
+ if (sectorType != CDXA_TYPE_VIDEO && sectorType != CDXA_TYPE_DATA) {
+ close();
+ return false;
+ }
+
+ sector->seek(40);
+
+ uint16 width = sector->readUint16LE();
+ uint16 height = sector->readUint16LE();
+ _surface->create(width, height, g_system->getScreenFormat());
+
+ _macroBlocksW = (width + 15) / 16;
+ _macroBlocksH = (height + 15) / 16;
+ _yBuffer = new byte[_macroBlocksW * _macroBlocksH * 16 * 16];
+ _cbBuffer = new byte[_macroBlocksW * _macroBlocksH * 8 * 8];
+ _crBuffer = new byte[_macroBlocksW * _macroBlocksH * 8 * 8];
+
+ delete sector;
+ _stream->seek(0);
+
+ return true;
+}
+
+void PSXStreamDecoder::close() {
+ if (!_stream)
+ return;
+
+ delete _stream;
+ _stream = 0;
+
+ // Deinitialize sound
+ g_system->getMixer()->stopHandle(_audHandle);
+ _audStream = 0;
+
+ _surface->free();
+
+ memset(&_adpcmStatus, 0, sizeof(_adpcmStatus));
+
+ _macroBlocksW = _macroBlocksH = 0;
+ delete[] _yBuffer; _yBuffer = 0;
+ delete[] _cbBuffer; _cbBuffer = 0;
+ delete[] _crBuffer; _crBuffer = 0;
+
+ reset();
+}
+
+uint32 PSXStreamDecoder::getElapsedTime() const {
+ // TODO: Currently, the audio is always after the video so using this
+ // can often lead to gaps in the audio...
+ //if (_audStream)
+ // return _mixer->getSoundElapsedTime(_audHandle);
+
+ return VideoDecoder::getElapsedTime();
+}
+
+uint32 PSXStreamDecoder::getTimeToNextFrame() const {
+ if (!isVideoLoaded() || endOfVideo())
+ return 0;
+
+ uint32 nextTimeMillis = _nextFrameStartTime.msecs();
+ uint32 elapsedTime = getElapsedTime();
+
+ if (elapsedTime > nextTimeMillis)
+ return 0;
+
+ return nextTimeMillis - elapsedTime;
+}
+
+#define VIDEO_DATA_CHUNK_SIZE 2016
+#define VIDEO_DATA_HEADER_SIZE 56
+
+const Graphics::Surface *PSXStreamDecoder::decodeNextFrame() {
+ Common::SeekableReadStream *sector = 0;
+ byte *partialFrame = 0;
+ int sectorsRead = 0;
+
+ while (!endOfVideo()) {
+ sector = readSector();
+ sectorsRead++;
+
+ if (!sector)
+ error("Corrupt PSX stream sector");
+
+ sector->seek(0x11);
+ byte track = sector->readByte();
+ if (track >= 32)
+ error("Bad PSX stream track");
+
+ byte sectorType = sector->readByte() & CDXA_TYPE_MASK;
+
+ switch (sectorType) {
+ case CDXA_TYPE_DATA:
+ case CDXA_TYPE_VIDEO:
+ if (track == 1) {
+ sector->seek(28);
+ uint16 curSector = sector->readUint16LE();
+ uint16 sectorCount = sector->readUint16LE();
+ sector->readUint32LE();
+ uint16 frameSize = sector->readUint32LE();
+
+ if (curSector >= sectorCount)
+ error("Bad sector");
+
+ if (!partialFrame)
+ partialFrame = (byte *)malloc(sectorCount * VIDEO_DATA_CHUNK_SIZE);
+
+ sector->seek(VIDEO_DATA_HEADER_SIZE);
+ sector->read(partialFrame + curSector * VIDEO_DATA_CHUNK_SIZE, VIDEO_DATA_CHUNK_SIZE);
+
+ if (curSector == sectorCount - 1) {
+ // Done assembling the frame
+ Common::SeekableReadStream *frame = new Common::MemoryReadStream(partialFrame, frameSize, DisposeAfterUse::YES);
+
+ decodeFrame(frame);
+
+ delete frame;
+ delete sector;
+
+ _curFrame++;
+ if (_curFrame == 0)
+ _startTime = g_system->getMillis();
+
+ // Increase the time by the amount of sectors we read
+ // One may notice that this is still not the most precise
+ // method since a frame takes up the time its sectors took
+ // up instead of the amount of time it takes the next frame
+ // to be read from the sectors. The actual frame rate should
+ // be constant instead of variable, so the slight difference
+ // in a frame's showing time is negligible (1/150 of a second).
+ _nextFrameStartTime = _nextFrameStartTime.addFrames(sectorsRead);
+
+ return _surface;
+ }
+ } else
+ error("Unhandled multi-track video");
+ break;
+ case CDXA_TYPE_AUDIO:
+ // We only handle one audio channel so far
+ if (track == 1)
+ queueAudioFromSector(sector);
+ else
+ warning("Unhandled multi-track audio");
+ break;
+ default:
+ // This shows up way too often, but the other sectors
+ // are safe to ignore
+ //warning("Unknown PSX sector type 0x%x", sectorType);
+ break;
+ }
+
+ delete sector;
+ }
+
+ return 0;
+}
+
+static const byte s_syncHeader[12] = { 0x00, 0xff ,0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
+
+Common::SeekableReadStream *PSXStreamDecoder::readSector() {
+ assert(_stream);
+
+ Common::SeekableReadStream *stream = _stream->readStream(RAW_CD_SECTOR_SIZE);
+
+ byte syncHeader[12];
+ stream->read(syncHeader, 12);
+ if (!memcmp(s_syncHeader, syncHeader, 12))
+ return stream;
+
+ return 0;
+}
+
+// Ha! It's palindromic!
+#define AUDIO_DATA_CHUNK_SIZE 2304
+#define AUDIO_DATA_SAMPLE_COUNT 4032
+
+static const int s_xaTable[5][2] = {
+ { 0, 0 },
+ { 60, 0 },
+ { 115, -52 },
+ { 98, -55 },
+ { 122, -60 }
+};
+
+void PSXStreamDecoder::queueAudioFromSector(Common::SeekableReadStream *sector) {
+ assert(sector);
+
+ if (!_audStream) {
+ // Initialize audio stream
+ sector->seek(19);
+ byte format = sector->readByte();
+
+ bool stereo = (format & (1 << 0)) != 0;
+ uint rate = (format & (1 << 2)) ? 18900 : 37800;
+
+ _audStream = Audio::makeQueuingAudioStream(rate, stereo);
+ g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_audHandle, _audStream);
+ }
+
+ sector->seek(24);
+
+ // This XA audio is different (yet similar) from normal XA audio! Watch out!
+ // TODO: It's probably similar enough to normal XA that we can merge it somehow...
+ // TODO: RTZ PSX needs the same audio code in a regular AudioStream class. Probably
+ // will do something similar to QuickTime and creating a base class 'ISOMode2Parser'
+ // or something similar.
+ byte *buf = new byte[AUDIO_DATA_CHUNK_SIZE];
+ sector->read(buf, AUDIO_DATA_CHUNK_SIZE);
+
+ int channels = _audStream->isStereo() ? 2 : 1;
+ int16 *dst = new int16[AUDIO_DATA_SAMPLE_COUNT];
+ int16 *leftChannel = dst;
+ int16 *rightChannel = dst + 1;
+
+ for (byte *src = buf; src < buf + AUDIO_DATA_CHUNK_SIZE; src += 128) {
+ for (int i = 0; i < 4; i++) {
+ int shift = 12 - (src[4 + i * 2] & 0xf);
+ int filter = src[4 + i * 2] >> 4;
+ int f0 = s_xaTable[filter][0];
+ int f1 = s_xaTable[filter][1];
+ int16 s_1 = _adpcmStatus[0].sample[0];
+ int16 s_2 = _adpcmStatus[0].sample[1];
+
+ for (int j = 0; j < 28; j++) {
+ byte d = src[16 + i + j * 4];
+ int t = (int8)(d << 4) >> 4;
+ int s = (t << shift) + ((s_1 * f0 + s_2 * f1 + 32) >> 6);
+ s_2 = s_1;
+ s_1 = CLIP<int>(s, -32768, 32767);
+ *leftChannel = s_1;
+ leftChannel += channels;
+ }
+
+ if (channels == 2) {
+ _adpcmStatus[0].sample[0] = s_1;
+ _adpcmStatus[0].sample[1] = s_2;
+ s_1 = _adpcmStatus[1].sample[0];
+ s_2 = _adpcmStatus[1].sample[1];
+ }
+
+ shift = 12 - (src[5 + i * 2] & 0xf);
+ filter = src[5 + i * 2] >> 4;
+ f0 = s_xaTable[filter][0];
+ f1 = s_xaTable[filter][1];
+
+ for (int j = 0; j < 28; j++) {
+ byte d = src[16 + i + j * 4];
+ int t = (int8)d >> 4;
+ int s = (t << shift) + ((s_1 * f0 + s_2 * f1 + 32) >> 6);
+ s_2 = s_1;
+ s_1 = CLIP<int>(s, -32768, 32767);
+
+ if (channels == 2) {
+ *rightChannel = s_1;
+ rightChannel += 2;
+ } else {
+ *leftChannel++ = s_1;
+ }
+ }
+
+ if (channels == 2) {
+ _adpcmStatus[1].sample[0] = s_1;
+ _adpcmStatus[1].sample[1] = s_2;
+ } else {
+ _adpcmStatus[0].sample[0] = s_1;
+ _adpcmStatus[0].sample[1] = s_2;
+ }
+ }
+ }
+
+ int flags = Audio::FLAG_16BITS;
+
+ if (_audStream->isStereo())
+ flags |= Audio::FLAG_STEREO;
+
+#ifdef SCUMM_LITTLE_ENDIAN
+ flags |= Audio::FLAG_LITTLE_ENDIAN;
+#endif
+
+ _audStream->queueBuffer((byte *)dst, AUDIO_DATA_SAMPLE_COUNT * 2, DisposeAfterUse::YES, flags);
+ delete[] buf;
+}
+
+void PSXStreamDecoder::decodeFrame(Common::SeekableReadStream *frame) {
+ // A frame is essentially an MPEG-1 intra frame
+
+ Common::BitStream16LEMSB bits(frame);
+
+ bits.skip(16); // unknown
+ bits.skip(16); // 0x3800
+ uint16 scale = bits.getBits(16);
+ uint16 version = bits.getBits(16);
+
+ if (version != 2 && version != 3)
+ error("Unknown PSX stream frame version");
+
+ // Initalize default v3 DC here
+ _lastDC[0] = _lastDC[1] = _lastDC[2] = 0;
+
+ for (int mbX = 0; mbX < _macroBlocksW; mbX++)
+ for (int mbY = 0; mbY < _macroBlocksH; mbY++)
+ decodeMacroBlock(&bits, mbX, mbY, scale, version);
+
+ // Output data onto the frame
+ Graphics::convertYUV420ToRGB(_surface, _yBuffer, _cbBuffer, _crBuffer, _surface->w, _surface->h, _macroBlocksW * 16, _macroBlocksW * 8);
+}
+
+void PSXStreamDecoder::decodeMacroBlock(Common::BitStream *bits, int mbX, int mbY, uint16 scale, uint16 version) {
+ int pitchY = _macroBlocksW * 16;
+ int pitchC = _macroBlocksW * 8;
+
+ // Note the strange order of red before blue
+ decodeBlock(bits, _crBuffer + (mbY * pitchC + mbX) * 8, pitchC, scale, version, kPlaneV);
+ decodeBlock(bits, _cbBuffer + (mbY * pitchC + mbX) * 8, pitchC, scale, version, kPlaneU);
+ decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16, pitchY, scale, version, kPlaneY);
+ decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8, pitchY, scale, version, kPlaneY);
+ decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8 * pitchY, pitchY, scale, version, kPlaneY);
+ decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8 * pitchY + 8, pitchY, scale, version, kPlaneY);
+}
+
+// Standard JPEG/MPEG zig zag table
+static const byte s_zigZagTable[8 * 8] = {
+ 0, 1, 5, 6, 14, 15, 27, 28,
+ 2, 4, 7, 13, 16, 26, 29, 42,
+ 3, 8, 12, 17, 25, 30, 41, 43,
+ 9, 11, 18, 24, 31, 40, 44, 53,
+ 10, 19, 23, 32, 39, 45, 52, 54,
+ 20, 22, 33, 38, 46, 51, 55, 60,
+ 21, 34, 37, 47, 50, 56, 59, 61,
+ 35, 36, 48, 49, 57, 58, 62, 63
+};
+
+// One byte different from the standard MPEG-1 table
+static const byte s_quantizationTable[8 * 8] = {
+ 2, 16, 19, 22, 26, 27, 29, 34,
+ 16, 16, 22, 24, 27, 29, 34, 37,
+ 19, 22, 26, 27, 29, 34, 34, 38,
+ 22, 22, 26, 27, 29, 34, 37, 40,
+ 22, 26, 27, 29, 32, 35, 40, 48,
+ 26, 27, 29, 32, 35, 40, 48, 58,
+ 26, 27, 29, 34, 38, 46, 56, 69,
+ 27, 29, 35, 38, 46, 56, 69, 83
+};
+
+void PSXStreamDecoder::dequantizeBlock(int *coefficients, float *block, uint16 scale) {
+ // Dequantize the data, un-zig-zagging as we go along
+ for (int i = 0; i < 8 * 8; i++) {
+ if (i == 0) // Special case for the DC coefficient
+ block[i] = coefficients[i] * s_quantizationTable[i];
+ else
+ block[i] = (float)coefficients[s_zigZagTable[i]] * s_quantizationTable[i] * scale / 8;
+ }
+}
+
+int PSXStreamDecoder::readDC(Common::BitStream *bits, uint16 version, PlaneType plane) {
+ // Version 2 just has its coefficient as 10-bits
+ if (version == 2)
+ return readSignedCoefficient(bits);
+
+ // Version 3 has it stored as huffman codes as a difference from the previous DC value
+
+ Common::Huffman *huffman = (plane == kPlaneY) ? _dcHuffmanLuma : _dcHuffmanChroma;
+
+ uint32 symbol = huffman->getSymbol(*bits);
+ int dc = 0;
+
+ if (GET_DC_BITS(symbol) != 0) {
+ bool negative = (bits->getBit() == 0);
+ dc = bits->getBits(GET_DC_BITS(symbol) - 1);
+
+ if (negative)
+ dc -= GET_DC_NEG(symbol);
+ else
+ dc += GET_DC_POS(symbol);
+ }
+
+ _lastDC[plane] += dc * 4; // convert from 8-bit to 10-bit
+ return _lastDC[plane];
+}
+
+#define BLOCK_OVERFLOW_CHECK() \
+ if (count > 63) \
+ error("PSXStreamDecoder::readAC(): Too many coefficients")
+
+void PSXStreamDecoder::readAC(Common::BitStream *bits, int *block) {
+ // Clear the block first
+ for (int i = 0; i < 63; i++)
+ block[i] = 0;
+
+ int count = 0;
+
+ while (!bits->eos()) {
+ uint32 symbol = _acHuffman->getSymbol(*bits);
+
+ if (symbol == ESCAPE_CODE) {
+ // The escape code!
+ int zeroes = bits->getBits(6);
+ count += zeroes + 1;
+ BLOCK_OVERFLOW_CHECK();
+ block += zeroes;
+ *block++ = readSignedCoefficient(bits);
+ } else if (symbol == END_OF_BLOCK) {
+ // We're done
+ break;
+ } else {
+ // Normal huffman code
+ int zeroes = GET_AC_ZERO_RUN(symbol);
+ count += zeroes + 1;
+ BLOCK_OVERFLOW_CHECK();
+ block += zeroes;
+
+ if (bits->getBit())
+ *block++ = -GET_AC_COEFFICIENT(symbol);
+ else
+ *block++ = GET_AC_COEFFICIENT(symbol);
+ }
+ }
+}
+
+int PSXStreamDecoder::readSignedCoefficient(Common::BitStream *bits) {
+ uint val = bits->getBits(10);
+
+ // extend the sign
+ uint shift = 8 * sizeof(int) - 10;
+ return (int)(val << shift) >> shift;
+}
+
+// IDCT table built with :
+// _idct8x8[x][y] = cos(((2 * x + 1) * y) * (M_PI / 16.0)) * 0.5;
+// _idct8x8[x][y] /= sqrt(2.0) if y == 0
+static const double s_idct8x8[8][8] = {
+ { 0.353553390593274, 0.490392640201615, 0.461939766255643, 0.415734806151273, 0.353553390593274, 0.277785116509801, 0.191341716182545, 0.097545161008064 },
+ { 0.353553390593274, 0.415734806151273, 0.191341716182545, -0.097545161008064, -0.353553390593274, -0.490392640201615, -0.461939766255643, -0.277785116509801 },
+ { 0.353553390593274, 0.277785116509801, -0.191341716182545, -0.490392640201615, -0.353553390593274, 0.097545161008064, 0.461939766255643, 0.415734806151273 },
+ { 0.353553390593274, 0.097545161008064, -0.461939766255643, -0.277785116509801, 0.353553390593274, 0.415734806151273, -0.191341716182545, -0.490392640201615 },
+ { 0.353553390593274, -0.097545161008064, -0.461939766255643, 0.277785116509801, 0.353553390593274, -0.415734806151273, -0.191341716182545, 0.490392640201615 },
+ { 0.353553390593274, -0.277785116509801, -0.191341716182545, 0.490392640201615, -0.353553390593273, -0.097545161008064, 0.461939766255643, -0.415734806151273 },
+ { 0.353553390593274, -0.415734806151273, 0.191341716182545, 0.097545161008064, -0.353553390593274, 0.490392640201615, -0.461939766255643, 0.277785116509801 },
+ { 0.353553390593274, -0.490392640201615, 0.461939766255643, -0.415734806151273, 0.353553390593273, -0.277785116509801, 0.191341716182545, -0.097545161008064 }
+};
+
+void PSXStreamDecoder::idct(float *dequantData, float *result) {
+ // IDCT code based on JPEG's IDCT code
+ // TODO: Switch to the integer-based one mentioned in the docs
+ // This is by far the costliest operation here
+
+ float tmp[8 * 8];
+
+ // Apply 1D IDCT to rows
+ for (int y = 0; y < 8; y++) {
+ for (int x = 0; x < 8; x++) {
+ tmp[y + x * 8] = dequantData[0] * s_idct8x8[x][0]
+ + dequantData[1] * s_idct8x8[x][1]
+ + dequantData[2] * s_idct8x8[x][2]
+ + dequantData[3] * s_idct8x8[x][3]
+ + dequantData[4] * s_idct8x8[x][4]
+ + dequantData[5] * s_idct8x8[x][5]
+ + dequantData[6] * s_idct8x8[x][6]
+ + dequantData[7] * s_idct8x8[x][7];
+ }
+
+ dequantData += 8;
+ }
+
+ // Apply 1D IDCT to columns
+ for (int x = 0; x < 8; x++) {
+ const float *u = tmp + x * 8;
+ for (int y = 0; y < 8; y++) {
+ result[y * 8 + x] = u[0] * s_idct8x8[y][0]
+ + u[1] * s_idct8x8[y][1]
+ + u[2] * s_idct8x8[y][2]
+ + u[3] * s_idct8x8[y][3]
+ + u[4] * s_idct8x8[y][4]
+ + u[5] * s_idct8x8[y][5]
+ + u[6] * s_idct8x8[y][6]
+ + u[7] * s_idct8x8[y][7];
+ }
+ }
+}
+
+void PSXStreamDecoder::decodeBlock(Common::BitStream *bits, byte *block, int pitch, uint16 scale, uint16 version, PlaneType plane) {
+ // Version 2 just has signed 10 bits for DC
+ // Version 3 has them huffman coded
+ int coefficients[8 * 8];
+ coefficients[0] = readDC(bits, version, plane);
+ readAC(bits, &coefficients[1]); // Read in the AC
+
+ // Dequantize
+ float dequantData[8 * 8];
+ dequantizeBlock(coefficients, dequantData, scale);
+
+ // Perform IDCT
+ float idctData[8 * 8];
+ idct(dequantData, idctData);
+
+ // Now output the data
+ for (int y = 0; y < 8; y++) {
+ byte *start = block + pitch * y;
+
+ // Convert the result to be in the range [0, 255]
+ for (int x = 0; x < 8; x++)
+ *start++ = (int)CLIP<float>(idctData[y * 8 + x], -128.0f, 127.0f) + 128;
+ }
+}
+
+} // End of namespace Video
diff --git a/video/psx_decoder.h b/video/psx_decoder.h
new file mode 100644
index 0000000000..c8ad92c45a
--- /dev/null
+++ b/video/psx_decoder.h
@@ -0,0 +1,128 @@
+/* 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_PSX_DECODER_H
+#define VIDEO_PSX_DECODER_H
+
+#include "common/endian.h"
+#include "common/rational.h"
+#include "common/rect.h"
+#include "common/str.h"
+#include "graphics/surface.h"
+#include "video/video_decoder.h"
+
+namespace Audio {
+class QueuingAudioStream;
+}
+
+namespace Common {
+class BitStream;
+class Huffman;
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct PixelFormat;
+}
+
+namespace Video {
+
+/**
+ * Decoder for PSX stream videos.
+ * This currently implements the most basic PSX stream format that is
+ * used by most games on the system. Special variants are not supported
+ * at this time.
+ *
+ * Video decoder used in engines:
+ * - sword1 (psx)
+ * - sword2 (psx)
+ */
+class PSXStreamDecoder : public VideoDecoder {
+public:
+ // CD speed in sectors/second
+ // Calling code should use these enum values instead of the constants
+ enum CDSpeed {
+ kCD1x = 75,
+ kCD2x = 150
+ };
+
+ PSXStreamDecoder(CDSpeed speed, uint32 frameCount = 0);
+ virtual ~PSXStreamDecoder();
+
+ bool loadStream(Common::SeekableReadStream *stream);
+ void close();
+
+ bool isVideoLoaded() const { return _stream != 0; }
+ uint16 getWidth() const { return _surface->w; }
+ uint16 getHeight() const { return _surface->h; }
+ uint32 getFrameCount() const { return _frameCount; }
+ uint32 getElapsedTime() const;
+ uint32 getTimeToNextFrame() const;
+ const Graphics::Surface *decodeNextFrame();
+ Graphics::PixelFormat getPixelFormat() const { return _surface->format; }
+ bool endOfVideo() const { return _stream->pos() >= _stream->size(); }
+
+private:
+ void initCommon();
+ Common::SeekableReadStream *_stream;
+ Graphics::Surface *_surface;
+
+ uint32 _frameCount;
+ Audio::Timestamp _nextFrameStartTime;
+
+ Audio::SoundHandle _audHandle;
+ Audio::QueuingAudioStream *_audStream;
+ void queueAudioFromSector(Common::SeekableReadStream *sector);
+
+ enum PlaneType {
+ kPlaneY = 0,
+ kPlaneU = 1,
+ kPlaneV = 2
+ };
+
+ uint16 _macroBlocksW, _macroBlocksH;
+ byte *_yBuffer, *_cbBuffer, *_crBuffer;
+ void decodeFrame(Common::SeekableReadStream *frame);
+ void decodeMacroBlock(Common::BitStream *bits, int mbX, int mbY, uint16 scale, uint16 version);
+ void decodeBlock(Common::BitStream *bits, byte *block, int pitch, uint16 scale, uint16 version, PlaneType plane);
+
+ void readAC(Common::BitStream *bits, int *block);
+ Common::Huffman *_acHuffman;
+
+ int readDC(Common::BitStream *bits, uint16 version, PlaneType plane);
+ Common::Huffman *_dcHuffmanLuma, *_dcHuffmanChroma;
+ int _lastDC[3];
+
+ void dequantizeBlock(int *coefficients, float *block, uint16 scale);
+ void idct(float *dequantData, float *result);
+ int readSignedCoefficient(Common::BitStream *bits);
+
+ struct ADPCMStatus {
+ int16 sample[2];
+ } _adpcmStatus[2];
+
+ Common::SeekableReadStream *readSector();
+};
+
+} // End of namespace Video
+
+#endif
diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp
index 74bf533e62..959e3c4fc7 100644
--- a/video/qt_decoder.cpp
+++ b/video/qt_decoder.cpp
@@ -56,155 +56,42 @@ namespace Video {
////////////////////////////////////////////
QuickTimeDecoder::QuickTimeDecoder() {
- _curFrame = -1;
- _startTime = _nextFrameStartTime = 0;
+ _setStartTime = false;
_audHandle = Audio::SoundHandle();
_scaledSurface = 0;
_dirtyPalette = false;
_palette = 0;
+ _width = _height = 0;
+ _needUpdate = false;
}
QuickTimeDecoder::~QuickTimeDecoder() {
close();
}
-uint16 QuickTimeDecoder::getWidth() const {
- if (_videoTrackIndex < 0)
- return 0;
-
- return (Common::Rational(_tracks[_videoTrackIndex]->width) / getScaleFactorX()).toInt();
-}
-
-uint16 QuickTimeDecoder::getHeight() const {
- if (_videoTrackIndex < 0)
- return 0;
-
- return (Common::Rational(_tracks[_videoTrackIndex]->height) / getScaleFactorY()).toInt();
-}
-
-uint32 QuickTimeDecoder::getFrameCount() const {
- if (_videoTrackIndex < 0)
- return 0;
-
- return _tracks[_videoTrackIndex]->frameCount;
-}
-
-Common::Rational QuickTimeDecoder::getScaleFactorX() const {
- if (_videoTrackIndex < 0)
- return 1;
-
- return (_scaleFactorX * _tracks[_videoTrackIndex]->scaleFactorX);
-}
-
-Common::Rational QuickTimeDecoder::getScaleFactorY() const {
- if (_videoTrackIndex < 0)
- return 1;
+int32 QuickTimeDecoder::getCurFrame() const {
+ // TODO: This is rather simplistic and doesn't take edits that
+ // repeat sections of the media into account. Doing that
+ // over-complicates things and shouldn't be necessary, but
+ // it would be nice to have in the future.
- return (_scaleFactorY * _tracks[_videoTrackIndex]->scaleFactorY);
-}
+ int32 frame = -1;
-uint32 QuickTimeDecoder::getFrameDuration() {
- if (_videoTrackIndex < 0)
- return 0;
+ for (uint32 i = 0; i < _handlers.size(); i++)
+ if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeVideo)
+ frame += ((VideoTrackHandler *)_handlers[i])->getCurFrame() + 1;
- uint32 curFrameIndex = 0;
- for (int32 i = 0; i < _tracks[_videoTrackIndex]->timeToSampleCount; i++) {
- curFrameIndex += _tracks[_videoTrackIndex]->timeToSample[i].count;
- if ((uint32)_curFrame < curFrameIndex) {
- // Ok, now we have what duration this frame has.
- return _tracks[_videoTrackIndex]->timeToSample[i].duration;
- }
- }
-
- // This should never occur
- error ("Cannot find duration for frame %d", _curFrame);
- return 0;
-}
-
-Graphics::PixelFormat QuickTimeDecoder::getPixelFormat() const {
- Codec *codec = findDefaultVideoCodec();
-
- if (!codec)
- return Graphics::PixelFormat::createFormatCLUT8();
-
- return codec->getPixelFormat();
-}
-
-uint32 QuickTimeDecoder::findKeyFrame(uint32 frame) const {
- for (int i = _tracks[_videoTrackIndex]->keyframeCount - 1; i >= 0; i--)
- if (_tracks[_videoTrackIndex]->keyframes[i] <= frame)
- return _tracks[_videoTrackIndex]->keyframes[i];
-
- // If none found, we'll assume the requested frame is a key frame
return frame;
}
-void QuickTimeDecoder::seekToFrame(uint32 frame) {
- assert(_videoTrackIndex >= 0);
- assert(frame < _tracks[_videoTrackIndex]->frameCount);
-
- // Stop all audio (for now)
- stopAudio();
-
- // Track down the keyframe
- _curFrame = findKeyFrame(frame) - 1;
- while (_curFrame < (int32)frame - 1)
- decodeNextFrame();
-
- // Map out the starting point
- _nextFrameStartTime = 0;
- uint32 curFrame = 0;
-
- for (int32 i = 0; i < _tracks[_videoTrackIndex]->timeToSampleCount && curFrame < frame; i++) {
- for (int32 j = 0; j < _tracks[_videoTrackIndex]->timeToSample[i].count && curFrame < frame; j++) {
- curFrame++;
- _nextFrameStartTime += _tracks[_videoTrackIndex]->timeToSample[i].duration;
- }
- }
-
- // Adjust the video starting point
- const Audio::Timestamp curVideoTime(0, _nextFrameStartTime, _tracks[_videoTrackIndex]->timeScale);
- _startTime = g_system->getMillis() - curVideoTime.msecs();
- resetPauseStartTime();
-
- // Adjust the audio starting point
- if (_audioTrackIndex >= 0) {
- _audioStartOffset = curVideoTime;
-
- // Seek to the new audio location
- setAudioStreamPos(_audioStartOffset);
-
- // Restart the audio
- startAudio();
-
- // Pause the audio again if we're still paused
- if (isPaused() && _audStream)
- g_system->getMixer()->pauseHandle(_audHandle, true);
- }
-}
-
-void QuickTimeDecoder::seekToTime(Audio::Timestamp time) {
- // Use makeQuickTimeStream() instead
- if (_videoTrackIndex < 0)
- error("Audio-only seeking not supported");
-
- // Try to find the last frame that should have been decoded
- uint32 frame = 0;
- Audio::Timestamp totalDuration(0, _tracks[_videoTrackIndex]->timeScale);
- bool done = false;
+uint32 QuickTimeDecoder::getFrameCount() const {
+ uint32 count = 0;
- for (int32 i = 0; i < _tracks[_videoTrackIndex]->timeToSampleCount && !done; i++) {
- for (int32 j = 0; j < _tracks[_videoTrackIndex]->timeToSample[i].count; j++) {
- totalDuration = totalDuration.addFrames(_tracks[_videoTrackIndex]->timeToSample[i].duration);
- if (totalDuration > time) {
- done = true;
- break;
- }
- frame++;
- }
- }
+ for (uint32 i = 0; i < _handlers.size(); i++)
+ if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeVideo)
+ count += ((VideoTrackHandler *)_handlers[i])->getFrameCount();
- seekToFrame(frame);
+ return count;
}
void QuickTimeDecoder::startAudio() {
@@ -224,98 +111,107 @@ void QuickTimeDecoder::pauseVideoIntern(bool pause) {
g_system->getMixer()->pauseHandle(_audHandle, pause);
}
-Codec *QuickTimeDecoder::findDefaultVideoCodec() const {
- if (_videoTrackIndex < 0 || _tracks[_videoTrackIndex]->sampleDescs.empty())
- return 0;
+QuickTimeDecoder::VideoTrackHandler *QuickTimeDecoder::findNextVideoTrack() const {
+ VideoTrackHandler *bestTrack = 0;
+ uint32 bestTime = 0xffffffff;
+
+ for (uint32 i = 0; i < _handlers.size(); i++) {
+ if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeVideo && !_handlers[i]->endOfTrack()) {
+ VideoTrackHandler *track = (VideoTrackHandler *)_handlers[i];
+ uint32 time = track->getNextFrameStartTime();
+
+ if (time < bestTime) {
+ bestTime = time;
+ bestTrack = track;
+ }
+ }
+ }
- return ((VideoSampleDesc *)_tracks[_videoTrackIndex]->sampleDescs[0])->_videoCodec;
+ return bestTrack;
}
const Graphics::Surface *QuickTimeDecoder::decodeNextFrame() {
- if (_videoTrackIndex < 0 || _curFrame >= (int32)getFrameCount() - 1)
+ if (!_nextVideoTrack)
return 0;
- if (_startTime == 0)
- _startTime = g_system->getMillis();
-
- _curFrame++;
- _nextFrameStartTime += getFrameDuration();
-
- // Update the audio while we're at it
- updateAudioBuffer();
-
- // Get the next packet
- uint32 descId;
- Common::SeekableReadStream *frameData = getNextFramePacket(descId);
-
- if (!frameData || !descId || descId > _tracks[_videoTrackIndex]->sampleDescs.size())
- return 0;
+ const Graphics::Surface *frame = _nextVideoTrack->decodeNextFrame();
- // Find which video description entry we want
- VideoSampleDesc *entry = (VideoSampleDesc *)_tracks[_videoTrackIndex]->sampleDescs[descId - 1];
+ if (!_setStartTime) {
+ _startTime = g_system->getMillis();
+ _setStartTime = true;
+ }
- if (!entry->_videoCodec)
- return 0;
+ _nextVideoTrack = findNextVideoTrack();
+ _needUpdate = false;
- const Graphics::Surface *frame = entry->_videoCodec->decodeImage(frameData);
- delete frameData;
+ // Update audio buffers too
+ // (needs to be done after we find the next track)
+ for (uint32 i = 0; i < _handlers.size(); i++)
+ if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeAudio)
+ ((AudioTrackHandler *)_handlers[i])->updateBuffer();
- // Update the palette
- if (entry->_videoCodec->containsPalette()) {
- // The codec itself contains a palette
- if (entry->_videoCodec->hasDirtyPalette()) {
- _palette = entry->_videoCodec->getPalette();
- _dirtyPalette = true;
- }
- } else {
- // Check if the video description has been updated
- byte *palette = entry->_palette;
-
- if (palette != _palette) {
- _palette = palette;
- _dirtyPalette = true;
- }
+ if (_scaledSurface) {
+ scaleSurface(frame, _scaledSurface, _scaleFactorX, _scaleFactorY);
+ return _scaledSurface;
}
- return scaleSurface(frame);
+ return frame;
}
-const Graphics::Surface *QuickTimeDecoder::scaleSurface(const Graphics::Surface *frame) {
- if (getScaleFactorX() == 1 && getScaleFactorY() == 1)
- return frame;
+void QuickTimeDecoder::scaleSurface(const Graphics::Surface *src, Graphics::Surface *dst, Common::Rational scaleFactorX, Common::Rational scaleFactorY) {
+ assert(src && dst);
- assert(_scaledSurface);
-
- for (int32 j = 0; j < _scaledSurface->h; j++)
- for (int32 k = 0; k < _scaledSurface->w; k++)
- memcpy(_scaledSurface->getBasePtr(k, j), frame->getBasePtr((k * getScaleFactorX()).toInt() , (j * getScaleFactorY()).toInt()), frame->format.bytesPerPixel);
-
- return _scaledSurface;
+ for (int32 j = 0; j < dst->h; j++)
+ for (int32 k = 0; k < dst->w; k++)
+ memcpy(dst->getBasePtr(k, j), src->getBasePtr((k * scaleFactorX).toInt() , (j * scaleFactorY).toInt()), src->format.bytesPerPixel);
}
bool QuickTimeDecoder::endOfVideo() const {
- return (!_audStream || _audStream->endOfData()) && (!findDefaultVideoCodec() || SeekableVideoDecoder::endOfVideo());
+ if (!isVideoLoaded())
+ return true;
+
+ for (uint32 i = 0; i < _handlers.size(); i++)
+ if (!_handlers[i]->endOfTrack())
+ return false;
+
+ return true;
}
uint32 QuickTimeDecoder::getElapsedTime() const {
- if (_audStream)
- return g_system->getMixer()->getSoundElapsedTime(_audHandle) + _audioStartOffset.msecs();
+ // TODO: Convert to multi-track
+ if (_audStream) {
+ // Use the audio time if present and the audio track's time is less than the
+ // total length of the audio track. The audio track can end before the video
+ // track, so we need to fall back on the getMillis() time tracking in that
+ // case.
+ uint32 time = g_system->getMixer()->getSoundElapsedTime(_audHandle) + _audioStartOffset.msecs();
+ if (time < _tracks[_audioTrackIndex]->mediaDuration * 1000 / _tracks[_audioTrackIndex]->timeScale)
+ return time;
+ }
+ // Just use time elapsed since the beginning
return SeekableVideoDecoder::getElapsedTime();
}
uint32 QuickTimeDecoder::getTimeToNextFrame() const {
- if (endOfVideo() || _curFrame < 0)
+ if (_needUpdate)
return 0;
- // Convert from the QuickTime rate base to 1000
- uint32 nextFrameStartTime = _nextFrameStartTime * 1000 / _tracks[_videoTrackIndex]->timeScale;
- uint32 elapsedTime = getElapsedTime();
+ if (_nextVideoTrack) {
+ uint32 nextFrameStartTime = _nextVideoTrack->getNextFrameStartTime();
- if (nextFrameStartTime <= elapsedTime)
- return 0;
+ if (nextFrameStartTime == 0)
+ return 0;
+
+ // TODO: Add support for rate modification
+
+ uint32 elapsedTime = getElapsedTime();
+
+ if (elapsedTime < nextFrameStartTime)
+ return nextFrameStartTime - elapsedTime;
+ }
- return nextFrameStartTime - elapsedTime;
+ return 0;
}
bool QuickTimeDecoder::loadFile(const Common::String &filename) {
@@ -337,30 +233,49 @@ bool QuickTimeDecoder::loadStream(Common::SeekableReadStream *stream) {
void QuickTimeDecoder::init() {
Audio::QuickTimeAudioDecoder::init();
- _videoTrackIndex = -1;
_startTime = 0;
-
- // Find video streams
- for (uint32 i = 0; i < _tracks.size(); i++)
- if (_tracks[i]->codecType == CODEC_TYPE_VIDEO && _videoTrackIndex < 0)
- _videoTrackIndex = i;
+ _setStartTime = false;
// Start the audio codec if we've got one that we can handle
if (_audStream) {
startAudio();
_audioStartOffset = Audio::Timestamp(0);
+
+ // TODO: Support multiple audio tracks
+ // For now, just push back a handler for the first audio track
+ _handlers.push_back(new AudioTrackHandler(this, _tracks[_audioTrackIndex]));
}
- // Initialize video, if present
- if (_videoTrackIndex >= 0) {
- for (uint32 i = 0; i < _tracks[_videoTrackIndex]->sampleDescs.size(); i++)
- ((VideoSampleDesc *)_tracks[_videoTrackIndex]->sampleDescs[i])->initCodec();
+ // Initialize all the video tracks
+ for (uint32 i = 0; i < _tracks.size(); i++) {
+ if (_tracks[i]->codecType == CODEC_TYPE_VIDEO) {
+ for (uint32 j = 0; j < _tracks[i]->sampleDescs.size(); j++)
+ ((VideoSampleDesc *)_tracks[i]->sampleDescs[j])->initCodec();
- if (getScaleFactorX() != 1 || getScaleFactorY() != 1) {
+ _handlers.push_back(new VideoTrackHandler(this, _tracks[i]));
+ }
+ }
+
+ // Prepare the first video track
+ _nextVideoTrack = findNextVideoTrack();
+
+ if (_nextVideoTrack) {
+ // Initialize the scaled surface
+ if (_scaleFactorX != 1 || _scaleFactorY != 1) {
// We have to initialize the scaled surface
_scaledSurface = new Graphics::Surface();
- _scaledSurface->create(getWidth(), getHeight(), getPixelFormat());
+ _scaledSurface->create((_nextVideoTrack->getWidth() / _scaleFactorX).toInt(),
+ (_nextVideoTrack->getHeight() / _scaleFactorY).toInt(), getPixelFormat());
+ _width = _scaledSurface->w;
+ _height = _scaledSurface->h;
+ } else {
+ _width = _nextVideoTrack->getWidth().toInt();
+ _height = _nextVideoTrack->getHeight().toInt();
}
+
+ _needUpdate = true;
+ } else {
+ _needUpdate = false;
}
}
@@ -464,6 +379,7 @@ Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Track *tra
void QuickTimeDecoder::close() {
stopAudio();
+ freeAllTrackHandlers();
if (_scaledSurface) {
_scaledSurface->free();
@@ -471,93 +387,46 @@ void QuickTimeDecoder::close() {
_scaledSurface = 0;
}
+ _width = _height = 0;
+
Common::QuickTimeParser::close();
SeekableVideoDecoder::reset();
}
-Common::SeekableReadStream *QuickTimeDecoder::getNextFramePacket(uint32 &descId) {
- if (_videoTrackIndex < 0)
- return NULL;
-
- // First, we have to track down which chunk holds the sample and which sample in the chunk contains the frame we are looking for.
- int32 totalSampleCount = 0;
- int32 sampleInChunk = 0;
- int32 actualChunk = -1;
- uint32 sampleToChunkIndex = 0;
+void QuickTimeDecoder::freeAllTrackHandlers() {
+ for (uint32 i = 0; i < _handlers.size(); i++)
+ delete _handlers[i];
- for (uint32 i = 0; i < _tracks[_videoTrackIndex]->chunkCount; i++) {
- if (sampleToChunkIndex < _tracks[_videoTrackIndex]->sampleToChunkCount && i >= _tracks[_videoTrackIndex]->sampleToChunk[sampleToChunkIndex].first)
- sampleToChunkIndex++;
-
- totalSampleCount += _tracks[_videoTrackIndex]->sampleToChunk[sampleToChunkIndex - 1].count;
-
- if (totalSampleCount > getCurFrame()) {
- actualChunk = i;
- descId = _tracks[_videoTrackIndex]->sampleToChunk[sampleToChunkIndex - 1].id;
- sampleInChunk = _tracks[_videoTrackIndex]->sampleToChunk[sampleToChunkIndex - 1].count - totalSampleCount + getCurFrame();
- break;
- }
- }
-
- if (actualChunk < 0) {
- warning ("Could not find data for frame %d", getCurFrame());
- return NULL;
- }
-
- // Next seek to that frame
- _fd->seek(_tracks[_videoTrackIndex]->chunkOffsets[actualChunk]);
-
- // Then, if the chunk holds more than one frame, seek to where the frame we want is located
- for (int32 i = getCurFrame() - sampleInChunk; i < getCurFrame(); i++) {
- if (_tracks[_videoTrackIndex]->sampleSize != 0)
- _fd->skip(_tracks[_videoTrackIndex]->sampleSize);
- else
- _fd->skip(_tracks[_videoTrackIndex]->sampleSizes[i]);
- }
+ _handlers.clear();
+}
- // Finally, read in the raw data for the frame
- //printf ("Frame Data[%d]: Offset = %d, Size = %d\n", getCurFrame(), _fd->pos(), _tracks[_videoTrackIndex]->sampleSizes[getCurFrame()]);
+void QuickTimeDecoder::seekToTime(Audio::Timestamp time) {
+ // Sets all tracks to this time
+ for (uint32 i = 0; i < _handlers.size(); i++)
+ _handlers[i]->seekToTime(time);
- if (_tracks[_videoTrackIndex]->sampleSize != 0)
- return _fd->readStream(_tracks[_videoTrackIndex]->sampleSize);
+ // Reset our start time
+ _startTime = g_system->getMillis() - time.msecs();
+ _setStartTime = true;
+ resetPauseStartTime();
- return _fd->readStream(_tracks[_videoTrackIndex]->sampleSizes[getCurFrame()]);
+ // Reset the next video track too
+ _nextVideoTrack = findNextVideoTrack();
+ _needUpdate = _nextVideoTrack != 0;
}
void QuickTimeDecoder::updateAudioBuffer() {
- if (!_audStream)
- return;
-
- uint32 numberOfChunksNeeded = 0;
-
- if (_videoTrackIndex < 0 || _curFrame == (int32)_tracks[_videoTrackIndex]->frameCount - 1) {
- // If we have no video, there's nothing to base our buffer against
- // However, one must ask why a QuickTimeDecoder is being used instead of the nice makeQuickTimeStream() function
-
- // If we're on the last frame, make sure all audio remaining is buffered
- numberOfChunksNeeded = _tracks[_audioTrackIndex]->chunkCount;
- } else {
- Audio::QuickTimeAudioDecoder::AudioSampleDesc *entry = (Audio::QuickTimeAudioDecoder::AudioSampleDesc *)_tracks[_audioTrackIndex]->sampleDescs[0];
-
- // Calculate the amount of chunks we need in memory until the next frame
- uint32 timeToNextFrame = getTimeToNextFrame();
- uint32 timeFilled = 0;
- uint32 curAudioChunk = _curAudioChunk - _audStream->numQueuedStreams();
-
- for (; timeFilled < timeToNextFrame && curAudioChunk < _tracks[_audioTrackIndex]->chunkCount; numberOfChunksNeeded++, curAudioChunk++) {
- uint32 sampleCount = entry->getAudioChunkSampleCount(curAudioChunk);
- assert(sampleCount);
-
- timeFilled += sampleCount * 1000 / entry->_sampleRate;
- }
+ // Updates the audio buffers for all audio tracks
+ for (uint32 i = 0; i < _handlers.size(); i++)
+ if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeAudio)
+ ((AudioTrackHandler *)_handlers[i])->updateBuffer();
+}
- // Add a couple extra to ensure we don't underrun
- numberOfChunksNeeded += 3;
- }
+Graphics::PixelFormat QuickTimeDecoder::getPixelFormat() const {
+ if (_nextVideoTrack)
+ return _nextVideoTrack->getPixelFormat();
- // Keep three streams in buffer so that if/when the first two end, it goes right into the next
- while (_audStream->numQueuedStreams() < numberOfChunksNeeded && _curAudioChunk < _tracks[_audioTrackIndex]->chunkCount)
- queueNextAudioChunk();
+ return Graphics::PixelFormat();
}
QuickTimeDecoder::VideoSampleDesc::VideoSampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag) : Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) {
@@ -612,4 +481,383 @@ void QuickTimeDecoder::VideoSampleDesc::initCodec() {
}
}
+bool QuickTimeDecoder::endOfVideoTracks() const {
+ for (uint32 i = 0; i < _handlers.size(); i++)
+ if (_handlers[i]->getTrackType() == TrackHandler::kTrackTypeVideo && !_handlers[i]->endOfTrack())
+ return false;
+
+ return true;
+}
+
+QuickTimeDecoder::TrackHandler::TrackHandler(QuickTimeDecoder *decoder, Track *parent) : _decoder(decoder), _parent(parent), _fd(_decoder->_fd) {
+ _curEdit = 0;
+}
+
+bool QuickTimeDecoder::TrackHandler::endOfTrack() {
+ // A track is over when we've finished going through all edits
+ return _curEdit == _parent->editCount;
+}
+
+QuickTimeDecoder::AudioTrackHandler::AudioTrackHandler(QuickTimeDecoder *decoder, Track *parent) : TrackHandler(decoder, parent) {
+}
+
+void QuickTimeDecoder::AudioTrackHandler::updateBuffer() {
+ if (!_decoder->_audStream)
+ return;
+
+ uint32 numberOfChunksNeeded = 0;
+
+ if (_decoder->endOfVideoTracks()) {
+ // If we have no video left (or no video), there's nothing to base our buffer against
+ numberOfChunksNeeded = _parent->chunkCount;
+ } else {
+ Audio::QuickTimeAudioDecoder::AudioSampleDesc *entry = (Audio::QuickTimeAudioDecoder::AudioSampleDesc *)_parent->sampleDescs[0];
+
+ // Calculate the amount of chunks we need in memory until the next frame
+ uint32 timeToNextFrame = _decoder->getTimeToNextFrame();
+ uint32 timeFilled = 0;
+ uint32 curAudioChunk = _decoder->_curAudioChunk - _decoder->_audStream->numQueuedStreams();
+
+ for (; timeFilled < timeToNextFrame && curAudioChunk < _parent->chunkCount; numberOfChunksNeeded++, curAudioChunk++) {
+ uint32 sampleCount = entry->getAudioChunkSampleCount(curAudioChunk);
+ assert(sampleCount);
+
+ timeFilled += sampleCount * 1000 / entry->_sampleRate;
+ }
+
+ // Add a couple extra to ensure we don't underrun
+ numberOfChunksNeeded += 3;
+ }
+
+ // Keep three streams in buffer so that if/when the first two end, it goes right into the next
+ while (_decoder->_audStream->numQueuedStreams() < numberOfChunksNeeded && _decoder->_curAudioChunk < _parent->chunkCount)
+ _decoder->queueNextAudioChunk();
+}
+
+bool QuickTimeDecoder::AudioTrackHandler::endOfTrack() {
+ // TODO: Handle edits
+ return (_decoder->_curAudioChunk == _parent->chunkCount) && _decoder->_audStream->endOfData();
+}
+
+void QuickTimeDecoder::AudioTrackHandler::seekToTime(Audio::Timestamp time) {
+ if (_decoder->_audStream) {
+ // Stop all audio
+ _decoder->stopAudio();
+
+ _decoder->_audioStartOffset = time;
+
+ // Seek to the new audio location
+ _decoder->setAudioStreamPos(_decoder->_audioStartOffset);
+
+ // Restart the audio
+ _decoder->startAudio();
+
+ // Pause the audio again if we're still paused
+ if (_decoder->isPaused() && _decoder->_audStream)
+ g_system->getMixer()->pauseHandle(_decoder->_audHandle, true);
+ }
+}
+
+QuickTimeDecoder::VideoTrackHandler::VideoTrackHandler(QuickTimeDecoder *decoder, Common::QuickTimeParser::Track *parent) : TrackHandler(decoder, parent) {
+ if (_parent->scaleFactorX != 1 || _parent->scaleFactorY != 1) {
+ _scaledSurface = new Graphics::Surface();
+ _scaledSurface->create(getWidth().toInt(), getHeight().toInt(), getPixelFormat());
+ } else {
+ _scaledSurface = 0;
+ }
+
+ enterNewEditList(false);
+
+ _holdNextFrameStartTime = false;
+ _curFrame = -1;
+ _durationOverride = -1;
+
+}
+
+QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() {
+ if (_scaledSurface) {
+ _scaledSurface->free();
+ delete _scaledSurface;
+ }
+}
+
+const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame() {
+ if (endOfTrack())
+ return 0;
+
+ const Graphics::Surface *frame = bufferNextFrame();
+
+ 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;
+ _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 (!endOfTrack() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) {
+ _curEdit++;
+
+ if (!endOfTrack())
+ enterNewEditList(true);
+ }
+
+ if (_scaledSurface) {
+ _decoder->scaleSurface(frame, _scaledSurface, _parent->scaleFactorX, _parent->scaleFactorY);
+ return _scaledSurface;
+ }
+
+ return frame;
+}
+
+void QuickTimeDecoder::VideoTrackHandler::enterNewEditList(bool bufferFrames) {
+ // Bypass all empty edit lists first
+ while (!endOfTrack() && _parent->editList[_curEdit].mediaTime == -1)
+ _curEdit++;
+
+ if (endOfTrack())
+ return;
+
+ uint32 frameNum = 0;
+ bool done = false;
+ uint32 totalDuration = 0;
+ uint32 prevDuration = 0;
+
+ // Track down where the mediaTime is in the media
+ 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;
+ }
+
+ prevDuration = totalDuration;
+ totalDuration += _parent->timeToSample[i].duration;
+ frameNum++;
+ }
+ }
+
+ if (bufferFrames) {
+ // Track down the keyframe
+ _curFrame = findKeyFrame(frameNum) - 1;
+ while (_curFrame < (int32)frameNum - 1)
+ bufferNextFrame();
+ } else {
+ _curFrame = frameNum - 1;
+ }
+
+ _nextFrameStartTime = getCurEditTimeOffset();
+
+ // Set an override for the duration since we came up in-between two frames
+ if (prevDuration != totalDuration)
+ _durationOverride = totalDuration - prevDuration;
+}
+
+const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::bufferNextFrame() {
+ _curFrame++;
+
+ // Get the next packet
+ uint32 descId;
+ Common::SeekableReadStream *frameData = getNextFramePacket(descId);
+
+ if (!frameData || !descId || descId > _parent->sampleDescs.size())
+ return 0;
+
+ // Find which video description entry we want
+ VideoSampleDesc *entry = (VideoSampleDesc *)_parent->sampleDescs[descId - 1];
+
+ if (!entry->_videoCodec)
+ return 0;
+
+ const Graphics::Surface *frame = entry->_videoCodec->decodeImage(frameData);
+ delete frameData;
+
+ // Update the palette
+ if (entry->_videoCodec->containsPalette()) {
+ // The codec itself contains a palette
+ if (entry->_videoCodec->hasDirtyPalette()) {
+ _decoder->_palette = entry->_videoCodec->getPalette();
+ _decoder->_dirtyPalette = true;
+ }
+ } else {
+ // Check if the video description has been updated
+ byte *palette = entry->_palette;
+
+ if (palette !=_decoder-> _palette) {
+ _decoder->_palette = palette;
+ _decoder->_dirtyPalette = true;
+ }
+ }
+
+ return frame;
+}
+
+uint32 QuickTimeDecoder::VideoTrackHandler::getNextFrameStartTime() {
+ if (endOfTrack())
+ return 0;
+
+ // Convert to milliseconds so the tracks can be compared
+ return getRateAdjustedFrameTime() * 1000 / _parent->timeScale;
+}
+
+uint32 QuickTimeDecoder::VideoTrackHandler::getFrameCount() {
+ return _parent->frameCount;
+}
+
+uint32 QuickTimeDecoder::VideoTrackHandler::getFrameDuration() {
+ uint32 curFrameIndex = 0;
+ for (int32 i = 0; i < _parent->timeToSampleCount; i++) {
+ curFrameIndex += _parent->timeToSample[i].count;
+ if ((uint32)_curFrame < curFrameIndex) {
+ // Ok, now we have what duration this frame has.
+ return _parent->timeToSample[i].duration;
+ }
+ }
+
+ // This should never occur
+ error("Cannot find duration for frame %d", _curFrame);
+ return 0;
+}
+
+Common::SeekableReadStream *QuickTimeDecoder::VideoTrackHandler::getNextFramePacket(uint32 &descId) {
+ // First, we have to track down which chunk holds the sample and which sample in the chunk contains the frame we are looking for.
+ int32 totalSampleCount = 0;
+ int32 sampleInChunk = 0;
+ int32 actualChunk = -1;
+ uint32 sampleToChunkIndex = 0;
+
+ for (uint32 i = 0; i < _parent->chunkCount; i++) {
+ if (sampleToChunkIndex < _parent->sampleToChunkCount && i >= _parent->sampleToChunk[sampleToChunkIndex].first)
+ sampleToChunkIndex++;
+
+ totalSampleCount += _parent->sampleToChunk[sampleToChunkIndex - 1].count;
+
+ if (totalSampleCount > _curFrame) {
+ actualChunk = i;
+ descId = _parent->sampleToChunk[sampleToChunkIndex - 1].id;
+ sampleInChunk = _parent->sampleToChunk[sampleToChunkIndex - 1].count - totalSampleCount + _curFrame;
+ break;
+ }
+ }
+
+ if (actualChunk < 0) {
+ warning("Could not find data for frame %d", _curFrame);
+ return 0;
+ }
+
+ // Next seek to that frame
+ _fd->seek(_parent->chunkOffsets[actualChunk]);
+
+ // Then, if the chunk holds more than one frame, seek to where the frame we want is located
+ for (int32 i = _curFrame - sampleInChunk; i < _curFrame; i++) {
+ if (_parent->sampleSize != 0)
+ _fd->skip(_parent->sampleSize);
+ else
+ _fd->skip(_parent->sampleSizes[i]);
+ }
+
+ // Finally, read in the raw data for the frame
+ //debug("Frame Data[%d]: Offset = %d, Size = %d", _curFrame, _fd->pos(), _parent->sampleSizes[_curFrame]);
+
+ if (_parent->sampleSize != 0)
+ return _fd->readStream(_parent->sampleSize);
+
+ return _fd->readStream(_parent->sampleSizes[_curFrame]);
+}
+
+uint32 QuickTimeDecoder::VideoTrackHandler::findKeyFrame(uint32 frame) const {
+ for (int i = _parent->keyframeCount - 1; i >= 0; i--)
+ if (_parent->keyframes[i] <= frame)
+ return _parent->keyframes[i];
+
+ // If none found, we'll assume the requested frame is a key frame
+ return frame;
+}
+
+void QuickTimeDecoder::VideoTrackHandler::seekToTime(Audio::Timestamp time) {
+ // First, figure out what edit we're in
+ time = time.convertToFramerate(_parent->timeScale);
+
+ // Continue until we get to where we need to be
+ for (_curEdit = 0; !endOfTrack(); _curEdit++)
+ if ((uint32)time.totalNumberOfFrames() >= getCurEditTimeOffset() && (uint32)time.totalNumberOfFrames() < getCurEditTimeOffset() + getCurEditTrackDuration())
+ break;
+
+ // This track is done
+ if (endOfTrack())
+ return;
+
+ enterNewEditList(false);
+
+ // One extra check for the end of a track
+ if (endOfTrack())
+ return;
+
+ // Now we're in the edit and need to figure out what frame we need
+ while (getRateAdjustedFrameTime() < (uint32)time.totalNumberOfFrames()) {
+ _curFrame++;
+ if (_durationOverride >= 0) {
+ _nextFrameStartTime += _durationOverride;
+ _durationOverride = -1;
+ } else {
+ _nextFrameStartTime += getFrameDuration();
+ }
+ }
+
+ // 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
+ if (_holdNextFrameStartTime)
+ _curFrame--;
+
+ // Handle the keyframe here
+ int32 destinationFrame = _curFrame + 1;
+
+ assert(destinationFrame < (int32)_parent->frameCount);
+ _curFrame = findKeyFrame(destinationFrame) - 1;
+ while (_curFrame < destinationFrame - 1)
+ bufferNextFrame();
+}
+
+Common::Rational QuickTimeDecoder::VideoTrackHandler::getWidth() const {
+ return Common::Rational(_parent->width) / _parent->scaleFactorX;
+}
+
+Common::Rational QuickTimeDecoder::VideoTrackHandler::getHeight() const {
+ return Common::Rational(_parent->height) / _parent->scaleFactorY;
+}
+
+Graphics::PixelFormat QuickTimeDecoder::VideoTrackHandler::getPixelFormat() const {
+ return ((VideoSampleDesc *)_parent->sampleDescs[0])->_videoCodec->getPixelFormat();
+}
+
+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();
+ return convertedTime + getCurEditTimeOffset();
+}
+
+uint32 QuickTimeDecoder::VideoTrackHandler::getCurEditTimeOffset() const {
+ // Need to convert to the track scale
+ return _parent->editList[_curEdit].timeOffset * _parent->timeScale / _decoder->_timeScale;
+}
+
+uint32 QuickTimeDecoder::VideoTrackHandler::getCurEditTrackDuration() const {
+ // Need to convert to the track scale
+ return _parent->editList[_curEdit].trackDuration * _parent->timeScale / _decoder->_timeScale;
+}
+
} // End of namespace Video
diff --git a/video/qt_decoder.h b/video/qt_decoder.h
index b51fd043e7..b2d7153f42 100644
--- a/video/qt_decoder.h
+++ b/video/qt_decoder.h
@@ -31,14 +31,14 @@
#ifndef VIDEO_QT_DECODER_H
#define VIDEO_QT_DECODER_H
+#include "audio/mixer.h"
+#include "audio/decoders/quicktime_intern.h"
#include "common/scummsys.h"
#include "common/rational.h"
+#include "graphics/pixelformat.h"
#include "video/video_decoder.h"
-#include "audio/mixer.h"
-#include "audio/decoders/quicktime_intern.h"
-
namespace Common {
class Rational;
}
@@ -63,13 +63,13 @@ public:
* Returns the width of the video
* @return the width of the video
*/
- uint16 getWidth() const;
+ uint16 getWidth() const { return _width; }
/**
* Returns the height of the video
* @return the height of the video
*/
- uint16 getHeight() const;
+ uint16 getHeight() const { return _height; }
/**
* Returns the amount of frames in the video
@@ -101,6 +101,8 @@ public:
const byte *getPalette() { _dirtyPalette = false; return _palette; }
bool hasDirtyPalette() const { return _dirtyPalette; }
+ int32 getCurFrame() const;
+
bool isVideoLoaded() const { return isOpen(); }
const Graphics::Surface *decodeNextFrame();
bool endOfVideo() const;
@@ -132,8 +134,6 @@ protected:
Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format);
private:
- Common::SeekableReadStream *getNextFramePacket(uint32 &descId);
- uint32 getFrameDuration();
void init();
void startAudio();
@@ -144,20 +144,108 @@ private:
Audio::Timestamp _audioStartOffset;
Codec *createCodec(uint32 codecTag, byte bitsPerPixel);
- Codec *findDefaultVideoCodec() const;
- uint32 _nextFrameStartTime;
- int _videoTrackIndex;
uint32 findKeyFrame(uint32 frame) const;
bool _dirtyPalette;
const byte *_palette;
+ bool _setStartTime;
+ bool _needUpdate;
+
+ uint16 _width, _height;
Graphics::Surface *_scaledSurface;
- const Graphics::Surface *scaleSurface(const Graphics::Surface *frame);
- Common::Rational getScaleFactorX() const;
- Common::Rational getScaleFactorY() const;
+ void scaleSurface(const Graphics::Surface *src, Graphics::Surface *dst,
+ Common::Rational scaleFactorX, Common::Rational scaleFactorY);
void pauseVideoIntern(bool pause);
+ bool endOfVideoTracks() const;
+
+ // The TrackHandler is a class that wraps around a QuickTime Track
+ // and handles playback in this decoder class.
+ class TrackHandler {
+ public:
+ TrackHandler(QuickTimeDecoder *decoder, Track *parent);
+ virtual ~TrackHandler() {}
+
+ enum TrackType {
+ kTrackTypeAudio,
+ kTrackTypeVideo
+ };
+
+ virtual TrackType getTrackType() const = 0;
+
+ virtual void seekToTime(Audio::Timestamp time) = 0;
+
+ virtual bool endOfTrack();
+
+ protected:
+ uint32 _curEdit;
+ QuickTimeDecoder *_decoder;
+ Common::SeekableReadStream *_fd;
+ Track *_parent;
+ };
+
+ // The AudioTrackHandler is currently just a wrapper around some
+ // QuickTimeDecoder functions. Eventually this can be made to
+ // handle multiple audio tracks, but I haven't seen a video with
+ // that yet.
+ class AudioTrackHandler : public TrackHandler {
+ public:
+ AudioTrackHandler(QuickTimeDecoder *decoder, Track *parent);
+ TrackType getTrackType() const { return kTrackTypeAudio; }
+
+ void updateBuffer();
+ void seekToTime(Audio::Timestamp time);
+ bool endOfTrack();
+ };
+
+ // The VideoTrackHandler is the bridge between the time of playback
+ // and the media for the given track. It calculates when to start
+ // tracks and at what rate to play the media using the edit list.
+ class VideoTrackHandler : public TrackHandler {
+ public:
+ VideoTrackHandler(QuickTimeDecoder *decoder, Track *parent);
+ ~VideoTrackHandler();
+
+ TrackType getTrackType() const { return kTrackTypeVideo; }
+
+ const Graphics::Surface *decodeNextFrame();
+
+ uint32 getNextFrameStartTime();
+
+ uint32 getFrameCount();
+
+ int32 getCurFrame() { return _curFrame; }
+
+ Graphics::PixelFormat getPixelFormat() const;
+
+ void seekToTime(Audio::Timestamp time);
+
+ Common::Rational getWidth() const;
+ Common::Rational getHeight() const;
+
+ private:
+ int32 _curFrame;
+ uint32 _nextFrameStartTime;
+ Graphics::Surface *_scaledSurface;
+ bool _holdNextFrameStartTime;
+ int32 _durationOverride;
+
+ Common::SeekableReadStream *getNextFramePacket(uint32 &descId);
+ uint32 getFrameDuration();
+ uint32 findKeyFrame(uint32 frame) const;
+ void enterNewEditList(bool bufferFrames);
+ const Graphics::Surface *bufferNextFrame();
+ uint32 getRateAdjustedFrameTime() const;
+ uint32 getCurEditTimeOffset() const;
+ uint32 getCurEditTrackDuration() const;
+ };
+
+ Common::Array<TrackHandler *> _handlers;
+ VideoTrackHandler *_nextVideoTrack;
+ VideoTrackHandler *findNextVideoTrack() const;
+
+ void freeAllTrackHandlers();
};
} // End of namespace Video