Merge pull request #376 from somaen/smush_decoder

Refactor the SMUSH-decoder into a subclass of VideoDecoder
This commit is contained in:
somaen 2011-10-03 08:46:33 -07:00
commit 4bddcfadac
18 changed files with 1024 additions and 875 deletions

View File

@ -37,11 +37,11 @@ enum enDebugLevels {
DEBUG_BITMAPS = 16,
DEBUG_MODEL = 32,
DEBUG_STUB = 64,
DEBUG_SMUSH = 128,
DEBUG_MOVIE = 128,
DEBUG_IMUSE = 256,
DEBUG_CHORES = 512,
DEBUG_ALL = DEBUG_NORMAL | DEBUG_WARN | DEBUG_ERROR | DEBUG_LUA | DEBUG_BITMAPS |
DEBUG_MODEL | DEBUG_STUB | DEBUG_SMUSH | DEBUG_IMUSE | DEBUG_CHORES
DEBUG_MODEL | DEBUG_STUB | DEBUG_MOVIE | DEBUG_IMUSE | DEBUG_CHORES
};
}

View File

@ -25,6 +25,10 @@
#include "math/vector3d.h"
namespace Graphics {
struct Surface;
}
namespace Grim {
struct Shadow;
@ -194,7 +198,7 @@ public:
* @see drawMovieFrame
* @see releaseMovieFrame
*/
virtual void prepareMovieFrame(int width, int height, byte *bitmap) = 0;
virtual void prepareMovieFrame(Graphics::Surface* frame) = 0;
virtual void drawMovieFrame(int offsetX, int offsetY) = 0;
/**

View File

@ -29,6 +29,8 @@
#include "common/endian.h"
#include "common/system.h"
#include "graphics/surface.h"
#include "engines/grim/actor.h"
#include "engines/grim/colormap.h"
#include "engines/grim/font.h"
@ -1040,7 +1042,11 @@ void GfxOpenGL::drawDepthBitmap(int x, int y, int w, int h, char *data) {
glDepthFunc(GL_LESS);
}
void GfxOpenGL::prepareMovieFrame(int width, int height, byte *bitmap) {
void GfxOpenGL::prepareMovieFrame(Graphics::Surface* frame) {
int height = frame->h;
int width = frame->w;
byte *bitmap = (byte *)frame->pixels;
// remove if already exist
if (_smushNumTex > 0) {
glDeleteTextures(_smushNumTex, _smushTexIds);

View File

@ -114,7 +114,7 @@ public:
void drawLine(PrimitiveObject *primitive);
void drawPolygon(PrimitiveObject *primitive);
void prepareMovieFrame(int width, int height, byte *bitmap);
void prepareMovieFrame(Graphics::Surface* frame);
void drawMovieFrame(int offsetX, int offsetY);
void releaseMovieFrame();

View File

@ -23,6 +23,8 @@
#include "common/endian.h"
#include "common/system.h"
#include "graphics/surface.h"
#include "engines/grim/actor.h"
#include "engines/grim/colormap.h"
#include "engines/grim/material.h"
@ -857,10 +859,10 @@ void GfxTinyGL::destroyMaterial(Texture *material) {
delete[] (TGLuint *)material->_texture;
}
void GfxTinyGL::prepareMovieFrame(int width, int height, byte *bitmap) {
_smushWidth = width;
_smushHeight = height;
_smushBitmap = bitmap;
void GfxTinyGL::prepareMovieFrame(Graphics::Surface* frame) {
_smushWidth = frame->w;
_smushHeight = frame->h;
_smushBitmap = (byte *)frame->pixels;
}
void GfxTinyGL::drawMovieFrame(int offsetX, int offsetY) {

View File

@ -106,7 +106,7 @@ public:
void drawLine(PrimitiveObject *primitive);
void drawPolygon(PrimitiveObject *primitive);
void prepareMovieFrame(int width, int height, byte *bitmap);
void prepareMovieFrame(Graphics::Surface* frame);
void drawMovieFrame(int offsetX, int offsetY);
void releaseMovieFrame();

View File

@ -215,12 +215,12 @@ Common::Error GrimEngine::run() {
g_resourceloader = new ResourceLoader();
g_localizer = new Localizer();
if (getGameType() == GType_GRIM)
g_movie = CreateSmushPlayer();
g_movie = CreateSmushPlayer(getGameFlags() & ADGF_DEMO);
else if (getGameType() == GType_MONKEY4) {
if (_gamePlatform == Common::kPlatformPS2)
g_movie = CreateMpegPlayer();
else
g_movie = CreateBinkPlayer();
g_movie = CreateBinkPlayer(getGameFlags() & ADGF_DEMO);
}
g_imuse = new Imuse(20);
@ -686,7 +686,7 @@ void GrimEngine::updateDisplayScene() {
if (g_movie->isPlaying()) {
_movieTime = g_movie->getMovieTime();
if (g_movie->isUpdateNeeded()) {
g_driver->prepareMovieFrame(g_movie->getWidth(), g_movie->getHeight(), g_movie->getDstPtr());
g_driver->prepareMovieFrame(g_movie->getDstSurface());
g_movie->clearUpdateNeeded();
}
int frame = g_movie->getFrame();
@ -741,7 +741,7 @@ void GrimEngine::updateDisplayScene() {
if (g_movie->isPlaying()) {
_movieTime = g_movie->getMovieTime();
if (g_movie->isUpdateNeeded()) {
g_driver->prepareMovieFrame(g_movie->getWidth(), g_movie->getHeight(), g_movie->getDstPtr());
g_driver->prepareMovieFrame(g_movie->getDstSurface());
g_movie->clearUpdateNeeded();
}
if (g_movie->getFrame() > 0)

View File

@ -35,6 +35,7 @@ MODULE_OBJS := \
movie/codecs/blocky8.o \
movie/codecs/blocky16.o \
movie/codecs/vima.o \
movie/codecs/smush_decoder.o \
movie/bink.o \
movie/mpeg.o \
movie/smush.o \

View File

@ -20,156 +20,30 @@
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
#define FORBIDDEN_SYMBOL_EXCEPTION_chdir
#define FORBIDDEN_SYMBOL_EXCEPTION_getcwd
#define FORBIDDEN_SYMBOL_EXCEPTION_getwd
#define FORBIDDEN_SYMBOL_EXCEPTION_mkdir
#define FORBIDDEN_SYMBOL_EXCEPTION_unlink
#include "common/endian.h"
#include "common/timer.h"
#include "common/file.h"
#include "common/events.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#include "graphics/surface.h"
#include "video/bink_decoder.h"
#include "engines/grim/movie/bink.h"
#include "engines/grim/debug.h"
#include "engines/grim/grim.h"
#ifdef USE_BINK
namespace Grim {
MoviePlayer *CreateBinkPlayer() {
return new BinkPlayer();
MoviePlayer *CreateBinkPlayer(bool demo) {
return new BinkPlayer(demo);
}
void BinkPlayer::timerCallback(void *) {
((BinkPlayer *)g_movie)->handleFrame();
}
BinkPlayer::BinkPlayer() : MoviePlayer() {
g_movie = this;
_binkDecoder = new Video::BinkDecoder();
_surface = new Graphics::Surface();
_externalSurface = new Graphics::Surface();
BinkPlayer::BinkPlayer(bool demo) : MoviePlayer(), _demo(demo) {
_videoDecoder = new Video::BinkDecoder();
_speed = 1000;
}
BinkPlayer::~BinkPlayer() {
deinit();
}
void BinkPlayer::init() {
_frame = 0;
_movieTime = 0;
_updateNeeded = false;
_videoFinished = false;
_width = 0;
_height = 0;
_x = 0;
_y = 0;
assert(!_externalBuffer);
g_system->getTimerManager()->installTimerProc(&timerCallback, _speed, NULL);
}
void BinkPlayer::deinit() {
g_system->getTimerManager()->removeTimerProc(&timerCallback);
_binkDecoder->close();
_surface->free();
if (_externalBuffer) {
delete[] _externalBuffer;
_externalBuffer = NULL;
}
if (_stream) {
_stream->finish();
_stream = NULL;
g_system->getMixer()->stopHandle(_soundHandle);
}
_videoPause = false;
}
void BinkPlayer::handleFrame() {
if (_binkDecoder->endOfVideo())
_videoFinished = true;
if (_videoPause)
return;
if (_videoFinished) {
g_grim->setMode(ENGINE_MODE_NORMAL);
_videoPause = true;
return;
}
if (_binkDecoder->getTimeToNextFrame() > 0)
return;
_surface->copyFrom(*_binkDecoder->decodeNextFrame());
_width = _surface->w;
_height = _surface->h;
_internalBuffer = (byte *)_surface->pixels;
// Avoid updating the _externalBuffer if it's flagged as updateNeeded
// since the draw-loop might access it then. This way, any late frames
// will be dropped, and the sound will continue, in synch.
if (!_updateNeeded) {
_externalSurface->copyFrom(*_surface);
_externalBuffer = (byte *)_externalSurface->pixels;
_updateNeeded = true;
}
_movieTime = _binkDecoder->getElapsedTime();
_frame = _binkDecoder->getCurFrame();
return;
}
void BinkPlayer::stop() {
deinit();
g_grim->setMode(ENGINE_MODE_NORMAL);
}
bool BinkPlayer::play(const char *filename, bool looping, int x, int y) {
deinit();
_x = x;
_y = y;
_fname = filename;
bool BinkPlayer::loadFile(Common::String filename) {
// The demo uses a weird .lab suffix instead of the normal .bik
_fname += (g_grim->getGameFlags() & ADGF_DEMO) ? ".lab" : ".bik";
_fname = filename;
_fname += (_demo) ? ".lab" : ".bik";
if (!_binkDecoder->loadFile(_fname))
return false;
if (gDebugLevel == DEBUG_SMUSH)
printf("Playing video '%s'.\n", filename);
init();
return true;
}
void BinkPlayer::saveState(SaveGame *state) {
}
void BinkPlayer::restoreState(SaveGame *state) {
return MoviePlayer::loadFile(_fname);
}
} // end of namespace Grim

View File

@ -23,46 +23,18 @@
#ifndef GRIM_BINK_PLAYER_H
#define GRIM_BINK_PLAYER_H
#include "common/scummsys.h"
#include "common/file.h"
#include "graphics/pixelformat.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "engines/grim/movie/movie.h"
#ifdef USE_BINK
namespace Video{
class BinkDecoder;
}
namespace Graphics{
struct Surface;
}
namespace Grim {
class BinkPlayer : public MoviePlayer {
private:
Video::BinkDecoder *_binkDecoder;
Graphics::Surface *_surface, *_externalSurface;
public:
BinkPlayer();
~BinkPlayer();
bool play(const char *filename, bool looping, int x, int y);
void stop();
void saveState(SaveGame *state);
void restoreState(SaveGame *state);
void deliverFrameFromDecode(int width, int height, uint16 *dat);
BinkPlayer(bool demo);
private:
static void timerCallback(void *ptr);
virtual void handleFrame();
void init();
void deinit();
bool loadFile(Common::String filename);
bool _demo;
};
} // end of namespace Grim

View File

@ -0,0 +1,565 @@
/* Residual - A 3D game interpreter
*
* Residual 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 library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library 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
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#include "common/endian.h"
#include "common/events.h"
#include "common/file.h"
#include "common/rational.h"
#include "common/system.h"
#include "common/timer.h"
#include "common/zlib.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#include "engines/grim/debug.h"
#include "engines/grim/movie/codecs/blocky8.h"
#include "engines/grim/movie/codecs/blocky16.h"
#include "engines/grim/movie/codecs/smush_decoder.h"
#ifdef USE_SMUSH
namespace Grim {
// Prototypes to avoid depending on grim.h
void vimaInit(uint16 *destTable);
void decompressVima(const byte *src, int16 *dest, int destLen, uint16 *destTable);
#define ANNO_HEADER "MakeAnim animation type 'Bl16' parameters: "
#define BUFFER_SIZE 16385
#define SMUSH_SPEED 66667
static uint16 smushDestTable[5786];
SmushDecoder::SmushDecoder() {
// Set colour-format statically here for SMUSH (5650), to allow for differing PixelFormat in engine and renderer (and conversion from Surface there)
// Which means 16 bpp, 565, shift of 11, 5, 0, 0 for RGBA
_format = Graphics::PixelFormat(16, 5, 6, 5, 0, 11, 5, 0, 0);
_nbframes = 0;
_file = 0;
_width = 0;
_height = 0;
_channels = -1;
_freq = 22050;
_videoLooping = false;
_startPos = 0;
_x = 0;
_y = 0;
_blocky8 = new Blocky8();
_blocky16 = new Blocky16();
init();
}
SmushDecoder::~SmushDecoder() {
delete _blocky8;
delete _blocky16;
}
void SmushDecoder::init() {
_IACTpos = 0;
_stream = NULL;
_curFrame = 0;
_videoPause = false;
if (!_demo) {
_surface.create(_width, _height, _format);
vimaInit(smushDestTable);
}
}
void SmushDecoder::close() {
_surface.free();
if (_stream) {
_stream->finish();
_stream = NULL;
g_system->getMixer()->stopHandle(_soundHandle);
}
_videoLooping = false;
_videoPause = true;
if (_file) {
delete _file;
_file = NULL;
}
}
void SmushDecoder::handleWave(const byte *src, uint32 size) {
int16 *dst = (int16 *) malloc(size * _channels * sizeof(int16));
decompressVima(src, dst, size * _channels * 2, smushDestTable);
int flags = Audio::FLAG_16BITS;
if (_channels == 2)
flags |= Audio::FLAG_STEREO;
if (!_stream) {
_stream = Audio::makeQueuingAudioStream(_freq, (_channels == 2));
g_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, _stream);
}
if (g_system->getMixer()->isReady()) {
_stream->queueBuffer((byte *)dst, size * _channels * 2, DisposeAfterUse::YES, flags);
} else {
free(dst);
}
}
void SmushDecoder::handleFrame() {
uint32 tag;
int32 size;
int pos = 0;
if (_curFrame == 0)
_startTime = g_system->getMillis();
if (_videoPause)
return;
if (endOfVideo()) { // Looping is handled outside, by rewinding the video.
_videoPause = true;
return;
}
if (_curFrame == _nbframes) {
// If the video has been looping and was previously on the last
// frame then reset the frame number and the movie time, this
// needs to occur at the beginning so the last frame has time to
// render appropriately
_curFrame = 1;
}
tag = _file->readUint32BE();
if (tag == MKTAG('A','N','N','O')) {
char *anno;
byte *data;
size = _file->readUint32BE();
data = new byte[size];
_file->read(data, size);
anno = (char *)data;
if (strncmp(anno, ANNO_HEADER, sizeof(ANNO_HEADER) - 1) == 0) {
//char *annoData = anno + sizeof(ANNO_HEADER);
// Examples:
// Water streaming around boat from Manny's balcony
// MakeAnim animation type 'Bl16' parameters: 10000;12000;100;1;0;0;0;0;25;0;
// Water in front of the Blue Casket
// MakeAnim animation type 'Bl16' parameters: 20000;25000;100;1;0;0;0;0;25;0;
// Scrimshaw exterior:
// MakeAnim animation type 'Bl16' parameters: 6000;8000;100;0;0;0;0;0;2;0;
// Lola engine room (loops a limited number of times?):
// MakeAnim animation type 'Bl16' parameters: 6000;8000;90;1;0;0;0;0;2;0;
if (gDebugLevel == DEBUG_MOVIE || gDebugLevel == DEBUG_NORMAL || gDebugLevel == DEBUG_ALL)
debug("Announcement data: %s\n", anno);
// It looks like the announcement data is actually for setting some of the
// header parameters, not for any looping purpose
} else {
if (gDebugLevel == DEBUG_MOVIE || gDebugLevel == DEBUG_NORMAL || gDebugLevel == DEBUG_ALL)
debug("Announcement header not understood: %s\n", anno);
}
delete[] anno;
tag = _file->readUint32BE();
}
assert(tag == MKTAG('F','R','M','E'));
size = _file->readUint32BE();
byte *frame = new byte[size];
_file->read(frame, size);
do {
if (READ_BE_UINT32(frame + pos) == MKTAG('B','l','1','6')) {
_blocky16->decode((byte *)_surface.pixels, frame + pos + 8);
pos += READ_BE_UINT32(frame + pos + 4) + 8;
} else if (READ_BE_UINT32(frame + pos) == MKTAG('W','a','v','e')) {
int decompressed_size = READ_BE_UINT32(frame + pos + 8);
if (decompressed_size < 0)
handleWave(frame + pos + 8 + 4 + 8, READ_BE_UINT32(frame + pos + 8 + 8));
else
handleWave(frame + pos + 8 + 4, decompressed_size);
pos += READ_BE_UINT32(frame + pos + 4) + 8;
} else if (gDebugLevel == DEBUG_MOVIE || gDebugLevel == DEBUG_ERROR || gDebugLevel == DEBUG_ALL) {
error("SmushDecoder::handleFrame() unknown tag");
}
} while (pos < size);
delete[] frame;
_curFrame++;
if (_curFrame == _nbframes) {
// If we're not supposed to loop (or looping fails) then end the video
if (!_videoLooping || !_file->seek(_startPos, SEEK_SET)) {
return;
}
}
}
static byte delta_color(byte org_color, int16 delta_color) {
int t = (org_color * 129 + delta_color) / 128;
return CLIP(t, 0, 255);
}
void SmushDecoder::handleDeltaPalette(byte *src, int32 size) {
if (size == 0x300 * 3 + 4) {
for (int i = 0; i < 0x300; i++)
_deltaPal[i] = READ_LE_UINT16(src + (i * 2) + 4);
memcpy(_pal, src + 0x600 + 4, 0x300);
} else if (size == 6) {
for (int i = 0; i < 0x300; i++)
_pal[i] = delta_color(_pal[i], _deltaPal[i]);
} else {
error("SmushDecoder::handleDeltaPalette() Wrong size for DeltaPalette");
}
}
void SmushDecoder::handleIACT(const byte *src, int32 size) {
int32 bsize = size - 18;
const byte *d_src = src + 18;
while (bsize > 0) {
if (_IACTpos >= 2) {
int32 len = READ_BE_UINT16(_IACToutput) + 2;
len -= _IACTpos;
if (len > bsize) {
memcpy(_IACToutput + _IACTpos, d_src, bsize);
_IACTpos += bsize;
bsize = 0;
} else {
byte *output_data = new byte[4096];
memcpy(_IACToutput + _IACTpos, d_src, len);
byte *dst = output_data;
byte *d_src2 = _IACToutput;
d_src2 += 2;
int32 count = 1024;
byte variable1 = *d_src2++;
byte variable2 = variable1 / 16;
variable1 &= 0x0f;
do {
byte value;
value = *(d_src2++);
if (value == 0x80) {
*dst++ = *d_src2++;
*dst++ = *d_src2++;
} else {
int16 val = (int8)value << variable2;
*dst++ = val >> 8;
*dst++ = (byte)(val);
}
value = *(d_src2++);
if (value == 0x80) {
*dst++ = *d_src2++;
*dst++ = *d_src2++;
} else {
int16 val = (int8)value << variable1;
*dst++ = val >> 8;
*dst++ = (byte)(val);
}
} while (--count);
if (!_stream) {
_stream = Audio::makeQueuingAudioStream(22050, true);
g_system->getMixer()->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, _stream);
}
_stream->queueBuffer(output_data, 0x1000, DisposeAfterUse::YES, Audio::FLAG_STEREO | Audio::FLAG_16BITS);
bsize -= len;
d_src += len;
_IACTpos = 0;
}
} else {
if (bsize > 1 && _IACTpos == 0) {
*(_IACToutput + 0) = *d_src++;
_IACTpos = 1;
bsize--;
}
*(_IACToutput + _IACTpos) = *d_src++;
_IACTpos++;
bsize--;
}
}
}
void SmushDecoder::handleFrameDemo() {
uint32 tag;
int32 size;
int pos = 0;
if (_videoPause)
return;
if (endOfVideo()) {
_videoPause = true;
return;
}
if (_curFrame == 0)
_startTime = g_system->getMillis();
tag = _file->readUint32BE();
assert(tag == MKTAG('F','R','M','E'));
size = _file->readUint32BE();
byte *frame = new byte[size];
_file->read(frame, size);
do {
if (READ_BE_UINT32(frame + pos) == MKTAG('F','O','B','J')) {
_x = READ_LE_UINT16(frame + pos + 10);
_y = READ_LE_UINT16(frame + pos + 12);
int width = READ_LE_UINT16(frame + pos + 14);
int height = READ_LE_UINT16(frame + pos + 16);
if (width != _width || height != _height) {
_width = width;
_height = height;
_surface.create(_width, _height, _format);
_blocky8->init(_width, _height);
}
_blocky8->decode((byte *)_surface.pixels, frame + pos + 8 + 14);
pos += READ_BE_UINT32(frame + pos + 4) + 8;
} else if (READ_BE_UINT32(frame + pos) == MKTAG('I','A','C','T')) {
handleIACT(frame + pos + 8, READ_BE_UINT32(frame + pos + 4));
int offset = READ_BE_UINT32(frame + pos + 4) + 8;
if (offset & 1)
offset += 1;
pos += offset;
} else if (READ_BE_UINT32(frame + pos) == MKTAG('X','P','A','L')) {
handleDeltaPalette(frame + pos + 8, READ_BE_UINT32(frame + pos + 4));
pos += READ_BE_UINT32(frame + pos + 4) + 8;
} else {
error("SmushDecoder::handleFrame() unknown tag");
}
} while (pos < size);
delete[] frame;
Graphics::Surface conversion;
conversion.create(0, 0, _format); // Avoid issues with copyFrom, by creating an empty surface.
conversion.copyFrom(_surface);
uint16 *d = (uint16 *)_surface.pixels;
for (int l = 0; l < _width * _height; l++) {
int index = ((byte *)conversion.pixels)[l];
d[l] = ((_pal[(index * 3) + 0] & 0xF8) << 8) | ((_pal[(index * 3) + 1] & 0xFC) << 3) | (_pal[(index * 3) + 2] >> 3);
}
conversion.free();
_curFrame++;
}
void SmushDecoder::handleFramesHeader() {
uint32 tag;
int32 size;
int pos = 0;
tag = _file->readUint32BE();
assert(tag == MKTAG('F','L','H','D'));
size = _file->readUint32BE();
byte *f_header = new byte[size];
_file->read(f_header, size);
do {
if (READ_BE_UINT32(f_header + pos) == MKTAG('B','l','1','6')) {
pos += READ_BE_UINT32(f_header + pos + 4) + 8;
} else if (READ_BE_UINT32(f_header + pos) == MKTAG('W','a','v','e')) {
_freq = READ_LE_UINT32(f_header + pos + 8);
_channels = READ_LE_UINT32(f_header + pos + 12);
pos += 20;
} else {
error("SmushDecoder::handleFramesHeader() unknown tag");
}
} while (pos < size);
delete[] f_header;
}
bool SmushDecoder::setupAnimDemo() {
uint32 tag;
int32 size;
tag = _file->readUint32BE();
assert(tag == MKTAG('A','N','I','M'));
size = _file->readUint32BE();
tag = _file->readUint32BE();
assert(tag == MKTAG('A','H','D','R'));
size = _file->readUint32BE();
_file->readUint16BE(); // version
_nbframes = _file->readUint16LE();
_file->readUint16BE(); // unknown
for (int l = 0; l < 0x300; l++) {
_pal[l] = _file->readByte();
}
_file->readUint32BE();
_file->readUint32BE();
_file->readUint32BE();
_file->readUint32BE();
_file->readUint32BE();
_x = -1;
_y = -1;
_width = -1;
_height = -1;
_videoLooping = false;
_startPos = 0;
setMsPerFrame(SMUSH_SPEED);
return true;
}
bool SmushDecoder::setupAnim() {
uint32 tag;
int32 size;
int16 flags;
if (!_file)
return false;
tag = _file->readUint32BE();
assert(tag == MKTAG('S','A','N','M'));
size = _file->readUint32BE();
tag = _file->readUint32BE();
assert(tag == MKTAG('S','H','D','R'));
size = _file->readUint32BE();
byte *s_header = new byte[size];
_file->read(s_header, size);
_nbframes = READ_LE_UINT32(s_header + 2);
int width = READ_LE_UINT16(s_header + 8);
int height = READ_LE_UINT16(s_header + 10);
if (_width != width || _height != height) {
_blocky16->init(width, height);
}
_width = width;
_height = height;
// If the video is NOT looping, setLooping will set the speed to the proper value
setMsPerFrame(READ_LE_UINT32(s_header + 14));
flags = READ_LE_UINT16(s_header + 18);
// Output information for checking out the flags
if (gDebugLevel == DEBUG_MOVIE || gDebugLevel == DEBUG_NORMAL || gDebugLevel == DEBUG_ALL) {
warning("SMUSH Flags:");
for (int i = 0; i < 16; i++)
warning(" %d", (flags & (1 << i)) != 0);
//printf("\n");
}
_videoLooping = true;
delete[] s_header;
return true;
}
bool SmushDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
// Load the video
if (_demo) {
_file = stream;
if (!setupAnimDemo())
return false;
} else {
_file = wrapCompressedReadStream(stream);
if (!setupAnim())
return false;
handleFramesHeader();
}
_startPos = _file->pos();
init();
if (!_demo)
_surface.create(_width, _height, _format);
return true;
}
const Graphics::Surface *SmushDecoder::decodeNextFrame() {
if (_demo)
handleFrameDemo();
else
handleFrame();
return &_surface;
}
void SmushDecoder::setLooping(bool l) {
_videoLooping = l;
if (!_videoLooping)
setMsPerFrame(SMUSH_SPEED);
}
void SmushDecoder::pauseVideoIntern(bool p) {
g_system->getMixer()->pauseHandle(_soundHandle, p);
}
uint32 SmushDecoder::getFrameCount() const {
return _nbframes;
}
void SmushDecoder::setMsPerFrame(int ms) {
_frameRate = Common::Rational(1000000, ms);
}
void SmushDecoder::seekToTime(Audio::Timestamp time) { // FIXME: This will be off by a second or two right now.
int32 wantedFrame = (uint32) ((time.msecs() / 1000.0f) * getFrameRate().toDouble());
warning("Seek to time: %d, frame: %d", time.msecs(), wantedFrame);
warning("Current frame: %d", _curFrame);
uint32 tag;
int32 size;
if (_stream)
_stream->finish();
if (wantedFrame > _nbframes)
return;
if (wantedFrame < _curFrame) {
_file->seek(_startPos, SEEK_SET);
}
_videoPause = true;
_startTime = g_system->getMillis() - time.msecs(); // This won't be correct, as we should round off to the frame-start.
while(_curFrame < wantedFrame) {
tag = _file->readUint32BE();
if (tag == MKTAG('A','N','N','O')) {
size = _file->readUint32BE();
_file->seek(size, SEEK_CUR);
tag = _file->readUint32BE();
}
assert(tag == MKTAG('F','R','M','E'));
size = _file->readUint32BE();
_file->seek(size, SEEK_CUR);
_curFrame++;
}
warning("Seek complete");
_videoPause = false;
}
uint32 SmushDecoder::getDuration() const {
return (uint32) (getFrameCount() / getFrameRate().toDouble());
}
} // end of namespace Grim
#endif // USE_SMUSH

View File

@ -0,0 +1,119 @@
/* Residual - A 3D game interpreter
*
* Residual 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 library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library 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
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#ifndef GRIM_SMUSH_DECODER_H
#define GRIM_SMUSH_DECODER_H
#include "common/rational.h"
#include "audio/mixer.h"
#include "video/video_decoder.h"
#include "graphics/surface.h"
#ifdef USE_SMUSH
namespace Audio {
class QueuingAudioStream;
}
namespace Grim {
class Blocky8;
class Blocky16;
class SmushDecoder : public virtual Video::SeekableVideoDecoder, public virtual Video::FixedRateVideoDecoder {
private:
int32 _nbframes;
int _width, _height;
int _x, _y;
Blocky8 *_blocky8;
Blocky16 *_blocky16;
Common::SeekableReadStream *_file;
Common::Rational _frameRate;
Graphics::Surface _surface;
Graphics::PixelFormat _format;
byte _pal[0x300];
int16 _deltaPal[0x300];
byte _IACToutput[4096];
int32 _IACTpos;
Audio::SoundHandle _soundHandle;
Audio::QueuingAudioStream *_stream;
uint32 _startPos;
int _channels;
int _freq;
bool _videoPause;
bool _videoLooping;
bool _demo;
public:
SmushDecoder();
~SmushDecoder();
int getX() { return _x; }
int getY() { return _y; }
void setLooping(bool l);
void setDemo(bool demo) { _demo = demo; }
uint16 getWidth() const { return _width; }
uint16 getHeight() const { return _height; }
Graphics::PixelFormat getPixelFormat() const { return _surface.format; }
bool isVideoLoaded() const { return _file != 0; }
bool loadStream(Common::SeekableReadStream *stream);
const Graphics::Surface *decodeNextFrame();
uint32 getFrameCount() const;
void close();
// Seekable
void seekToTime(Audio::Timestamp time);
uint32 getDuration() const;
private:
void pauseVideoIntern(bool p);
void parseNextFrame();
void init();
void handleDeltaPalette(byte *src, int32 size);
void handleFramesHeader();
void handleFrameDemo();
void handleFrame();
void handleBlocky16(byte *src);
void handleWave(const byte *src, uint32 size);
void handleIACT(const byte *src, int32 size);
bool setupAnim();
bool setupAnimDemo();
void setMsPerFrame(int ms);
protected:
// Fixed Rate:
Common::Rational getFrameRate() const { return _frameRate; }
};
} // end of namespace Grim
#endif // USE_SMUSH
#endif

View File

@ -8,35 +8,192 @@
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library 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
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#include "engines/grim/movie/movie.h"
#include "graphics/surface.h"
#if !defined(USE_MPEG2) || !defined(USE_SMUSH) || !defined(USE_BINK)
#define NEED_NULLPLAYER
#endif
#include "common/system.h"
#include "common/timer.h"
#include "engines/grim/movie/movie.h"
#include "engines/grim/grim.h"
#include "engines/grim/debug.h"
#include "engines/grim/savegame.h"
namespace Grim {
MoviePlayer *g_movie;
void MoviePlayer::pause(bool p) {
_videoPause = p;
g_system->getMixer()->pauseHandle(_soundHandle, p);
MoviePlayer::MoviePlayer() {
_speed = 0;
_channels = -1;
_freq = 22050;
_videoFinished = false;
_videoLooping = false;
_videoPause = true;
_updateNeeded = false;
_movieTime = 0;
_frame = 0;
_x = 0;
_y = 0;
_videoDecoder = NULL;
_surface = new Graphics::Surface();
_externalSurface = new Graphics::Surface();
}
// Fallback for when USE_MPEG2 isnt defined, might want to do something similar
// for USE_BINK if that comes over from ScummVM
MoviePlayer::~MoviePlayer() {
deinit();
delete _videoDecoder;
}
void MoviePlayer::pause(bool p) {
_videoPause = p;
_videoDecoder->pauseVideo(p);
}
void MoviePlayer::stop() {
deinit();
g_grim->setMode(ENGINE_MODE_NORMAL);
}
void MoviePlayer::timerCallback(void *) {
g_movie->_frameMutex.lock();
if (g_movie->prepareFrame())
g_movie->handleFrame();
g_movie->_frameMutex.unlock();
}
bool MoviePlayer::prepareFrame() {
if (_videoDecoder->endOfVideo())
_videoFinished = true;
if (_videoPause)
return false;
if (_videoFinished) {
g_grim->setMode(ENGINE_MODE_NORMAL);
_videoPause = true;
return false;
}
if (_videoDecoder->getTimeToNextFrame() > 0)
return false;
_surface->copyFrom(*_videoDecoder->decodeNextFrame());
// Avoid updating the _externalBuffer if it's flagged as updateNeeded
// since the draw-loop might access it then. This way, any late frames
// will be dropped, and the sound will continue, in synch.
if (!_updateNeeded) {
_externalSurface->copyFrom(*_surface);
_updateNeeded = true;
}
_movieTime = _videoDecoder->getElapsedTime();
_frame = _videoDecoder->getCurFrame();
return true;
}
Graphics::Surface *MoviePlayer::getDstSurface() {
return _externalSurface;
}
void MoviePlayer::init() {
_frame = 0;
_movieTime = 0;
_updateNeeded = false;
_videoFinished = false;
g_system->getTimerManager()->installTimerProc(&timerCallback, _speed, NULL);
}
void MoviePlayer::deinit() {
_frameMutex.unlock();
g_system->getTimerManager()->removeTimerProc(&timerCallback);
_videoDecoder->close();
_surface->free();
_externalSurface->free();
_videoPause = false;
_videoFinished = true;
}
bool MoviePlayer::play(Common::String filename, bool looping, int x, int y) {
deinit();
_x = x;
_y = y;
_fname = filename;
_videoLooping = looping;
if (!loadFile(_fname))
return false;
if (gDebugLevel == DEBUG_MOVIE)
warning("Playing video '%s'.\n", filename.c_str());
init();
return true;
}
bool MoviePlayer::loadFile(Common::String filename) {
return _videoDecoder->loadFile(filename);
}
void MoviePlayer::saveState(SaveGame *state) {
state->beginSection('SMUS');
state->writeString(_fname);
state->writeLESint32(_frame);
state->writeFloat(_movieTime);
state->writeLESint32(_videoFinished);
state->writeLESint32(_videoLooping);
state->writeLESint32(_x);
state->writeLESint32(_y);
state->endSection();
}
void MoviePlayer::restoreState(SaveGame *state) {
state->beginSection('SMUS');
_fname = state->readString();
int32 frame = state->readLESint32();
float movieTime = state->readFloat();
bool videoFinished = state->readLESint32();
bool videoLooping = state->readLESint32();
int x = state->readLESint32();
int y = state->readLESint32();
if (!videoFinished) {
play(_fname.c_str(), videoLooping, x, y);
}
_frame = frame;
_movieTime = movieTime;
state->endSection();
}
#if !defined(USE_MPEG2) || !defined(USE_SMUSH) || !defined(USE_BINK)
#define NEED_NULLPLAYER
#endif
// Fallback for when USE_MPEG2 / USE_BINK / USE_SMUSH isnt defined
#ifdef NEED_NULLPLAYER
class NullPlayer : public MoviePlayer {
@ -44,7 +201,7 @@ public:
NullPlayer(const char* codecID) {
warning("%s-playback not compiled in, but needed", codecID);
_videoFinished = true; // Rigs all movies to be completed.
}
}
~NullPlayer() {}
bool play(const char* filename, bool looping, int x, int y) {return true;}
void stop() {}

View File

@ -23,31 +23,21 @@
#ifndef GRIM_MOVIE_PLAYER_H
#define GRIM_MOVIE_PLAYER_H
#include <zlib.h>
#include "common/file.h"
#include "common/mutex.h"
#include "common/system.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "video/video_decoder.h"
namespace Grim {
class SaveGame;
struct SavePos {
uint32 filePos;
z_stream streamBuf;
byte *tmpBuf;
};
class MoviePlayer {
protected:
Common::File _f;
Common::String _fname;
Audio::SoundHandle _soundHandle;
Audio::QueuingAudioStream *_stream;
Common::Mutex _frameMutex;
Video::VideoDecoder *_videoDecoder; //< Initialize this to your needed subclass of VideoDecoder in the constructor
Graphics::Surface *_surface, *_externalSurface;
int32 _frame;
bool _updateNeeded;
int32 _speed;
@ -57,63 +47,105 @@ protected:
bool _videoFinished;
bool _videoPause;
bool _videoLooping;
struct SavePos *_startPos;
int _x, _y;
int _width, _height;
byte *_internalBuffer, *_externalBuffer;
public:
MoviePlayer() {
_internalBuffer = NULL;
_externalBuffer = NULL;
_width = 0;
_height = 0;
_speed = 0;
_channels = -1;
_freq = 22050;
_videoFinished = false;
_videoLooping = false;
_videoPause = true;
_updateNeeded = false;
_startPos = NULL;
_stream = NULL;
_movieTime = 0;
_frame = 0;
_x = 0;
_y = 0;
};
virtual ~MoviePlayer() {}
MoviePlayer();
virtual ~MoviePlayer();
virtual bool play(const char *filename, bool looping, int x, int y) = 0;
virtual void stop() = 0;
/**
* Loads a file for playing, and starts playing it.
* the default implementation calls init()/deinit() to handle
* any necessary setup.
*
* @param filename the file to open
* @param looping true if we want the video to loop, false otherwise
* @param x the x-coordinate for the draw-position
* @param y the y-coordinate for the draw-position
* @see init
* @see stop
*/
virtual bool play(Common::String filename, bool looping, int x, int y);
virtual void stop();
virtual void pause(bool p);
virtual bool isPlaying() { return !_videoFinished; }
virtual bool isUpdateNeeded() { return _updateNeeded; }
virtual byte *getDstPtr() { return _externalBuffer; }
virtual Graphics::Surface *getDstSurface();
virtual int getX() { return _x; }
virtual int getY() { return _y; }
virtual int getWidth() {return _width; }
virtual int getHeight() { return _height; }
virtual int getFrame() { return _frame; }
virtual void clearUpdateNeeded() { _updateNeeded = false; }
virtual int32 getMovieTime() { return (int32)_movieTime; }
virtual void saveState(SaveGame *state) = 0;
virtual void restoreState(SaveGame *state) = 0;
/**
* Saves the state of the video to a savegame
*
* If you overload this in a subclass, call this first thing in the
* overloaded function
*
* @param state the state to save to
*/
virtual void saveState(SaveGame *state);
virtual void restoreState(SaveGame *state);
protected:
static void timerCallback(void *ptr);
virtual void handleFrame() = 0;
virtual void init() = 0;
virtual void deinit() = 0;
/**
* Handles basic stuff per frame, like copying the latest frame to
* _externalBuffer, and updating the frame-counters.
*
* @return false if a frame wasnt drawn to _externalBuffer, true otherwise.
* @see handleFrame
*/
virtual bool prepareFrame();
/**
* Frame-handling function.
*
* Perform any codec-specific per-frame operations after prepareFrame has been
* run, this function is called whenever prepareFrame returns true.
*
* @see prepareFrame
* @see clearUpdateNeeded
* @see isUpdateNeeded
*/
virtual void handleFrame() {};
/**
* Initialization of buffers
* This function is called by the default-implementation of play,
* and is expected to get the necessary datastructures set up for
* playback, as well as initializing the callback.
*
* @see deinit
*/
virtual void init();
/**
* Closes any file/codec-handles, and resets the movie-state to
* a blank MoviePlayer.
*
* @see init
*/
virtual void deinit();
/**
* Loads a file for playback, any additional setup is not done here, but in
* the play-function. This function is supposed to handle any specifics w.r.t.
* files vs containers (i.e. load from LAB vs load from file).
*
* @see play
* @param filename The filename to be handled.
*/
virtual bool loadFile(Common::String filename);
};
// Factory-like functions:
MoviePlayer *CreateMpegPlayer();
MoviePlayer *CreateSmushPlayer();
MoviePlayer *CreateBinkPlayer();
MoviePlayer *CreateSmushPlayer(bool demo);
MoviePlayer *CreateBinkPlayer(bool demo);
extern MoviePlayer *g_movie;
} // end of namespace Grim

View File

@ -38,16 +38,18 @@
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#include "graphics/surface.h"
#include "engines/grim/movie/mpeg.h"
#include "engines/grim/debug.h"
#include "engines/grim/grim.h"
#ifdef USE_MPEG2
#define MWIDTH 640
#define MHEIGHT 400
#ifdef USE_MPEG2
namespace Grim {
MoviePlayer *CreateMpegPlayer() {
@ -67,49 +69,25 @@ protected:
}
};
void MpegPlayer::timerCallback(void *) {
((MpegPlayer*)g_movie)->handleFrame();
}
MpegPlayer::MpegPlayer() : MoviePlayer() {
g_movie = this;
_speed = 50;
_videoBase = new MpegHandler(this, g_system, MWIDTH, MHEIGHT);
}
MpegPlayer::~MpegPlayer() {
deinit();
}
void MpegPlayer::init() {
_frame = 0;
_movieTime = 0;
_videoPause = false;
_updateNeeded = false;
_width = MWIDTH;
_height = MHEIGHT;
MoviePlayer::init();
assert(!_externalBuffer);
// FIXME, deal with pixelformat differently when we get this properly tested.
Graphics::PixelFormat format = Graphics::PixelFormat(16, 5, 6, 5, 0, 11, 5, 0, 0);
_externalSurface->create(MWIDTH, MHEIGHT, format);
_externalBuffer = new byte[_width * _height * 2];
warning("Trying to play %s",_fname.c_str());
_videoBase->init(_fname.c_str());
g_system->getTimerManager()->installTimerProc(&timerCallback, _speed, NULL);
}
void MpegPlayer::deinit() {
g_system->getTimerManager()->removeTimerProc(&timerCallback);
if (_externalBuffer) {
delete[] _externalBuffer;
_externalBuffer = NULL;
}
if (_videoLooping && _startPos) {
delete[] _startPos->tmpBuf;
delete[] _startPos;
_startPos = NULL;
}
if (_stream) {
_stream->finish();
_stream = NULL;
@ -133,32 +111,14 @@ void MpegPlayer::handleFrame() {
}
void MpegPlayer::deliverFrameFromDecode(int width, int height, uint16 *dat) {
memcpy(_externalBuffer, dat, _width * _height * 2);
memcpy(_externalSurface->pixels, dat, _externalSurface->w * _externalSurface->h * 2);
_frame++;
_updateNeeded = true;
}
void MpegPlayer::stop() {
deinit();
g_grim->setMode(ENGINE_MODE_NORMAL);
}
bool MpegPlayer::play(const char *filename, bool looping, int x, int y) {
deinit();
_fname = filename;
if (gDebugLevel == DEBUG_SMUSH)
printf("Playing video '%s'.\n", filename);
init();
return true;
}
void MpegPlayer::saveState(SaveGame *state) {
}
void MpegPlayer::restoreState(SaveGame *state) {
bool MpegPlayer::loadFile(Common::String filename) {
_videoBase->init(_fname.c_str());
return true; // FIXME
}
} // end of namespace Grim

View File

@ -41,23 +41,18 @@ namespace Grim {
class MpegPlayer : public MoviePlayer {
private:
Common::File _f;
Video::BaseAnimationState *_videoBase;
Audio::SoundHandle _soundHandle;
Audio::QueuingAudioStream *_stream;
public:
MpegPlayer();
virtual ~MpegPlayer();
virtual bool play(const char *filename, bool looping, int x, int y);
virtual void stop();
virtual void saveState(SaveGame *state);
virtual void restoreState(SaveGame *state);
void deliverFrameFromDecode(int width, int height, uint16 *dat);
private:
static void timerCallback(void *ptr);
virtual void handleFrame();
void handleFrame();
void init();
void deinit();
bool loadFile(Common::String filename);
};
} // end of namespace Grim

View File

@ -20,572 +20,72 @@
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_printf
#define FORBIDDEN_SYMBOL_EXCEPTION_chdir
#define FORBIDDEN_SYMBOL_EXCEPTION_getcwd
#define FORBIDDEN_SYMBOL_EXCEPTION_getwd
#define FORBIDDEN_SYMBOL_EXCEPTION_mkdir
#define FORBIDDEN_SYMBOL_EXCEPTION_unlink
#include "common/endian.h"
#include "common/timer.h"
#include "common/file.h"
#include "common/events.h"
#include "common/system.h"
#include "common/mutex.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#include "engines/grim/movie/codecs/smush_decoder.h"
#include "engines/grim/movie/smush.h"
#include "engines/grim/movie/codecs/vima.h"
#include "engines/grim/debug.h"
#include "engines/grim/grim.h"
#include "engines/grim/resource.h"
#include "engines/grim/savegame.h"
#include "engines/grim/grim.h"
#ifdef USE_SMUSH
namespace Grim {
#define ANNO_HEADER "MakeAnim animation type 'Bl16' parameters: "
#define BUFFER_SIZE 16385
static uint16 smushDestTable[5786];
MoviePlayer *CreateSmushPlayer() {
return new SmushPlayer();
MoviePlayer *CreateSmushPlayer(bool demo) {
return new SmushPlayer(demo);
}
void SmushPlayer::timerCallback(void *) {
SmushPlayer *smush = static_cast<SmushPlayer *>(g_movie);
// Use a mutex to protect against multiple threads running handleFrame
// at the same time.
Common::StackLock lock(smush->_frameMutex);
SmushPlayer::SmushPlayer(bool demo) : MoviePlayer(), _demo(demo) {
_speed = 5000;
_videoDecoder = new Grim::SmushDecoder();
getDecoder()->setDemo(_demo);
}
if (g_grim->getGameFlags() & ADGF_DEMO)
smush->handleFrameDemo();
bool SmushPlayer::loadFile(Common::String filename) {
if (!_demo)
return _videoDecoder->loadStream(g_resourceloader->openNewSubStreamFile(filename.c_str()));
else
smush->handleFrame();
return _videoDecoder->loadFile(filename);
}
SmushPlayer::SmushPlayer() {
g_movie = this;
_IACTpos = 0;
_nbframes = 0;
_file = 0;
}
SmushPlayer::~SmushPlayer() {
deinit();
SmushDecoder* SmushPlayer::getDecoder() {
return dynamic_cast<SmushDecoder*>(_videoDecoder);
}
void SmushPlayer::init() {
_IACTpos = 0;
_stream = NULL;
_frame = 0;
_movieTime = 0;
_videoFinished = false;
_videoPause = false;
_updateNeeded = false;
assert(!_internalBuffer);
assert(!_externalBuffer);
if (!(g_grim->getGameFlags() & ADGF_DEMO)) {
_internalBuffer = new byte[_width * _height * 2];
_externalBuffer = new byte[_width * _height * 2];
vimaInit(smushDestTable);
}
g_system->getTimerManager()->installTimerProc(&timerCallback, _speed, NULL);
// Get a valid frame to draw
timerCallback(0);
}
void SmushPlayer::deinit() {
g_system->getTimerManager()->removeTimerProc(&timerCallback);
if (_internalBuffer) {
delete[] _internalBuffer;
_internalBuffer = NULL;
}
if (_externalBuffer) {
delete[] _externalBuffer;
_externalBuffer = NULL;
}
if (_videoLooping && _startPos) {
delete[] _startPos->tmpBuf;
delete _startPos;
_startPos = NULL;
}
if (_stream) {
_stream->finish();
_stream = NULL;
g_system->getMixer()->stopHandle(_soundHandle);
}
_videoLooping = false;
_videoFinished = true;
_videoPause = true;
if (g_grim->getGameFlags() & ADGF_DEMO)
_f.close();
else if (_file) {
delete _file;
_file = NULL;
}
}
void SmushPlayer::handleWave(const byte *src, uint32 size) {
int16 *dst = (int16 *) malloc(size * _channels * sizeof(int16));
decompressVima(src, dst, size * _channels * 2, smushDestTable);
int flags = Audio::FLAG_16BITS;
if (_channels == 2)
flags |= Audio::FLAG_STEREO;
if (!_stream) {
_stream = Audio::makeQueuingAudioStream(_freq, (_channels == 2));
g_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, _stream);
}
if (g_system->getMixer()->isReady()) {
_stream->queueBuffer((byte *)dst, size * _channels * 2, DisposeAfterUse::YES, flags);
if (_demo) {
_x = getDecoder()->getX();
_y = getDecoder()->getY();
} else {
free(dst);
getDecoder()->setLooping(_videoLooping);
}
MoviePlayer::init();
}
void SmushPlayer::handleFrame() {
uint32 tag;
int32 size;
int pos = 0;
if (_videoPause)
return;
if (_videoFinished) {
_videoPause = true;
return;
if (_demo) {
_x = getDecoder()->getX();
_y = getDecoder()->getY();
}
if (_frame == _nbframes) {
// If the video has been looping and was previously on the last
// frame then reset the frame number and the movie time, this
// needs to occur at the beginning so the last frame has time to
// render appropriately
_frame = 1;
_movieTime = 0;
}
tag = _file->readUint32BE();
if (tag == MKTAG('A','N','N','O')) {
char *anno;
byte *data;
size = _file->readUint32BE();
data = new byte[size];
_file->read(data, size);
anno = (char *)data;
if (strncmp(anno, ANNO_HEADER, sizeof(ANNO_HEADER) - 1) == 0) {
//char *annoData = anno + sizeof(ANNO_HEADER);
// Examples:
// Water streaming around boat from Manny's balcony
// MakeAnim animation type 'Bl16' parameters: 10000;12000;100;1;0;0;0;0;25;0;
// Water in front of the Blue Casket
// MakeAnim animation type 'Bl16' parameters: 20000;25000;100;1;0;0;0;0;25;0;
// Scrimshaw exterior:
// MakeAnim animation type 'Bl16' parameters: 6000;8000;100;0;0;0;0;0;2;0;
// Lola engine room (loops a limited number of times?):
// MakeAnim animation type 'Bl16' parameters: 6000;8000;90;1;0;0;0;0;2;0;
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_NORMAL || gDebugLevel == DEBUG_ALL)
debug("Announcement data: %s\n", anno);
// It looks like the announcement data is actually for setting some of the
// header parameters, not for any looping purpose
} else {
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_NORMAL || gDebugLevel == DEBUG_ALL)
debug("Announcement header not understood: %s\n", anno);
}
delete[] anno;
tag = _file->readUint32BE();
}
assert(tag == MKTAG('F','R','M','E'));
size = _file->readUint32BE();
byte *frame = new byte[size];
_file->read(frame, size);
do {
if (READ_BE_UINT32(frame + pos) == MKTAG('B','l','1','6')) {
_blocky16.decode(_internalBuffer, frame + pos + 8);
pos += READ_BE_UINT32(frame + pos + 4) + 8;
} else if (READ_BE_UINT32(frame + pos) == MKTAG('W','a','v','e')) {
int decompressed_size = READ_BE_UINT32(frame + pos + 8);
if (decompressed_size < 0)
handleWave(frame + pos + 8 + 4 + 8, READ_BE_UINT32(frame + pos + 8 + 8));
else
handleWave(frame + pos + 8 + 4, decompressed_size);
pos += READ_BE_UINT32(frame + pos + 4) + 8;
} else if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_ERROR || gDebugLevel == DEBUG_ALL) {
error("SmushPlayer::handleFrame() unknown tag");
}
} while (pos < size);
delete[] frame;
memcpy(_externalBuffer, _internalBuffer, _width * _height * 2);
_updateNeeded = true;
_frame++;
_movieTime += _speed / 1000.f;
if (_frame == _nbframes) {
if (_videoDecoder->endOfVideo()) {
// If we're not supposed to loop (or looping fails) then end the video
if (!_videoLooping || !_file->seek(_startPos->filePos, SEEK_SET)) {
if (!_videoLooping ) {
_videoFinished = true;
g_grim->setMode(ENGINE_MODE_NORMAL);
deinit();
return;
}
}
}
static byte delta_color(byte org_color, int16 delta_color) {
int t = (org_color * 129 + delta_color) / 128;
return CLIP(t, 0, 255);
}
void SmushPlayer::handleDeltaPalette(byte *src, int32 size) {
if (size == 0x300 * 3 + 4) {
for (int i = 0; i < 0x300; i++)
_deltaPal[i] = READ_LE_UINT16(src + (i * 2) + 4);
memcpy(_pal, src + 0x600 + 4, 0x300);
} else if (size == 6) {
for (int i = 0; i < 0x300; i++)
_pal[i] = delta_color(_pal[i], _deltaPal[i]);
} else {
error("SmushPlayer::handleDeltaPalette() Wrong size for DeltaPalette");
}
}
void SmushPlayer::handleIACT(const byte *src, int32 size) {
int32 bsize = size - 18;
const byte *d_src = src + 18;
while (bsize > 0) {
if (_IACTpos >= 2) {
int32 len = READ_BE_UINT16(_IACToutput) + 2;
len -= _IACTpos;
if (len > bsize) {
memcpy(_IACToutput + _IACTpos, d_src, bsize);
_IACTpos += bsize;
bsize = 0;
} else {
byte *output_data = new byte[4096];
memcpy(_IACToutput + _IACTpos, d_src, len);
byte *dst = output_data;
byte *d_src2 = _IACToutput;
d_src2 += 2;
int32 count = 1024;
byte variable1 = *d_src2++;
byte variable2 = variable1 / 16;
variable1 &= 0x0f;
do {
byte value;
value = *(d_src2++);
if (value == 0x80) {
*dst++ = *d_src2++;
*dst++ = *d_src2++;
} else {
int16 val = (int8)value << variable2;
*dst++ = val >> 8;
*dst++ = (byte)(val);
}
value = *(d_src2++);
if (value == 0x80) {
*dst++ = *d_src2++;
*dst++ = *d_src2++;
} else {
int16 val = (int8)value << variable1;
*dst++ = val >> 8;
*dst++ = (byte)(val);
}
} while (--count);
if (!_stream) {
_stream = Audio::makeQueuingAudioStream(22050, true);
g_system->getMixer()->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, _stream);
}
_stream->queueBuffer(output_data, 0x1000, DisposeAfterUse::YES, Audio::FLAG_STEREO | Audio::FLAG_16BITS);
bsize -= len;
d_src += len;
_IACTpos = 0;
}
} else {
if (bsize > 1 && _IACTpos == 0) {
*(_IACToutput + 0) = *d_src++;
_IACTpos = 1;
bsize--;
}
*(_IACToutput + _IACTpos) = *d_src++;
_IACTpos++;
bsize--;
getDecoder()->rewind(); // This doesnt handle if looping fails.
}
}
}
void SmushPlayer::handleFrameDemo() {
uint32 tag;
int32 size;
int pos = 0;
if (_videoPause)
return;
if (_videoFinished) {
_videoPause = true;
return;
}
tag = _f.readUint32BE();
assert(tag == MKTAG('F','R','M','E'));
size = _f.readUint32BE();
byte *frame = new byte[size];
_f.read(frame, size);
do {
if (READ_BE_UINT32(frame + pos) == MKTAG('F','O','B','J')) {
_x = READ_LE_UINT16(frame + pos + 10);
_y = READ_LE_UINT16(frame + pos + 12);
int width = READ_LE_UINT16(frame + pos + 14);
int height = READ_LE_UINT16(frame + pos + 16);
if (width != _width || height != _height) {
delete[] _internalBuffer;
delete[] _externalBuffer;
_width = width;
_height = height;
_internalBuffer = new byte[_width * _height];
_externalBuffer = new byte[_width * _height * 2];
_blocky8.init(_width, _height);
}
_blocky8.decode(_internalBuffer, frame + pos + 8 + 14);
pos += READ_BE_UINT32(frame + pos + 4) + 8;
} else if (READ_BE_UINT32(frame + pos) == MKTAG('I','A','C','T')) {
handleIACT(frame + pos + 8, READ_BE_UINT32(frame + pos + 4));
int offset = READ_BE_UINT32(frame + pos + 4) + 8;
if (offset & 1)
offset += 1;
pos += offset;
} else if (READ_BE_UINT32(frame + pos) == MKTAG('X','P','A','L')) {
handleDeltaPalette(frame + pos + 8, READ_BE_UINT32(frame + pos + 4));
pos += READ_BE_UINT32(frame + pos + 4) + 8;
} else {
error("SmushPlayer::handleFrame() unknown tag");
}
} while (pos < size);
delete[] frame;
uint16 *d = (uint16 *)_externalBuffer;
for (int l = 0; l < _width * _height; l++) {
int index = _internalBuffer[l];
d[l] = ((_pal[(index * 3) + 0] & 0xF8) << 8) | ((_pal[(index * 3) + 1] & 0xFC) << 3) | (_pal[(index * 3) + 2] >> 3);
}
_updateNeeded = true;
_frame++;
_movieTime += _speed / 1000.f;
if (_frame == _nbframes) {
_videoFinished = true;
g_grim->setMode(ENGINE_MODE_NORMAL);
return;
}
}
void SmushPlayer::handleFramesHeader() {
uint32 tag;
int32 size;
int pos = 0;
tag = _file->readUint32BE();
assert(tag == MKTAG('F','L','H','D'));
size = _file->readUint32BE();
byte *f_header = new byte[size];
_file->read(f_header, size);
do {
if (READ_BE_UINT32(f_header + pos) == MKTAG('B','l','1','6')) {
pos += READ_BE_UINT32(f_header + pos + 4) + 8;
} else if (READ_BE_UINT32(f_header + pos) == MKTAG('W','a','v','e')) {
_freq = READ_LE_UINT32(f_header + pos + 8);
_channels = READ_LE_UINT32(f_header + pos + 12);
pos += 20;
} else {
error("SmushPlayer::handleFramesHeader() unknown tag");
}
} while (pos < size);
delete[] f_header;
}
bool SmushPlayer::setupAnimDemo(const char *file) {
uint32 tag;
int32 size;
if (!_f.open(file))
return false;
tag = _f.readUint32BE();
assert(tag == MKTAG('A','N','I','M'));
size = _f.readUint32BE();
tag = _f.readUint32BE();
assert(tag == MKTAG('A','H','D','R'));
size = _f.readUint32BE();
_f.readUint16BE(); // version
_nbframes = _f.readUint16LE();
_f.readUint16BE(); // unknown
for (int l = 0; l < 0x300; l++) {
_pal[l] = _f.readByte();
}
_f.readUint32BE();
_f.readUint32BE();
_f.readUint32BE();
_f.readUint32BE();
_f.readUint32BE();
_x = -1;
_y = -1;
_width = -1;
_height = -1;
_videoLooping = false;
_startPos = NULL;
_speed = 66667;
return true;
}
bool SmushPlayer::setupAnim(const char *file, bool looping, int x, int y) {
uint32 tag;
int32 size;
int16 flags;
_file = wrapCompressedReadStream(g_resourceloader->openNewSubStreamFile(file));
if (!_file)
return false;
tag = _file->readUint32BE();
assert(tag == MKTAG('S','A','N','M'));
size = _file->readUint32BE();
tag = _file->readUint32BE();
assert(tag == MKTAG('S','H','D','R'));
size = _file->readUint32BE();
byte *s_header = new byte[size];
_file->read(s_header, size);
_nbframes = READ_LE_UINT32(s_header + 2);
int width = READ_LE_UINT16(s_header + 8);
int height = READ_LE_UINT16(s_header + 10);
if (_width != width || _height != height) {
_blocky16.init(width, height);
}
_x = x;
_y = y;
_width = width;
_height = height;
if (looping)
_speed = READ_LE_UINT32(s_header + 14);
else
_speed = 66667;
flags = READ_LE_UINT16(s_header + 18);
// Output information for checking out the flags
if (gDebugLevel == DEBUG_SMUSH || gDebugLevel == DEBUG_NORMAL || gDebugLevel == DEBUG_ALL) {
printf("SMUSH Flags:");
for (int i = 0; i < 16; i++)
printf(" %d", (flags & (1 << i)) != 0);
printf("\n");
}
_videoLooping = looping;
_startPos = NULL; // Set later
delete[] s_header;
return true;
}
void SmushPlayer::stop() {
deinit();
g_grim->setMode(ENGINE_MODE_NORMAL);
}
bool SmushPlayer::play(const char *filename, bool looping, int x, int y) {
deinit();
_fname = filename;
if (gDebugLevel == DEBUG_SMUSH)
printf("Playing video '%s'.\n", filename);
// Load the video
if (g_grim->getGameFlags() & ADGF_DEMO) {
if (!setupAnimDemo(filename))
return false;
} else {
if (!setupAnim(filename, looping, x, y))
return false;
handleFramesHeader();
}
if (_videoLooping) {
_startPos = new SavePos;
_startPos->tmpBuf = NULL;
_startPos->filePos = _file->pos();
}
init();
return true;
}
void SmushPlayer::saveState(SaveGame *state) {
state->beginSection('SMUS');
state->writeString(_fname);
state->writeLESint32(_frame);
state->writeFloat(_movieTime);
state->writeLESint32(_videoFinished);
state->writeLESint32(_videoLooping);
state->writeLESint32(_x);
state->writeLESint32(_y);
state->endSection();
}
void SmushPlayer::restoreState(SaveGame *state) {
state->beginSection('SMUS');
_fname = state->readString();
int32 frame = state->readLESint32();
float movieTime = state->readFloat();
bool videoFinished = state->readLESint32();
bool videoLooping = state->readLESint32();
int x = state->readLESint32();
int y = state->readLESint32();
if (!videoFinished) {
play(_fname.c_str(), videoLooping, x, y);
}
_frame = frame;
_movieTime = movieTime;
state->endSection();
MoviePlayer::restoreState(state);
getDecoder()->seekToTime((uint32)_movieTime); // Currently not fully working (out of synch)
}
} // end of namespace Grim
#endif // USE_SMUSH

View File

@ -23,63 +23,25 @@
#ifndef GRIM_SMUSH_PLAYER_H
#define GRIM_SMUSH_PLAYER_H
#include "common/zlib.h"
#include "common/file.h"
#include "engines/grim/movie/movie.h"
#include "engines/grim/movie/codecs/blocky8.h"
#include "engines/grim/movie/codecs/blocky16.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#ifdef USE_SMUSH
namespace Common {
class Mutex;
}
namespace Grim {
class SaveGame;
class SmushDecoder;
class SmushPlayer : public MoviePlayer {
private:
int32 _nbframes;
Blocky8 _blocky8;
Blocky16 _blocky16;
Common::SeekableReadStream *_file;
Common::Mutex _frameMutex;
byte _pal[0x300];
int16 _deltaPal[0x300];
byte _IACToutput[4096];
int32 _IACTpos;
public:
SmushPlayer();
virtual ~SmushPlayer();
SmushPlayer(bool demo);
bool play(const char *filename, bool looping, int x, int y);
void stop();
void saveState(SaveGame *state);
void restoreState(SaveGame *state);
private:
static void timerCallback(void *ptr);
void parseNextFrame();
void init();
void deinit();
void handleDeltaPalette(byte *src, int32 size);
void handleFramesHeader();
void handleFrameDemo();
bool loadFile(Common::String filename);
void handleFrame();
void handleBlocky16(byte *src);
void handleWave(const byte *src, uint32 size);
void handleIACT(const byte *src, int32 size);
bool setupAnim(const char *file, bool looping, int x, int y);
bool setupAnimDemo(const char *file);
SmushDecoder* getDecoder();
void init();
bool _demo;
};
} // end of namespace Grim