diff options
| author | Willem Jan Palenstijn | 2012-02-23 22:43:56 +0100 | 
|---|---|---|
| committer | Willem Jan Palenstijn | 2012-02-23 22:49:59 +0100 | 
| commit | 4637d55337d083d85bea762db7d59b47e92c0fbe (patch) | |
| tree | 38a53feb1d8ffcbb53a62635da18127e492e2905 | |
| parent | 6e70f7728915b766d647668a156b09f84a3c40cf (diff) | |
| parent | 7a3e0ea45300ba950d71ba0eae9381a0b0869f01 (diff) | |
| download | scummvm-rg350-4637d55337d083d85bea762db7d59b47e92c0fbe.tar.gz scummvm-rg350-4637d55337d083d85bea762db7d59b47e92c0fbe.tar.bz2 scummvm-rg350-4637d55337d083d85bea762db7d59b47e92c0fbe.zip | |
Merge pull request #171 from clone2727/psx-stream-2
This is a manual merge based on clone2727's merge of his branch
with the sword1 subtitle changes on master.
| -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 | 
