SCI: Rewrite RobotDecoder to use the AdvancedVideoDecoder API
This commit is contained in:
parent
0f0c6f9354
commit
d4231fda1c
5 changed files with 298 additions and 259 deletions
|
@ -251,25 +251,25 @@ void Console::postEnter() {
|
||||||
|
|
||||||
if (_videoFile.hasSuffix(".seq")) {
|
if (_videoFile.hasSuffix(".seq")) {
|
||||||
videoDecoder = new SEQDecoder(_videoFrameDelay);
|
videoDecoder = new SEQDecoder(_videoFrameDelay);
|
||||||
((Video::AdvancedVideoDecoder *)videoDecoder)->start(); // TODO: Remove after new API is complete
|
|
||||||
#ifdef ENABLE_SCI32
|
#ifdef ENABLE_SCI32
|
||||||
} else if (_videoFile.hasSuffix(".vmd")) {
|
} else if (_videoFile.hasSuffix(".vmd")) {
|
||||||
videoDecoder = new Video::VMDDecoder(g_system->getMixer());
|
videoDecoder = new Video::VMDDecoder(g_system->getMixer());
|
||||||
} else if (_videoFile.hasSuffix(".rbt")) {
|
} else if (_videoFile.hasSuffix(".rbt")) {
|
||||||
videoDecoder = new RobotDecoder(g_system->getMixer(), _engine->getPlatform() == Common::kPlatformMacintosh);
|
videoDecoder = new RobotDecoder(_engine->getPlatform() == Common::kPlatformMacintosh);
|
||||||
} else if (_videoFile.hasSuffix(".duk")) {
|
} else if (_videoFile.hasSuffix(".duk")) {
|
||||||
duckMode = true;
|
duckMode = true;
|
||||||
videoDecoder = new Video::AVIDecoder();
|
videoDecoder = new Video::AVIDecoder();
|
||||||
((Video::AdvancedVideoDecoder *)videoDecoder)->start();
|
|
||||||
#endif
|
#endif
|
||||||
} else if (_videoFile.hasSuffix(".avi")) {
|
} else if (_videoFile.hasSuffix(".avi")) {
|
||||||
videoDecoder = new Video::AVIDecoder();
|
videoDecoder = new Video::AVIDecoder();
|
||||||
((Video::AdvancedVideoDecoder *)videoDecoder)->start();
|
|
||||||
} else {
|
} else {
|
||||||
warning("Unrecognized video type");
|
warning("Unrecognized video type");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoDecoder && videoDecoder->loadFile(_videoFile)) {
|
if (videoDecoder && videoDecoder->loadFile(_videoFile)) {
|
||||||
|
if (!_videoFile.hasSuffix(".vmd")) // TODO: Remove after new API is complete
|
||||||
|
((Video::AdvancedVideoDecoder *)videoDecoder)->start();
|
||||||
|
|
||||||
_engine->_gfxCursor->kernelHide();
|
_engine->_gfxCursor->kernelHide();
|
||||||
|
|
||||||
#ifdef ENABLE_SCI32
|
#ifdef ENABLE_SCI32
|
||||||
|
|
|
@ -254,6 +254,7 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) {
|
||||||
int16 y = argv[5].toUint16();
|
int16 y = argv[5].toUint16();
|
||||||
warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y);
|
warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y);
|
||||||
g_sci->_robotDecoder->load(id);
|
g_sci->_robotDecoder->load(id);
|
||||||
|
g_sci->_robotDecoder->start();
|
||||||
g_sci->_robotDecoder->setPos(x, y);
|
g_sci->_robotDecoder->setPos(x, y);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -269,13 +270,13 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) {
|
||||||
warning("kRobot(%d)", subop);
|
warning("kRobot(%d)", subop);
|
||||||
break;
|
break;
|
||||||
case 8: // sync
|
case 8: // sync
|
||||||
//if (false) { // debug: automatically skip all robot videos
|
//if (true) { // debug: automatically skip all robot videos
|
||||||
if ((uint32)g_sci->_robotDecoder->getCurFrame() != g_sci->_robotDecoder->getFrameCount() - 1) {
|
if (g_sci->_robotDecoder->endOfVideo()) {
|
||||||
writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG);
|
|
||||||
} else {
|
|
||||||
g_sci->_robotDecoder->close();
|
g_sci->_robotDecoder->close();
|
||||||
// Signal the engine scripts that the video is done
|
// Signal the engine scripts that the video is done
|
||||||
writeSelector(s->_segMan, argv[1], SELECTOR(signal), SIGNAL_REG);
|
writeSelector(s->_segMan, argv[1], SELECTOR(signal), SIGNAL_REG);
|
||||||
|
} else {
|
||||||
|
writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -632,7 +632,7 @@ void SciEngine::initGraphics() {
|
||||||
_gfxPaint = _gfxPaint32;
|
_gfxPaint = _gfxPaint32;
|
||||||
_gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache, _gfxScreen);
|
_gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache, _gfxScreen);
|
||||||
_gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxText32);
|
_gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxText32);
|
||||||
_robotDecoder = new RobotDecoder(g_system->getMixer(), getPlatform() == Common::kPlatformMacintosh);
|
_robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh);
|
||||||
_gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette, _gfxPaint32);
|
_gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette, _gfxPaint32);
|
||||||
} else {
|
} else {
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -22,11 +22,13 @@
|
||||||
|
|
||||||
#include "common/archive.h"
|
#include "common/archive.h"
|
||||||
#include "common/stream.h"
|
#include "common/stream.h"
|
||||||
|
#include "common/substream.h"
|
||||||
#include "common/system.h"
|
#include "common/system.h"
|
||||||
#include "common/textconsole.h"
|
#include "common/textconsole.h"
|
||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
|
|
||||||
#include "graphics/surface.h"
|
#include "graphics/surface.h"
|
||||||
|
#include "audio/audiostream.h"
|
||||||
#include "audio/decoders/raw.h"
|
#include "audio/decoders/raw.h"
|
||||||
|
|
||||||
#include "sci/resource.h"
|
#include "sci/resource.h"
|
||||||
|
@ -63,29 +65,49 @@ namespace Sci {
|
||||||
// our graphics engine, it looks just like a part of the room. A RBT can move
|
// our graphics engine, it looks just like a part of the room. A RBT can move
|
||||||
// around the screen and go behind other objects. (...)
|
// around the screen and go behind other objects. (...)
|
||||||
|
|
||||||
#ifdef ENABLE_SCI32
|
enum RobotPalTypes {
|
||||||
|
|
||||||
enum robotPalTypes {
|
|
||||||
kRobotPalVariable = 0,
|
kRobotPalVariable = 0,
|
||||||
kRobotPalConstant = 1
|
kRobotPalConstant = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
RobotDecoder::RobotDecoder(Audio::Mixer *mixer, bool isBigEndian) {
|
RobotDecoder::RobotDecoder(bool isBigEndian) {
|
||||||
_surface = 0;
|
|
||||||
_width = 0;
|
|
||||||
_height = 0;
|
|
||||||
_fileStream = 0;
|
_fileStream = 0;
|
||||||
_audioStream = 0;
|
|
||||||
_dirtyPalette = false;
|
|
||||||
_pos = Common::Point(0, 0);
|
_pos = Common::Point(0, 0);
|
||||||
_mixer = mixer;
|
|
||||||
_isBigEndian = isBigEndian;
|
_isBigEndian = isBigEndian;
|
||||||
|
_frameTotalSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
RobotDecoder::~RobotDecoder() {
|
RobotDecoder::~RobotDecoder() {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RobotDecoder::loadStream(Common::SeekableReadStream *stream) {
|
||||||
|
close();
|
||||||
|
|
||||||
|
_fileStream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), _isBigEndian, DisposeAfterUse::YES);
|
||||||
|
|
||||||
|
readHeaderChunk();
|
||||||
|
|
||||||
|
// There are several versions of robot files, ranging from 3 to 6.
|
||||||
|
// v3: no known examples
|
||||||
|
// v4: PQ:SWAT demo
|
||||||
|
// v5: SCI2.1 and SCI3 games
|
||||||
|
// v6: SCI3 games
|
||||||
|
if (_header.version < 4 || _header.version > 6)
|
||||||
|
error("Unknown robot version: %d", _header.version);
|
||||||
|
|
||||||
|
RobotVideoTrack *videoTrack = new RobotVideoTrack(_header.frameCount);
|
||||||
|
addTrack(videoTrack);
|
||||||
|
|
||||||
|
if (_header.hasSound)
|
||||||
|
addTrack(new RobotAudioTrack());
|
||||||
|
|
||||||
|
videoTrack->readPaletteChunk(_fileStream, _header.paletteDataSize);
|
||||||
|
readFrameSizesChunk();
|
||||||
|
videoTrack->calculateVideoDimensions(_fileStream, _frameTotalSize);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool RobotDecoder::load(GuiResourceId id) {
|
bool RobotDecoder::load(GuiResourceId id) {
|
||||||
// TODO: RAMA's robot 1003 cannot be played (shown at the menu screen) -
|
// TODO: RAMA's robot 1003 cannot be played (shown at the menu screen) -
|
||||||
// its drawn at odd coordinates. SV can't play it either (along with some
|
// its drawn at odd coordinates. SV can't play it either (along with some
|
||||||
|
@ -109,33 +131,123 @@ bool RobotDecoder::load(GuiResourceId id) {
|
||||||
return loadStream(stream);
|
return loadStream(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RobotDecoder::loadStream(Common::SeekableReadStream *stream) {
|
void RobotDecoder::close() {
|
||||||
close();
|
AdvancedVideoDecoder::close();
|
||||||
|
|
||||||
_fileStream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), _isBigEndian, DisposeAfterUse::YES);
|
delete _fileStream;
|
||||||
_surface = new Graphics::Surface();
|
_fileStream = 0;
|
||||||
|
|
||||||
readHeaderChunk();
|
delete[] _frameTotalSize;
|
||||||
|
_frameTotalSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// There are several versions of robot files, ranging from 3 to 6.
|
void RobotDecoder::readNextPacket() {
|
||||||
// v3: no known examples
|
// Get our track
|
||||||
// v4: PQ:SWAT demo
|
RobotVideoTrack *videoTrack = (RobotVideoTrack *)getTrack(0);
|
||||||
// v5: SCI2.1 and SCI3 games
|
videoTrack->increaseCurFrame();
|
||||||
// v6: SCI3 games
|
Graphics::Surface *surface = videoTrack->getSurface();
|
||||||
if (_header.version < 4 || _header.version > 6)
|
|
||||||
error("Unknown robot version: %d", _header.version);
|
|
||||||
|
|
||||||
if (_header.hasSound) {
|
if (videoTrack->endOfTrack())
|
||||||
_audioStream = Audio::makeQueuingAudioStream(11025, false);
|
return;
|
||||||
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_audioHandle, _audioStream, -1, getVolume(), getBalance());
|
|
||||||
|
// Read frame image header (24 bytes)
|
||||||
|
_fileStream->skip(3);
|
||||||
|
byte frameScale = _fileStream->readByte();
|
||||||
|
uint16 frameWidth = _fileStream->readUint16();
|
||||||
|
uint16 frameHeight = _fileStream->readUint16();
|
||||||
|
_fileStream->skip(4); // unknown, almost always 0
|
||||||
|
uint16 frameX = _fileStream->readUint16();
|
||||||
|
uint16 frameY = _fileStream->readUint16();
|
||||||
|
|
||||||
|
// TODO: In v4 robot files, frameX and frameY have a different meaning.
|
||||||
|
// Set them both to 0 for v4 for now, so that robots in PQ:SWAT show up
|
||||||
|
// correctly.
|
||||||
|
if (_header.version == 4)
|
||||||
|
frameX = frameY = 0;
|
||||||
|
|
||||||
|
uint16 compressedSize = _fileStream->readUint16();
|
||||||
|
uint16 frameFragments = _fileStream->readUint16();
|
||||||
|
_fileStream->skip(4); // unknown
|
||||||
|
uint32 decompressedSize = frameWidth * frameHeight * frameScale / 100;
|
||||||
|
|
||||||
|
// FIXME: A frame's height + position can go off limits... why? With the
|
||||||
|
// following, we cut the contents to fit the frame
|
||||||
|
uint16 scaledHeight = CLIP<uint16>(decompressedSize / frameWidth, 0, surface->h - frameY);
|
||||||
|
|
||||||
|
// FIXME: Same goes for the frame's width + position. In this case, we
|
||||||
|
// modify the position to fit the contents on screen.
|
||||||
|
if (frameWidth + frameX > surface->w)
|
||||||
|
frameX = surface->w - frameWidth;
|
||||||
|
|
||||||
|
assert(frameWidth + frameX <= surface->w && scaledHeight + frameY <= surface->h);
|
||||||
|
|
||||||
|
DecompressorLZS lzs;
|
||||||
|
byte *decompressedFrame = new byte[decompressedSize];
|
||||||
|
byte *outPtr = decompressedFrame;
|
||||||
|
|
||||||
|
if (_header.version == 4) {
|
||||||
|
// v4 has just the one fragment, it seems, and ignores the fragment count
|
||||||
|
Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedSize);
|
||||||
|
lzs.unpack(&fragmentStream, outPtr, compressedSize, decompressedSize);
|
||||||
|
} else {
|
||||||
|
for (uint16 i = 0; i < frameFragments; ++i) {
|
||||||
|
uint32 compressedFragmentSize = _fileStream->readUint32();
|
||||||
|
uint32 decompressedFragmentSize = _fileStream->readUint32();
|
||||||
|
uint16 compressionType = _fileStream->readUint16();
|
||||||
|
|
||||||
|
if (compressionType == 0) {
|
||||||
|
Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedFragmentSize);
|
||||||
|
lzs.unpack(&fragmentStream, outPtr, compressedFragmentSize, decompressedFragmentSize);
|
||||||
|
} else if (compressionType == 2) { // untested
|
||||||
|
_fileStream->read(outPtr, compressedFragmentSize);
|
||||||
|
} else {
|
||||||
|
error("Unknown frame compression found: %d", compressionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
outPtr += decompressedFragmentSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readPaletteChunk(_header.paletteDataSize);
|
// Copy over the decompressed frame
|
||||||
readFrameSizesChunk();
|
byte *inFrame = decompressedFrame;
|
||||||
calculateVideoDimensions();
|
byte *outFrame = (byte *)surface->pixels;
|
||||||
_surface->create(_width, _height, Graphics::PixelFormat::createFormatCLUT8());
|
|
||||||
|
|
||||||
return true;
|
// Black out the surface
|
||||||
|
memset(outFrame, 0, surface->w * surface->h);
|
||||||
|
|
||||||
|
// Move to the correct y coordinate
|
||||||
|
outFrame += surface->w * frameY;
|
||||||
|
|
||||||
|
for (uint16 y = 0; y < scaledHeight; y++) {
|
||||||
|
memcpy(outFrame + frameX, inFrame, frameWidth);
|
||||||
|
inFrame += frameWidth;
|
||||||
|
outFrame += surface->w;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] decompressedFrame;
|
||||||
|
|
||||||
|
uint32 audioChunkSize = _frameTotalSize[videoTrack->getCurFrame()] - (24 + compressedSize);
|
||||||
|
|
||||||
|
// TODO: The audio chunk size below is usually correct, but there are some
|
||||||
|
// exceptions (e.g. robot 4902 in Phantasmagoria, towards its end)
|
||||||
|
#if 0
|
||||||
|
// Read frame audio header (14 bytes)
|
||||||
|
_fileStream->skip(2); // buffer position
|
||||||
|
_fileStream->skip(2); // unknown (usually 1)
|
||||||
|
_fileStream->skip(2); /*uint16 audioChunkSize = _fileStream->readUint16() + 8;*/
|
||||||
|
_fileStream->skip(2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Queue the next audio frame
|
||||||
|
// FIXME: For some reason, there are audio hiccups/gaps
|
||||||
|
if (_header.hasSound) {
|
||||||
|
RobotAudioTrack *audioTrack = (RobotAudioTrack *)getTrack(1);
|
||||||
|
_fileStream->skip(8); // header
|
||||||
|
audioChunkSize -= 8;
|
||||||
|
audioTrack->queueBuffer(g_sci->_audio->getDecodedRobotAudioFrame(_fileStream, audioChunkSize), audioChunkSize * 2);
|
||||||
|
} else {
|
||||||
|
_fileStream->skip(audioChunkSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RobotDecoder::readHeaderChunk() {
|
void RobotDecoder::readHeaderChunk() {
|
||||||
|
@ -160,31 +272,6 @@ void RobotDecoder::readHeaderChunk() {
|
||||||
_fileStream->skip(_header.unkChunkDataSize);
|
_fileStream->skip(_header.unkChunkDataSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RobotDecoder::readPaletteChunk(uint16 chunkSize) {
|
|
||||||
byte *paletteData = new byte[chunkSize];
|
|
||||||
_fileStream->read(paletteData, chunkSize);
|
|
||||||
|
|
||||||
// SCI1.1 palette
|
|
||||||
byte palFormat = paletteData[32];
|
|
||||||
uint16 palColorStart = paletteData[25];
|
|
||||||
uint16 palColorCount = READ_SCI11ENDIAN_UINT16(paletteData + 29);
|
|
||||||
|
|
||||||
int palOffset = 37;
|
|
||||||
memset(_palette, 0, 256 * 3);
|
|
||||||
|
|
||||||
for (uint16 colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) {
|
|
||||||
if (palFormat == kRobotPalVariable)
|
|
||||||
palOffset++;
|
|
||||||
_palette[colorNo * 3 + 0] = paletteData[palOffset++];
|
|
||||||
_palette[colorNo * 3 + 1] = paletteData[palOffset++];
|
|
||||||
_palette[colorNo * 3 + 2] = paletteData[palOffset++];
|
|
||||||
}
|
|
||||||
|
|
||||||
_dirtyPalette = true;
|
|
||||||
delete[] paletteData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void RobotDecoder::readFrameSizesChunk() {
|
void RobotDecoder::readFrameSizesChunk() {
|
||||||
// The robot video file contains 2 tables, with one entry for each frame:
|
// The robot video file contains 2 tables, with one entry for each frame:
|
||||||
// - A table containing the size of the image in each video frame
|
// - A table containing the size of the image in each video frame
|
||||||
|
@ -230,158 +317,90 @@ void RobotDecoder::readFrameSizesChunk() {
|
||||||
_fileStream->seek((curPos & ~0x7ff) + 2048);
|
_fileStream->seek((curPos & ~0x7ff) + 2048);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RobotDecoder::calculateVideoDimensions() {
|
RobotDecoder::RobotVideoTrack::RobotVideoTrack(int frameCount) : _frameCount(frameCount) {
|
||||||
// This is an O(n) operation, as each frame has a different size.
|
_surface = new Graphics::Surface();
|
||||||
// We need to know the actual frame size to have a constant video size.
|
_curFrame = -1;
|
||||||
uint32 pos = _fileStream->pos();
|
_dirtyPalette = false;
|
||||||
|
|
||||||
for (uint32 curFrame = 0; curFrame < _header.frameCount; curFrame++) {
|
|
||||||
_fileStream->skip(4);
|
|
||||||
uint16 frameWidth = _fileStream->readUint16();
|
|
||||||
uint16 frameHeight = _fileStream->readUint16();
|
|
||||||
if (frameWidth > _width)
|
|
||||||
_width = frameWidth;
|
|
||||||
if (frameHeight > _height)
|
|
||||||
_height = frameHeight;
|
|
||||||
_fileStream->skip(_frameTotalSize[curFrame] - 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
_fileStream->seek(pos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Graphics::Surface *RobotDecoder::decodeNextFrame() {
|
RobotDecoder::RobotVideoTrack::~RobotVideoTrack() {
|
||||||
// Read frame image header (24 bytes)
|
|
||||||
_fileStream->skip(3);
|
|
||||||
byte frameScale = _fileStream->readByte();
|
|
||||||
uint16 frameWidth = _fileStream->readUint16();
|
|
||||||
uint16 frameHeight = _fileStream->readUint16();
|
|
||||||
_fileStream->skip(4); // unknown, almost always 0
|
|
||||||
uint16 frameX = _fileStream->readUint16();
|
|
||||||
uint16 frameY = _fileStream->readUint16();
|
|
||||||
// TODO: In v4 robot files, frameX and frameY have a different meaning.
|
|
||||||
// Set them both to 0 for v4 for now, so that robots in PQ:SWAT show up
|
|
||||||
// correctly.
|
|
||||||
if (_header.version == 4)
|
|
||||||
frameX = frameY = 0;
|
|
||||||
uint16 compressedSize = _fileStream->readUint16();
|
|
||||||
uint16 frameFragments = _fileStream->readUint16();
|
|
||||||
_fileStream->skip(4); // unknown
|
|
||||||
uint32 decompressedSize = frameWidth * frameHeight * frameScale / 100;
|
|
||||||
// FIXME: A frame's height + position can go off limits... why? With the
|
|
||||||
// following, we cut the contents to fit the frame
|
|
||||||
uint16 scaledHeight = CLIP<uint16>(decompressedSize / frameWidth, 0, _height - frameY);
|
|
||||||
// FIXME: Same goes for the frame's width + position. In this case, we
|
|
||||||
// modify the position to fit the contents on screen.
|
|
||||||
if (frameWidth + frameX > _width)
|
|
||||||
frameX = _width - frameWidth;
|
|
||||||
assert (frameWidth + frameX <= _width && scaledHeight + frameY <= _height);
|
|
||||||
|
|
||||||
DecompressorLZS lzs;
|
|
||||||
byte *decompressedFrame = new byte[decompressedSize];
|
|
||||||
byte *outPtr = decompressedFrame;
|
|
||||||
|
|
||||||
if (_header.version == 4) {
|
|
||||||
// v4 has just the one fragment, it seems, and ignores the fragment count
|
|
||||||
Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedSize);
|
|
||||||
lzs.unpack(&fragmentStream, outPtr, compressedSize, decompressedSize);
|
|
||||||
} else {
|
|
||||||
for (uint16 i = 0; i < frameFragments; ++i) {
|
|
||||||
uint32 compressedFragmentSize = _fileStream->readUint32();
|
|
||||||
uint32 decompressedFragmentSize = _fileStream->readUint32();
|
|
||||||
uint16 compressionType = _fileStream->readUint16();
|
|
||||||
|
|
||||||
if (compressionType == 0) {
|
|
||||||
Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedFragmentSize);
|
|
||||||
lzs.unpack(&fragmentStream, outPtr, compressedFragmentSize, decompressedFragmentSize);
|
|
||||||
} else if (compressionType == 2) { // untested
|
|
||||||
_fileStream->read(outPtr, compressedFragmentSize);
|
|
||||||
} else {
|
|
||||||
error("Unknown frame compression found: %d", compressionType);
|
|
||||||
}
|
|
||||||
|
|
||||||
outPtr += decompressedFragmentSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy over the decompressed frame
|
|
||||||
byte *inFrame = decompressedFrame;
|
|
||||||
byte *outFrame = (byte *)_surface->pixels;
|
|
||||||
|
|
||||||
// Black out the surface
|
|
||||||
memset(outFrame, 0, _width * _height);
|
|
||||||
|
|
||||||
// Move to the correct y coordinate
|
|
||||||
outFrame += _width * frameY;
|
|
||||||
|
|
||||||
for (uint16 y = 0; y < scaledHeight; y++) {
|
|
||||||
memcpy(outFrame + frameX, inFrame, frameWidth);
|
|
||||||
inFrame += frameWidth;
|
|
||||||
outFrame += _width;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete[] decompressedFrame;
|
|
||||||
|
|
||||||
// +1 because we start with frame number -1
|
|
||||||
uint32 audioChunkSize = _frameTotalSize[_curFrame + 1] - (24 + compressedSize);
|
|
||||||
|
|
||||||
// TODO: The audio chunk size below is usually correct, but there are some
|
|
||||||
// exceptions (e.g. robot 4902 in Phantasmagoria, towards its end)
|
|
||||||
#if 0
|
|
||||||
// Read frame audio header (14 bytes)
|
|
||||||
_fileStream->skip(2); // buffer position
|
|
||||||
_fileStream->skip(2); // unknown (usually 1)
|
|
||||||
_fileStream->skip(2); /*uint16 audioChunkSize = _fileStream->readUint16() + 8;*/
|
|
||||||
_fileStream->skip(2);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Queue the next audio frame
|
|
||||||
// FIXME: For some reason, there are audio hiccups/gaps
|
|
||||||
if (_header.hasSound) {
|
|
||||||
_fileStream->skip(8); // header
|
|
||||||
_audioStream->queueBuffer(g_sci->_audio->getDecodedRobotAudioFrame(_fileStream, audioChunkSize - 8),
|
|
||||||
(audioChunkSize - 8) * 2, DisposeAfterUse::NO,
|
|
||||||
Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
|
|
||||||
} else {
|
|
||||||
_fileStream->skip(audioChunkSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_curFrame == -1)
|
|
||||||
_startTime = g_system->getMillis();
|
|
||||||
|
|
||||||
_curFrame++;
|
|
||||||
|
|
||||||
return _surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RobotDecoder::close() {
|
|
||||||
if (!_fileStream)
|
|
||||||
return;
|
|
||||||
|
|
||||||
delete _fileStream;
|
|
||||||
_fileStream = 0;
|
|
||||||
|
|
||||||
_surface->free();
|
_surface->free();
|
||||||
delete _surface;
|
delete _surface;
|
||||||
_surface = 0;
|
}
|
||||||
|
|
||||||
if (_header.hasSound) {
|
uint16 RobotDecoder::RobotVideoTrack::getWidth() const {
|
||||||
_mixer->stopHandle(_audioHandle);
|
return _surface->w;
|
||||||
//delete _audioStream; _audioStream = 0;
|
}
|
||||||
|
|
||||||
|
uint16 RobotDecoder::RobotVideoTrack::getHeight() const {
|
||||||
|
return _surface->h;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graphics::PixelFormat RobotDecoder::RobotVideoTrack::getPixelFormat() const {
|
||||||
|
return _surface->format;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDecoder::RobotVideoTrack::readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize) {
|
||||||
|
byte *paletteData = new byte[chunkSize];
|
||||||
|
stream->read(paletteData, chunkSize);
|
||||||
|
|
||||||
|
// SCI1.1 palette
|
||||||
|
byte palFormat = paletteData[32];
|
||||||
|
uint16 palColorStart = paletteData[25];
|
||||||
|
uint16 palColorCount = READ_SCI11ENDIAN_UINT16(paletteData + 29);
|
||||||
|
|
||||||
|
int palOffset = 37;
|
||||||
|
memset(_palette, 0, 256 * 3);
|
||||||
|
|
||||||
|
for (uint16 colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) {
|
||||||
|
if (palFormat == kRobotPalVariable)
|
||||||
|
palOffset++;
|
||||||
|
_palette[colorNo * 3 + 0] = paletteData[palOffset++];
|
||||||
|
_palette[colorNo * 3 + 1] = paletteData[palOffset++];
|
||||||
|
_palette[colorNo * 3 + 2] = paletteData[palOffset++];
|
||||||
}
|
}
|
||||||
|
|
||||||
reset();
|
_dirtyPalette = true;
|
||||||
|
delete[] paletteData;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RobotDecoder::updateVolume() {
|
void RobotDecoder::RobotVideoTrack::calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes) {
|
||||||
if (g_system->getMixer()->isSoundHandleActive(_audioHandle))
|
// This is an O(n) operation, as each frame has a different size.
|
||||||
g_system->getMixer()->setChannelVolume(_audioHandle, getVolume());
|
// We need to know the actual frame size to have a constant video size.
|
||||||
|
uint32 pos = stream->pos();
|
||||||
|
|
||||||
|
uint16 width = 0, height = 0;
|
||||||
|
|
||||||
|
for (int curFrame = 0; curFrame < _frameCount; curFrame++) {
|
||||||
|
stream->skip(4);
|
||||||
|
uint16 frameWidth = stream->readUint16();
|
||||||
|
uint16 frameHeight = stream->readUint16();
|
||||||
|
if (frameWidth > width)
|
||||||
|
width = frameWidth;
|
||||||
|
if (frameHeight > height)
|
||||||
|
height = frameHeight;
|
||||||
|
stream->skip(frameSizes[curFrame] - 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->seek(pos);
|
||||||
|
|
||||||
|
_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RobotDecoder::updateBalance() {
|
RobotDecoder::RobotAudioTrack::RobotAudioTrack() {
|
||||||
if (g_system->getMixer()->isSoundHandleActive(_audioHandle))
|
_audioStream = Audio::makeQueuingAudioStream(11025, false);
|
||||||
g_system->getMixer()->setChannelBalance(_audioHandle, getBalance());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
RobotDecoder::RobotAudioTrack::~RobotAudioTrack() {
|
||||||
|
delete _audioStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDecoder::RobotAudioTrack::queueBuffer(byte *buffer, int size) {
|
||||||
|
_audioStream->queueBuffer(buffer, size, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
Audio::AudioStream *RobotDecoder::RobotAudioTrack::getAudioStream() const {
|
||||||
|
return _audioStream;
|
||||||
|
}
|
||||||
|
|
||||||
} // End of namespace Sci
|
} // End of namespace Sci
|
||||||
|
|
|
@ -25,84 +25,103 @@
|
||||||
|
|
||||||
#include "common/rational.h"
|
#include "common/rational.h"
|
||||||
#include "common/rect.h"
|
#include "common/rect.h"
|
||||||
#include "common/stream.h"
|
|
||||||
#include "common/substream.h"
|
|
||||||
#include "audio/audiostream.h"
|
|
||||||
#include "audio/mixer.h"
|
|
||||||
#include "graphics/pixelformat.h"
|
|
||||||
#include "video/video_decoder.h"
|
#include "video/video_decoder.h"
|
||||||
|
|
||||||
|
namespace Audio {
|
||||||
|
class QueuingAudioStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
class SeekableSubReadStreamEndian;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Sci {
|
namespace Sci {
|
||||||
|
|
||||||
#ifdef ENABLE_SCI32
|
class RobotDecoder : public Video::AdvancedVideoDecoder {
|
||||||
|
|
||||||
struct RobotHeader {
|
|
||||||
// 6 bytes, identifier bytes
|
|
||||||
uint16 version;
|
|
||||||
uint16 audioChunkSize;
|
|
||||||
uint16 audioSilenceSize;
|
|
||||||
// 2 bytes, unknown
|
|
||||||
uint16 frameCount;
|
|
||||||
uint16 paletteDataSize;
|
|
||||||
uint16 unkChunkDataSize;
|
|
||||||
// 5 bytes, unknown
|
|
||||||
byte hasSound;
|
|
||||||
// 34 bytes, unknown
|
|
||||||
};
|
|
||||||
|
|
||||||
class RobotDecoder : public Video::FixedRateVideoDecoder {
|
|
||||||
public:
|
public:
|
||||||
RobotDecoder(Audio::Mixer *mixer, bool isBigEndian);
|
RobotDecoder(bool isBigEndian);
|
||||||
virtual ~RobotDecoder();
|
virtual ~RobotDecoder();
|
||||||
|
|
||||||
bool loadStream(Common::SeekableReadStream *stream);
|
bool loadStream(Common::SeekableReadStream *stream);
|
||||||
bool load(GuiResourceId id);
|
bool load(GuiResourceId id);
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
bool isVideoLoaded() const { return _fileStream != 0; }
|
|
||||||
uint16 getWidth() const { return _width; }
|
|
||||||
uint16 getHeight() const { return _height; }
|
|
||||||
uint32 getFrameCount() const { return _header.frameCount; }
|
|
||||||
const Graphics::Surface *decodeNextFrame();
|
|
||||||
Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); }
|
|
||||||
const byte *getPalette() { _dirtyPalette = false; return _palette; }
|
|
||||||
bool hasDirtyPalette() const { return _dirtyPalette; }
|
|
||||||
void setPos(uint16 x, uint16 y) { _pos = Common::Point(x, y); }
|
void setPos(uint16 x, uint16 y) { _pos = Common::Point(x, y); }
|
||||||
Common::Point getPos() const { return _pos; }
|
Common::Point getPos() const { return _pos; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// VideoDecoder API
|
void readNextPacket();
|
||||||
void updateVolume();
|
|
||||||
void updateBalance();
|
|
||||||
|
|
||||||
// FixedRateVideoDecoder API
|
|
||||||
Common::Rational getFrameRate() const { return Common::Rational(60, 10); }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
class RobotVideoTrack : public FixedRateVideoTrack {
|
||||||
|
public:
|
||||||
|
RobotVideoTrack(int frameCount);
|
||||||
|
~RobotVideoTrack();
|
||||||
|
|
||||||
|
uint16 getWidth() const;
|
||||||
|
uint16 getHeight() const;
|
||||||
|
Graphics::PixelFormat getPixelFormat() const;
|
||||||
|
int getCurFrame() const { return _curFrame; }
|
||||||
|
int getFrameCount() const { return _frameCount; }
|
||||||
|
const Graphics::Surface *decodeNextFrame() { return _surface; }
|
||||||
|
const byte *getPalette() const { _dirtyPalette = false; return _palette; }
|
||||||
|
bool hasDirtyPalette() const { return _dirtyPalette; }
|
||||||
|
|
||||||
|
void readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize);
|
||||||
|
void calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes);
|
||||||
|
Graphics::Surface *getSurface() { return _surface; }
|
||||||
|
void increaseCurFrame() { _curFrame++; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Common::Rational getFrameRate() const { return Common::Rational(60, 10); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _frameCount;
|
||||||
|
int _curFrame;
|
||||||
|
byte _palette[256 * 3];
|
||||||
|
mutable bool _dirtyPalette;
|
||||||
|
Graphics::Surface *_surface;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RobotAudioTrack : public AudioTrack {
|
||||||
|
public:
|
||||||
|
RobotAudioTrack();
|
||||||
|
~RobotAudioTrack();
|
||||||
|
|
||||||
|
Audio::Mixer::SoundType getSoundType() const { return Audio::Mixer::kMusicSoundType; }
|
||||||
|
|
||||||
|
void queueBuffer(byte *buffer, int size);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Audio::AudioStream *getAudioStream() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Audio::QueuingAudioStream *_audioStream;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RobotHeader {
|
||||||
|
// 6 bytes, identifier bytes
|
||||||
|
uint16 version;
|
||||||
|
uint16 audioChunkSize;
|
||||||
|
uint16 audioSilenceSize;
|
||||||
|
// 2 bytes, unknown
|
||||||
|
uint16 frameCount;
|
||||||
|
uint16 paletteDataSize;
|
||||||
|
uint16 unkChunkDataSize;
|
||||||
|
// 5 bytes, unknown
|
||||||
|
byte hasSound;
|
||||||
|
// 34 bytes, unknown
|
||||||
|
} _header;
|
||||||
|
|
||||||
void readHeaderChunk();
|
void readHeaderChunk();
|
||||||
void readPaletteChunk(uint16 chunkSize);
|
|
||||||
void readFrameSizesChunk();
|
void readFrameSizesChunk();
|
||||||
void calculateVideoDimensions();
|
|
||||||
|
|
||||||
void freeData();
|
|
||||||
|
|
||||||
RobotHeader _header;
|
|
||||||
Common::Point _pos;
|
Common::Point _pos;
|
||||||
bool _isBigEndian;
|
bool _isBigEndian;
|
||||||
|
uint32 *_frameTotalSize;
|
||||||
|
|
||||||
Common::SeekableSubReadStreamEndian *_fileStream;
|
Common::SeekableSubReadStreamEndian *_fileStream;
|
||||||
|
|
||||||
uint16 _width;
|
|
||||||
uint16 _height;
|
|
||||||
uint32 *_frameTotalSize;
|
|
||||||
byte _palette[256 * 3];
|
|
||||||
bool _dirtyPalette;
|
|
||||||
Graphics::Surface *_surface;
|
|
||||||
Audio::QueuingAudioStream *_audioStream;
|
|
||||||
Audio::SoundHandle _audioHandle;
|
|
||||||
Audio::Mixer *_mixer;
|
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
|
||||||
} // End of namespace Sci
|
} // End of namespace Sci
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue