diff options
-rw-r--r-- | engines/sword1/animation.cpp | 147 | ||||
-rw-r--r-- | engines/sword1/animation.h | 13 | ||||
-rw-r--r-- | engines/sword1/sword1.cpp | 5 | ||||
-rw-r--r-- | engines/sword2/animation.cpp | 140 | ||||
-rw-r--r-- | engines/sword2/animation.h | 17 | ||||
-rw-r--r-- | engines/sword2/function.cpp | 4 | ||||
-rw-r--r-- | engines/sword2/sword2.cpp | 1 | ||||
-rw-r--r-- | video/module.mk | 1 | ||||
-rw-r--r-- | video/psx_decoder.cpp | 697 | ||||
-rw-r--r-- | video/psx_decoder.h | 128 |
10 files changed, 1087 insertions, 66 deletions
diff --git a/engines/sword1/animation.cpp b/engines/sword1/animation.cpp index e274e02cfd..1e2964054a 100644 --- a/engines/sword1/animation.cpp +++ b/engines/sword1/animation.cpp @@ -37,6 +37,11 @@ #include "gui/message.h" +#include "video/psx_decoder.h" +#include "video/smk_decoder.h" + +#include "engines/util.h" + namespace Sword1 { static const char *const sequenceList[20] = { @@ -62,6 +67,31 @@ static const char *const sequenceList[20] = { "credits", // 19 CD2 credits, to follow "finale" sequence }; +// This is the list of the names of the PlayStation videos +// TODO: fight.str, flashy.str, +static const char *const sequenceListPSX[20] = { + "e_ferr1", + "ladder1", + "steps1", + "sewer1", + "e_intro1", + "river1", + "truck1", + "grave1", + "montfcn1", + "tapesty1", + "ireland1", + "e_fin1", + "e_hist1", + "spanish1", + "well1", + "candle1", + "geodrop1", + "vulture1", + "", // demo video not present + "" // credits are not a video +}; + /////////////////////////////////////////////////////////////////////////////// // Basic movie player /////////////////////////////////////////////////////////////////////////////// @@ -150,6 +180,21 @@ bool MoviePlayer::load(uint32 id) { case kVideoDecoderSMK: filename = Common::String::format("%s.smk", sequenceList[id]); break; + case kVideoDecoderPSX: + filename = Common::String::format("%s.str", (_vm->_systemVars.isDemo) ? sequenceList[id] : sequenceListPSX[id]); + + // Need to switch to true color + initGraphics(g_system->getWidth(), g_system->getHeight(), true, 0); + + // Need to load here in case it fails in which case we'd need + // to go back to paletted mode + if (_decoder->loadFile(filename)) { + return true; + } else { + initGraphics(g_system->getWidth(), g_system->getHeight(), true); + return false; + } + break; } return _decoder->loadFile(filename.c_str()); @@ -187,6 +232,11 @@ void MoviePlayer::play() { } void MoviePlayer::performPostProcessing(byte *screen) { + // TODO: We don't support the PSX stream videos yet + // nor using the PSX fonts to display subtitles. + if (_vm->isPsx()) + return; + if (!_movieTexts.empty()) { if (_decoder->getCurFrame() == _movieTexts.front()._startFrame) { _textMan->makeTextSprite(2, (const uint8 *)_movieTexts.front()._text.c_str(), 600, LETTER_COL); @@ -215,10 +265,10 @@ void MoviePlayer::performPostProcessing(byte *screen) { for (x = 0; x < _textWidth; x++) { switch (src[x]) { case BORDER_COL: - dst[x] = findBlackPalIndex(); + dst[x] = getBlackColor(); break; case LETTER_COL: - dst[x] = findTextColorPalIndex(); + dst[x] = findTextColor(); break; } } @@ -238,12 +288,12 @@ void MoviePlayer::performPostProcessing(byte *screen) { for (y = 0; y < _textHeight; y++) { if (_textY + y < frameY || _textY + y >= frameY + frameHeight) { - memset(dst + _textX, findBlackPalIndex(), _textWidth); + memset(dst + _textX, getBlackColor(), _textWidth); } else { if (frameX > _textX) - memset(dst + _textX, findBlackPalIndex(), frameX - _textX); + memset(dst + _textX, getBlackColor(), frameX - _textX); if (frameX + frameWidth < _textX + _textWidth) - memset(dst + frameX + frameWidth, findBlackPalIndex(), _textX + _textWidth - (frameX + frameWidth)); + memset(dst + frameX + frameWidth, getBlackColor(), _textX + _textWidth - (frameX + frameWidth)); } dst += _system->getWidth(); @@ -255,14 +305,19 @@ void MoviePlayer::performPostProcessing(byte *screen) { } bool MoviePlayer::playVideo() { + bool skipped = false; uint16 x = (g_system->getWidth() - _decoder->getWidth()) / 2; uint16 y = (g_system->getHeight() - _decoder->getHeight()) / 2; - while (!_vm->shouldQuit() && !_decoder->endOfVideo()) { + while (!_vm->shouldQuit() && !_decoder->endOfVideo() && !skipped) { if (_decoder->needsUpdate()) { const Graphics::Surface *frame = _decoder->decodeNextFrame(); - if (frame) - _vm->_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, frame->w, frame->h); + if (frame) { + if (_decoderType == kVideoDecoderPSX) + drawFramePSX(frame); + else + _vm->_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, frame->w, frame->h); + } if (_decoder->hasDirtyPalette()) { _decoder->setSystemPalette(); @@ -362,19 +417,40 @@ bool MoviePlayer::playVideo() { Common::Event event; while (_vm->_system->getEventManager()->pollEvent(event)) if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP) - return false; + skipped = true; _vm->_system->delayMillis(10); } - return !_vm->shouldQuit(); + if (_decoderType == kVideoDecoderPSX) { + // Need to jump back to paletted color + initGraphics(g_system->getWidth(), g_system->getHeight(), true); + } + + return !_vm->shouldQuit() && !skipped; } -byte MoviePlayer::findBlackPalIndex() { - return _black; +uint32 MoviePlayer::getBlackColor() { + return (_decoderType == kVideoDecoderPSX) ? g_system->getScreenFormat().RGBToColor(0x00, 0x00, 0x00) : _black; } - -byte MoviePlayer::findTextColorPalIndex() { + +uint32 MoviePlayer::findTextColor() { + if (_decoderType == kVideoDecoderPSX) { + // We're in true color mode, so return the actual colors + switch (_textColor) { + case 1: + return g_system->getScreenFormat().RGBToColor(248, 252, 248); + case 2: + return g_system->getScreenFormat().RGBToColor(184, 188, 184); + case 3: + return g_system->getScreenFormat().RGBToColor(200, 120, 184); + case 4: + return g_system->getScreenFormat().RGBToColor(80, 152, 184); + } + + return g_system->getScreenFormat().RGBToColor(0xFF, 0xFF, 0xFF); + } + switch (_textColor) { case 1: return _c1Color; @@ -413,6 +489,23 @@ void MoviePlayer::convertColor(byte r, byte g, byte b, float &h, float &s, float } } +void MoviePlayer::drawFramePSX(const Graphics::Surface *frame) { + // The PSX videos have half resolution + + Graphics::Surface scaledFrame; + scaledFrame.create(frame->w, frame->h * 2, frame->format); + + for (int y = 0; y < scaledFrame.h; y++) + memcpy(scaledFrame.getBasePtr(0, y), frame->getBasePtr(0, y / 2), scaledFrame.w * scaledFrame.format.bytesPerPixel); + + uint16 x = (g_system->getWidth() - scaledFrame.w) / 2; + uint16 y = (g_system->getHeight() - scaledFrame.h) / 2; + + _vm->_system->copyRectToScreen((byte *)scaledFrame.pixels, scaledFrame.pitch, x, y, scaledFrame.w, scaledFrame.h); + + scaledFrame.free(); +} + DXADecoderWithSound::DXADecoderWithSound(Audio::Mixer *mixer, Audio::SoundHandle *bgSoundHandle) : _mixer(mixer), _bgSoundHandle(bgSoundHandle) { } @@ -432,6 +525,24 @@ MoviePlayer *makeMoviePlayer(uint32 id, SwordEngine *vm, Text *textMan, ResMan * Common::String filename; Audio::SoundHandle *bgSoundHandle = new Audio::SoundHandle; + // For the PSX version, we'll try the PlayStation stream files + if (vm->isPsx()) { + // The demo uses the normal file names + filename = ((vm->_systemVars.isDemo) ? Common::String(sequenceList[id]) : Common::String(sequenceListPSX[id])) + ".str"; + + if (Common::File::exists(filename)) { +#ifdef USE_RGB_COLOR + // All BS1 PSX videos run the videos at 2x speed + Video::VideoDecoder *psxDecoder = new Video::PSXStreamDecoder(Video::PSXStreamDecoder::kCD2x); + return new MoviePlayer(vm, textMan, resMan, snd, system, bgSoundHandle, psxDecoder, kVideoDecoderPSX); +#else + GUI::MessageDialog dialog(Common::String::format(_("PSX stream cutscene '%s' cannot be played in paletted mode"), filename.c_str()), _("OK")); + dialog.runModal(); + return 0; +#endif + } + } + filename = Common::String::format("%s.smk", sequenceList[id]); if (Common::File::exists(filename)) { @@ -461,9 +572,11 @@ MoviePlayer *makeMoviePlayer(uint32 id, SwordEngine *vm, Text *textMan, ResMan * return NULL; } - Common::String buf = Common::String::format(_("Cutscene '%s' not found"), sequenceList[id]); - GUI::MessageDialog dialog(buf, _("OK")); - dialog.runModal(); + if (!vm->isPsx() || scumm_stricmp(sequenceList[id], "enddemo") != 0) { + Common::String buf = Common::String::format(_("Cutscene '%s' not found"), sequenceList[id]); + GUI::MessageDialog dialog(buf, _("OK")); + dialog.runModal(); + } return NULL; } diff --git a/engines/sword1/animation.h b/engines/sword1/animation.h index c436607211..f64b03dd1b 100644 --- a/engines/sword1/animation.h +++ b/engines/sword1/animation.h @@ -24,7 +24,6 @@ #define SWORD1_ANIMATION_H #include "video/dxa_decoder.h" -#include "video/smk_decoder.h" #include "video/video_decoder.h" #include "common/list.h" @@ -38,7 +37,8 @@ namespace Sword1 { enum DecoderType { kVideoDecoderDXA = 0, - kVideoDecoderSMK = 1 + kVideoDecoderSMK = 1, + kVideoDecoderPSX = 2 }; class MovieText { @@ -83,8 +83,8 @@ protected: Common::List<MovieText> _movieTexts; int _textX, _textY, _textWidth, _textHeight; int _textColor; - byte _black; - byte _c1Color, _c2Color, _c3Color, _c4Color; + uint32 _black; + uint32 _c1Color, _c2Color, _c3Color, _c4Color; DecoderType _decoderType; Video::VideoDecoder *_decoder; @@ -93,9 +93,10 @@ protected: bool playVideo(); void performPostProcessing(byte *screen); + void drawFramePSX(const Graphics::Surface *frame); - byte findBlackPalIndex(); - byte findTextColorPalIndex(); + uint32 getBlackColor(); + uint32 findTextColor(); void convertColor(byte r, byte g, byte b, float &h, float &s, float &v); }; diff --git a/engines/sword1/sword1.cpp b/engines/sword1/sword1.cpp index 865e025786..75e8f72d9d 100644 --- a/engines/sword1/sword1.cpp +++ b/engines/sword1/sword1.cpp @@ -62,8 +62,9 @@ SwordEngine::SwordEngine(OSystem *syst) SearchMan.addSubDirectoryMatching(gameDataDir, "speech"); SearchMan.addSubDirectoryMatching(gameDataDir, "video"); SearchMan.addSubDirectoryMatching(gameDataDir, "smackshi"); - SearchMan.addSubDirectoryMatching(gameDataDir, "english");//PSX Demo - SearchMan.addSubDirectoryMatching(gameDataDir, "italian");//PSX Demo + SearchMan.addSubDirectoryMatching(gameDataDir, "streams"); // PSX videos + SearchMan.addSubDirectoryMatching(gameDataDir, "english"); // PSX Demo + SearchMan.addSubDirectoryMatching(gameDataDir, "italian"); // PSX Demo _console = new SwordConsole(this); } diff --git a/engines/sword2/animation.cpp b/engines/sword2/animation.cpp index 80b4809722..e77ae98163 100644 --- a/engines/sword2/animation.cpp +++ b/engines/sword2/animation.cpp @@ -40,6 +40,11 @@ #include "gui/message.h" +#include "video/smk_decoder.h" +#include "video/psx_decoder.h" + +#include "engines/util.h" + namespace Sword2 { /////////////////////////////////////////////////////////////////////////////// @@ -66,6 +71,10 @@ MoviePlayer::~MoviePlayer() { * @param id the id of the file */ bool MoviePlayer::load(const char *name) { + // This happens when quitting during the "eye" cutscene. + if (_vm->shouldQuit()) + return false; + if (_decoderType == kVideoDecoderDXA) _bgSoundStream = Audio::SeekableAudioStream::openStreamFile(name); else @@ -81,16 +90,26 @@ bool MoviePlayer::load(const char *name) { case kVideoDecoderSMK: filename = Common::String::format("%s.smk", name); break; + case kVideoDecoderPSX: + filename = Common::String::format("%s.str", name); + + // Need to switch to true color + initGraphics(640, 480, true, 0); + + // Need to load here in case it fails in which case we'd need + // to go back to paletted mode + if (_decoder->loadFile(filename)) { + return true; + } else { + initGraphics(640, 480, true); + return false; + } } return _decoder->loadFile(filename.c_str()); } void MoviePlayer::play(MovieText *movieTexts, uint32 numMovieTexts, uint32 leadIn, uint32 leadOut) { - // This happens when quitting during the "eye" cutscene. - if (_vm->shouldQuit()) - return; - _leadOutFrame = _decoder->getFrameCount(); if (_leadOutFrame > 60) _leadOutFrame -= 60; @@ -120,6 +139,11 @@ void MoviePlayer::play(MovieText *movieTexts, uint32 numMovieTexts, uint32 leadI while (_snd->isSoundHandleActive(*_bgSoundHandle)) _system->delayMillis(100); + + if (_decoderType == kVideoDecoderPSX) { + // Need to jump back to paletted color + initGraphics(640, 480, true); + } } void MoviePlayer::openTextObject(uint32 index) { @@ -165,7 +189,7 @@ void MoviePlayer::openTextObject(uint32 index) { } } -void MoviePlayer::closeTextObject(uint32 index, byte *screen, uint16 pitch) { +void MoviePlayer::closeTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch) { if (index < _numMovieTexts) { MovieText *text = &_movieTexts[index]; @@ -180,23 +204,23 @@ void MoviePlayer::closeTextObject(uint32 index, byte *screen, uint16 pitch) { int frameWidth = _decoder->getWidth(); int frameHeight = _decoder->getHeight(); + + if (_decoderType == kVideoDecoderPSX) + frameHeight *= 2; + int frameX = (_system->getWidth() - frameWidth) / 2; int frameY = (_system->getHeight() - frameHeight) / 2; - byte black = findBlackPalIndex(); - - byte *dst = screen + _textY * pitch; + uint32 black = getBlackColor(); for (int y = 0; y < text->_textSprite.h; y++) { if (_textY + y < frameY || _textY + y >= frameY + frameHeight) { - memset(dst + _textX, black, text->_textSprite.w); + screen->hLine(_textX, _textY + y, _textX + text->_textSprite.w, black); } else { if (frameX > _textX) - memset(dst + _textX, black, frameX - _textX); + screen->hLine(_textX, _textY + y, frameX, black); if (frameX + frameWidth < _textX + text->_textSprite.w) - memset(dst + frameX + frameWidth, black, _textX + text->_textSprite.w - (frameX + frameWidth)); + screen->hLine(frameX + frameWidth, _textY + y, text->_textSprite.w, black); } - - dst += pitch; } } @@ -206,11 +230,24 @@ void MoviePlayer::closeTextObject(uint32 index, byte *screen, uint16 pitch) { } } -void MoviePlayer::drawTextObject(uint32 index, byte *screen, uint16 pitch) { +#define PUT_PIXEL(c) \ + switch (screen->format.bytesPerPixel) { \ + case 1: \ + *dst = (c); \ + break; \ + case 2: \ + WRITE_UINT16(dst, (c)); \ + break; \ + case 4: \ + WRITE_UINT32(dst, (c)); \ + break; \ + } + +void MoviePlayer::drawTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch) { MovieText *text = &_movieTexts[index]; - byte white = findWhitePalIndex(); - byte black = findBlackPalIndex(); + uint32 white = getWhiteColor(); + uint32 black = getBlackColor(); if (text->_textMem && _textSurface) { byte *src = text->_textSprite.data; @@ -226,17 +263,20 @@ void MoviePlayer::drawTextObject(uint32 index, byte *screen, uint16 pitch) { src = psxSpriteBuffer; } - byte *dst = screen + _textY * pitch + _textX; - for (int y = 0; y < height; y++) { + byte *dst = (byte *)screen->getBasePtr(_textX, _textY + y); + for (int x = 0; x < width; x++) { - if (src[x] == 1) - dst[x] = black; - else if (src[x] == 255) - dst[x] = white; + if (src[x] == 1) { + PUT_PIXEL(black); + } else if (src[x] == 255) { + PUT_PIXEL(white); + } + + dst += screen->format.bytesPerPixel; } + src += width; - dst += pitch; } // Free buffer used to resize psx sprite @@ -245,7 +285,9 @@ void MoviePlayer::drawTextObject(uint32 index, byte *screen, uint16 pitch) { } } -void MoviePlayer::performPostProcessing(byte *screen, uint16 pitch) { +#undef PUT_PIXEL + +void MoviePlayer::performPostProcessing(Graphics::Surface *screen, uint16 pitch) { MovieText *text; int frame = _decoder->getCurFrame(); @@ -286,8 +328,12 @@ bool MoviePlayer::playVideo() { while (!_vm->shouldQuit() && !_decoder->endOfVideo()) { if (_decoder->needsUpdate()) { const Graphics::Surface *frame = _decoder->decodeNextFrame(); - if (frame) - _vm->_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, frame->w, frame->h); + if (frame) { + if (_decoderType == kVideoDecoderPSX) + drawFramePSX(frame); + else + _vm->_system->copyRectToScreen((byte *)frame->pixels, frame->pitch, x, y, frame->w, frame->h); + } if (_decoder->hasDirtyPalette()) { _decoder->setSystemPalette(); @@ -319,7 +365,7 @@ bool MoviePlayer::playVideo() { } Graphics::Surface *screen = _vm->_system->lockScreen(); - performPostProcessing((byte *)screen->pixels, screen->pitch); + performPostProcessing(screen, screen->pitch); _vm->_system->unlockScreen(); _vm->_system->updateScreen(); } @@ -335,12 +381,29 @@ bool MoviePlayer::playVideo() { return !_vm->shouldQuit(); } -byte MoviePlayer::findBlackPalIndex() { - return _black; +uint32 MoviePlayer::getBlackColor() { + return (_decoderType == kVideoDecoderPSX) ? g_system->getScreenFormat().RGBToColor(0x00, 0x00, 0x00) : _black; } -byte MoviePlayer::findWhitePalIndex() { - return _white; +uint32 MoviePlayer::getWhiteColor() { + return (_decoderType == kVideoDecoderPSX) ? g_system->getScreenFormat().RGBToColor(0xFF, 0xFF, 0xFF) : _white; +} + +void MoviePlayer::drawFramePSX(const Graphics::Surface *frame) { + // The PSX videos have half resolution + + Graphics::Surface scaledFrame; + scaledFrame.create(frame->w, frame->h * 2, frame->format); + + for (int y = 0; y < scaledFrame.h; y++) + memcpy(scaledFrame.getBasePtr(0, y), frame->getBasePtr(0, y / 2), scaledFrame.w * scaledFrame.format.bytesPerPixel); + + uint16 x = (g_system->getWidth() - scaledFrame.w) / 2; + uint16 y = (g_system->getHeight() - scaledFrame.h) / 2; + + _vm->_system->copyRectToScreen((byte *)scaledFrame.pixels, scaledFrame.pitch, x, y, scaledFrame.w, scaledFrame.h); + + scaledFrame.free(); } DXADecoderWithSound::DXADecoderWithSound(Audio::Mixer *mixer, Audio::SoundHandle *bgSoundHandle) @@ -358,10 +421,23 @@ uint32 DXADecoderWithSound::getElapsedTime() const { // Factory function for creating the appropriate cutscene player /////////////////////////////////////////////////////////////////////////////// -MoviePlayer *makeMoviePlayer(const char *name, Sword2Engine *vm, Audio::Mixer *snd, OSystem *system) { +MoviePlayer *makeMoviePlayer(const char *name, Sword2Engine *vm, Audio::Mixer *snd, OSystem *system, uint32 frameCount) { Common::String filename; Audio::SoundHandle *bgSoundHandle = new Audio::SoundHandle; + filename = Common::String::format("%s.str", name); + + if (Common::File::exists(filename)) { +#ifdef USE_RGB_COLOR + Video::VideoDecoder *psxDecoder = new Video::PSXStreamDecoder(Video::PSXStreamDecoder::kCD2x, frameCount); + return new MoviePlayer(vm, snd, system, bgSoundHandle, psxDecoder, kVideoDecoderPSX); +#else + GUI::MessageDialog dialog(_("PSX cutscenes found but ScummVM has been built without RGB color support"), _("OK")); + dialog.runModal(); + return NULL; +#endif + } + filename = Common::String::format("%s.smk", name); if (Common::File::exists(filename)) { diff --git a/engines/sword2/animation.h b/engines/sword2/animation.h index 1f5fced03b..3ef8dac754 100644 --- a/engines/sword2/animation.h +++ b/engines/sword2/animation.h @@ -26,7 +26,6 @@ #define SWORD2_ANIMATION_H #include "video/dxa_decoder.h" -#include "video/smk_decoder.h" #include "video/video_decoder.h" #include "audio/mixer.h" @@ -36,7 +35,8 @@ namespace Sword2 { enum DecoderType { kVideoDecoderDXA = 0, - kVideoDecoderSMK = 1 + kVideoDecoderSMK = 1, + kVideoDecoderPSX = 2 }; struct MovieText { @@ -93,18 +93,19 @@ protected: uint32 _leadOut; int _leadOutFrame; - void performPostProcessing(byte *screen, uint16 pitch); + void performPostProcessing(Graphics::Surface *screen, uint16 pitch); bool playVideo(); + void drawFramePSX(const Graphics::Surface *frame); void openTextObject(uint32 index); - void closeTextObject(uint32 index, byte *screen, uint16 pitch); - void drawTextObject(uint32 index, byte *screen, uint16 pitch); + void closeTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch); + void drawTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch); - byte findBlackPalIndex(); - byte findWhitePalIndex(); + uint32 getBlackColor(); + uint32 getWhiteColor(); }; -MoviePlayer *makeMoviePlayer(const char *name, Sword2Engine *vm, Audio::Mixer *snd, OSystem *system); +MoviePlayer *makeMoviePlayer(const char *name, Sword2Engine *vm, Audio::Mixer *snd, OSystem *system, uint32 frameCount); } // End of namespace Sword2 diff --git a/engines/sword2/function.cpp b/engines/sword2/function.cpp index 60ee6176a4..836b252d6c 100644 --- a/engines/sword2/function.cpp +++ b/engines/sword2/function.cpp @@ -2137,7 +2137,9 @@ int32 Logic::fnPlaySequence(int32 *params) { // pause sfx during sequence _vm->_sound->pauseFx(); - _moviePlayer = makeMoviePlayer(filename, _vm, _vm->_mixer, _vm->_system); + uint32 frameCount = Sword2Engine::isPsx() ? params[1] : 0; + + _moviePlayer = makeMoviePlayer(filename, _vm, _vm->_mixer, _vm->_system, frameCount); if (_moviePlayer && _moviePlayer->load(filename)) { _moviePlayer->play(_sequenceTextList, _sequenceTextLines, _smackerLeadIn, _smackerLeadOut); diff --git a/engines/sword2/sword2.cpp b/engines/sword2/sword2.cpp index 3b7965259c..bdfc388c5f 100644 --- a/engines/sword2/sword2.cpp +++ b/engines/sword2/sword2.cpp @@ -255,6 +255,7 @@ Sword2Engine::Sword2Engine(OSystem *syst) : Engine(syst), _rnd("sword2") { SearchMan.addSubDirectoryMatching(gameDataDir, "sword2"); SearchMan.addSubDirectoryMatching(gameDataDir, "video"); SearchMan.addSubDirectoryMatching(gameDataDir, "smacks"); + SearchMan.addSubDirectoryMatching(gameDataDir, "streams"); // PSX video if (!scumm_stricmp(ConfMan.get("gameid").c_str(), "sword2demo") || !scumm_stricmp(ConfMan.get("gameid").c_str(), "sword2psxdemo")) _features = GF_DEMO; 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 |