mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 14:18:37 +00:00
709df52d48
This workaround solves audio/video sync issues that used to appear frequently in many animations, e.g. when Adibou kicks the ball in the garden. Those animations easily get out of sync when the timing is done by hotspots::evaluate, which sometimes does not call animate() as often as needed for good sync (mouse events processing, in particular, can delay the call). The original game seems to use also some kind of frame skipping to address this problem.
2942 lines
68 KiB
C++
2942 lines
68 KiB
C++
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/scummsys.h"
|
|
#include "common/rect.h"
|
|
#include "common/endian.h"
|
|
#include "common/stream.h"
|
|
#include "common/system.h"
|
|
#include "common/textconsole.h"
|
|
#include "common/types.h"
|
|
#include "common/util.h"
|
|
|
|
#include "video/coktel_decoder.h"
|
|
|
|
#include "image/codecs/indeo3.h"
|
|
|
|
#ifdef VIDEO_COKTELDECODER_H
|
|
|
|
#include "audio/audiostream.h"
|
|
#include "audio/decoders/raw.h"
|
|
#include "audio/decoders/adpcm_intern.h"
|
|
#include "common/memstream.h"
|
|
|
|
static const uint32 kVideoCodecIndeo3 = MKTAG('i','v','3','2');
|
|
|
|
namespace Video {
|
|
|
|
CoktelDecoder::State::State() : flags(0), speechId(0) {
|
|
}
|
|
|
|
|
|
CoktelDecoder::CoktelDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) :
|
|
_mixer(mixer), _soundType(soundType), _width(0), _height(0), _x(0), _y(0),
|
|
_defaultX(0), _defaultY(0), _features(0), _frameCount(0), _paletteDirty(false),
|
|
_ownSurface(true), _frameRate(12), _hasSound(false), _soundEnabled(false),
|
|
_soundStage(kSoundNone), _audioStream(0), _startTime(0), _pauseStartTime(0),
|
|
_isPaused(false) {
|
|
|
|
assert(_mixer);
|
|
|
|
memset(_palette, 0, 768);
|
|
}
|
|
|
|
CoktelDecoder::~CoktelDecoder() {
|
|
}
|
|
|
|
bool CoktelDecoder::evaluateSeekFrame(int32 &frame, int whence) const {
|
|
if (!isVideoLoaded())
|
|
// Nothing to do
|
|
return false;
|
|
|
|
// Find the frame to which to seek
|
|
if (whence == SEEK_CUR)
|
|
frame += _curFrame;
|
|
else if (whence == SEEK_END)
|
|
frame = _frameCount - frame - 1;
|
|
else if (whence == SEEK_SET)
|
|
frame--;
|
|
else
|
|
return false;
|
|
|
|
if ((frame < -1) || (frame >= ((int32) _frameCount)))
|
|
// Out of range
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CoktelDecoder::setSurfaceMemory(void *mem, uint16 width, uint16 height, uint8 bpp) {
|
|
freeSurface();
|
|
|
|
if (!hasVideo())
|
|
return;
|
|
|
|
// Sanity checks
|
|
assert((width > 0) && (height > 0));
|
|
assert(bpp == getPixelFormat().bytesPerPixel);
|
|
|
|
// Create a surface over this memory
|
|
// TODO: Check whether it is fine to assume we want the setup PixelFormat.
|
|
_surface.init(width, height, width * bpp, mem, getPixelFormat());
|
|
|
|
_ownSurface = false;
|
|
}
|
|
|
|
void CoktelDecoder::setSurfaceMemory() {
|
|
freeSurface();
|
|
createSurface();
|
|
|
|
_ownSurface = true;
|
|
}
|
|
|
|
const Graphics::Surface *CoktelDecoder::getSurface() const {
|
|
if (!isVideoLoaded())
|
|
return 0;
|
|
|
|
return &_surface;
|
|
}
|
|
|
|
bool CoktelDecoder::hasSurface() {
|
|
return _surface.getPixels();
|
|
}
|
|
|
|
void CoktelDecoder::createSurface() {
|
|
if (hasSurface())
|
|
return;
|
|
|
|
if (!hasVideo())
|
|
return;
|
|
|
|
if ((_width > 0) && (_height > 0))
|
|
_surface.create(_width, _height, getPixelFormat());
|
|
|
|
_ownSurface = true;
|
|
}
|
|
|
|
void CoktelDecoder::freeSurface() {
|
|
if (!_ownSurface) {
|
|
_surface.w = 0;
|
|
_surface.h = 0;
|
|
_surface.pitch = 0;
|
|
_surface.setPixels(0);
|
|
_surface.format = Graphics::PixelFormat();
|
|
} else
|
|
_surface.free();
|
|
|
|
_ownSurface = true;
|
|
}
|
|
|
|
void CoktelDecoder::setXY(uint16 x, uint16 y) {
|
|
_x = x;
|
|
_y = y;
|
|
}
|
|
|
|
void CoktelDecoder::setXY() {
|
|
setXY(_defaultX, _defaultY);
|
|
}
|
|
|
|
void CoktelDecoder::setFrameRate(Common::Rational frameRate) {
|
|
_frameRate = frameRate;
|
|
}
|
|
|
|
uint16 CoktelDecoder::getDefaultX() const {
|
|
return _defaultX;
|
|
}
|
|
|
|
uint16 CoktelDecoder::getDefaultY() const {
|
|
return _defaultY;
|
|
}
|
|
|
|
const Common::List<Common::Rect> &CoktelDecoder::getDirtyRects() const {
|
|
return _dirtyRects;
|
|
}
|
|
|
|
bool CoktelDecoder::hasPalette() const {
|
|
return (_features & kFeaturesPalette) != 0;
|
|
}
|
|
|
|
bool CoktelDecoder::hasVideo() const {
|
|
return true;
|
|
}
|
|
|
|
bool CoktelDecoder::hasSound() const {
|
|
return _hasSound;
|
|
}
|
|
|
|
bool CoktelDecoder::isSoundEnabled() const {
|
|
return _soundEnabled;
|
|
}
|
|
|
|
bool CoktelDecoder::isSoundPlaying() const {
|
|
return _audioStream && _mixer->isSoundHandleActive(_audioHandle);
|
|
}
|
|
|
|
void CoktelDecoder::enableSound() {
|
|
if (!hasSound() || isSoundEnabled())
|
|
return;
|
|
|
|
// Sanity check
|
|
if (_mixer->getOutputRate() == 0)
|
|
return;
|
|
|
|
// Only possible on the first frame
|
|
if (_curFrame > -1)
|
|
return;
|
|
|
|
_soundEnabled = true;
|
|
}
|
|
|
|
void CoktelDecoder::disableSound() {
|
|
if (_audioStream) {
|
|
if ((_soundStage == kSoundPlaying) || (_soundStage == kSoundFinished)) {
|
|
_audioStream->finish();
|
|
_mixer->stopHandle(_audioHandle);
|
|
}
|
|
|
|
delete _audioStream;
|
|
}
|
|
|
|
_soundEnabled = false;
|
|
_soundStage = kSoundNone;
|
|
|
|
_audioStream = 0;
|
|
}
|
|
|
|
void CoktelDecoder::finishSound() {
|
|
if (!_audioStream)
|
|
return;
|
|
|
|
_audioStream->finish();
|
|
_soundStage = kSoundFinished;
|
|
}
|
|
|
|
void CoktelDecoder::colorModeChanged() {
|
|
}
|
|
|
|
bool CoktelDecoder::getFrameCoords(int16 frame, int16 &x, int16 &y, int16 &width, int16 &height) {
|
|
return false;
|
|
}
|
|
|
|
bool CoktelDecoder::hasEmbeddedFiles() const {
|
|
return false;
|
|
}
|
|
|
|
bool CoktelDecoder::hasEmbeddedFile(const Common::String &fileName) const {
|
|
return false;
|
|
}
|
|
|
|
Common::SeekableReadStream *CoktelDecoder::getEmbeddedFile(const Common::String &fileName) const {
|
|
return 0;
|
|
}
|
|
|
|
int32 CoktelDecoder::getSubtitleIndex() const {
|
|
return -1;
|
|
}
|
|
|
|
bool CoktelDecoder::isPaletted() const {
|
|
return true;
|
|
}
|
|
|
|
int CoktelDecoder::getCurFrame() const {
|
|
return _curFrame;
|
|
}
|
|
|
|
void CoktelDecoder::close() {
|
|
disableSound();
|
|
freeSurface();
|
|
|
|
_x = 0;
|
|
_y = 0;
|
|
|
|
_defaultX = 0;
|
|
_defaultY = 0;
|
|
|
|
_features = 0;
|
|
|
|
_curFrame = -1;
|
|
_frameCount = 0;
|
|
|
|
_startTime = 0;
|
|
|
|
_hasSound = false;
|
|
|
|
_isPaused = false;
|
|
}
|
|
|
|
Audio::Mixer::SoundType CoktelDecoder::getSoundType() const {
|
|
return _soundType;
|
|
}
|
|
|
|
Audio::AudioStream *CoktelDecoder::getAudioStream() const {
|
|
return _audioStream;
|
|
}
|
|
|
|
uint16 CoktelDecoder::getWidth() const {
|
|
return _width;
|
|
}
|
|
|
|
uint16 CoktelDecoder::getHeight() const {
|
|
return _height;
|
|
}
|
|
|
|
uint32 CoktelDecoder::getFrameCount() const {
|
|
return _frameCount;
|
|
}
|
|
|
|
const byte *CoktelDecoder::getPalette() {
|
|
_paletteDirty = false;
|
|
return _palette;
|
|
}
|
|
|
|
bool CoktelDecoder::hasDirtyPalette() const {
|
|
return (_features & kFeaturesPalette) && _paletteDirty;
|
|
}
|
|
|
|
uint32 CoktelDecoder::deLZ77(byte *dest, const byte *src, uint32 srcSize, uint32 destSize) {
|
|
uint32 frameLength = READ_LE_UINT32(src);
|
|
if (frameLength > destSize) {
|
|
warning("CoktelDecoder::deLZ77(): Uncompressed size bigger than buffer size (%d > %d)", frameLength, destSize);
|
|
return 0;
|
|
}
|
|
|
|
assert(srcSize >= 4);
|
|
|
|
uint32 realSize = frameLength;
|
|
|
|
src += 4;
|
|
srcSize -= 4;
|
|
|
|
uint16 bufPos1;
|
|
bool mode;
|
|
if ((READ_LE_UINT16(src) == 0x1234) && (READ_LE_UINT16(src + 2) == 0x5678)) {
|
|
assert(srcSize >= 4);
|
|
|
|
src += 4;
|
|
srcSize -= 4;
|
|
|
|
bufPos1 = 273;
|
|
mode = 1; // 123Ch (cmp al, 12h)
|
|
} else {
|
|
bufPos1 = 4078;
|
|
mode = 0; // 275h (jnz +2)
|
|
}
|
|
|
|
byte buf[4370];
|
|
memset(buf, 32, bufPos1);
|
|
|
|
uint8 chunkCount = 1;
|
|
uint8 chunkBitField = 0;
|
|
|
|
while (frameLength > 0) {
|
|
chunkCount--;
|
|
|
|
if (chunkCount == 0) {
|
|
chunkCount = 8;
|
|
chunkBitField = *src++;
|
|
}
|
|
|
|
if (chunkBitField % 2) {
|
|
assert(srcSize >= 1);
|
|
|
|
chunkBitField >>= 1;
|
|
buf[bufPos1] = *src;
|
|
*dest++ = *src++;
|
|
bufPos1 = (bufPos1 + 1) % 4096;
|
|
frameLength--;
|
|
srcSize--;
|
|
continue;
|
|
}
|
|
chunkBitField >>= 1;
|
|
|
|
assert(srcSize >= 2);
|
|
|
|
uint16 tmp = READ_LE_UINT16(src);
|
|
uint16 chunkLength = ((tmp & 0xF00) >> 8) + 3;
|
|
|
|
src += 2;
|
|
srcSize -= 2;
|
|
|
|
if ((mode && ((chunkLength & 0xFF) == 0x12)) ||
|
|
(!mode && (chunkLength == 0))) {
|
|
assert(srcSize >= 1);
|
|
|
|
chunkLength = *src++ + 0x12;
|
|
srcSize--;
|
|
}
|
|
|
|
uint16 bufPos2 = (tmp & 0xFF) + ((tmp >> 4) & 0x0F00);
|
|
if (((tmp + chunkLength) >= 4096) ||
|
|
((chunkLength + bufPos1) >= 4096)) {
|
|
|
|
for (int i = 0; i < chunkLength; i++, dest++) {
|
|
*dest = buf[bufPos2];
|
|
buf[bufPos1] = buf[bufPos2];
|
|
bufPos1 = (bufPos1 + 1) % 4096;
|
|
bufPos2 = (bufPos2 + 1) % 4096;
|
|
}
|
|
|
|
} else if (((tmp + chunkLength) < bufPos1) ||
|
|
((chunkLength + bufPos1) < bufPos2)) {
|
|
|
|
memcpy(dest, buf + bufPos2, chunkLength);
|
|
memmove(buf + bufPos1, buf + bufPos2, chunkLength);
|
|
|
|
dest += chunkLength;
|
|
bufPos1 += chunkLength;
|
|
bufPos2 += chunkLength;
|
|
|
|
} else {
|
|
|
|
for (int i = 0; i < chunkLength; i++, dest++, bufPos1++, bufPos2++) {
|
|
*dest = buf[bufPos2];
|
|
buf[bufPos1] = buf[bufPos2];
|
|
}
|
|
|
|
}
|
|
frameLength -= chunkLength;
|
|
|
|
}
|
|
|
|
return realSize;
|
|
}
|
|
|
|
void CoktelDecoder::deRLE(byte *&destPtr, const byte *&srcPtr, int16 destLen, int16 srcLen) {
|
|
srcPtr++;
|
|
|
|
if (srcLen & 1) {
|
|
byte data = *srcPtr++;
|
|
|
|
if (destLen > 0) {
|
|
*destPtr++ = data;
|
|
destLen--;
|
|
}
|
|
}
|
|
|
|
srcLen >>= 1;
|
|
|
|
while (srcLen > 0) {
|
|
uint8 tmp = *srcPtr++;
|
|
if (tmp & 0x80) { // Verbatim copy
|
|
tmp &= 0x7F;
|
|
|
|
int16 copyCount = MAX<int16>(0, MIN<int16>(destLen, tmp * 2));
|
|
|
|
memcpy(destPtr, srcPtr, copyCount);
|
|
|
|
srcPtr += tmp * 2;
|
|
destPtr += copyCount;
|
|
destLen -= copyCount;
|
|
} else { // 2 bytes tmp times
|
|
for (int i = 0; (i < tmp) && (destLen > 0); i++) {
|
|
for (int j = 0; j < 2; j++) {
|
|
if (destLen <= 0)
|
|
break;
|
|
|
|
*destPtr++ = srcPtr[j];
|
|
destLen--;
|
|
}
|
|
}
|
|
srcPtr += 2;
|
|
}
|
|
srcLen -= tmp;
|
|
}
|
|
}
|
|
|
|
// A whole, completely filled block
|
|
void CoktelDecoder::renderBlockWhole(Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect) {
|
|
Common::Rect srcRect = rect;
|
|
|
|
rect.clip(dstSurf.w, dstSurf.h);
|
|
|
|
byte *dst = (byte *)dstSurf.getBasePtr(rect.left, rect.top);
|
|
for (int i = 0; i < rect.height(); i++) {
|
|
memcpy(dst, src, rect.width() * dstSurf.format.bytesPerPixel);
|
|
|
|
src += srcRect.width() * dstSurf.format.bytesPerPixel;
|
|
dst += dstSurf.pitch;
|
|
}
|
|
}
|
|
|
|
// A quarter-wide whole, completely filled block
|
|
void CoktelDecoder::renderBlockWhole4X(Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect) {
|
|
Common::Rect srcRect = rect;
|
|
|
|
rect.clip(dstSurf.w, dstSurf.h);
|
|
|
|
byte *dst = (byte *)dstSurf.getBasePtr(rect.left, rect.top);
|
|
for (int i = 0; i < rect.height(); i++) {
|
|
byte *dstRow = dst;
|
|
const byte *srcRow = src;
|
|
|
|
int16 count = rect.width();
|
|
while (count >= 0) {
|
|
memset(dstRow, *srcRow, MIN<int16>(count, 4));
|
|
|
|
count -= 4;
|
|
dstRow += 4;
|
|
srcRow += 1;
|
|
}
|
|
|
|
src += srcRect.width() / 4;
|
|
dst += dstSurf.pitch;
|
|
}
|
|
}
|
|
|
|
// A half-high whole, completely filled block
|
|
void CoktelDecoder::renderBlockWhole2Y(Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect) {
|
|
Common::Rect srcRect = rect;
|
|
|
|
rect.clip(dstSurf.w, dstSurf.h);
|
|
|
|
int16 height = rect.height();
|
|
|
|
byte *dst = (byte *)dstSurf.getBasePtr(rect.left, rect.top);
|
|
while (height > 1) {
|
|
memcpy(dst , src, rect.width());
|
|
memcpy(dst + dstSurf.pitch, src, rect.width());
|
|
|
|
height -= 2;
|
|
src += srcRect.width();
|
|
dst += 2 * dstSurf.pitch;
|
|
}
|
|
|
|
if (height == 1)
|
|
memcpy(dst, src, rect.width());
|
|
}
|
|
|
|
// A sparse block
|
|
void CoktelDecoder::renderBlockSparse(Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect) {
|
|
Common::Rect srcRect = rect;
|
|
|
|
rect.clip(dstSurf.w, dstSurf.h);
|
|
|
|
byte *dst = (byte *)dstSurf.getBasePtr(rect.left, rect.top);
|
|
for (int i = 0; i < rect.height(); i++) {
|
|
byte *dstRow = dst;
|
|
int16 pixWritten = 0;
|
|
|
|
while (pixWritten < srcRect.width()) {
|
|
int16 pixCount = *src++;
|
|
|
|
if (pixCount & 0x80) { // Data
|
|
int16 copyCount;
|
|
|
|
pixCount = MIN<int16>((pixCount & 0x7F) + 1, srcRect.width() - pixWritten);
|
|
copyCount = CLIP<int16>(rect.width() - pixWritten, 0, pixCount);
|
|
memcpy(dstRow, src, copyCount);
|
|
|
|
pixWritten += pixCount;
|
|
dstRow += pixCount;
|
|
src += pixCount;
|
|
} else { // "Hole"
|
|
pixWritten += pixCount + 1;
|
|
dstRow += pixCount + 1;
|
|
}
|
|
|
|
}
|
|
|
|
dst += dstSurf.pitch;
|
|
}
|
|
}
|
|
|
|
// A half-high sparse block
|
|
void CoktelDecoder::renderBlockSparse2Y(Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect) {
|
|
warning("renderBlockSparse2Y");
|
|
|
|
Common::Rect srcRect = rect;
|
|
|
|
rect.clip(dstSurf.w, dstSurf.h);
|
|
|
|
byte *dst = (byte *)dstSurf.getBasePtr(rect.left, rect.top);
|
|
for (int i = 0; i < rect.height(); i += 2) {
|
|
byte *dstRow = dst;
|
|
int16 pixWritten = 0;
|
|
|
|
while (pixWritten < srcRect.width()) {
|
|
int16 pixCount = *src++;
|
|
|
|
if (pixCount & 0x80) { // Data
|
|
pixCount = MIN<int16>((pixCount & 0x7F) + 1, srcRect.width() - pixWritten);
|
|
memcpy(dstRow , src, pixCount);
|
|
memcpy(dstRow + dstSurf.pitch, src, pixCount);
|
|
|
|
pixWritten += pixCount;
|
|
dstRow += pixCount;
|
|
src += pixCount;
|
|
} else { // "Hole"
|
|
pixWritten += pixCount + 1;
|
|
dstRow += pixCount + 1;
|
|
}
|
|
|
|
}
|
|
|
|
dst += dstSurf.pitch;
|
|
}
|
|
}
|
|
|
|
void CoktelDecoder::renderBlockRLE(Graphics::Surface &dstSurf, const byte *src, Common::Rect &rect) {
|
|
Common::Rect srcRect = rect;
|
|
|
|
rect.clip(dstSurf.w, dstSurf.h);
|
|
|
|
byte *dst = (byte *)dstSurf.getBasePtr(rect.left, rect.top);
|
|
for (int i = 0; i < rect.height(); i++) {
|
|
byte *dstRow = dst;
|
|
int16 pixWritten = 0;
|
|
|
|
while (pixWritten < srcRect.width()) {
|
|
int16 pixCount = *src++;
|
|
|
|
if (pixCount & 0x80) {
|
|
int16 copyCount;
|
|
|
|
pixCount = MIN<int16>((pixCount & 0x7F) + 1, srcRect.width() - pixWritten);
|
|
copyCount = CLIP<int16>(rect.width() - pixWritten, 0, pixCount);
|
|
|
|
if (*src != 0xFF) { // Normal copy
|
|
|
|
memcpy(dstRow, src, copyCount);
|
|
dstRow += copyCount;
|
|
src += pixCount;
|
|
} else
|
|
deRLE(dstRow, src, copyCount, pixCount);
|
|
|
|
pixWritten += pixCount;
|
|
} else { // "Hole"
|
|
int16 copyCount = CLIP<int16>(rect.width() - pixWritten, 0, pixCount + 1);
|
|
|
|
dstRow += copyCount;
|
|
pixWritten += pixCount + 1;
|
|
}
|
|
|
|
}
|
|
|
|
dst += dstSurf.pitch;
|
|
}
|
|
}
|
|
|
|
Common::Rational CoktelDecoder::getFrameRate() const {
|
|
return _frameRate;
|
|
}
|
|
|
|
uint32 CoktelDecoder::getTimeToNextFrame() const {
|
|
if (endOfVideo() || _curFrame < 0)
|
|
return 0;
|
|
|
|
uint32 elapsedTime = g_system->getMillis() - _startTime;
|
|
uint32 nextFrameStartTime = (Common::Rational((_curFrame + 1) * 1000) / getFrameRate()).toInt();
|
|
|
|
if (nextFrameStartTime <= elapsedTime)
|
|
return 0;
|
|
|
|
return nextFrameStartTime - elapsedTime;
|
|
}
|
|
|
|
int32 CoktelDecoder::getExpectedFrameFromCurrentTime() const {
|
|
if (endOfVideo() || _curFrame < 0)
|
|
return -1;
|
|
|
|
int32 elapsedTime = g_system->getMillis() - _startTime;
|
|
if (elapsedTime < 0)
|
|
return -1;
|
|
|
|
return (Common::Rational(elapsedTime * getFrameRate()) / 1000).toInt();
|
|
}
|
|
|
|
uint32 CoktelDecoder::getStaticTimeToNextFrame() const {
|
|
return (1000 / _frameRate).toInt();
|
|
}
|
|
|
|
void CoktelDecoder::pauseVideo(bool pause) {
|
|
if (_isPaused != pause) {
|
|
if (_isPaused) {
|
|
// Add the time we were paused to the initial starting time
|
|
_startTime += g_system->getMillis() - _pauseStartTime;
|
|
} else {
|
|
// Store the time we paused for use later
|
|
_pauseStartTime = g_system->getMillis();
|
|
}
|
|
|
|
_isPaused = pause;
|
|
}
|
|
}
|
|
|
|
inline void CoktelDecoder::unsignedToSigned(byte *buffer, int length) {
|
|
while (length-- > 0) *buffer++ ^= 0x80;
|
|
}
|
|
|
|
bool CoktelDecoder::endOfVideo() const {
|
|
return !isVideoLoaded() || (getCurFrame() >= (int32)getFrameCount() - 1);
|
|
}
|
|
|
|
|
|
PreIMDDecoder::PreIMDDecoder(uint16 width, uint16 height,
|
|
Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) : CoktelDecoder(mixer, soundType),
|
|
_stream(0), _videoBuffer(0), _videoBufferSize(0) {
|
|
|
|
_width = width;
|
|
_height = height;
|
|
}
|
|
|
|
PreIMDDecoder::~PreIMDDecoder() {
|
|
close();
|
|
}
|
|
|
|
bool PreIMDDecoder::reloadStream(Common::SeekableReadStream *stream) {
|
|
if (!_stream)
|
|
return false;
|
|
|
|
if (!stream->seek(_stream->pos())) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
delete _stream;
|
|
_stream = stream;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PreIMDDecoder::seek(int32 frame, int whence, bool restart) {
|
|
if (!evaluateSeekFrame(frame, whence))
|
|
return false;
|
|
|
|
if (frame == _curFrame)
|
|
// Nothing to do
|
|
return true;
|
|
|
|
// Run through the frames
|
|
_curFrame = -1;
|
|
_stream->seek(2);
|
|
while (_curFrame != frame) {
|
|
uint16 frameSize = _stream->readUint16LE();
|
|
|
|
_stream->skip(frameSize + 2);
|
|
|
|
_curFrame++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PreIMDDecoder::loadStream(Common::SeekableReadStream *stream) {
|
|
// Since PreIMDs don't have any width and height values stored,
|
|
// we need them to be specified in the constructor
|
|
assert((_width > 0) && (_height > 0));
|
|
|
|
close();
|
|
|
|
_stream = stream;
|
|
|
|
_stream->seek(0);
|
|
|
|
_frameCount = _stream->readUint16LE();
|
|
|
|
_videoBufferSize = _width * _height;
|
|
_videoBuffer = new byte[_videoBufferSize]();
|
|
|
|
return true;
|
|
}
|
|
|
|
void PreIMDDecoder::close() {
|
|
CoktelDecoder::close();
|
|
|
|
delete _stream;
|
|
|
|
delete[] _videoBuffer;
|
|
|
|
_stream = 0;
|
|
|
|
_videoBuffer = 0;
|
|
_videoBufferSize = 0;
|
|
}
|
|
|
|
bool PreIMDDecoder::isVideoLoaded() const {
|
|
return _stream != 0;
|
|
}
|
|
|
|
const Graphics::Surface *PreIMDDecoder::decodeNextFrame() {
|
|
if (!isVideoLoaded() || endOfVideo())
|
|
return 0;
|
|
|
|
createSurface();
|
|
|
|
processFrame();
|
|
renderFrame();
|
|
|
|
if (_curFrame == 0)
|
|
_startTime = g_system->getMillis();
|
|
|
|
return &_surface;
|
|
}
|
|
|
|
void PreIMDDecoder::processFrame() {
|
|
_curFrame++;
|
|
|
|
uint16 frameSize = _stream->readUint16LE();
|
|
if (_stream->eos() || (frameSize == 0))
|
|
return;
|
|
|
|
uint32 nextFramePos = _stream->pos() + frameSize + 2;
|
|
|
|
byte cmd;
|
|
|
|
cmd = _stream->readByte();
|
|
frameSize--;
|
|
|
|
if (cmd == 0) {
|
|
// Palette. Ignored by Fascination, though.
|
|
|
|
// NOTE: If we ever find another game using this format,
|
|
// palettes may need to be evaluated.
|
|
|
|
_stream->skip(768);
|
|
|
|
frameSize -= 769;
|
|
|
|
cmd = _stream->readByte();
|
|
}
|
|
|
|
if (cmd != 2) {
|
|
// Partial frame data
|
|
|
|
uint32 fSize = frameSize;
|
|
uint32 vidSize = _videoBufferSize;
|
|
|
|
byte *vidBuffer = _videoBuffer;
|
|
|
|
while ((fSize > 0) && (vidSize > 0)) {
|
|
uint32 n = _stream->readByte();
|
|
fSize--;
|
|
|
|
if ((n & 0x80) != 0) {
|
|
// Data
|
|
|
|
n = MIN<uint32>((n & 0x7F) + 1, MIN(fSize, vidSize));
|
|
|
|
_stream->read(vidBuffer, n);
|
|
|
|
vidBuffer += n;
|
|
vidSize -= n;
|
|
fSize -= n;
|
|
|
|
} else {
|
|
// Skip
|
|
|
|
n = MIN<uint32>(n + 1, vidSize);
|
|
|
|
vidBuffer += n;
|
|
vidSize -= n;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// Full direct frame
|
|
|
|
uint32 vidSize = MIN<uint32>(_videoBufferSize, frameSize);
|
|
|
|
_stream->read(_videoBuffer, vidSize);
|
|
}
|
|
|
|
_stream->seek(nextFramePos);
|
|
}
|
|
|
|
// Just a simple blit
|
|
void PreIMDDecoder::renderFrame() {
|
|
_dirtyRects.clear();
|
|
|
|
uint16 w = CLIP<int32>(_surface.w - _x, 0, _width);
|
|
uint16 h = CLIP<int32>(_surface.h - _y, 0, _height);
|
|
|
|
const byte *src = _videoBuffer;
|
|
byte *dst = (byte *)_surface.getBasePtr(_x, _y);
|
|
|
|
uint32 frameDataSize = _videoBufferSize;
|
|
|
|
while (h-- > 0) {
|
|
uint32 n = MIN<uint32>(w, frameDataSize);
|
|
|
|
memcpy(dst, src, n);
|
|
|
|
src += _width;
|
|
dst += _surface.pitch;
|
|
|
|
frameDataSize -= n;
|
|
}
|
|
|
|
_dirtyRects.push_back(Common::Rect(_x, _y, _x + _width, _y + _height));
|
|
}
|
|
|
|
uint32 PreIMDDecoder::getFlags() const {
|
|
return 0;
|
|
}
|
|
|
|
Graphics::PixelFormat PreIMDDecoder::getPixelFormat() const {
|
|
return Graphics::PixelFormat::createFormatCLUT8();
|
|
}
|
|
|
|
|
|
IMDDecoder::IMDDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) : CoktelDecoder(mixer, soundType),
|
|
_stream(0), _version(0), _stdX(-1), _stdY(-1), _stdWidth(-1), _stdHeight(-1),
|
|
_flags(0), _firstFramePos(0), _framePos(0), _frameCoords(0), _videoBufferSize(0),
|
|
_soundFlags(0), _soundFreq(0), _soundSliceSize(0), _soundSlicesCount(0) {
|
|
|
|
_videoBuffer [0] = 0;
|
|
_videoBuffer [1] = 0;
|
|
_videoBufferLen[0] = 0;
|
|
_videoBufferLen[1] = 0;
|
|
}
|
|
|
|
IMDDecoder::~IMDDecoder() {
|
|
close();
|
|
}
|
|
|
|
bool IMDDecoder::reloadStream(Common::SeekableReadStream *stream) {
|
|
if (!_stream)
|
|
return false;
|
|
|
|
if (!stream->seek(_stream->pos())) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
delete _stream;
|
|
_stream = stream;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IMDDecoder::seek(int32 frame, int whence, bool restart) {
|
|
if (!evaluateSeekFrame(frame, whence))
|
|
return false;
|
|
|
|
if (frame == _curFrame)
|
|
// Nothing to do
|
|
return true;
|
|
|
|
// Try every possible way to find a file offset to that frame
|
|
uint32 framePos = 0;
|
|
if (frame == -1) {
|
|
// First frame, we know that position
|
|
|
|
framePos = _firstFramePos;
|
|
|
|
} else if (frame == 0) {
|
|
// Second frame, can be calculated from the first frame's position
|
|
|
|
framePos = _firstFramePos;
|
|
_stream->seek(framePos);
|
|
framePos += _stream->readUint16LE() + 4;
|
|
|
|
} else if (_framePos) {
|
|
// If we have an array of frame positions, use that
|
|
|
|
framePos = _framePos[frame + 1];
|
|
|
|
} else if (restart && (_soundStage == kSoundNone)) {
|
|
// If we are asked to restart the video if necessary and have no
|
|
// audio to worry about, restart the video and run through the frames
|
|
|
|
_curFrame = 0;
|
|
_stream->seek(_firstFramePos);
|
|
|
|
for (int i = ((frame > _curFrame) ? _curFrame : 0); i <= frame; i++)
|
|
processFrame();
|
|
|
|
return true;
|
|
|
|
} else {
|
|
// Not possible
|
|
|
|
warning("IMDDecoder::seek(): Frame %d is not directly accessible", frame + 1);
|
|
return false;
|
|
}
|
|
|
|
// Seek
|
|
_stream->seek(framePos);
|
|
_curFrame = frame;
|
|
|
|
return true;
|
|
}
|
|
|
|
void IMDDecoder::setXY(uint16 x, uint16 y) {
|
|
// Adjusting the standard coordinates
|
|
if (_stdX != -1) {
|
|
if (x != 0xFFFF)
|
|
_stdX = _stdX - _x + x;
|
|
if (y != 0xFFFF)
|
|
_stdY = _stdY - _y + y;
|
|
}
|
|
|
|
// Going through the coordinate table as well
|
|
if (_frameCoords) {
|
|
for (uint32 i = 0; i < _frameCount; i++) {
|
|
if (_frameCoords[i].left != -1) {
|
|
if (x != 0xFFFF) {
|
|
_frameCoords[i].left = _frameCoords[i].left - _x + x;
|
|
_frameCoords[i].right = _frameCoords[i].right - _x + x;
|
|
}
|
|
if (y != 0xFFFF) {
|
|
_frameCoords[i].top = _frameCoords[i].top - _y + y;
|
|
_frameCoords[i].bottom = _frameCoords[i].bottom - _y + y;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (x != 0xFFFF)
|
|
_x = x;
|
|
if (y != 0xFFFF)
|
|
_y = y;
|
|
}
|
|
|
|
bool IMDDecoder::loadStream(Common::SeekableReadStream *stream) {
|
|
close();
|
|
|
|
_stream = stream;
|
|
|
|
uint16 handle;
|
|
|
|
handle = _stream->readUint16LE();
|
|
_version = _stream->readByte();
|
|
|
|
// Version checking
|
|
if ((handle != 0) || (_version < 2)) {
|
|
warning("IMDDecoder::loadStream(): Version incorrect (%d, 0x%X)", handle, _version);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
// Rest header
|
|
_features = _stream->readByte();
|
|
_frameCount = _stream->readUint16LE();
|
|
_defaultX = _stream->readSint16LE();
|
|
_defaultY = _stream->readSint16LE();
|
|
_width = _stream->readSint16LE();
|
|
_height = _stream->readSint16LE();
|
|
_flags = _stream->readUint16LE();
|
|
_firstFramePos = _stream->readUint16LE();
|
|
|
|
_x = _defaultX;
|
|
_y = _defaultY;
|
|
|
|
// IMDs always have video
|
|
_features |= kFeaturesVideo;
|
|
// IMDs always have palettes
|
|
_features |= kFeaturesPalette;
|
|
|
|
// Palette
|
|
for (int i = 0; i < 768; i++)
|
|
_palette[i] = _stream->readByte() << 2;
|
|
|
|
_paletteDirty = true;
|
|
|
|
if (!loadCoordinates()) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
uint32 framePosPos, frameCoordsPos;
|
|
if (!loadFrameTableOffsets(framePosPos, frameCoordsPos)) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
if (!assessAudioProperties()) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
if (!assessVideoProperties()) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
if (!loadFrameTables(framePosPos, frameCoordsPos)) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
// Seek to the first frame
|
|
_stream->seek(_firstFramePos);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IMDDecoder::loadCoordinates() {
|
|
// Standard coordinates
|
|
if (_version >= 3) {
|
|
uint16 count = _stream->readUint16LE();
|
|
if (count > 1) {
|
|
warning("IMDDecoder::loadCoordinates(): More than one standard coordinate quad found (%d)", count);
|
|
return false;
|
|
}
|
|
|
|
if (count != 0) {
|
|
_stdX = _stream->readSint16LE();
|
|
_stdY = _stream->readSint16LE();
|
|
_stdWidth = _stream->readSint16LE();
|
|
_stdHeight = _stream->readSint16LE();
|
|
_features |= kFeaturesStdCoords;
|
|
} else
|
|
_stdX = _stdY = _stdWidth = _stdHeight = -1;
|
|
|
|
} else
|
|
_stdX = _stdY = _stdWidth = _stdHeight = -1;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IMDDecoder::loadFrameTableOffsets(uint32 &framePosPos, uint32 &frameCoordsPos) {
|
|
framePosPos = 0;
|
|
frameCoordsPos = 0;
|
|
|
|
// Frame positions
|
|
if (_version >= 4) {
|
|
framePosPos = _stream->readUint32LE();
|
|
if (framePosPos != 0) {
|
|
_framePos = new uint32[_frameCount];
|
|
_features |= kFeaturesFramePos;
|
|
}
|
|
}
|
|
|
|
// Frame coordinates
|
|
if (_features & kFeaturesFrameCoords)
|
|
frameCoordsPos = _stream->readUint32LE();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IMDDecoder::assessVideoProperties() {
|
|
uint32 suggestedVideoBufferSize = 0;
|
|
|
|
// Sizes of the frame data and extra video buffer
|
|
if (_features & kFeaturesDataSize) {
|
|
uint32 size1, size2;
|
|
|
|
size1 = _stream->readUint16LE();
|
|
if (size1 == 0) {
|
|
size1 = _stream->readUint32LE();
|
|
size2 = _stream->readUint32LE();
|
|
} else
|
|
size2 = _stream->readUint16LE();
|
|
|
|
suggestedVideoBufferSize = MAX(size1, size2);
|
|
}
|
|
|
|
_videoBufferSize = _width * _height + 1000;
|
|
|
|
if (suggestedVideoBufferSize > _videoBufferSize) {
|
|
warning("Suggested video buffer size greater than what should be needed (%d, %d, %dx%d",
|
|
suggestedVideoBufferSize, _videoBufferSize, _width, _height);
|
|
|
|
_videoBufferSize = suggestedVideoBufferSize;
|
|
}
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
_videoBuffer[i] = new byte[_videoBufferSize]();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IMDDecoder::assessAudioProperties() {
|
|
if (_features & kFeaturesSound) {
|
|
_soundFreq = _stream->readSint16LE();
|
|
_soundSliceSize = _stream->readSint16LE();
|
|
_soundSlicesCount = _stream->readSint16LE();
|
|
|
|
if (_soundFreq < 0)
|
|
_soundFreq = -_soundFreq;
|
|
|
|
if (_soundSlicesCount < 0)
|
|
_soundSlicesCount = -_soundSlicesCount - 1;
|
|
|
|
if (_soundSlicesCount > 40) {
|
|
warning("IMDDecoder::assessAudioProperties(): More than 40 sound slices found (%d)", _soundSlicesCount);
|
|
return false;
|
|
}
|
|
|
|
_frameRate = Common::Rational(_soundFreq, _soundSliceSize);
|
|
|
|
_hasSound = true;
|
|
_soundEnabled = true;
|
|
_soundStage = kSoundLoaded;
|
|
|
|
_audioStream = Audio::makeQueuingAudioStream(_soundFreq, false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IMDDecoder::loadFrameTables(uint32 framePosPos, uint32 frameCoordsPos) {
|
|
// Positions table
|
|
if (_framePos) {
|
|
_stream->seek(framePosPos);
|
|
for (uint32 i = 0; i < _frameCount; i++)
|
|
_framePos[i] = _stream->readUint32LE();
|
|
}
|
|
|
|
// Coordinates table
|
|
if (_features & kFeaturesFrameCoords) {
|
|
_stream->seek(frameCoordsPos);
|
|
_frameCoords = new Coord[_frameCount];
|
|
assert(_frameCoords);
|
|
for (uint32 i = 0; i < _frameCount; i++) {
|
|
_frameCoords[i].left = _stream->readSint16LE();
|
|
_frameCoords[i].top = _stream->readSint16LE();
|
|
_frameCoords[i].right = _stream->readSint16LE();
|
|
_frameCoords[i].bottom = _stream->readSint16LE();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void IMDDecoder::close() {
|
|
CoktelDecoder::close();
|
|
|
|
delete _stream;
|
|
|
|
delete[] _framePos;
|
|
delete[] _frameCoords;
|
|
|
|
delete[] _videoBuffer[0];
|
|
delete[] _videoBuffer[1];
|
|
|
|
_stream = 0;
|
|
|
|
_version = 0;
|
|
|
|
_stdX = -1;
|
|
_stdY = -1;
|
|
_stdWidth = -1;
|
|
_stdHeight = -1;
|
|
|
|
_flags = 0;
|
|
|
|
_firstFramePos = 0;
|
|
_framePos = 0;
|
|
_frameCoords = 0;
|
|
|
|
_videoBufferSize = 0;
|
|
_videoBuffer [0] = 0;
|
|
_videoBuffer [1] = 0;
|
|
_videoBufferLen[0] = 0;
|
|
_videoBufferLen[1] = 0;
|
|
|
|
_soundFlags = 0;
|
|
_soundFreq = 0;
|
|
_soundSliceSize = 0;
|
|
_soundSlicesCount = 0;
|
|
|
|
_hasSound = false;
|
|
_soundEnabled = false;
|
|
_soundStage = kSoundNone;
|
|
}
|
|
|
|
bool IMDDecoder::isVideoLoaded() const {
|
|
return _stream != 0;
|
|
}
|
|
|
|
const Graphics::Surface *IMDDecoder::decodeNextFrame() {
|
|
if (!isVideoLoaded() || endOfVideo())
|
|
return 0;
|
|
|
|
createSurface();
|
|
|
|
processFrame();
|
|
|
|
if (_curFrame == 0)
|
|
_startTime = g_system->getMillis();
|
|
|
|
return &_surface;
|
|
}
|
|
|
|
void IMDDecoder::processFrame() {
|
|
_curFrame++;
|
|
|
|
_dirtyRects.clear();
|
|
|
|
uint32 cmd = 0;
|
|
bool hasNextCmd = false;
|
|
bool startSound = false;
|
|
|
|
do {
|
|
cmd = _stream->readUint16LE();
|
|
|
|
if ((cmd & kCommandBreakMask) == kCommandBreak) {
|
|
// Flow control
|
|
|
|
if (cmd == kCommandBreak) {
|
|
_stream->skip(2);
|
|
cmd = _stream->readUint16LE();
|
|
}
|
|
|
|
// Break
|
|
if (cmd == kCommandBreakSkip0) {
|
|
continue;
|
|
} else if (cmd == kCommandBreakSkip16) {
|
|
cmd = _stream->readUint16LE();
|
|
_stream->skip(cmd);
|
|
continue;
|
|
} else if (cmd == kCommandBreakSkip32) {
|
|
cmd = _stream->readUint32LE();
|
|
_stream->skip(cmd);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Audio
|
|
if (cmd == kCommandNextSound) {
|
|
|
|
nextSoundSlice(hasNextCmd);
|
|
cmd = _stream->readUint16LE();
|
|
|
|
} else if (cmd == kCommandStartSound) {
|
|
|
|
startSound = initialSoundSlice(hasNextCmd);
|
|
cmd = _stream->readUint16LE();
|
|
|
|
} else
|
|
emptySoundSlice(hasNextCmd);
|
|
|
|
// Set palette
|
|
if (cmd == kCommandPalette) {
|
|
_stream->skip(2);
|
|
|
|
_paletteDirty = true;
|
|
|
|
for (int i = 0; i < 768; i++)
|
|
_palette[i] = _stream->readByte() << 2;
|
|
|
|
cmd = _stream->readUint16LE();
|
|
}
|
|
|
|
hasNextCmd = false;
|
|
|
|
if (cmd == kCommandJump) {
|
|
// Jump to frame
|
|
|
|
int16 frame = _stream->readSint16LE();
|
|
if (_framePos) {
|
|
_curFrame = frame - 1;
|
|
_stream->seek(_framePos[frame]);
|
|
|
|
hasNextCmd = true;
|
|
}
|
|
|
|
} else if (cmd == kCommandVideoData) {
|
|
|
|
_videoBufferLen[0] = _stream->readUint32LE() + 2;
|
|
_stream->read(_videoBuffer[0], _videoBufferLen[0]);
|
|
|
|
Common::Rect rect = calcFrameCoords(_curFrame);
|
|
|
|
if (renderFrame(rect))
|
|
_dirtyRects.push_back(rect);
|
|
|
|
} else if (cmd != 0) {
|
|
|
|
_videoBufferLen[0] = cmd + 2;
|
|
_stream->read(_videoBuffer[0], _videoBufferLen[0]);
|
|
|
|
Common::Rect rect = calcFrameCoords(_curFrame);
|
|
|
|
if (renderFrame(rect))
|
|
_dirtyRects.push_back(rect);
|
|
|
|
}
|
|
|
|
} while (hasNextCmd);
|
|
|
|
// Start the audio stream if necessary
|
|
if (startSound && _soundEnabled) {
|
|
_mixer->playStream(_soundType, &_audioHandle, _audioStream,
|
|
-1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
|
|
_soundStage = kSoundPlaying;
|
|
}
|
|
|
|
// End the audio stream if necessary
|
|
if ((_curFrame >= (int32)(_frameCount - 1)) && (_soundStage == kSoundPlaying)) {
|
|
_audioStream->finish();
|
|
_soundStage = kSoundFinished;
|
|
}
|
|
|
|
}
|
|
|
|
Common::Rect IMDDecoder::calcFrameCoords(uint32 frame) {
|
|
Common::Rect rect;
|
|
|
|
if (frame == 0) {
|
|
// First frame is always a full "keyframe"
|
|
|
|
rect.left = _x;
|
|
rect.top = _y;
|
|
rect.right = _x + _width;
|
|
rect.bottom = _y + _height;
|
|
} else if (_frameCoords && ((_frameCoords[frame].left != -1))) {
|
|
// We have frame coordinates for that frame
|
|
|
|
rect.left = _frameCoords[frame].left;
|
|
rect.top = _frameCoords[frame].top;
|
|
rect.right = _frameCoords[frame].right + 1;
|
|
rect.bottom = _frameCoords[frame].bottom + 1;
|
|
} else if (_stdX != -1) {
|
|
// We have standard coordinates
|
|
|
|
rect.left = _stdX;
|
|
rect.top = _stdY;
|
|
rect.right = _stdX + _stdWidth;
|
|
rect.bottom = _stdY + _stdHeight;
|
|
} else {
|
|
// Otherwise, it must be a full "keyframe"
|
|
|
|
rect.left = _x;
|
|
rect.top = _y;
|
|
rect.right = _x + _width;
|
|
rect.bottom = _y + _height;
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
bool IMDDecoder::renderFrame(Common::Rect &rect) {
|
|
if (!rect.isValidRect())
|
|
// Invalid rendering area
|
|
return false;
|
|
|
|
// Clip the rendering area to the video's visible area
|
|
rect.clip(Common::Rect(_x, _y, _x + _width, _y + _height));
|
|
if (!rect.isValidRect() || rect.isEmpty())
|
|
// Result is empty => nothing to do
|
|
return false;
|
|
|
|
byte *dataPtr = _videoBuffer[0];
|
|
uint32 dataSize = _videoBufferLen[0] - 1;
|
|
|
|
uint8 type = *dataPtr++;
|
|
|
|
if (type & 0x10) {
|
|
// Palette data
|
|
|
|
// One byte index
|
|
int index = *dataPtr++;
|
|
|
|
int count = MIN((255 - index) * 3, 48);
|
|
for (int i = 0; i < count; i++)
|
|
_palette[index * 3 + i] = dataPtr[i] << 2;
|
|
|
|
dataPtr += 48;
|
|
dataSize -= 49;
|
|
type ^= 0x10;
|
|
|
|
_paletteDirty = true;
|
|
}
|
|
|
|
if (type & 0x80) {
|
|
// Frame data is compressed
|
|
|
|
type &= 0x7F;
|
|
|
|
if ((type == 2) && (rect.width() == _surface.w) && (_x == 0)) {
|
|
// Directly uncompress onto the video surface
|
|
const int offsetX = rect.left * _surface.format.bytesPerPixel;
|
|
const int offsetY = (_y + rect.top) * _surface.pitch;
|
|
const int offset = offsetX + offsetY;
|
|
|
|
if (deLZ77((byte *)_surface.getPixels() + offset, dataPtr, dataSize,
|
|
_surface.w * _surface.h * _surface.format.bytesPerPixel - offset))
|
|
return true;
|
|
}
|
|
|
|
_videoBufferLen[1] = deLZ77(_videoBuffer[1], dataPtr, dataSize, _videoBufferSize);
|
|
|
|
dataPtr = _videoBuffer[1];
|
|
dataSize = _videoBufferLen[1];
|
|
}
|
|
|
|
// Evaluate the block type
|
|
if (type == 0x01)
|
|
renderBlockSparse (_surface, dataPtr, rect);
|
|
else if (type == 0x02)
|
|
renderBlockWhole (_surface, dataPtr, rect);
|
|
else if (type == 0x42)
|
|
renderBlockWhole4X (_surface, dataPtr, rect);
|
|
else if ((type & 0x0F) == 0x02)
|
|
renderBlockWhole2Y (_surface, dataPtr, rect);
|
|
else
|
|
renderBlockSparse2Y(_surface, dataPtr, rect);
|
|
|
|
return true;
|
|
}
|
|
|
|
void IMDDecoder::nextSoundSlice(bool hasNextCmd) {
|
|
if (hasNextCmd || !_soundEnabled || !_audioStream) {
|
|
// Skip sound
|
|
|
|
_stream->skip(_soundSliceSize);
|
|
return;
|
|
}
|
|
|
|
// Read, convert, queue
|
|
|
|
byte *soundBuf = (byte *)malloc(_soundSliceSize);
|
|
|
|
_stream->read(soundBuf, _soundSliceSize);
|
|
unsignedToSigned(soundBuf, _soundSliceSize);
|
|
|
|
_audioStream->queueBuffer(soundBuf, _soundSliceSize, DisposeAfterUse::YES, 0);
|
|
}
|
|
|
|
bool IMDDecoder::initialSoundSlice(bool hasNextCmd) {
|
|
int dataLength = _soundSliceSize * _soundSlicesCount;
|
|
|
|
if (hasNextCmd || !_soundEnabled) {
|
|
// Skip sound
|
|
|
|
_stream->skip(dataLength);
|
|
return false;
|
|
}
|
|
|
|
if (!_audioStream || (_soundStage == kSoundFinished)) {
|
|
delete _audioStream;
|
|
|
|
_audioStream = Audio::makeQueuingAudioStream(_soundFreq, false);
|
|
_soundStage = kSoundLoaded;
|
|
}
|
|
|
|
// Read, convert, queue
|
|
|
|
byte *soundBuf = (byte *)malloc(dataLength);
|
|
|
|
_stream->read(soundBuf, dataLength);
|
|
unsignedToSigned(soundBuf, dataLength);
|
|
|
|
_audioStream->queueBuffer(soundBuf, dataLength, DisposeAfterUse::YES, 0);
|
|
|
|
return _soundStage == kSoundLoaded;
|
|
}
|
|
|
|
void IMDDecoder::emptySoundSlice(bool hasNextCmd) {
|
|
if (hasNextCmd || !_soundEnabled || !_audioStream)
|
|
return;
|
|
|
|
// Create an empty sound buffer and queue it
|
|
|
|
byte *soundBuf = (byte *)malloc(_soundSliceSize);
|
|
|
|
memset(soundBuf, 0, _soundSliceSize);
|
|
|
|
_audioStream->queueBuffer(soundBuf, _soundSliceSize, DisposeAfterUse::YES, 0);
|
|
}
|
|
|
|
uint32 IMDDecoder::getFlags() const {
|
|
return _flags;
|
|
}
|
|
|
|
Graphics::PixelFormat IMDDecoder::getPixelFormat() const {
|
|
return Graphics::PixelFormat::createFormatCLUT8();
|
|
}
|
|
|
|
class DPCMStream : public Audio::AudioStream {
|
|
public:
|
|
DPCMStream(Common::SeekableReadStream *stream, int rate, int channels, bool oldStereo) {
|
|
_stream = stream;
|
|
_rate = rate;
|
|
_channels = channels;
|
|
_oldStereo = oldStereo;
|
|
if (oldStereo) {
|
|
_buffer[0] = _buffer[1] = 0;
|
|
}
|
|
}
|
|
|
|
~DPCMStream() {
|
|
delete _stream;
|
|
}
|
|
|
|
int readBuffer(int16 *buffer, const int numSamples);
|
|
bool isStereo() const { return _channels == 2; }
|
|
int getRate() const { return _rate; }
|
|
bool endOfData() const { return _stream->pos() >= _stream->size() || _stream->eos() || _stream->err(); }
|
|
|
|
private:
|
|
Common::SeekableReadStream *_stream;
|
|
int _channels;
|
|
int _rate;
|
|
int _buffer[2];
|
|
bool _oldStereo;
|
|
};
|
|
|
|
int DPCMStream::readBuffer(int16 *buffer, const int numSamples) {
|
|
static const uint16 tableDPCM[128] = {
|
|
0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080,
|
|
0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120,
|
|
0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0,
|
|
0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230,
|
|
0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280,
|
|
0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0,
|
|
0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320,
|
|
0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370,
|
|
0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0,
|
|
0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480,
|
|
0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700,
|
|
0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00,
|
|
0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
|
|
};
|
|
|
|
assert((numSamples % _channels) == 0);
|
|
|
|
int samples = 0;
|
|
|
|
// Our starting position
|
|
if (!_oldStereo && _stream->pos() == 0) {
|
|
for (int i = 0; i < _channels; i++)
|
|
*buffer++ = _buffer[i] = _stream->readSint16LE();
|
|
|
|
samples += _channels;
|
|
}
|
|
|
|
while (!endOfData() && samples < numSamples) {
|
|
if (_channels == 2 && _stream->size() == 1) {
|
|
warning("Buffer underrun in DPCMStream");
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < _channels; i++) {
|
|
byte data = _stream->readByte();
|
|
|
|
if (data & 0x80)
|
|
_buffer[i] -= tableDPCM[data & 0x7f];
|
|
else
|
|
_buffer[i] += tableDPCM[data];
|
|
|
|
// Emulating x86 16-bit signed register overflow
|
|
if (_buffer[i] > 32767) {
|
|
_buffer[i] -= 65536;
|
|
} else if (_buffer[i] < -32768) {
|
|
_buffer[i] += 65536;
|
|
}
|
|
|
|
*buffer++ = _buffer[i];
|
|
}
|
|
|
|
samples += _channels;
|
|
}
|
|
|
|
return samples;
|
|
}
|
|
|
|
VMDDecoder::File::File() {
|
|
offset = 0;
|
|
size = 0;
|
|
realSize = 0;
|
|
}
|
|
|
|
|
|
VMDDecoder::Part::Part() {
|
|
type = kPartTypeSeparator;
|
|
field_1 = 0;
|
|
field_E = 0;
|
|
size = 0;
|
|
left = 0;
|
|
top = 0;
|
|
right = 0;
|
|
bottom = 0;
|
|
id = 0;
|
|
flags = 0;
|
|
}
|
|
|
|
|
|
VMDDecoder::Frame::Frame() {
|
|
parts = 0;
|
|
offset = 0;
|
|
}
|
|
|
|
VMDDecoder::Frame::~Frame() {
|
|
delete[] parts;
|
|
}
|
|
|
|
VMDDecoder::VMDDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) : CoktelDecoder(mixer, soundType),
|
|
_stream(0), _version(0), _flags(0), _frameInfoOffset(0), _partsPerFrame(0), _frames(0),
|
|
_soundFlags(0), _soundFreq(0), _soundSliceSize(0), _soundSlicesCount(0),
|
|
_soundBytesPerSample(0), _soundStereo(0), _soundHeaderSize(0), _soundDataSize(0),
|
|
_soundLastFilledFrame(0), _audioFormat(kAudioFormat8bitRaw),
|
|
_hasVideo(false), _videoCodec(0), _blitMode(0), _bytesPerPixel(0),
|
|
_firstFramePos(0), _videoBufferSize(0), _externalCodec(false), _codec(0),
|
|
_subtitle(-1), _isPaletted(true), _autoStartSound(true), _oldStereoBuffer(nullptr) {
|
|
|
|
_videoBuffer [0] = 0;
|
|
_videoBuffer [1] = 0;
|
|
_videoBuffer [2] = 0;
|
|
_videoBufferLen[0] = 0;
|
|
_videoBufferLen[1] = 0;
|
|
_videoBufferLen[2] = 0;
|
|
}
|
|
|
|
VMDDecoder::~VMDDecoder() {
|
|
close();
|
|
}
|
|
|
|
bool VMDDecoder::reloadStream(Common::SeekableReadStream *stream) {
|
|
if (!_stream)
|
|
return false;
|
|
|
|
if (!stream->seek(_stream->pos())) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
delete _stream;
|
|
_stream = stream;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VMDDecoder::seek(int32 frame, int whence, bool restart) {
|
|
if (!evaluateSeekFrame(frame, whence))
|
|
return false;
|
|
|
|
if (frame == _curFrame)
|
|
// Nothing to do
|
|
return true;
|
|
|
|
// Restart sound
|
|
if (_hasSound && (frame == -1) &&
|
|
((_soundStage == kSoundNone) || (_soundStage == kSoundFinished))) {
|
|
|
|
delete _audioStream;
|
|
|
|
_soundStage = kSoundLoaded;
|
|
createAudioStream();
|
|
}
|
|
|
|
_subtitle = -1;
|
|
|
|
if ((_blitMode > 0) && (_flags & 0x4000)) {
|
|
if (_curFrame > frame) {
|
|
_stream->seek(_frames[0].offset);
|
|
_curFrame = -1;
|
|
}
|
|
|
|
while (frame > _curFrame)
|
|
decodeNextFrame();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Seek
|
|
_stream->seek(_frames[frame + 1].offset);
|
|
_curFrame = frame;
|
|
_startTime = g_system->getMillis() - ((frame + 2) * getStaticTimeToNextFrame());
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
void VMDDecoder::setXY(uint16 x, uint16 y) {
|
|
uint16 curX = _x;
|
|
uint16 setX = x;
|
|
|
|
if ((x != 0xFFFF) && (_blitMode == 1)) {
|
|
curX *= _bytesPerPixel;
|
|
setX *= _bytesPerPixel;
|
|
}
|
|
|
|
for (uint32 i = 0; i < _frameCount; i++) {
|
|
for (int j = 0; j < _partsPerFrame; j++) {
|
|
|
|
if (_frames[i].parts[j].type == kPartTypeVideo) {
|
|
if (x != 0xFFFF) {
|
|
_frames[i].parts[j].left = _frames[i].parts[j].left - curX + setX;
|
|
_frames[i].parts[j].right = _frames[i].parts[j].right - curX + setX;
|
|
}
|
|
if (y != 0xFFFF) {
|
|
_frames[i].parts[j].top = _frames[i].parts[j].top - _y + y;
|
|
_frames[i].parts[j].bottom = _frames[i].parts[j].bottom - _y + y;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (x != 0xFFFF)
|
|
_x = x;
|
|
if (y != 0xFFFF)
|
|
_y = y;
|
|
}
|
|
|
|
bool VMDDecoder::openExternalCodec() {
|
|
delete _codec;
|
|
_codec = 0;
|
|
|
|
if (_externalCodec) {
|
|
if (_videoCodec == kVideoCodecIndeo3) {
|
|
_isPaletted = false;
|
|
|
|
_codec = new Image::Indeo3Decoder(_width, _height, g_system->getScreenFormat().bpp());
|
|
|
|
} else {
|
|
warning("VMDDecoder::openExternalCodec(): Unknown video codec FourCC \"%s\"",
|
|
tag2str(_videoCodec));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void VMDDecoder::colorModeChanged() {
|
|
openExternalCodec();
|
|
}
|
|
|
|
bool VMDDecoder::loadStream(Common::SeekableReadStream *stream) {
|
|
close();
|
|
|
|
_stream = stream;
|
|
|
|
_stream->seek(0);
|
|
|
|
uint16 headerLength;
|
|
uint16 handle;
|
|
|
|
headerLength = _stream->readUint16LE();
|
|
handle = _stream->readUint16LE();
|
|
_version = _stream->readUint16LE();
|
|
|
|
// Version checking
|
|
if (headerLength == 50) {
|
|
// Newer version, used in Addy 5 upwards
|
|
warning("VMDDecoder::loadStream(): TODO: Addy 5 videos");
|
|
} else if (headerLength == 814) {
|
|
// Old version
|
|
_features |= kFeaturesPalette;
|
|
} else {
|
|
warning("VMDDecoder::loadStream(): Version incorrect (%d, %d, %d)", headerLength, handle, _version);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
_frameCount = _stream->readUint16LE();
|
|
|
|
_defaultX = _stream->readSint16LE();
|
|
_defaultY = _stream->readSint16LE();
|
|
_width = _stream->readSint16LE();
|
|
_height = _stream->readSint16LE();
|
|
|
|
_x = _defaultX;
|
|
_y = _defaultY;
|
|
|
|
if ((_width != 0) && (_height != 0)) {
|
|
|
|
_hasVideo = true;
|
|
_features |= kFeaturesVideo;
|
|
|
|
} else
|
|
_hasVideo = false;
|
|
|
|
_bytesPerPixel = 1;
|
|
if (_version & 4)
|
|
_bytesPerPixel = handle + 1;
|
|
|
|
if (_bytesPerPixel > 3) {
|
|
warning("VMDDecoder::loadStream(): Requested %d bytes per pixel (%d, %d, %d)",
|
|
_bytesPerPixel, headerLength, handle, _version);
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
_flags = _stream->readUint16LE();
|
|
|
|
_partsPerFrame = _stream->readUint16LE();
|
|
_firstFramePos = _stream->readUint32LE();
|
|
|
|
_videoCodec = _stream->readUint32BE();
|
|
|
|
if (_features & kFeaturesPalette) {
|
|
for (int i = 0; i < 768; i++)
|
|
_palette[i] = _stream->readByte() << 2;
|
|
|
|
_paletteDirty = true;
|
|
}
|
|
|
|
uint32 videoBufferSize1 = _stream->readUint32LE();
|
|
uint32 videoBufferSize2 = _stream->readUint32LE();
|
|
|
|
_videoBufferSize = MAX(videoBufferSize1, videoBufferSize2);
|
|
|
|
if (_hasVideo) {
|
|
if (!assessVideoProperties()) {
|
|
close();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
_soundFreq = _stream->readSint16LE();
|
|
_soundSliceSize = _stream->readSint16LE();
|
|
_soundSlicesCount = _stream->readSint16LE();
|
|
_soundFlags = _stream->readUint16LE();
|
|
|
|
_hasSound = (_soundFreq != 0);
|
|
|
|
if (_hasSound) {
|
|
if (!assessAudioProperties()) {
|
|
close();
|
|
return false;
|
|
}
|
|
} else
|
|
_frameRate = 12;
|
|
|
|
_frameInfoOffset = _stream->readUint32LE();
|
|
|
|
int numFiles;
|
|
if (!readFrameTable(numFiles)) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
_stream->seek(_firstFramePos);
|
|
|
|
if (numFiles == 0)
|
|
return true;
|
|
|
|
_files.reserve(numFiles);
|
|
if (!readFiles()) {
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
_stream->seek(_firstFramePos);
|
|
return true;
|
|
}
|
|
|
|
bool VMDDecoder::assessVideoProperties() {
|
|
_isPaletted = true;
|
|
|
|
if ((_version & 2) && !(_version & 8)) {
|
|
_externalCodec = true;
|
|
_videoBufferSize = 0;
|
|
} else
|
|
_externalCodec = false;
|
|
|
|
if (!openExternalCodec())
|
|
return false;
|
|
|
|
if (_externalCodec)
|
|
_blitMode = 0;
|
|
else if (_bytesPerPixel == 1)
|
|
_blitMode = 0;
|
|
else if ((_bytesPerPixel == 2) || (_bytesPerPixel == 3)) {
|
|
int n = (_flags & 0x80) ? 2 : 3;
|
|
|
|
_blitMode = _bytesPerPixel - 1;
|
|
_bytesPerPixel = n;
|
|
|
|
_isPaletted = false;
|
|
}
|
|
|
|
if (_blitMode == 1) {
|
|
_width /= _bytesPerPixel;
|
|
_defaultX /= _bytesPerPixel;
|
|
_x /= _bytesPerPixel;
|
|
}
|
|
|
|
if (_hasVideo) {
|
|
uint32 suggestedVideoBufferSize = _videoBufferSize;
|
|
|
|
_videoBufferSize = _width * _height * _bytesPerPixel + 1000;
|
|
|
|
if ((suggestedVideoBufferSize > _videoBufferSize) && (suggestedVideoBufferSize < 2097152)) {
|
|
warning("Suggested video buffer size greater than what should be needed (%d, %d, %dx%d",
|
|
suggestedVideoBufferSize, _videoBufferSize, _width, _height);
|
|
|
|
_videoBufferSize = suggestedVideoBufferSize;
|
|
}
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
_videoBuffer[i] = new byte[_videoBufferSize]();
|
|
|
|
_8bppSurface[i].init(_width * _bytesPerPixel, _height, _width * _bytesPerPixel,
|
|
_videoBuffer[i], Graphics::PixelFormat::createFormatCLUT8());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VMDDecoder::assessAudioProperties() {
|
|
bool supportedFormat = true;
|
|
|
|
_features |= kFeaturesSound;
|
|
|
|
_soundStereo = (_soundFlags & 0x8000) ? 1 : ((_soundFlags & 0x200) ? 2 : 0);
|
|
|
|
if (_soundSliceSize < 0) {
|
|
_soundBytesPerSample = 2;
|
|
_soundSliceSize = -_soundSliceSize;
|
|
|
|
if (_soundFlags & 0x10) {
|
|
_audioFormat = kAudioFormat16bitADPCM;
|
|
_soundHeaderSize = 3;
|
|
_soundDataSize = _soundSliceSize >> 1;
|
|
|
|
if (_soundStereo > 0)
|
|
supportedFormat = false;
|
|
|
|
} else {
|
|
_audioFormat = kAudioFormat16bitDPCM;
|
|
_soundHeaderSize = 1;
|
|
_soundDataSize = _soundSliceSize;
|
|
|
|
if (_soundStereo == 1) {
|
|
supportedFormat = false;
|
|
} else if (_soundStereo == 2) {
|
|
_soundDataSize = 2 * _soundDataSize + 2;
|
|
_soundHeaderSize = 4;
|
|
}
|
|
|
|
}
|
|
} else {
|
|
if (_soundStereo == 2) {
|
|
supportedFormat = false;
|
|
}
|
|
|
|
_soundBytesPerSample = 1;
|
|
_soundHeaderSize = 0;
|
|
_soundDataSize = _soundSliceSize;
|
|
|
|
if (_soundStereo == 1) {
|
|
_audioFormat = kAudioFormat16bitDPCM;
|
|
} else {
|
|
_audioFormat = kAudioFormat8bitRaw;
|
|
}
|
|
}
|
|
|
|
if (!supportedFormat) {
|
|
warning("VMDDecoder::assessAudioProperties(): Unsupported audio format: %d bits, encoding %d, stereo %d",
|
|
_soundBytesPerSample * 8, _audioFormat, _soundStereo);
|
|
return false;
|
|
}
|
|
|
|
_frameRate = Common::Rational(_soundFreq, _soundSliceSize / (_soundStereo == 1 ? 2 : 1));
|
|
|
|
_hasSound = true;
|
|
_soundEnabled = true;
|
|
_soundStage = kSoundLoaded;
|
|
createAudioStream();
|
|
return true;
|
|
}
|
|
|
|
bool VMDDecoder::readFrameTable(int &numFiles) {
|
|
numFiles = 0;
|
|
|
|
_stream->seek(_frameInfoOffset);
|
|
_frames = new Frame[_frameCount];
|
|
for (uint16 i = 0; i < _frameCount; i++) {
|
|
_frames[i].parts = new Part[_partsPerFrame];
|
|
_stream->skip(2); // Unknown
|
|
_frames[i].offset = _stream->readUint32LE();
|
|
}
|
|
|
|
_soundLastFilledFrame = 0;
|
|
for (uint16 i = 0; i < _frameCount; i++) {
|
|
bool separator = false;
|
|
|
|
for (uint16 j = 0; j < _partsPerFrame; j++) {
|
|
|
|
_frames[i].parts[j].type = (PartType) _stream->readByte();
|
|
_frames[i].parts[j].field_1 = _stream->readByte();
|
|
_frames[i].parts[j].size = _stream->readUint32LE();
|
|
|
|
if (_frames[i].parts[j].type == kPartTypeAudio) {
|
|
|
|
_frames[i].parts[j].flags = _stream->readByte();
|
|
_stream->skip(9); // Unknown
|
|
|
|
if (_frames[i].parts[j].flags != 3)
|
|
_soundLastFilledFrame = i;
|
|
|
|
} else if (_frames[i].parts[j].type == kPartTypeVideo) {
|
|
|
|
_frames[i].parts[j].left = _stream->readUint16LE();
|
|
_frames[i].parts[j].top = _stream->readUint16LE();
|
|
_frames[i].parts[j].right = _stream->readUint16LE();
|
|
_frames[i].parts[j].bottom = _stream->readUint16LE();
|
|
_frames[i].parts[j].field_E = _stream->readByte();
|
|
_frames[i].parts[j].flags = _stream->readByte();
|
|
|
|
} else if (_frames[i].parts[j].type == kPartTypeSubtitle) {
|
|
_frames[i].parts[j].id = _stream->readByte();
|
|
// Speech text file name
|
|
_stream->skip(9);
|
|
} else if (_frames[i].parts[j].type == kPartTypeFile) {
|
|
if (!separator)
|
|
numFiles++;
|
|
_stream->skip(10);
|
|
} else if (_frames[i].parts[j].type == kPartTypeSeparator) {
|
|
separator = true;
|
|
_stream->skip(10);
|
|
} else {
|
|
// Unknown type
|
|
_stream->skip(10);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VMDDecoder::readFiles() {
|
|
uint32 ssize = _stream->size();
|
|
for (uint16 i = 0; i < _frameCount; i++) {
|
|
_stream->seek(_frames[i].offset);
|
|
|
|
for (uint16 j = 0; j < _partsPerFrame; j++) {
|
|
if (_frames[i].parts[j].type == kPartTypeSeparator)
|
|
break;
|
|
|
|
if (_frames[i].parts[j].type == kPartTypeFile) {
|
|
File file;
|
|
|
|
file.offset = _stream->pos() + 20;
|
|
file.size = _frames[i].parts[j].size;
|
|
file.realSize = _stream->readUint32LE();
|
|
|
|
char name[16];
|
|
|
|
_stream->read(name, 16);
|
|
name[15] = '\0';
|
|
|
|
file.name = name;
|
|
|
|
_stream->skip(_frames[i].parts[j].size - 20);
|
|
|
|
if ((((uint32) file.realSize) >= ssize) || (file.name == ""))
|
|
continue;
|
|
|
|
_files.push_back(file);
|
|
|
|
} else
|
|
_stream->skip(_frames[i].parts[j].size);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void VMDDecoder::close() {
|
|
CoktelDecoder::close();
|
|
|
|
delete _stream;
|
|
|
|
delete[] _frames;
|
|
|
|
delete[] _videoBuffer[0];
|
|
delete[] _videoBuffer[1];
|
|
delete[] _videoBuffer[2];
|
|
|
|
delete _codec;
|
|
|
|
_files.clear();
|
|
|
|
|
|
_stream = 0;
|
|
|
|
_version = 0;
|
|
_flags = 0;
|
|
|
|
_frameInfoOffset = 0;
|
|
_partsPerFrame = 0;
|
|
_frames = 0;
|
|
|
|
_soundFlags = 0;
|
|
_soundFreq = 0;
|
|
_soundSliceSize = 0;
|
|
_soundSlicesCount = 0;
|
|
_soundBytesPerSample = 0;
|
|
_soundStereo = 0;
|
|
_soundHeaderSize = 0;
|
|
_soundDataSize = 0;
|
|
_soundLastFilledFrame = 0;
|
|
_audioFormat = kAudioFormat8bitRaw;
|
|
_oldStereoBuffer = nullptr;
|
|
|
|
_hasVideo = false;
|
|
_videoCodec = 0;
|
|
_blitMode = 0;
|
|
_bytesPerPixel = 0;
|
|
|
|
_firstFramePos = 0;
|
|
|
|
_videoBufferSize = 0;
|
|
_videoBuffer [0] = 0;
|
|
_videoBuffer [1] = 0;
|
|
_videoBuffer [2] = 0;
|
|
_videoBufferLen[0] = 0;
|
|
_videoBufferLen[1] = 0;
|
|
_videoBufferLen[2] = 0;
|
|
|
|
_externalCodec = false;
|
|
_codec = 0;
|
|
|
|
_isPaletted = true;
|
|
}
|
|
|
|
bool VMDDecoder::isVideoLoaded() const {
|
|
return _stream != 0;
|
|
}
|
|
|
|
const Graphics::Surface *VMDDecoder::decodeNextFrame() {
|
|
if (!isVideoLoaded() || endOfVideo())
|
|
return 0;
|
|
|
|
createSurface();
|
|
|
|
processFrame();
|
|
|
|
if (_curFrame == 0)
|
|
_startTime = g_system->getMillis();
|
|
|
|
return &_surface;
|
|
}
|
|
|
|
void VMDDecoder::processFrame() {
|
|
_curFrame++;
|
|
|
|
_dirtyRects.clear();
|
|
|
|
_subtitle = -1;
|
|
|
|
bool startSound = false;
|
|
|
|
_stream->seek(_frames[_curFrame].offset, SEEK_SET);
|
|
|
|
for (uint16 i = 0; i < _partsPerFrame; i++) {
|
|
Part &part = _frames[_curFrame].parts[i];
|
|
|
|
if (part.type == kPartTypeAudio) {
|
|
|
|
if (part.flags == 1) {
|
|
// Next sound slice data
|
|
|
|
if (_soundEnabled) {
|
|
filledSoundSlice(part.size);
|
|
|
|
if (_soundStage == kSoundLoaded)
|
|
startSound = true;
|
|
|
|
} else
|
|
_stream->skip(part.size);
|
|
|
|
} else if (part.flags == 2) {
|
|
// Initial sound data (all slices)
|
|
|
|
if (_soundEnabled) {
|
|
uint32 mask = _stream->readUint32LE();
|
|
filledSoundSlices(part.size - /* mask size */ 4, mask);
|
|
|
|
if (_soundStage == kSoundLoaded)
|
|
startSound = true;
|
|
|
|
} else
|
|
_stream->skip(part.size);
|
|
|
|
} else if (part.flags == 3) {
|
|
// Empty sound slice
|
|
|
|
if (_soundEnabled) {
|
|
if ((uint32)_curFrame < _soundLastFilledFrame)
|
|
emptySoundSlice(_soundDataSize * _soundBytesPerSample);
|
|
|
|
if (_soundStage == kSoundLoaded)
|
|
startSound = true;
|
|
}
|
|
|
|
_stream->skip(part.size);
|
|
} else if (part.flags == 4) {
|
|
warning("VMDDecoder::processFrame(): TODO: Addy 5 sound type 4 (%d)", part.size);
|
|
disableSound();
|
|
_stream->skip(part.size);
|
|
} else {
|
|
warning("VMDDecoder::processFrame(): Unknown sound type %d", part.flags);
|
|
_stream->skip(part.size);
|
|
}
|
|
|
|
} else if ((part.type == kPartTypeVideo) && !_hasVideo) {
|
|
|
|
warning("VMDDecoder::processFrame(): Header claims there's no video, but video found (%d)", part.size);
|
|
_stream->skip(part.size);
|
|
|
|
} else if ((part.type == kPartTypeVideo) && _hasVideo) {
|
|
|
|
uint32 size = part.size;
|
|
|
|
// New palette
|
|
if (part.flags & 2) {
|
|
uint8 index = _stream->readByte();
|
|
uint8 count = _stream->readByte();
|
|
|
|
for (int j = 0; j < ((count + 1) * 3); j++)
|
|
_palette[index * 3 + j] = _stream->readByte() << 2;
|
|
|
|
_stream->skip((255 - count) * 3);
|
|
|
|
_paletteDirty = true;
|
|
|
|
size -= (768 + 2);
|
|
}
|
|
|
|
_stream->read(_videoBuffer[0], size);
|
|
_videoBufferLen[0] = size;
|
|
|
|
Common::Rect rect(part.left, part.top, part.right + 1, part.bottom + 1);
|
|
if (renderFrame(rect))
|
|
_dirtyRects.push_back(rect);
|
|
|
|
} else if (part.type == kPartTypeSeparator) {
|
|
|
|
// Ignore
|
|
|
|
} else if (part.type == kPartTypeFile) {
|
|
|
|
// Ignore
|
|
_stream->skip(part.size);
|
|
|
|
} else if (part.type == kPartType4) {
|
|
|
|
// Unknown, ignore
|
|
_stream->skip(part.size);
|
|
|
|
} else if (part.type == kPartTypeSubtitle) {
|
|
|
|
_subtitle = part.id;
|
|
_stream->skip(part.size);
|
|
|
|
} else {
|
|
|
|
warning("VMDDecoder::processFrame(): Unknown frame part type %d, size %d (%d of %d)",
|
|
part.type, part.size, i + 1, _partsPerFrame);
|
|
|
|
}
|
|
}
|
|
|
|
if (startSound && _soundEnabled) {
|
|
if (_hasSound && _audioStream) {
|
|
if (_autoStartSound)
|
|
_mixer->playStream(_soundType, &_audioHandle, _audioStream,
|
|
-1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
|
|
_soundStage = kSoundPlaying;
|
|
} else
|
|
_soundStage = kSoundNone;
|
|
}
|
|
|
|
if (((uint32)_curFrame == (_frameCount - 1)) && (_soundStage == 2)) {
|
|
_audioStream->finish();
|
|
_soundStage = kSoundFinished;
|
|
}
|
|
}
|
|
|
|
bool VMDDecoder::renderFrame(Common::Rect &rect) {
|
|
Common::Rect realRect, fakeRect;
|
|
if (!getRenderRects(rect, realRect, fakeRect))
|
|
return false;
|
|
|
|
if (_externalCodec) {
|
|
if (!_codec)
|
|
return false;
|
|
|
|
Common::MemoryReadStream frameStream(_videoBuffer[0], _videoBufferLen[0]);
|
|
const Graphics::Surface *codecSurf = _codec->decodeFrame(frameStream);
|
|
if (!codecSurf)
|
|
return false;
|
|
|
|
rect = Common::Rect(_x, _y, _x + codecSurf->w, _y + codecSurf->h);
|
|
rect.clip(Common::Rect(_x, _y, _x + _width, _y + _height));
|
|
|
|
renderBlockWhole(_surface, (const byte *)codecSurf->getPixels(), rect);
|
|
return true;
|
|
}
|
|
|
|
uint8 srcBuffer = 0;
|
|
byte *dataPtr = _videoBuffer[srcBuffer];
|
|
uint32 dataSize = _videoBufferLen[srcBuffer] - 1;
|
|
|
|
uint8 type = *dataPtr++;
|
|
|
|
if (type & 0x80) {
|
|
// Frame data is compressed
|
|
|
|
type &= 0x7F;
|
|
|
|
if ((type == 2) && (rect.width() == _surface.w) && (_x == 0) && (_blitMode == 0)) {
|
|
// Directly uncompress onto the video surface
|
|
const int offsetX = rect.left * _surface.format.bytesPerPixel;
|
|
const int offsetY = rect.top * _surface.pitch;
|
|
const int offset = offsetX + offsetY;
|
|
|
|
if (deLZ77((byte *)_surface.getPixels() + offset, dataPtr, dataSize,
|
|
_surface.w * _surface.h * _surface.format.bytesPerPixel - offset))
|
|
return true;
|
|
}
|
|
|
|
srcBuffer = 1;
|
|
_videoBufferLen[srcBuffer] =
|
|
deLZ77(_videoBuffer[srcBuffer], dataPtr, dataSize, _videoBufferSize);
|
|
|
|
dataPtr = _videoBuffer[srcBuffer];
|
|
dataSize = _videoBufferLen[srcBuffer];
|
|
}
|
|
|
|
Common::Rect *blockRect = &fakeRect;
|
|
Graphics::Surface *surface = &_surface;
|
|
if (_blitMode == 0) {
|
|
*blockRect = Common::Rect(blockRect->left + _x, blockRect->top + _y,
|
|
blockRect->right + _x, blockRect->bottom + _y);
|
|
} else {
|
|
surface = &_8bppSurface[2];
|
|
}
|
|
|
|
// Evaluate the block type
|
|
if (type == 0x01)
|
|
renderBlockSparse (*surface, dataPtr, *blockRect);
|
|
else if (type == 0x02)
|
|
renderBlockWhole (*surface, dataPtr, *blockRect);
|
|
else if (type == 0x03)
|
|
renderBlockRLE (*surface, dataPtr, *blockRect);
|
|
else if (type == 0x42)
|
|
renderBlockWhole4X (*surface, dataPtr, *blockRect);
|
|
else if ((type & 0x0F) == 0x02)
|
|
renderBlockWhole2Y (*surface, dataPtr, *blockRect);
|
|
else
|
|
renderBlockSparse2Y(*surface, dataPtr, *blockRect);
|
|
|
|
if (_blitMode > 0) {
|
|
if (_bytesPerPixel == 2)
|
|
blit16(*surface, *blockRect);
|
|
else if (_bytesPerPixel == 3)
|
|
blit24(*surface, *blockRect);
|
|
|
|
*blockRect = Common::Rect(blockRect->left + _x, blockRect->top + _y,
|
|
blockRect->right + _x, blockRect->bottom + _y);
|
|
}
|
|
|
|
rect = *blockRect;
|
|
return true;
|
|
}
|
|
|
|
bool VMDDecoder::getRenderRects(const Common::Rect &rect,
|
|
Common::Rect &realRect, Common::Rect &fakeRect) {
|
|
|
|
realRect = rect;
|
|
fakeRect = rect;
|
|
|
|
if (_blitMode == 0) {
|
|
|
|
realRect = Common::Rect(realRect.left - _x, realRect.top - _y,
|
|
realRect.right - _x, realRect.bottom - _y);
|
|
|
|
fakeRect = Common::Rect(fakeRect.left - _x, fakeRect.top - _y,
|
|
fakeRect.right - _x, fakeRect.bottom - _y);
|
|
|
|
} else if (_blitMode == 1) {
|
|
|
|
realRect = Common::Rect(rect.left / _bytesPerPixel, rect.top,
|
|
rect.right / _bytesPerPixel, rect.bottom);
|
|
|
|
realRect = Common::Rect(realRect.left - _x, realRect.top - _y,
|
|
realRect.right - _x, realRect.bottom - _y);
|
|
|
|
fakeRect = Common::Rect(fakeRect.left - _x * _bytesPerPixel, fakeRect.top - _y,
|
|
fakeRect.right - _x * _bytesPerPixel, fakeRect.bottom - _y);
|
|
|
|
} else if (_blitMode == 2) {
|
|
|
|
fakeRect = Common::Rect(rect.left * _bytesPerPixel, rect.top,
|
|
rect.right * _bytesPerPixel, rect.bottom);
|
|
|
|
realRect = Common::Rect(realRect.left - _x, realRect.top - _y,
|
|
realRect.right - _x, realRect.bottom - _y);
|
|
|
|
fakeRect = Common::Rect(fakeRect.left - _x * _bytesPerPixel, fakeRect.top - _y,
|
|
fakeRect.right - _x * _bytesPerPixel, fakeRect.bottom - _y);
|
|
|
|
}
|
|
|
|
realRect.clip(Common::Rect(_surface.w, _surface.h));
|
|
fakeRect.clip(Common::Rect(_surface.w * _bytesPerPixel, _surface.h));
|
|
|
|
if (!realRect.isValidRect() || realRect.isEmpty())
|
|
return false;
|
|
if (!fakeRect.isValidRect() || realRect.isEmpty())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void VMDDecoder::blit16(const Graphics::Surface &srcSurf, Common::Rect &rect) {
|
|
rect = Common::Rect(rect.left / 2, rect.top, rect.right / 2, rect.bottom);
|
|
|
|
Common::Rect srcRect = rect;
|
|
|
|
rect.clip(_surface.w, _surface.h);
|
|
|
|
Graphics::PixelFormat pixelFormat = getPixelFormat();
|
|
|
|
// We cannot use getBasePtr here because srcSurf.format.bytesPerPixel is
|
|
// different from _bytesPerPixel.
|
|
const byte *src = (const byte *)srcSurf.getPixels() +
|
|
(srcRect.top * srcSurf.pitch) + srcRect.left * _bytesPerPixel;
|
|
byte *dst = (byte *)_surface.getBasePtr(_x + rect.left, _y + rect.top);
|
|
|
|
for (int i = 0; i < rect.height(); i++) {
|
|
const byte *srcRow = src;
|
|
byte *dstRow = dst;
|
|
|
|
for (int j = 0; j < rect.width(); j++, srcRow += 2, dstRow += _surface.format.bytesPerPixel) {
|
|
uint16 data = READ_LE_UINT16(srcRow);
|
|
|
|
byte r = ((data & 0x7C00) >> 10) << 3;
|
|
byte g = ((data & 0x03E0) >> 5) << 3;
|
|
byte b = ((data & 0x001F) >> 0) << 3;
|
|
|
|
uint32 c = pixelFormat.RGBToColor(r, g, b);
|
|
if ((r == 0) && (g == 0) && (b == 0))
|
|
c = 0;
|
|
|
|
if (_surface.format.bytesPerPixel == 2)
|
|
*((uint16 *)dstRow) = (uint16) c;
|
|
else if (_surface.format.bytesPerPixel == 4)
|
|
*((uint32 *)dstRow) = (uint32) c;
|
|
}
|
|
|
|
src += srcSurf .pitch;
|
|
dst += _surface.pitch;
|
|
}
|
|
}
|
|
|
|
void VMDDecoder::blit24(const Graphics::Surface &srcSurf, Common::Rect &rect) {
|
|
rect = Common::Rect(rect.left / 3, rect.top, rect.right / 3, rect.bottom);
|
|
|
|
Common::Rect srcRect = rect;
|
|
|
|
rect.clip(_surface.w, _surface.h);
|
|
|
|
Graphics::PixelFormat pixelFormat = getPixelFormat();
|
|
|
|
// We cannot use getBasePtr here because srcSurf.format.bytesPerPixel is
|
|
// different from _bytesPerPixel.
|
|
const byte *src = (const byte *)srcSurf.getPixels() +
|
|
(srcRect.top * srcSurf.pitch) + srcRect.left * _bytesPerPixel;
|
|
byte *dst = (byte *)_surface.getBasePtr(_x + rect.left, _y + rect.top);
|
|
|
|
for (int i = 0; i < rect.height(); i++) {
|
|
const byte *srcRow = src;
|
|
byte *dstRow = dst;
|
|
|
|
for (int j = 0; j < rect.width(); j++, srcRow += 3, dstRow += _surface.format.bytesPerPixel) {
|
|
byte r = srcRow[2];
|
|
byte g = srcRow[1];
|
|
byte b = srcRow[0];
|
|
|
|
uint32 c = pixelFormat.RGBToColor(r, g, b);
|
|
if ((r == 0) && (g == 0) && (b == 0))
|
|
c = 0;
|
|
|
|
if (_surface.format.bytesPerPixel == 2)
|
|
*((uint16 *)dstRow) = (uint16) c;
|
|
else if (_surface.format.bytesPerPixel == 4)
|
|
*((uint32 *)dstRow) = (uint32) c;
|
|
}
|
|
|
|
src += srcSurf .pitch;
|
|
dst += _surface.pitch;
|
|
}
|
|
}
|
|
|
|
void VMDDecoder::emptySoundSlice(uint32 size) {
|
|
if (_soundStereo == 1) {
|
|
// Technically an empty slice could be used at the very beginning of the
|
|
// stream, but anywhere else it would need to dynamically calculate the
|
|
// delta between the current sample and zero sample level and the steps
|
|
// to get a zero level
|
|
error("Old-style stereo cannot be filled with an empty slice");
|
|
}
|
|
|
|
byte *soundBuf = (byte *)malloc(size);
|
|
|
|
if (soundBuf) {
|
|
uint32 flags = 0;
|
|
memset(soundBuf, 0, size);
|
|
flags |= (_soundBytesPerSample == 2) ? Audio::FLAG_16BITS : 0;
|
|
flags |= (_soundStereo > 0) ? Audio::FLAG_STEREO : 0;
|
|
|
|
_audioStream->queueBuffer(soundBuf, size, DisposeAfterUse::YES, flags);
|
|
}
|
|
}
|
|
|
|
void VMDDecoder::filledSoundSlice(uint32 size) {
|
|
if (!_audioStream) {
|
|
_stream->skip(size);
|
|
return;
|
|
}
|
|
|
|
if (_soundStereo == 1) {
|
|
void *buf = malloc(size);
|
|
assert(buf);
|
|
const uint32 numBytesRead = _stream->read(buf, size);
|
|
assert(numBytesRead == size);
|
|
const uint32 numBytesWritten = _oldStereoBuffer->write(buf, size);
|
|
assert(numBytesWritten == size);
|
|
free(buf);
|
|
return;
|
|
}
|
|
|
|
Common::SeekableReadStream *data = _stream->readStream(size);
|
|
Audio::AudioStream *sliceStream = 0;
|
|
|
|
if (_audioFormat == kAudioFormat8bitRaw)
|
|
sliceStream = create8bitRaw(data);
|
|
else if (_audioFormat == kAudioFormat16bitDPCM)
|
|
sliceStream = create16bitDPCM(data);
|
|
else if (_audioFormat == kAudioFormat16bitADPCM)
|
|
sliceStream = create16bitADPCM(data);
|
|
|
|
if (sliceStream)
|
|
_audioStream->queueAudioStream(sliceStream);
|
|
}
|
|
|
|
void VMDDecoder::filledSoundSlices(uint32 size, uint32 mask) {
|
|
bool fillInfo[32];
|
|
|
|
uint8 max;
|
|
uint8 n = evaluateMask(mask, fillInfo, max);
|
|
|
|
// extraSize is needed by videos in some games (GK2) or audio data will be
|
|
// incomplete
|
|
int32 extraSize = size - n * _soundDataSize;
|
|
|
|
if (_soundSlicesCount > 32)
|
|
extraSize -= (_soundSlicesCount - 32) * _soundDataSize;
|
|
|
|
if (n > 0)
|
|
extraSize /= n;
|
|
|
|
// extraSize cannot be negative or audio data will be incomplete in some
|
|
// games (old-style stereo videos in Lighthouse)
|
|
if (extraSize < 0)
|
|
extraSize = 0;
|
|
|
|
for (uint8 i = 0; i < max; i++)
|
|
if (fillInfo[i])
|
|
filledSoundSlice(_soundDataSize + extraSize);
|
|
else
|
|
emptySoundSlice(_soundDataSize * _soundBytesPerSample);
|
|
|
|
if (_soundSlicesCount > 32)
|
|
filledSoundSlice((_soundSlicesCount - 32) * _soundDataSize + _soundHeaderSize);
|
|
}
|
|
|
|
void VMDDecoder::createAudioStream() {
|
|
_audioStream = Audio::makeQueuingAudioStream(_soundFreq, _soundStereo != 0);
|
|
if (_soundStereo == 1) {
|
|
_oldStereoBuffer = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
|
|
_audioStream->queueAudioStream(new DPCMStream(_oldStereoBuffer, _soundFreq, 2, true));
|
|
}
|
|
}
|
|
|
|
uint8 VMDDecoder::evaluateMask(uint32 mask, bool *fillInfo, uint8 &max) {
|
|
max = MIN<int>(_soundSlicesCount - 1, 31);
|
|
|
|
uint8 n = 0;
|
|
for (int i = 0; i < max; i++) {
|
|
|
|
if (!(mask & 1)) {
|
|
n++;
|
|
*fillInfo++ = true;
|
|
} else
|
|
*fillInfo++ = false;
|
|
|
|
mask >>= 1;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
Audio::AudioStream *VMDDecoder::create8bitRaw(Common::SeekableReadStream *stream) {
|
|
int flags = Audio::FLAG_UNSIGNED;
|
|
|
|
if (_soundStereo != 0)
|
|
flags |= Audio::FLAG_STEREO;
|
|
|
|
return Audio::makeRawStream(stream, _soundFreq, flags, DisposeAfterUse::YES);
|
|
}
|
|
|
|
Audio::AudioStream *VMDDecoder::create16bitDPCM(Common::SeekableReadStream *stream) {
|
|
// Old-style stereo audio blocks are not self-contained so cannot be played
|
|
// using this mechanism
|
|
assert(_soundStereo != 1);
|
|
|
|
return new DPCMStream(stream, _soundFreq, (_soundStereo == 0) ? 1 : 2, false);
|
|
}
|
|
|
|
class VMD_ADPCMStream : public Audio::DVI_ADPCMStream {
|
|
public:
|
|
VMD_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse,
|
|
int rate, int channels) : Audio::DVI_ADPCMStream(stream, disposeAfterUse, stream->size(), rate, channels, 0) {
|
|
// FIXME: Using the same predictor/index for two channels probably won't work
|
|
// properly However, we have no samples of this, so an assert is here for now.
|
|
// Also, since the DPCM stereo has a second predictor, I'm lead to believe
|
|
// all VMD with ADPCM are mono unless they changed the code in a later
|
|
// revision.
|
|
assert(channels == 1);
|
|
_startPredictorValue = stream->readSint16LE();
|
|
_startIndexValue = stream->readByte();
|
|
_startpos = 3;
|
|
reset();
|
|
}
|
|
|
|
protected:
|
|
virtual void reset() {
|
|
Audio::DVI_ADPCMStream::reset();
|
|
_status.ima_ch[0].last = _startPredictorValue;
|
|
_status.ima_ch[0].stepIndex = _startIndexValue;
|
|
}
|
|
|
|
private:
|
|
int32 _startPredictorValue;
|
|
int32 _startIndexValue;
|
|
};
|
|
|
|
Audio::AudioStream *VMDDecoder::create16bitADPCM(Common::SeekableReadStream *stream) {
|
|
return new VMD_ADPCMStream(stream, DisposeAfterUse::YES, _soundFreq, (_soundStereo == 0) ? 1 : 2);
|
|
}
|
|
|
|
uint32 VMDDecoder::getFlags() const {
|
|
return _flags;
|
|
}
|
|
|
|
Graphics::PixelFormat VMDDecoder::getPixelFormat() const {
|
|
if (_externalCodec) {
|
|
if (_codec)
|
|
return _codec->getPixelFormat();
|
|
|
|
// If we don't have the needed codec, just assume it's in the
|
|
// current screen format
|
|
return g_system->getScreenFormat();
|
|
}
|
|
|
|
if (_blitMode > 0)
|
|
return g_system->getScreenFormat();
|
|
|
|
return Graphics::PixelFormat::createFormatCLUT8();
|
|
}
|
|
|
|
bool VMDDecoder::getPartCoords(int16 frame, PartType type, int16 &x, int16 &y, int16 &width, int16 &height) {
|
|
if (frame >= ((int32) _frameCount))
|
|
return false;
|
|
|
|
Frame &f = _frames[frame];
|
|
|
|
// Look for a part matching the requested type, stopping at a separator
|
|
Part *part = 0;
|
|
for (int i = 0; i < _partsPerFrame; i++) {
|
|
Part &p = f.parts[i];
|
|
|
|
if ((p.type == kPartTypeSeparator) || (p.type == type)) {
|
|
part = &p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!part)
|
|
return false;
|
|
|
|
x = part->left;
|
|
y = part->top;
|
|
width = part->right - part->left + 1;
|
|
height = part->bottom - part->top + 1;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VMDDecoder::getFrameCoords(int16 frame, int16 &x, int16 &y, int16 &width, int16 &height) {
|
|
return getPartCoords(frame, kPartTypeVideo, x, y, width, height);
|
|
}
|
|
|
|
bool VMDDecoder::hasEmbeddedFiles() const {
|
|
return !_files.empty();
|
|
}
|
|
|
|
bool VMDDecoder::hasEmbeddedFile(const Common::String &fileName) const {
|
|
for (Common::Array<File>::const_iterator file = _files.begin(); file != _files.end(); ++file)
|
|
if (!file->name.compareToIgnoreCase(fileName))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
Common::SeekableReadStream *VMDDecoder::getEmbeddedFile(const Common::String &fileName) const {
|
|
const File *file = 0;
|
|
|
|
for (Common::Array<File>::const_iterator it = _files.begin(); it != _files.end(); ++it)
|
|
if (!it->name.compareToIgnoreCase(fileName)) {
|
|
file = &*it;
|
|
break;
|
|
}
|
|
|
|
if (!file)
|
|
return 0;
|
|
|
|
if ((file->size - 20) != file->realSize) {
|
|
warning("VMDDecoder::getEmbeddedFile(): Sizes for \"%s\" differ! (%d, %d)",
|
|
fileName.c_str(), (file->size - 20), file->realSize);
|
|
return 0;
|
|
}
|
|
|
|
if (!_stream->seek(file->offset)) {
|
|
warning("VMDDecoder::getEmbeddedFile(): Can't seek to offset %d to (file \"%s\")",
|
|
file->offset, fileName.c_str());
|
|
return 0;
|
|
}
|
|
|
|
byte *data = (byte *) malloc(file->realSize);
|
|
if (_stream->read(data, file->realSize) != file->realSize) {
|
|
free(data);
|
|
warning("VMDDecoder::getEmbeddedFile(): Couldn't read %d bytes (file \"%s\")",
|
|
file->realSize, fileName.c_str());
|
|
return 0;
|
|
}
|
|
|
|
Common::MemoryReadStream *stream =
|
|
new Common::MemoryReadStream(data, file->realSize, DisposeAfterUse::YES);
|
|
|
|
return stream;
|
|
}
|
|
|
|
int32 VMDDecoder::getSubtitleIndex() const {
|
|
return _subtitle;
|
|
}
|
|
|
|
bool VMDDecoder::hasVideo() const {
|
|
return _hasVideo;
|
|
}
|
|
|
|
bool VMDDecoder::isPaletted() const {
|
|
return _isPaletted;
|
|
}
|
|
|
|
void VMDDecoder::setAutoStartSound(bool autoStartSound) {
|
|
_autoStartSound = autoStartSound;
|
|
}
|
|
|
|
AdvancedVMDDecoder::AdvancedVMDDecoder(Audio::Mixer::SoundType soundType) {
|
|
setSoundType(soundType);
|
|
_decoder = new VMDDecoder(g_system->getMixer(), soundType);
|
|
_decoder->setAutoStartSound(false);
|
|
}
|
|
|
|
AdvancedVMDDecoder::~AdvancedVMDDecoder() {
|
|
close();
|
|
delete _decoder;
|
|
}
|
|
|
|
bool AdvancedVMDDecoder::loadStream(Common::SeekableReadStream *stream) {
|
|
close();
|
|
|
|
if (!_decoder->loadStream(stream))
|
|
return false;
|
|
|
|
if (_decoder->hasVideo()) {
|
|
_videoTrack = new VMDVideoTrack(_decoder);
|
|
addTrack(_videoTrack);
|
|
}
|
|
|
|
if (_decoder->hasSound()) {
|
|
_audioTrack = new VMDAudioTrack(_decoder);
|
|
addTrack(_audioTrack);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AdvancedVMDDecoder::close() {
|
|
VideoDecoder::close();
|
|
_decoder->close();
|
|
}
|
|
|
|
void AdvancedVMDDecoder::setSurfaceMemory(void *mem, uint16 width, uint16 height, uint8 bpp) {
|
|
_decoder->setSurfaceMemory(mem, width, height, bpp);
|
|
}
|
|
|
|
AdvancedVMDDecoder::VMDVideoTrack::VMDVideoTrack(VMDDecoder *decoder) : _decoder(decoder) {
|
|
}
|
|
|
|
uint16 AdvancedVMDDecoder::VMDVideoTrack::getWidth() const {
|
|
return _decoder->getWidth();
|
|
}
|
|
|
|
uint16 AdvancedVMDDecoder::VMDVideoTrack::getHeight() const {
|
|
return _decoder->getHeight();
|
|
}
|
|
|
|
Graphics::PixelFormat AdvancedVMDDecoder::VMDVideoTrack::getPixelFormat() const {
|
|
return _decoder->getPixelFormat();
|
|
}
|
|
|
|
int AdvancedVMDDecoder::VMDVideoTrack::getCurFrame() const {
|
|
return _decoder->getCurFrame();
|
|
}
|
|
|
|
int AdvancedVMDDecoder::VMDVideoTrack::getFrameCount() const {
|
|
return _decoder->getFrameCount();
|
|
}
|
|
|
|
const Graphics::Surface *AdvancedVMDDecoder::VMDVideoTrack::decodeNextFrame() {
|
|
return _decoder->decodeNextFrame();
|
|
}
|
|
|
|
const byte *AdvancedVMDDecoder::VMDVideoTrack::getPalette() const {
|
|
return _decoder->getPalette();
|
|
}
|
|
|
|
bool AdvancedVMDDecoder::VMDVideoTrack::hasDirtyPalette() const {
|
|
return _decoder->hasDirtyPalette();
|
|
}
|
|
|
|
Common::Rational AdvancedVMDDecoder::VMDVideoTrack::getFrameRate() const {
|
|
return _decoder->getFrameRate();
|
|
}
|
|
|
|
AdvancedVMDDecoder::VMDAudioTrack::VMDAudioTrack(VMDDecoder *decoder) :
|
|
AudioTrack(decoder->getSoundType()),
|
|
_decoder(decoder) {
|
|
}
|
|
|
|
Audio::AudioStream *AdvancedVMDDecoder::VMDAudioTrack::getAudioStream() const {
|
|
return _decoder->getAudioStream();
|
|
}
|
|
|
|
} // End of namespace Video
|
|
|
|
#endif // VIDEO_COKTELDECODER_H
|