diff options
Diffstat (limited to 'video')
| -rw-r--r-- | video/avi_decoder.h | 4 | ||||
| -rw-r--r-- | video/bink_decoder.cpp | 64 | ||||
| -rw-r--r-- | video/bink_decoder.h | 15 | ||||
| -rw-r--r-- | video/codecs/cinepak.h | 4 | ||||
| -rw-r--r-- | video/codecs/mjpeg.h | 1 | ||||
| -rw-r--r-- | video/coktel_decoder.cpp | 287 | ||||
| -rw-r--r-- | video/coktel_decoder.h | 16 | ||||
| -rw-r--r-- | video/dxa_decoder.cpp | 12 | ||||
| -rw-r--r-- | video/dxa_decoder.h | 4 | ||||
| -rw-r--r-- | video/module.mk | 1 | ||||
| -rw-r--r-- | video/psx_decoder.cpp | 697 | ||||
| -rw-r--r-- | video/psx_decoder.h | 128 | ||||
| -rw-r--r-- | video/qt_decoder.cpp | 806 | ||||
| -rw-r--r-- | video/qt_decoder.h | 114 |
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 |
