aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/sword1/animation.cpp147
-rw-r--r--engines/sword1/animation.h13
-rw-r--r--engines/sword1/sword1.cpp5
-rw-r--r--engines/sword2/animation.cpp140
-rw-r--r--engines/sword2/animation.h17
-rw-r--r--engines/sword2/function.cpp4
-rw-r--r--engines/sword2/sword2.cpp1
-rw-r--r--video/module.mk1
-rw-r--r--video/psx_decoder.cpp697
-rw-r--r--video/psx_decoder.h128
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