From a652f6669e2e9225aee17431784b433397b41ae3 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Mon, 23 Jul 2012 21:04:51 -0400 Subject: VIDEO: Rewrite SmackerDecoder to use the new API --- engines/agos/animation.cpp | 12 +- engines/agos/animation.h | 4 +- engines/saga/introproc_saga2.cpp | 4 +- engines/scumm/he/animation_he.cpp | 5 +- engines/sword1/animation.cpp | 9 +- engines/sword2/animation.cpp | 9 +- engines/toon/movie.cpp | 35 ++-- engines/toon/movie.h | 14 +- engines/toon/toon.cpp | 2 +- video/smk_decoder.cpp | 391 ++++++++++++++++++++------------------ video/smk_decoder.h | 117 ++++++++---- 11 files changed, 338 insertions(+), 264 deletions(-) diff --git a/engines/agos/animation.cpp b/engines/agos/animation.cpp index 10c01741ae..3e8488d7d5 100644 --- a/engines/agos/animation.cpp +++ b/engines/agos/animation.cpp @@ -415,7 +415,7 @@ void MoviePlayerDXA::updateBalance() { MoviePlayerSMK::MoviePlayerSMK(AGOSEngine_Feeble *vm, const char *name) - : MoviePlayer(vm), SmackerDecoder(vm->_mixer) { + : MoviePlayer(vm), SmackerDecoder() { debug(0, "Creating SMK cutscene player"); memset(baseName, 0, sizeof(baseName)); @@ -431,12 +431,12 @@ bool MoviePlayerSMK::load() { if (!loadStream(videoStream)) error("Failed to load video stream from file %s", videoName.c_str()); + start(); + debug(0, "Playing video %s", videoName.c_str()); CursorMan.showMouse(false); - _firstFrameOffset = _fileStream->pos(); - return true; } @@ -477,10 +477,8 @@ void MoviePlayerSMK::handleNextFrame() { } void MoviePlayerSMK::nextFrame() { - if (_vm->_interactiveVideo == TYPE_LOOPING && endOfVideo()) { - _fileStream->seek(_firstFrameOffset); - _curFrame = -1; - } + if (_vm->_interactiveVideo == TYPE_LOOPING && endOfVideo()) + rewind(); if (!endOfVideo()) { decodeNextFrame(); diff --git a/engines/agos/animation.h b/engines/agos/animation.h index d1ff074b03..37a666b201 100644 --- a/engines/agos/animation.h +++ b/engines/agos/animation.h @@ -67,9 +67,6 @@ protected: virtual void handleNextFrame(); virtual bool processFrame() = 0; virtual void startSound() {} - -protected: - uint32 _firstFrameOffset; }; class MoviePlayerDXA : public MoviePlayer, Video::DXADecoder { @@ -93,6 +90,7 @@ private: bool processFrame(); void startSound(); void copyFrameToBuffer(byte *dst, uint x, uint y, uint pitch); + uint32 _firstFrameOffset; }; class MoviePlayerSMK : public MoviePlayer, Video::SmackerDecoder { diff --git a/engines/saga/introproc_saga2.cpp b/engines/saga/introproc_saga2.cpp index b6470370af..15f7f4dc15 100644 --- a/engines/saga/introproc_saga2.cpp +++ b/engines/saga/introproc_saga2.cpp @@ -92,7 +92,7 @@ int Scene::FTA2EndProc(FTA2Endings whichEnding) { } void Scene::playMovie(const char *filename) { - Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder(_vm->_mixer); + Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder(); if (!smkDecoder->loadFile(filename)) return; @@ -101,6 +101,8 @@ void Scene::playMovie(const char *filename) { uint16 y = (g_system->getHeight() - smkDecoder->getHeight()) / 2; bool skipVideo = false; + smkDecoder->start(); + while (!_vm->shouldQuit() && !smkDecoder->endOfVideo() && !skipVideo) { if (smkDecoder->needsUpdate()) { const Graphics::Surface *frame = smkDecoder->decodeNextFrame(); diff --git a/engines/scumm/he/animation_he.cpp b/engines/scumm/he/animation_he.cpp index 40e99c26a8..b37a565aab 100644 --- a/engines/scumm/he/animation_he.cpp +++ b/engines/scumm/he/animation_he.cpp @@ -40,7 +40,10 @@ MoviePlayer::MoviePlayer(ScummEngine_v90he *vm, Audio::Mixer *mixer) : _vm(vm) { _video = new Video::BinkDecoder(); else #endif - _video = new Video::SmackerDecoder(mixer); + { + _video = new Video::SmackerDecoder(); + ((Video::AdvancedVideoDecoder *)_video)->start(); + } _flags = 0; _wizResNum = 0; diff --git a/engines/sword1/animation.cpp b/engines/sword1/animation.cpp index 49c5ef7312..8f863d1e09 100644 --- a/engines/sword1/animation.cpp +++ b/engines/sword1/animation.cpp @@ -179,6 +179,13 @@ bool MoviePlayer::load(uint32 id) { break; case kVideoDecoderSMK: filename = Common::String::format("%s.smk", sequenceList[id]); + + if (_decoder->loadFile(filename)) { + ((Video::AdvancedVideoDecoder *)_decoder)->start(); // TODO: Remove after new API is complete + return true; + } else { + return false; + } break; case kVideoDecoderPSX: filename = Common::String::format("%s.str", (_vm->_systemVars.isDemo) ? sequenceList[id] : sequenceListPSX[id]); @@ -547,7 +554,7 @@ MoviePlayer *makeMoviePlayer(uint32 id, SwordEngine *vm, Text *textMan, ResMan * filename = Common::String::format("%s.smk", sequenceList[id]); if (Common::File::exists(filename)) { - Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder(snd); + Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder(); return new MoviePlayer(vm, textMan, resMan, snd, system, bgSoundHandle, smkDecoder, kVideoDecoderSMK); } diff --git a/engines/sword2/animation.cpp b/engines/sword2/animation.cpp index c1cf33ff09..e257ec9029 100644 --- a/engines/sword2/animation.cpp +++ b/engines/sword2/animation.cpp @@ -89,6 +89,13 @@ bool MoviePlayer::load(const char *name) { break; case kVideoDecoderSMK: filename = Common::String::format("%s.smk", name); + + if (_decoder->loadFile(filename)) { + ((Video::AdvancedVideoDecoder *)_decoder)->start(); // TODO: Remove after new API is complete + return true; + } else { + return false; + } break; case kVideoDecoderPSX: filename = Common::String::format("%s.str", name); @@ -442,7 +449,7 @@ MoviePlayer *makeMoviePlayer(const char *name, Sword2Engine *vm, Audio::Mixer *s filename = Common::String::format("%s.smk", name); if (Common::File::exists(filename)) { - Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder(snd); + Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder(); return new MoviePlayer(vm, snd, system, bgSoundHandle, smkDecoder, kVideoDecoderSMK); } diff --git a/engines/toon/movie.cpp b/engines/toon/movie.cpp index 93e41adf57..d988a3ed60 100644 --- a/engines/toon/movie.cpp +++ b/engines/toon/movie.cpp @@ -33,6 +33,10 @@ namespace Toon { +ToonstruckSmackerDecoder::ToonstruckSmackerDecoder() : Video::SmackerDecoder() { + _lowRes = false; +} + void ToonstruckSmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize) { debugC(6, kDebugMovie, "handleAudioTrack(%d, %d, %d)", track, chunkSize, unpackedSize); @@ -40,33 +44,21 @@ void ToonstruckSmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, ui /* uint16 width = */ _fileStream->readUint16LE(); uint16 height = _fileStream->readUint16LE(); _lowRes = (height == getHeight() / 2); - } else + } else { Video::SmackerDecoder::handleAudioTrack(track, chunkSize, unpackedSize); + } } -bool ToonstruckSmackerDecoder::loadFile(const Common::String &filename) { - debugC(1, kDebugMovie, "loadFile(%s)", filename.c_str()); +bool ToonstruckSmackerDecoder::loadStream(Common::SeekableReadStream *stream) { + if (!Video::SmackerDecoder::loadStream(stream)) + return false; _lowRes = false; - - if (Video::SmackerDecoder::loadFile(filename)) { - if (_surface->h == 200) { - if (_surface) { - _surface->free(); - delete _surface; - } - _surface = new Graphics::Surface(); - _surface->create(640, 400, Graphics::PixelFormat::createFormatCLUT8()); - _header.flags = 4; - } - - return true; - } - return false; + return true; } -ToonstruckSmackerDecoder::ToonstruckSmackerDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) : Video::SmackerDecoder(mixer, soundType) { - _lowRes = false; +Video::SmackerDecoder::SmackerVideoTrack *ToonstruckSmackerDecoder::createVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) const { + return Video::SmackerDecoder::createVideoTrack(width, height, frameCount, frameRate, (height == 200) ? 4 : flags, signature); } // decoder is deallocated with Movie destruction i.e. new ToonstruckSmackerDecoder is needed @@ -103,6 +95,9 @@ void Movie::play(const Common::String &video, int32 flags) { bool Movie::playVideo(bool isFirstIntroVideo) { debugC(1, kDebugMovie, "playVideo(isFirstIntroVideo: %d)", isFirstIntroVideo); + + _decoder->start(); + while (!_vm->shouldQuit() && !_decoder->endOfVideo()) { if (_decoder->needsUpdate()) { const Graphics::Surface *frame = _decoder->decodeNextFrame(); diff --git a/engines/toon/movie.h b/engines/toon/movie.h index 2cd33302f2..e795182cba 100644 --- a/engines/toon/movie.h +++ b/engines/toon/movie.h @@ -30,13 +30,17 @@ namespace Toon { class ToonstruckSmackerDecoder : public Video::SmackerDecoder { public: - ToonstruckSmackerDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType); - virtual ~ToonstruckSmackerDecoder() {} - void handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize); - bool loadFile(const Common::String &filename); + ToonstruckSmackerDecoder(); + + bool loadStream(Common::SeekableReadStream *stream); bool isLowRes() { return _lowRes; } + protected: - bool _lowRes; + void handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize); + SmackerVideoTrack *createVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) const; + +private: + bool _lowRes; }; class Movie { diff --git a/engines/toon/toon.cpp b/engines/toon/toon.cpp index ee427652d8..9fd8415676 100644 --- a/engines/toon/toon.cpp +++ b/engines/toon/toon.cpp @@ -51,7 +51,7 @@ void ToonEngine::init() { _currentScriptRegion = 0; _resources = new Resources(this); _animationManager = new AnimationManager(this); - _moviePlayer = new Movie(this, new ToonstruckSmackerDecoder(_mixer)); + _moviePlayer = new Movie(this, new ToonstruckSmackerDecoder()); _hotspots = new Hotspots(this); _mainSurface = new Graphics::Surface(); diff --git a/video/smk_decoder.cpp b/video/smk_decoder.cpp index 359f4cb9bd..d707ad519f 100644 --- a/video/smk_decoder.cpp +++ b/video/smk_decoder.cpp @@ -204,8 +204,7 @@ BigHuffmanTree::BigHuffmanTree(Common::BitStream &bs, int allocSize) delete _hiBytes; } -BigHuffmanTree::~BigHuffmanTree() -{ +BigHuffmanTree::~BigHuffmanTree() { delete[] _tree; } @@ -278,24 +277,17 @@ uint32 BigHuffmanTree::getCode(Common::BitStream &bs) { return v; } -SmackerDecoder::SmackerDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) - : _audioStarted(false), _audioStream(0), _mixer(mixer), _soundType(soundType) { - _surface = 0; +SmackerDecoder::SmackerDecoder(Audio::Mixer::SoundType soundType) : _soundType(soundType) { _fileStream = 0; - _dirtyPalette = false; + _firstFrameStart = 0; + _frameTypes = 0; + _frameSizes = 0; } SmackerDecoder::~SmackerDecoder() { close(); } -uint32 SmackerDecoder::getTime() const { - if (_audioStream && _audioStarted) - return _mixer->getSoundElapsedTime(_audioHandle); - - return FixedRateVideoDecoder::getTime(); -} - bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) { close(); @@ -309,16 +301,17 @@ bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) { uint32 width = _fileStream->readUint32LE(); uint32 height = _fileStream->readUint32LE(); - _frameCount = _fileStream->readUint32LE(); - int32 frameRate = _fileStream->readSint32LE(); - - // framerate contains 2 digits after the comma, so 1497 is actually 14.97 fps - if (frameRate > 0) - _frameRate = Common::Rational(1000, frameRate); - else if (frameRate < 0) - _frameRate = Common::Rational(100000, -frameRate); + uint32 frameCount = _fileStream->readUint32LE(); + int32 frameDelay = _fileStream->readSint32LE(); + + // frame rate contains 2 digits after the comma, so 1497 is actually 14.97 fps + Common::Rational frameRate; + if (frameDelay > 0) + frameRate = Common::Rational(1000, frameDelay); + else if (frameDelay < 0) + frameRate = Common::Rational(100000, -frameDelay); else - _frameRate = 1000; + frameRate = 1000; // Flags are determined by which bit is set, which can be one of the following: // 0 - set to 1 if file contains a ring frame. @@ -328,6 +321,9 @@ bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) { // before it is displayed. _header.flags = _fileStream->readUint32LE(); + SmackerVideoTrack *videoTrack = createVideoTrack(width, height, frameCount, frameRate, _header.flags, _header.signature); + addTrack(videoTrack); + // TODO: should we do any extra processing for Smacker files with ring frames? // TODO: should we do any extra processing for Y-doubled videos? Are they the @@ -374,92 +370,78 @@ bool SmackerDecoder::loadStream(Common::SeekableReadStream *stream) { warning("Unhandled Smacker v2 audio compression"); if (i == 0) - _audioStream = Audio::makeQueuingAudioStream(_header.audioInfo[0].sampleRate, _header.audioInfo[0].isStereo); + addTrack(new SmackerAudioTrack(_header.audioInfo[i], _soundType)); } } _header.dummy = _fileStream->readUint32LE(); - _frameSizes = new uint32[_frameCount]; - for (i = 0; i < _frameCount; ++i) + _frameSizes = new uint32[frameCount]; + for (i = 0; i < frameCount; ++i) _frameSizes[i] = _fileStream->readUint32LE(); - _frameTypes = new byte[_frameCount]; - for (i = 0; i < _frameCount; ++i) + _frameTypes = new byte[frameCount]; + for (i = 0; i < frameCount; ++i) _frameTypes[i] = _fileStream->readByte(); byte *huffmanTrees = (byte *) malloc(_header.treesSize); _fileStream->read(huffmanTrees, _header.treesSize); Common::BitStream8LSB bs(new Common::MemoryReadStream(huffmanTrees, _header.treesSize, DisposeAfterUse::YES), true); + videoTrack->readTrees(bs, _header.mMapSize, _header.mClrSize, _header.fullSize, _header.typeSize); - _MMapTree = new BigHuffmanTree(bs, _header.mMapSize); - _MClrTree = new BigHuffmanTree(bs, _header.mClrSize); - _FullTree = new BigHuffmanTree(bs, _header.fullSize); - _TypeTree = new BigHuffmanTree(bs, _header.typeSize); - - _surface = new Graphics::Surface(); + _firstFrameStart = _fileStream->pos(); - // Height needs to be doubled if we have flags (Y-interlaced or Y-doubled) - _surface->create(width, height * (_header.flags ? 2 : 1), Graphics::PixelFormat::createFormatCLUT8()); - - memset(_palette, 0, 3 * 256); return true; } void SmackerDecoder::close() { - if (!_fileStream) - return; - - if (_audioStream) { - if (_audioStarted) { - // The mixer will delete the stream. - _mixer->stopHandle(_audioHandle); - _audioStarted = false; - } else { - delete _audioStream; - } - _audioStream = 0; - } + AdvancedVideoDecoder::close(); delete _fileStream; _fileStream = 0; - _surface->free(); - delete _surface; - _surface = 0; - - delete _MMapTree; - delete _MClrTree; - delete _FullTree; - delete _TypeTree; + delete[] _frameTypes; + _frameTypes = 0; delete[] _frameSizes; - delete[] _frameTypes; + _frameSizes = 0; +} - reset(); +bool SmackerDecoder::rewind() { + // Call the parent method to rewind the tracks first + // In particular, only videos without sound can be rewound + if (!AdvancedVideoDecoder::rewind()) + return false; + + // And seek back to where the first frame begins + _fileStream->seek(_firstFrameStart); + return true; } -const Graphics::Surface *SmackerDecoder::decodeNextFrame() { +void SmackerDecoder::readNextPacket() { + SmackerVideoTrack *videoTrack = (SmackerVideoTrack *)getTrack(0); + + if (videoTrack->endOfTrack()) + return; + + videoTrack->increaseCurFrame(); + uint i; uint32 chunkSize = 0; uint32 dataSizeUnpacked = 0; uint32 startPos = _fileStream->pos(); - _curFrame++; - // Check if we got a frame with palette data, and // call back the virtual setPalette function to set // the current palette - if (_frameTypes[_curFrame] & 1) { - unpackPalette(); - _dirtyPalette = true; - } + if (_frameTypes[videoTrack->getCurFrame()] & 1) + videoTrack->unpackPalette(_fileStream); // Load audio tracks for (i = 0; i < 7; ++i) { - if (!(_frameTypes[_curFrame] & (2 << i))) + if (!(_frameTypes[videoTrack->getCurFrame()] & (2 << i))) continue; chunkSize = _fileStream->readUint32LE(); @@ -475,29 +457,109 @@ const Graphics::Surface *SmackerDecoder::decodeNextFrame() { handleAudioTrack(i, chunkSize, dataSizeUnpacked); } - uint32 frameSize = _frameSizes[_curFrame] & ~3; -// uint32 remainder = _frameSizes[_curFrame] & 3; + uint32 frameSize = _frameSizes[videoTrack->getCurFrame()] & ~3; +// uint32 remainder = _frameSizes[videoTrack->getCurFrame()] & 3; if (_fileStream->pos() - startPos > frameSize) error("Smacker actual frame size exceeds recorded frame size"); uint32 frameDataSize = frameSize - (_fileStream->pos() - startPos); - _frameData = (byte *)malloc(frameDataSize + 1); + byte *frameData = (byte *)malloc(frameDataSize + 1); // Padding to keep the BigHuffmanTrees from reading past the data end - _frameData[frameDataSize] = 0x00; + frameData[frameDataSize] = 0x00; + + _fileStream->read(frameData, frameDataSize); - _fileStream->read(_frameData, frameDataSize); + Common::BitStream8LSB bs(new Common::MemoryReadStream(frameData, frameDataSize + 1, DisposeAfterUse::YES), true); + videoTrack->decodeFrame(bs); - Common::BitStream8LSB bs(new Common::MemoryReadStream(_frameData, frameDataSize + 1, DisposeAfterUse::YES), true); + _fileStream->seek(startPos + frameSize); +} +void SmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize) { + if (_header.audioInfo[track].hasAudio && chunkSize > 0 && track == 0) { + // Get the audio track, which start at offset 1 (first track is video) + SmackerAudioTrack *audioTrack = (SmackerAudioTrack *)getTrack(track + 1); + + // If it's track 0, play the audio data + byte *soundBuffer = (byte *)malloc(chunkSize + 1); + // Padding to keep the SmallHuffmanTrees from reading past the data end + soundBuffer[chunkSize] = 0x00; + + _fileStream->read(soundBuffer, chunkSize); + + if (_header.audioInfo[track].compression == kCompressionRDFT || _header.audioInfo[track].compression == kCompressionDCT) { + // TODO: Compressed audio (Bink RDFT/DCT encoded) + free(soundBuffer); + return; + } else if (_header.audioInfo[track].compression == kCompressionDPCM) { + // Compressed audio (Huffman DPCM encoded) + audioTrack->queueCompressedBuffer(soundBuffer, chunkSize + 1, unpackedSize); + free(soundBuffer); + } else { + // Uncompressed audio (PCM) + audioTrack->queuePCM(soundBuffer, chunkSize); + } + } else { + // Ignore the rest of the audio tracks, if they exist + // TODO: Are there any Smacker videos with more than one audio stream? + // If yes, we should play the rest of the audio streams as well + if (chunkSize > 0) + _fileStream->skip(chunkSize); + } +} + +SmackerDecoder::SmackerVideoTrack::SmackerVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) { + _surface = new Graphics::Surface(); + _surface->create(width, height * (flags ? 2 : 1), Graphics::PixelFormat::createFormatCLUT8()); + _frameCount = frameCount; + _frameRate = frameRate; + _flags = flags; + _signature = signature; + _curFrame = -1; + _dirtyPalette = false; + _MMapTree = _MClrTree = _FullTree = _TypeTree = 0; + memset(_palette, 0, 3 * 256); +} + +SmackerDecoder::SmackerVideoTrack::~SmackerVideoTrack() { + _surface->free(); + delete _surface; + + delete _MMapTree; + delete _MClrTree; + delete _FullTree; + delete _TypeTree; +} + +uint16 SmackerDecoder::SmackerVideoTrack::getWidth() const { + return _surface->w; +} + +uint16 SmackerDecoder::SmackerVideoTrack::getHeight() const { + return _surface->h; +} + +Graphics::PixelFormat SmackerDecoder::SmackerVideoTrack::getPixelFormat() const { + return _surface->format; +} + +void SmackerDecoder::SmackerVideoTrack::readTrees(Common::BitStream &bs, uint32 mMapSize, uint32 mClrSize, uint32 fullSize, uint32 typeSize) { + _MMapTree = new BigHuffmanTree(bs, mMapSize); + _MClrTree = new BigHuffmanTree(bs, mClrSize); + _FullTree = new BigHuffmanTree(bs, fullSize); + _TypeTree = new BigHuffmanTree(bs, typeSize); +} + +void SmackerDecoder::SmackerVideoTrack::decodeFrame(Common::BitStream &bs) { _MMapTree->reset(); _MClrTree->reset(); _FullTree->reset(); _TypeTree->reset(); // Height needs to be doubled if we have flags (Y-interlaced or Y-doubled) - uint doubleY = _header.flags ? 2 : 1; + uint doubleY = _flags ? 2 : 1; uint bw = getWidth() / 4; uint bh = getHeight() / doubleY / 4; @@ -508,6 +570,7 @@ const Graphics::Surface *SmackerDecoder::decodeNextFrame() { uint type, run, j, mode; uint32 p1, p2, clr, map; byte hi, lo; + uint i; while (block < blocks) { type = _TypeTree->getCode(bs); @@ -536,7 +599,7 @@ const Graphics::Surface *SmackerDecoder::decodeNextFrame() { break; case SMK_BLOCK_FULL: // Smacker v2 has one mode, Smacker v4 has three - if (_header.signature == MKTAG('S','M','K','2')) { + if (_signature == MKTAG('S','M','K','2')) { mode = 0; } else { // 00 - mode 0 @@ -628,60 +691,75 @@ const Graphics::Surface *SmackerDecoder::decodeNextFrame() { break; } } +} - _fileStream->seek(startPos + frameSize); +void SmackerDecoder::SmackerVideoTrack::unpackPalette(Common::SeekableReadStream *stream) { + uint startPos = stream->pos(); + uint32 len = 4 * stream->readByte(); - if (_curFrame == 0) - _startTime = g_system->getMillis(); + byte *chunk = (byte *)malloc(len); + stream->read(chunk, len); + byte *p = chunk; - return _surface; -} + byte oldPalette[3 * 256]; + memcpy(oldPalette, _palette, 3 * 256); -void SmackerDecoder::handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize) { - if (_header.audioInfo[track].hasAudio && chunkSize > 0 && track == 0) { - // If it's track 0, play the audio data - byte *soundBuffer = (byte *)malloc(chunkSize + 1); - // Padding to keep the SmallHuffmanTrees from reading past the data end - soundBuffer[chunkSize] = 0x00; + byte *pal = _palette; - _fileStream->read(soundBuffer, chunkSize); + int sz = 0; + byte b0; + while (sz < 256) { + b0 = *p++; + if (b0 & 0x80) { // if top bit is 1 (0x80 = 10000000) + sz += (b0 & 0x7f) + 1; // get lower 7 bits + 1 (0x7f = 01111111) + pal += 3 * ((b0 & 0x7f) + 1); + } else if (b0 & 0x40) { // if top 2 bits are 01 (0x40 = 01000000) + byte c = (b0 & 0x3f) + 1; // get lower 6 bits + 1 (0x3f = 00111111) + uint s = 3 * *p++; + sz += c; - if (_header.audioInfo[track].compression == kCompressionRDFT || _header.audioInfo[track].compression == kCompressionDCT) { - // TODO: Compressed audio (Bink RDFT/DCT encoded) - free(soundBuffer); - return; - } else if (_header.audioInfo[track].compression == kCompressionDPCM) { - // Compressed audio (Huffman DPCM encoded) - queueCompressedBuffer(soundBuffer, chunkSize + 1, unpackedSize, track); - free(soundBuffer); - } else { - // Uncompressed audio (PCM) - byte flags = 0; - if (_header.audioInfo[track].is16Bits) - flags = flags | Audio::FLAG_16BITS; - if (_header.audioInfo[track].isStereo) - flags = flags | Audio::FLAG_STEREO; - - _audioStream->queueBuffer(soundBuffer, chunkSize, DisposeAfterUse::YES, flags); - // The sound buffer will be deleted by QueuingAudioStream - } + while (c--) { + *pal++ = oldPalette[s + 0]; + *pal++ = oldPalette[s + 1]; + *pal++ = oldPalette[s + 2]; + s += 3; + } + } else { // top 2 bits are 00 + sz++; + // get the lower 6 bits for each component (0x3f = 00111111) + byte b = b0 & 0x3f; + byte g = (*p++) & 0x3f; + byte r = (*p++) & 0x3f; + + assert(g < 0xc0 && b < 0xc0); - if (!_audioStarted) { - _mixer->playStream(_soundType, &_audioHandle, _audioStream, -1, getVolume(), getBalance()); - _audioStarted = true; + // upscale to full 8-bit color values by multiplying by 4 + *pal++ = b * 4; + *pal++ = g * 4; + *pal++ = r * 4; } - } else { - // Ignore the rest of the audio tracks, if they exist - // TODO: Are there any Smacker videos with more than one audio stream? - // If yes, we should play the rest of the audio streams as well - if (chunkSize > 0) - _fileStream->skip(chunkSize); } + + stream->seek(startPos + len); + free(chunk); + + _dirtyPalette = true; } -void SmackerDecoder::queueCompressedBuffer(byte *buffer, uint32 bufferSize, - uint32 unpackedSize, int streamNum) { +SmackerDecoder::SmackerAudioTrack::SmackerAudioTrack(const AudioInfo &audioInfo, Audio::Mixer::SoundType soundType) : + _audioInfo(audioInfo), _soundType(soundType) { + _audioStream = Audio::makeQueuingAudioStream(_audioInfo.sampleRate, _audioInfo.isStereo); +} +SmackerDecoder::SmackerAudioTrack::~SmackerAudioTrack() { + delete _audioStream; +} + +Audio::AudioStream *SmackerDecoder::SmackerAudioTrack::getAudioStream() const { + return _audioStream; +} + +void SmackerDecoder::SmackerAudioTrack::queueCompressedBuffer(byte *buffer, uint32 bufferSize, uint32 unpackedSize) { Common::BitStream8LSB audioBS(new Common::MemoryReadStream(buffer, bufferSize), true); bool dataPresent = audioBS.getBit(); @@ -689,9 +767,9 @@ void SmackerDecoder::queueCompressedBuffer(byte *buffer, uint32 bufferSize, return; bool isStereo = audioBS.getBit(); - assert(isStereo == _header.audioInfo[streamNum].isStereo); + assert(isStereo == _audioInfo.isStereo); bool is16Bits = audioBS.getBit(); - assert(is16Bits == _header.audioInfo[streamNum].is16Bits); + assert(is16Bits == _audioInfo.is16Bits); int numBytes = 1 * (isStereo ? 2 : 1) * (is16Bits ? 2 : 1); @@ -759,74 +837,21 @@ void SmackerDecoder::queueCompressedBuffer(byte *buffer, uint32 bufferSize, for (int k = 0; k < numBytes; k++) delete audioTrees[k]; - byte flags = 0; - if (_header.audioInfo[0].is16Bits) - flags = flags | Audio::FLAG_16BITS; - if (_header.audioInfo[0].isStereo) - flags = flags | Audio::FLAG_STEREO; - _audioStream->queueBuffer(unpackedBuffer, unpackedSize, DisposeAfterUse::YES, flags); - // unpackedBuffer will be deleted by QueuingAudioStream + queuePCM(unpackedBuffer, unpackedSize); } -void SmackerDecoder::unpackPalette() { - uint startPos = _fileStream->pos(); - uint32 len = 4 * _fileStream->readByte(); - - byte *chunk = (byte *)malloc(len); - _fileStream->read(chunk, len); - byte *p = chunk; - - byte oldPalette[3*256]; - memcpy(oldPalette, _palette, 3 * 256); - - byte *pal = _palette; - - int sz = 0; - byte b0; - while (sz < 256) { - b0 = *p++; - if (b0 & 0x80) { // if top bit is 1 (0x80 = 10000000) - sz += (b0 & 0x7f) + 1; // get lower 7 bits + 1 (0x7f = 01111111) - pal += 3 * ((b0 & 0x7f) + 1); - } else if (b0 & 0x40) { // if top 2 bits are 01 (0x40 = 01000000) - byte c = (b0 & 0x3f) + 1; // get lower 6 bits + 1 (0x3f = 00111111) - uint s = 3 * *p++; - sz += c; - - while (c--) { - *pal++ = oldPalette[s + 0]; - *pal++ = oldPalette[s + 1]; - *pal++ = oldPalette[s + 2]; - s += 3; - } - } else { // top 2 bits are 00 - sz++; - // get the lower 6 bits for each component (0x3f = 00111111) - byte b = b0 & 0x3f; - byte g = (*p++) & 0x3f; - byte r = (*p++) & 0x3f; - - assert(g < 0xc0 && b < 0xc0); - - // upscale to full 8-bit color values by multiplying by 4 - *pal++ = b * 4; - *pal++ = g * 4; - *pal++ = r * 4; - } - } - - _fileStream->seek(startPos + len); - free(chunk); -} +void SmackerDecoder::SmackerAudioTrack::queuePCM(byte *buffer, uint32 bufferSize) { + byte flags = 0; + if (_audioInfo.is16Bits) + flags |= Audio::FLAG_16BITS; + if (_audioInfo.isStereo) + flags |= Audio::FLAG_STEREO; -void SmackerDecoder::updateVolume() { - if (g_system->getMixer()->isSoundHandleActive(_audioHandle)) - g_system->getMixer()->setChannelVolume(_audioHandle, getVolume()); + _audioStream->queueBuffer(buffer, bufferSize, DisposeAfterUse::YES, flags); } -void SmackerDecoder::updateBalance() { - if (g_system->getMixer()->isSoundHandleActive(_audioHandle)) - g_system->getMixer()->setChannelBalance(_audioHandle, getBalance()); +SmackerDecoder::SmackerVideoTrack *SmackerDecoder::createVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) const { + return new SmackerVideoTrack(width, height, frameCount, frameRate, flags, signature); } } // End of namespace Video diff --git a/video/smk_decoder.h b/video/smk_decoder.h index 516882e7c8..78a4ded0fc 100644 --- a/video/smk_decoder.h +++ b/video/smk_decoder.h @@ -34,6 +34,7 @@ class QueuingAudioStream; } namespace Common { +class BitStream; class SeekableReadStream; } @@ -56,42 +57,72 @@ class BigHuffmanTree; * - sword2 * - toon */ -class SmackerDecoder : public FixedRateVideoDecoder { +class SmackerDecoder : public AdvancedVideoDecoder { public: - SmackerDecoder(Audio::Mixer *mixer, - Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType); + SmackerDecoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType); virtual ~SmackerDecoder(); - bool loadStream(Common::SeekableReadStream *stream); + virtual bool loadStream(Common::SeekableReadStream *stream); void close(); - bool isVideoLoaded() const { return _fileStream != 0; } - uint16 getWidth() const { return _surface->w; } - uint16 getHeight() const { return _surface->h; } - uint32 getFrameCount() const { return _frameCount; } - uint32 getTime() const; - const Graphics::Surface *decodeNextFrame(); - Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); } - const byte *getPalette() { _dirtyPalette = false; return _palette; } - bool hasDirtyPalette() const { return _dirtyPalette; } - virtual void handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize); + bool rewind(); protected: - Common::SeekableReadStream *_fileStream; + void readNextPacket(); + + virtual void handleAudioTrack(byte track, uint32 chunkSize, uint32 unpackedSize); - // VideoDecoder API - void updateVolume(); - void updateBalance(); + class SmackerVideoTrack : public FixedRateVideoTrack { + public: + SmackerVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature); + ~SmackerVideoTrack(); - // FixedRateVideoDecoder API - Common::Rational getFrameRate() const { return _frameRate; } + bool isRewindable() const { return true; } + bool rewind() { _curFrame = -1; return true; } -protected: - void unpackPalette(); - // Possible runs of blocks - uint getBlockRun(int index) { return (index <= 58) ? index + 1 : 128 << (index - 59); } - void queueCompressedBuffer(byte *buffer, uint32 bufferSize, uint32 unpackedSize, int streamNum); + uint16 getWidth() const; + uint16 getHeight() const; + Graphics::PixelFormat getPixelFormat() const; + int getCurFrame() const { return _curFrame; } + int getFrameCount() const { return _frameCount; } + const Graphics::Surface *decodeNextFrame() { return _surface; } + const byte *getPalette() const { _dirtyPalette = false; return _palette; } + bool hasDirtyPalette() const { return _dirtyPalette; } + + void readTrees(Common::BitStream &bs, uint32 mMapSize, uint32 mClrSize, uint32 fullSize, uint32 typeSize); + void increaseCurFrame() { _curFrame++; } + void decodeFrame(Common::BitStream &bs); + void unpackPalette(Common::SeekableReadStream *stream); + + protected: + Common::Rational getFrameRate() const { return _frameRate; } + + Graphics::Surface *_surface; + + private: + Common::Rational _frameRate; + uint32 _flags, _signature; + + byte _palette[3 * 256]; + mutable bool _dirtyPalette; + + int _curFrame; + uint32 _frameCount; + + BigHuffmanTree *_MMapTree; + BigHuffmanTree *_MClrTree; + BigHuffmanTree *_FullTree; + BigHuffmanTree *_TypeTree; + // Possible runs of blocks + static uint getBlockRun(int index) { return (index <= 58) ? index + 1 : 128 << (index - 59); } + }; + + virtual SmackerVideoTrack *createVideoTrack(uint32 width, uint32 height, uint32 frameCount, const Common::Rational &frameRate, uint32 flags, uint32 signature) const; + + Common::SeekableReadStream *_fileStream; + +private: enum AudioCompression { kCompressionNone, kCompressionDPCM, @@ -120,6 +151,25 @@ protected: uint32 dummy; } _header; + class SmackerAudioTrack : public AudioTrack { + public: + SmackerAudioTrack(const AudioInfo &audioInfo, Audio::Mixer::SoundType soundType); + ~SmackerAudioTrack(); + + Audio::Mixer::SoundType getSoundType() const { return _soundType; } + + void queueCompressedBuffer(byte *buffer, uint32 bufferSize, uint32 unpackedSize); + void queuePCM(byte *buffer, uint32 bufferSize); + + protected: + Audio::AudioStream *getAudioStream() const; + + private: + Audio::Mixer::SoundType _soundType; + Audio::QueuingAudioStream *_audioStream; + AudioInfo _audioInfo; + }; + uint32 *_frameSizes; // The FrameTypes section of a Smacker file contains an array of bytes, where // the 8 bits of each byte describe the contents of the corresponding frame. @@ -127,25 +177,10 @@ protected: // and so on), so there can be up to 7 different audio tracks. When the lowest bit // (bit 0) is set, it denotes a frame that contains a palette record byte *_frameTypes; - byte *_frameData; - // The RGB palette - byte _palette[3 * 256]; - bool _dirtyPalette; - Common::Rational _frameRate; - uint32 _frameCount; - Graphics::Surface *_surface; + uint32 _firstFrameStart; Audio::Mixer::SoundType _soundType; - Audio::Mixer *_mixer; - bool _audioStarted; - Audio::QueuingAudioStream *_audioStream; - Audio::SoundHandle _audioHandle; - - BigHuffmanTree *_MMapTree; - BigHuffmanTree *_MClrTree; - BigHuffmanTree *_FullTree; - BigHuffmanTree *_TypeTree; }; } // End of namespace Video -- cgit v1.2.3