From 307908662af4f171e442e3b6b209e72845f30c06 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 6 Jan 2012 23:00:37 -0500 Subject: VIDEO: Add a PlayStation stream decoder To be used for sword1/sword2 PSX video playback --- video/module.mk | 1 + video/psx_decoder.cpp | 638 ++++++++++++++++++++++++++++++++++++++++++++++++++ video/psx_decoder.h | 120 ++++++++++ 3 files changed, 759 insertions(+) create mode 100644 video/psx_decoder.cpp create mode 100644 video/psx_decoder.h 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..4ed5a16294 --- /dev/null +++ b/video/psx_decoder.cpp @@ -0,0 +1,638 @@ +/* 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 { + +PSXStreamDecoder::PSXStreamDecoder(Common::Rational frameRate) { + assert(frameRate != 0); + initCommon(); + _frameRate = frameRate; + _frameCount = 0; + _speed = kCDUnk; +} + +PSXStreamDecoder::PSXStreamDecoder(CDSpeed speed, uint32 frameCount) { + assert(speed != kCDUnk); + assert(frameCount != 0); + initCommon(); + _frameCount = frameCount; + _speed = speed; + // frame rate will be calculated in loadStream() +} + +PSXStreamDecoder::~PSXStreamDecoder() { + close(); + delete _surface; + delete _huffman; +} + +#define CODE_COUNT 113 +#define HUFFVAL(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_ZEROES(code) (code >> 8) +#define GET_AC(code) ((int)(code & 0xff)) + +static const uint32 s_huffmanCodes[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_huffmanLengths[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_huffmanSymbols[CODE_COUNT] = { + // Regular codes + HUFFVAL(0, 1), HUFFVAL(1, 1), HUFFVAL(0, 2), HUFFVAL(2, 1), HUFFVAL(0, 3), + HUFFVAL(4, 1), HUFFVAL(3, 1), HUFFVAL(7, 1), HUFFVAL(6, 1), HUFFVAL(1, 2), + HUFFVAL(5, 1), HUFFVAL(2, 2), HUFFVAL(9, 1), HUFFVAL(0, 4), HUFFVAL(8, 1), + HUFFVAL(13, 1), HUFFVAL(0, 6), HUFFVAL(12, 1), HUFFVAL(11, 1), HUFFVAL(3, 2), + HUFFVAL(1, 3), HUFFVAL(0, 5), HUFFVAL(10, 1), HUFFVAL(16, 1), HUFFVAL(5, 2), + HUFFVAL(0, 7), HUFFVAL(2, 3), HUFFVAL(1, 4), HUFFVAL(15, 1), HUFFVAL(14, 1), + HUFFVAL(4, 2), HUFFVAL(0, 11), HUFFVAL(8, 2), HUFFVAL(4, 3), HUFFVAL(0, 10), + HUFFVAL(2, 4), HUFFVAL(7, 2), HUFFVAL(21, 1), HUFFVAL(20, 1), HUFFVAL(0, 9), + HUFFVAL(19, 1), HUFFVAL(18, 1), HUFFVAL(1, 5), HUFFVAL(3, 3), HUFFVAL(0, 8), + HUFFVAL(6, 2), HUFFVAL(17, 1), HUFFVAL(10, 2), HUFFVAL(9, 2), HUFFVAL(5, 3), + HUFFVAL(3, 4), HUFFVAL(2, 5), HUFFVAL(1, 7), HUFFVAL(1, 6), HUFFVAL(0, 15), + HUFFVAL(0, 14), HUFFVAL(0, 13), HUFFVAL(0, 12), HUFFVAL(26, 1), HUFFVAL(25, 1), + HUFFVAL(24, 1), HUFFVAL(23, 1), HUFFVAL(22, 1), HUFFVAL(0, 31), HUFFVAL(0, 30), + HUFFVAL(0, 29), HUFFVAL(0, 28), HUFFVAL(0, 27), HUFFVAL(0, 26), HUFFVAL(0, 25), + HUFFVAL(0, 24), HUFFVAL(0, 23), HUFFVAL(0, 22), HUFFVAL(0, 21), HUFFVAL(0, 20), + HUFFVAL(0, 19), HUFFVAL(0, 18), HUFFVAL(0, 17), HUFFVAL(0, 16), HUFFVAL(0, 40), + HUFFVAL(0, 39), HUFFVAL(0, 38), HUFFVAL(0, 37), HUFFVAL(0, 36), HUFFVAL(0, 35), + HUFFVAL(0, 34), HUFFVAL(0, 33), HUFFVAL(0, 32), HUFFVAL(1, 14), HUFFVAL(1, 13), + HUFFVAL(1, 12), HUFFVAL(1, 11), HUFFVAL(1, 10), HUFFVAL(1, 9), HUFFVAL(1, 8), + HUFFVAL(1, 18), HUFFVAL(1, 17), HUFFVAL(1, 16), HUFFVAL(1, 15), HUFFVAL(6, 3), + HUFFVAL(16, 2), HUFFVAL(15, 2), HUFFVAL(14, 2), HUFFVAL(13, 2), HUFFVAL(12, 2), + HUFFVAL(11, 2), HUFFVAL(31, 1), HUFFVAL(30, 1), HUFFVAL(29, 1), HUFFVAL(28, 1), + HUFFVAL(27, 1), + + // Escape code + ESCAPE_CODE, + // End of block code + END_OF_BLOCK +}; + +void PSXStreamDecoder::initCommon() { + _stream = 0; + _audStream = 0; + _surface = new Graphics::Surface(); + _yBuffer = _cbBuffer = _crBuffer = 0; + _huffman = new Common::Huffman(0, CODE_COUNT, s_huffmanCodes, s_huffmanLengths, s_huffmanSymbols); +} + +#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); + + // Calculate frame rate based on CD speed + if (_speed != kCDUnk) { + // TODO: This algorithm is too basic and not accurate enough + // TODO: Count the number of sectors per frame to get a better estimate + _frameRate = Common::Rational(_speed * _frameCount, _stream->size() / RAW_CD_SECTOR_SIZE); + _frameRate.debugPrint(0, "Approximate PSX Stream Frame Rate:"); + } + + 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 FixedRateVideoDecoder::getElapsedTime(); +} + +#define VIDEO_DATA_CHUNK_SIZE 2016 +#define VIDEO_DATA_HEADER_SIZE 56 + +const Graphics::Surface *PSXStreamDecoder::decodeNextFrame() { + Common::SeekableReadStream *sector = 0; + byte *partialFrame = 0; + + while (!endOfVideo()) { + sector = readSector(); + + 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(); + + 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(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(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"); + + 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); + decodeBlock(bits, _cbBuffer + (mbY * pitchC + mbX) * 8, pitchC, scale, version); + decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16, pitchY, scale, version); + decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8, pitchY, scale, version); + decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8 * pitchY, pitchY, scale, version); + decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8 * pitchY + 8, pitchY, scale, version); +} + +// 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; + } +} + +#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 = _huffman->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_ZEROES(symbol); + count += zeroes + 1; + BLOCK_OVERFLOW_CHECK(); + block += zeroes; + + if (bits->getBit()) + *block++ = -GET_AC(symbol); + else + *block++ = GET_AC(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) { + int dc; + + if (version == 2) { + dc = readSignedCoefficient(bits); + } else { + // TODO + error("Unhandled PSX stream version 3 DC"); + } + + int coefficients[8 * 8]; + coefficients[0] = dc; // Start us off with the DC + 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(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..e8de507589 --- /dev/null +++ b/video/psx_decoder.h @@ -0,0 +1,120 @@ +/* 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. + * + * Video decoder used in engines: + * - sword1 (psx) + * - sword2 (psx) + */ +class PSXStreamDecoder : public FixedRateVideoDecoder { +public: + // CD speed in sectors/second + // Calling code should use these enum values instead of the constants + enum CDSpeed { + kCDUnk = 0, + kCD1x = 75, + kCD2x = 150 + }; + + PSXStreamDecoder(Common::Rational frameRate); + PSXStreamDecoder(CDSpeed speed, uint32 frameCount); + 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; + const Graphics::Surface *decodeNextFrame(); + Graphics::PixelFormat getPixelFormat() const { return _surface->format; } + bool endOfVideo() const { return _stream->pos() >= _stream->size(); } + +protected: + // Hardcoded frame rate + Common::Rational getFrameRate() const { return _frameRate; } + +private: + void initCommon(); + Common::SeekableReadStream *_stream; + Graphics::Surface *_surface; + + CDSpeed _speed; + uint32 _frameCount; + Common::Rational _frameRate; + + Audio::SoundHandle _audHandle; + Audio::QueuingAudioStream *_audStream; + void queueAudioFromSector(Common::SeekableReadStream *sector); + + Common::Huffman *_huffman; + 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); + + void dequantizeBlock(int *coefficients, float *block, uint16 scale); + void readAC(Common::BitStream *bits, int *block); + 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 -- cgit v1.2.3 From df21e72fe22132a802856bccc5f4bba27e4d3c17 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Fri, 6 Jan 2012 23:50:58 -0500 Subject: SWORD1: Add support for PSX stream playback --- engines/sword1/animation.cpp | 121 ++++++++++++++++++++++++++++++++++++++----- engines/sword1/animation.h | 11 ++-- engines/sword1/sword1.cpp | 5 +- 3 files changed, 116 insertions(+), 21 deletions(-) diff --git a/engines/sword1/animation.cpp b/engines/sword1/animation.cpp index d55a08293e..9e190e01d0 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", + "", + "" // no credits? +}; + /////////////////////////////////////////////////////////////////////////////// // Basic movie player /////////////////////////////////////////////////////////////////////////////// @@ -72,8 +102,8 @@ MoviePlayer::MoviePlayer(SwordEngine *vm, Text *textMan, ResMan *resMan, Audio:: _decoderType = decoderType; _decoder = decoder; - _white = 255; - _black = 0; + _white = (decoderType == kVideoDecoderPSX) ? _system->getScreenFormat().RGBToColor(0xff, 0xff, 0xff) : 255; + _black = (decoderType == kVideoDecoderPSX) ? _system->getScreenFormat().RGBToColor(0x00, 0x00, 0x00) : 0; } MoviePlayer::~MoviePlayer() { @@ -142,6 +172,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", 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()); @@ -179,6 +224,10 @@ void MoviePlayer::play() { } void MoviePlayer::performPostProcessing(byte *screen) { + // TODO + if (_decoderType == kVideoDecoderPSX) + return; + if (!_movieTexts.empty()) { if (_decoder->getCurFrame() == _movieTexts.front()._startFrame) { _textMan->makeTextSprite(2, (const uint8 *)_movieTexts.front()._text.c_str(), 600, LETTER_COL); @@ -206,10 +255,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] = findWhitePalIndex(); + dst[x] = getWhiteColor(); break; } } @@ -229,12 +278,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(); @@ -246,14 +295,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(); @@ -293,22 +347,44 @@ 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() { +uint32 MoviePlayer::getBlackColor() { return _black; } -byte MoviePlayer::findWhitePalIndex() { +uint32 MoviePlayer::getWhiteColor() { return _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) : _mixer(mixer), _bgSoundHandle(bgSoundHandle) { } @@ -328,6 +404,23 @@ 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()) { + filename = Common::String(sequenceListPSX[id]) + ".str"; + + if (Common::File::exists(filename)) { +#ifdef USE_RGB_COLOR + // All BS1 PSX videos seem to be 15fps + Video::VideoDecoder *psxDecoder = new Video::PSXStreamDecoder(15); + 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)) { diff --git a/engines/sword1/animation.h b/engines/sword1/animation.h index 1c03c66342..23dae7f56d 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 { @@ -80,7 +80,7 @@ protected: OSystem *_system; Common::List _movieTexts; int _textX, _textY, _textWidth, _textHeight; - byte _white, _black; + uint32 _white, _black; DecoderType _decoderType; Video::VideoDecoder *_decoder; @@ -89,9 +89,10 @@ protected: bool playVideo(); void performPostProcessing(byte *screen); + void drawFramePSX(const Graphics::Surface *frame); - byte findBlackPalIndex(); - byte findWhitePalIndex(); + uint32 getBlackColor(); + uint32 getWhiteColor(); }; MoviePlayer *makeMoviePlayer(uint32 id, SwordEngine *vm, Text *textMan, ResMan *resMan, Audio::Mixer *snd, OSystem *system); 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); } -- cgit v1.2.3 From 8812f885bc95f0e4af05c333780eb77a8c2eaa19 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sat, 7 Jan 2012 00:20:27 -0500 Subject: SWORD2: Add support for PSX stream playback --- engines/sword2/animation.cpp | 140 +++++++++++++++++++++++++++++++++---------- engines/sword2/animation.h | 17 +++--- engines/sword2/function.cpp | 4 +- engines/sword2/sword2.cpp | 1 + 4 files changed, 121 insertions(+), 41 deletions(-) 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; -- cgit v1.2.3 From ee35d32a362d58891fedff9843867397f2d4497b Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Mon, 9 Jan 2012 14:52:06 -0500 Subject: VIDEO: Implement PSX stream v3 frame support --- video/psx_decoder.cpp | 166 ++++++++++++++++++++++++++++++++++---------------- video/psx_decoder.h | 17 +++++- 2 files changed, 127 insertions(+), 56 deletions(-) diff --git a/video/psx_decoder.cpp b/video/psx_decoder.cpp index 4ed5a16294..a0705e8804 100644 --- a/video/psx_decoder.cpp +++ b/video/psx_decoder.cpp @@ -58,17 +58,53 @@ PSXStreamDecoder::PSXStreamDecoder(CDSpeed speed, uint32 frameCount) { PSXStreamDecoder::~PSXStreamDecoder() { close(); delete _surface; - delete _huffman; + delete _acHuffman; + delete _dcHuffmanLuma; + delete _dcHuffmanChroma; } -#define CODE_COUNT 113 -#define HUFFVAL(z, a) ((z << 8) | a) +// 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_ZEROES(code) (code >> 8) -#define GET_AC(code) ((int)(code & 0xff)) +#define GET_AC_ZERO_RUN(code) (code >> 8) +#define GET_AC_COEFFICIENT(code) ((int)(code & 0xff)) -static const uint32 s_huffmanCodes[CODE_COUNT] = { +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, @@ -87,7 +123,7 @@ static const uint32 s_huffmanCodes[CODE_COUNT] = { 2 }; -static const byte s_huffmanLengths[CODE_COUNT] = { +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, @@ -106,31 +142,31 @@ static const byte s_huffmanLengths[CODE_COUNT] = { 2 }; -static const uint32 s_huffmanSymbols[CODE_COUNT] = { +static const uint32 s_huffmanACSymbols[AC_CODE_COUNT] = { // Regular codes - HUFFVAL(0, 1), HUFFVAL(1, 1), HUFFVAL(0, 2), HUFFVAL(2, 1), HUFFVAL(0, 3), - HUFFVAL(4, 1), HUFFVAL(3, 1), HUFFVAL(7, 1), HUFFVAL(6, 1), HUFFVAL(1, 2), - HUFFVAL(5, 1), HUFFVAL(2, 2), HUFFVAL(9, 1), HUFFVAL(0, 4), HUFFVAL(8, 1), - HUFFVAL(13, 1), HUFFVAL(0, 6), HUFFVAL(12, 1), HUFFVAL(11, 1), HUFFVAL(3, 2), - HUFFVAL(1, 3), HUFFVAL(0, 5), HUFFVAL(10, 1), HUFFVAL(16, 1), HUFFVAL(5, 2), - HUFFVAL(0, 7), HUFFVAL(2, 3), HUFFVAL(1, 4), HUFFVAL(15, 1), HUFFVAL(14, 1), - HUFFVAL(4, 2), HUFFVAL(0, 11), HUFFVAL(8, 2), HUFFVAL(4, 3), HUFFVAL(0, 10), - HUFFVAL(2, 4), HUFFVAL(7, 2), HUFFVAL(21, 1), HUFFVAL(20, 1), HUFFVAL(0, 9), - HUFFVAL(19, 1), HUFFVAL(18, 1), HUFFVAL(1, 5), HUFFVAL(3, 3), HUFFVAL(0, 8), - HUFFVAL(6, 2), HUFFVAL(17, 1), HUFFVAL(10, 2), HUFFVAL(9, 2), HUFFVAL(5, 3), - HUFFVAL(3, 4), HUFFVAL(2, 5), HUFFVAL(1, 7), HUFFVAL(1, 6), HUFFVAL(0, 15), - HUFFVAL(0, 14), HUFFVAL(0, 13), HUFFVAL(0, 12), HUFFVAL(26, 1), HUFFVAL(25, 1), - HUFFVAL(24, 1), HUFFVAL(23, 1), HUFFVAL(22, 1), HUFFVAL(0, 31), HUFFVAL(0, 30), - HUFFVAL(0, 29), HUFFVAL(0, 28), HUFFVAL(0, 27), HUFFVAL(0, 26), HUFFVAL(0, 25), - HUFFVAL(0, 24), HUFFVAL(0, 23), HUFFVAL(0, 22), HUFFVAL(0, 21), HUFFVAL(0, 20), - HUFFVAL(0, 19), HUFFVAL(0, 18), HUFFVAL(0, 17), HUFFVAL(0, 16), HUFFVAL(0, 40), - HUFFVAL(0, 39), HUFFVAL(0, 38), HUFFVAL(0, 37), HUFFVAL(0, 36), HUFFVAL(0, 35), - HUFFVAL(0, 34), HUFFVAL(0, 33), HUFFVAL(0, 32), HUFFVAL(1, 14), HUFFVAL(1, 13), - HUFFVAL(1, 12), HUFFVAL(1, 11), HUFFVAL(1, 10), HUFFVAL(1, 9), HUFFVAL(1, 8), - HUFFVAL(1, 18), HUFFVAL(1, 17), HUFFVAL(1, 16), HUFFVAL(1, 15), HUFFVAL(6, 3), - HUFFVAL(16, 2), HUFFVAL(15, 2), HUFFVAL(14, 2), HUFFVAL(13, 2), HUFFVAL(12, 2), - HUFFVAL(11, 2), HUFFVAL(31, 1), HUFFVAL(30, 1), HUFFVAL(29, 1), HUFFVAL(28, 1), - HUFFVAL(27, 1), + 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, @@ -143,7 +179,9 @@ void PSXStreamDecoder::initCommon() { _audStream = 0; _surface = new Graphics::Surface(); _yBuffer = _cbBuffer = _crBuffer = 0; - _huffman = new Common::Huffman(0, CODE_COUNT, s_huffmanCodes, s_huffmanLengths, s_huffmanSymbols); + _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); } #define RAW_CD_SECTOR_SIZE 2352 @@ -448,6 +486,9 @@ void PSXStreamDecoder::decodeFrame(Common::SeekableReadStream *frame) { 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); @@ -461,12 +502,12 @@ void PSXStreamDecoder::decodeMacroBlock(Common::BitStream *bits, int mbX, int mb int pitchC = _macroBlocksW * 8; // Note the strange order of red before blue - decodeBlock(bits, _crBuffer + (mbY * pitchC + mbX) * 8, pitchC, scale, version); - decodeBlock(bits, _cbBuffer + (mbY * pitchC + mbX) * 8, pitchC, scale, version); - decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16, pitchY, scale, version); - decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8, pitchY, scale, version); - decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8 * pitchY, pitchY, scale, version); - decodeBlock(bits, _yBuffer + (mbY * pitchY + mbX) * 16 + 8 * pitchY + 8, pitchY, scale, version); + 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 @@ -503,6 +544,32 @@ void PSXStreamDecoder::dequantizeBlock(int *coefficients, float *block, uint16 s } } +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") @@ -515,7 +582,7 @@ void PSXStreamDecoder::readAC(Common::BitStream *bits, int *block) { int count = 0; while (!bits->eos()) { - uint32 symbol = _huffman->getSymbol(*bits); + uint32 symbol = _acHuffman->getSymbol(*bits); if (symbol == ESCAPE_CODE) { // The escape code! @@ -529,15 +596,15 @@ void PSXStreamDecoder::readAC(Common::BitStream *bits, int *block) { break; } else { // Normal huffman code - int zeroes = GET_ZEROES(symbol); + int zeroes = GET_AC_ZERO_RUN(symbol); count += zeroes + 1; BLOCK_OVERFLOW_CHECK(); block += zeroes; if (bits->getBit()) - *block++ = -GET_AC(symbol); + *block++ = -GET_AC_COEFFICIENT(symbol); else - *block++ = GET_AC(symbol); + *block++ = GET_AC_COEFFICIENT(symbol); } } } @@ -603,18 +670,11 @@ void PSXStreamDecoder::idct(float *dequantData, float *result) { } } -void PSXStreamDecoder::decodeBlock(Common::BitStream *bits, byte *block, int pitch, uint16 scale, uint16 version) { - int dc; - - if (version == 2) { - dc = readSignedCoefficient(bits); - } else { - // TODO - error("Unhandled PSX stream version 3 DC"); - } - +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] = dc; // Start us off with the DC + coefficients[0] = readDC(bits, version, plane); readAC(bits, &coefficients[1]); // Read in the AC // Dequantize diff --git a/video/psx_decoder.h b/video/psx_decoder.h index e8de507589..4223f0344e 100644 --- a/video/psx_decoder.h +++ b/video/psx_decoder.h @@ -96,15 +96,26 @@ private: Audio::QueuingAudioStream *_audStream; void queueAudioFromSector(Common::SeekableReadStream *sector); - Common::Huffman *_huffman; + 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); + void decodeBlock(Common::BitStream *bits, byte *block, int pitch, uint16 scale, uint16 version, PlaneType plane); - void dequantizeBlock(int *coefficients, float *block, uint16 scale); 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); -- cgit v1.2.3 From 66cd8bdd68d9e0d14e40c9f55c06f8ea9cf2006b Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Tue, 10 Jan 2012 22:15:04 -0500 Subject: VIDEO: Make PSX streams calculate frame timing solely from CD speed BS2 videos now play at the proper rate and BS1 videos have improved a/v sync. --- engines/sword1/animation.cpp | 4 +-- video/psx_decoder.cpp | 69 ++++++++++++++++++++++---------------------- video/psx_decoder.h | 14 +++------ 3 files changed, 40 insertions(+), 47 deletions(-) diff --git a/engines/sword1/animation.cpp b/engines/sword1/animation.cpp index 9e190e01d0..a1ace0f331 100644 --- a/engines/sword1/animation.cpp +++ b/engines/sword1/animation.cpp @@ -410,8 +410,8 @@ MoviePlayer *makeMoviePlayer(uint32 id, SwordEngine *vm, Text *textMan, ResMan * if (Common::File::exists(filename)) { #ifdef USE_RGB_COLOR - // All BS1 PSX videos seem to be 15fps - Video::VideoDecoder *psxDecoder = new Video::PSXStreamDecoder(15); + // 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")); diff --git a/video/psx_decoder.cpp b/video/psx_decoder.cpp index a0705e8804..7c04b7f041 100644 --- a/video/psx_decoder.cpp +++ b/video/psx_decoder.cpp @@ -38,31 +38,6 @@ namespace Video { -PSXStreamDecoder::PSXStreamDecoder(Common::Rational frameRate) { - assert(frameRate != 0); - initCommon(); - _frameRate = frameRate; - _frameCount = 0; - _speed = kCDUnk; -} - -PSXStreamDecoder::PSXStreamDecoder(CDSpeed speed, uint32 frameCount) { - assert(speed != kCDUnk); - assert(frameCount != 0); - initCommon(); - _frameCount = frameCount; - _speed = speed; - // frame rate will be calculated in loadStream() -} - -PSXStreamDecoder::~PSXStreamDecoder() { - close(); - delete _surface; - delete _acHuffman; - delete _dcHuffmanLuma; - delete _dcHuffmanChroma; -} - // Here are the codes/lengths/symbols that are used for decoding // DC coefficients (version 3 frames only) @@ -174,7 +149,7 @@ static const uint32 s_huffmanACSymbols[AC_CODE_COUNT] = { END_OF_BLOCK }; -void PSXStreamDecoder::initCommon() { +PSXStreamDecoder::PSXStreamDecoder(CDSpeed speed, uint32 frameCount) : _nextFrameStartTime(0, speed), _frameCount(frameCount) { _stream = 0; _audStream = 0; _surface = new Graphics::Surface(); @@ -184,6 +159,14 @@ void PSXStreamDecoder::initCommon() { _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 @@ -227,14 +210,6 @@ bool PSXStreamDecoder::loadStream(Common::SeekableReadStream *stream) { delete sector; _stream->seek(0); - // Calculate frame rate based on CD speed - if (_speed != kCDUnk) { - // TODO: This algorithm is too basic and not accurate enough - // TODO: Count the number of sectors per frame to get a better estimate - _frameRate = Common::Rational(_speed * _frameCount, _stream->size() / RAW_CD_SECTOR_SIZE); - _frameRate.debugPrint(0, "Approximate PSX Stream Frame Rate:"); - } - return true; } @@ -267,7 +242,20 @@ uint32 PSXStreamDecoder::getElapsedTime() const { //if (_audStream) // return _mixer->getSoundElapsedTime(_audHandle); - return FixedRateVideoDecoder::getElapsedTime(); + 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 @@ -276,9 +264,11 @@ uint32 PSXStreamDecoder::getElapsedTime() const { 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"); @@ -322,6 +312,15 @@ const Graphics::Surface *PSXStreamDecoder::decodeNextFrame() { 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 diff --git a/video/psx_decoder.h b/video/psx_decoder.h index 4223f0344e..621870448b 100644 --- a/video/psx_decoder.h +++ b/video/psx_decoder.h @@ -53,18 +53,16 @@ namespace Video { * - sword1 (psx) * - sword2 (psx) */ -class PSXStreamDecoder : public FixedRateVideoDecoder { +class PSXStreamDecoder : public VideoDecoder { public: // CD speed in sectors/second // Calling code should use these enum values instead of the constants enum CDSpeed { - kCDUnk = 0, kCD1x = 75, kCD2x = 150 }; - PSXStreamDecoder(Common::Rational frameRate); - PSXStreamDecoder(CDSpeed speed, uint32 frameCount); + PSXStreamDecoder(CDSpeed speed, uint32 frameCount = 0); virtual ~PSXStreamDecoder(); bool loadStream(Common::SeekableReadStream *stream); @@ -75,22 +73,18 @@ public: 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(); } -protected: - // Hardcoded frame rate - Common::Rational getFrameRate() const { return _frameRate; } - private: void initCommon(); Common::SeekableReadStream *_stream; Graphics::Surface *_surface; - CDSpeed _speed; uint32 _frameCount; - Common::Rational _frameRate; + Audio::Timestamp _nextFrameStartTime; Audio::SoundHandle _audHandle; Audio::QueuingAudioStream *_audStream; -- cgit v1.2.3 From 8fea4968909eb5f3d154ae7f2964afba20033c50 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Tue, 10 Jan 2012 22:36:33 -0500 Subject: VIDEO: Clarify which PSX streams we can play --- video/psx_decoder.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/video/psx_decoder.h b/video/psx_decoder.h index 621870448b..c8ad92c45a 100644 --- a/video/psx_decoder.h +++ b/video/psx_decoder.h @@ -48,6 +48,9 @@ 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) -- cgit v1.2.3 From a352c3cc000fb13f0dae32e84a859a8fb5cb5966 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Mon, 16 Jan 2012 20:27:36 -0500 Subject: SWORD1: Add support for the PSX demo videos --- engines/sword1/animation.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/engines/sword1/animation.cpp b/engines/sword1/animation.cpp index a1ace0f331..e25b986d40 100644 --- a/engines/sword1/animation.cpp +++ b/engines/sword1/animation.cpp @@ -88,8 +88,8 @@ static const char *const sequenceListPSX[20] = { "candle1", "geodrop1", "vulture1", - "", - "" // no credits? + "", // not present + "" // credits are not a video }; /////////////////////////////////////////////////////////////////////////////// @@ -173,7 +173,7 @@ bool MoviePlayer::load(uint32 id) { filename = Common::String::format("%s.smk", sequenceList[id]); break; case kVideoDecoderPSX: - filename = Common::String::format("%s.str", sequenceListPSX[id]); + 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); @@ -406,7 +406,8 @@ MoviePlayer *makeMoviePlayer(uint32 id, SwordEngine *vm, Text *textMan, ResMan * // For the PSX version, we'll try the PlayStation stream files if (vm->isPsx()) { - filename = Common::String(sequenceListPSX[id]) + ".str"; + // 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 @@ -450,9 +451,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; } -- cgit v1.2.3 From 7a3e0ea45300ba950d71ba0eae9381a0b0869f01 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Mon, 16 Jan 2012 20:19:17 -0500 Subject: SWORD1: Add some TODO's for PSX stream subtitles And some other minor cleanup --- engines/sword1/animation.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/engines/sword1/animation.cpp b/engines/sword1/animation.cpp index e25b986d40..a3d732e13b 100644 --- a/engines/sword1/animation.cpp +++ b/engines/sword1/animation.cpp @@ -88,7 +88,7 @@ static const char *const sequenceListPSX[20] = { "candle1", "geodrop1", "vulture1", - "", // not present + "", // demo video not present "" // credits are not a video }; @@ -102,8 +102,8 @@ MoviePlayer::MoviePlayer(SwordEngine *vm, Text *textMan, ResMan *resMan, Audio:: _decoderType = decoderType; _decoder = decoder; - _white = (decoderType == kVideoDecoderPSX) ? _system->getScreenFormat().RGBToColor(0xff, 0xff, 0xff) : 255; - _black = (decoderType == kVideoDecoderPSX) ? _system->getScreenFormat().RGBToColor(0x00, 0x00, 0x00) : 0; + _white = 255; + _black = 0; } MoviePlayer::~MoviePlayer() { @@ -224,8 +224,9 @@ void MoviePlayer::play() { } void MoviePlayer::performPostProcessing(byte *screen) { - // TODO - if (_decoderType == kVideoDecoderPSX) + // 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()) { @@ -361,11 +362,11 @@ bool MoviePlayer::playVideo() { } uint32 MoviePlayer::getBlackColor() { - return _black; + return (_decoderType == kVideoDecoderPSX) ? g_system->getScreenFormat().RGBToColor(0x00, 0x00, 0x00) : _black; } uint32 MoviePlayer::getWhiteColor() { - return _white; + return (_decoderType == kVideoDecoderPSX) ? g_system->getScreenFormat().RGBToColor(0xFF, 0xFF, 0xFF) : _white; } void MoviePlayer::drawFramePSX(const Graphics::Surface *frame) { -- cgit v1.2.3