diff options
Diffstat (limited to 'engines/wintermute/video')
-rw-r--r-- | engines/wintermute/video/decoders/theora_decoder.cpp | 570 | ||||
-rw-r--r-- | engines/wintermute/video/decoders/theora_decoder.h | 147 | ||||
-rw-r--r-- | engines/wintermute/video/video_player.cpp | 109 | ||||
-rw-r--r-- | engines/wintermute/video/video_player.h | 90 | ||||
-rw-r--r-- | engines/wintermute/video/video_theora_player.cpp | 500 | ||||
-rw-r--r-- | engines/wintermute/video/video_theora_player.h | 148 |
6 files changed, 1564 insertions, 0 deletions
diff --git a/engines/wintermute/video/decoders/theora_decoder.cpp b/engines/wintermute/video/decoders/theora_decoder.cpp new file mode 100644 index 0000000000..6267e300f9 --- /dev/null +++ b/engines/wintermute/video/decoders/theora_decoder.cpp @@ -0,0 +1,570 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * Source is based on the player example from libvorbis package, + * available at: http://svn.xiph.org/trunk/theora/examples/player_example.c + * + * THIS FILE IS PART OF THE OggTheora SOFTWARE CODEC SOURCE CODE. + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. + * + * THE Theora SOURCE CODE IS COPYRIGHT (C) 2002-2009 + * by the Xiph.Org Foundation and contributors http://www.xiph.org/ + * + */ + +#include "engines/wintermute/video/decoders/theora_decoder.h" + +#ifdef USE_THEORADEC +#include "common/system.h" +#include "common/textconsole.h" +#include "common/util.h" +#include "graphics/yuv_to_rgb.h" +#include "audio/decoders/raw.h" +#include "common/stream.h" +#include "common/debug.h" + +namespace Wintermute { + +#define AUDIOFD_FRAGSIZE 10240 + +static double rint(double v) { + return floor(v + 0.5); +} + +TheoraDecoder::TheoraDecoder(Audio::Mixer::SoundType soundType) { + _fileStream = 0; + + _theoraPacket = 0; + _vorbisPacket = 0; + _theoraDecode = 0; + _theoraSetup = 0; + _nextFrameStartTime = 0.0; + + _soundType = soundType; + _audStream = 0; + _audHandle = new Audio::SoundHandle(); + + ogg_sync_init(&_oggSync); + + _curFrame = -1; + _audiobuf = (ogg_int16_t *)malloc(AUDIOFD_FRAGSIZE * sizeof(ogg_int16_t)); + + reset(); +} + +TheoraDecoder::~TheoraDecoder() { + close(); + delete _fileStream; + delete _audHandle; + free(_audiobuf); +} + +void TheoraDecoder::queuePage(ogg_page *page) { + if (_theoraPacket) + ogg_stream_pagein(&_theoraOut, page); + + if (_vorbisPacket) + ogg_stream_pagein(&_vorbisOut, page); +} + +int TheoraDecoder::bufferData() { + char *buffer = ogg_sync_buffer(&_oggSync, 4096); + int bytes = _fileStream->read(buffer, 4096); + + ogg_sync_wrote(&_oggSync, bytes); + + return bytes; +} + +bool TheoraDecoder::loadStream(Common::SeekableReadStream *stream) { + close(); + + _endOfAudio = false; + _endOfVideo = false; + _fileStream = stream; + + // start up Ogg stream synchronization layer + ogg_sync_init(&_oggSync); + + // init supporting Vorbis structures needed in header parsing + vorbis_info_init(&_vorbisInfo); + vorbis_comment_init(&_vorbisComment); + + // init supporting Theora structures needed in header parsing + th_comment_init(&_theoraComment); + th_info_init(&_theoraInfo); + + // Ogg file open; parse the headers + // Only interested in Vorbis/Theora streams + bool foundHeader = false; + while (!foundHeader) { + int ret = bufferData(); + + if (ret == 0) + break; + + while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) { + ogg_stream_state test; + + // is this a mandated initial header? If not, stop parsing + if (!ogg_page_bos(&_oggPage)) { + // don't leak the page; get it into the appropriate stream + queuePage(&_oggPage); + foundHeader = true; + break; + } + + ogg_stream_init(&test, ogg_page_serialno(&_oggPage)); + ogg_stream_pagein(&test, &_oggPage); + ogg_stream_packetout(&test, &_oggPacket); + + // identify the codec: try theora + if (!_theoraPacket && th_decode_headerin(&_theoraInfo, &_theoraComment, &_theoraSetup, &_oggPacket) >= 0) { + // it is theora + memcpy(&_theoraOut, &test, sizeof(test)); + _theoraPacket = 1; + } else if (!_vorbisPacket && vorbis_synthesis_headerin(&_vorbisInfo, &_vorbisComment, &_oggPacket) >= 0) { + // it is vorbis + memcpy(&_vorbisOut, &test, sizeof(test)); + _vorbisPacket = 1; + } else { + // whatever it is, we don't care about it + ogg_stream_clear(&test); + } + } + // fall through to non-bos page parsing + } + + // we're expecting more header packets. + while ((_theoraPacket && _theoraPacket < 3) || (_vorbisPacket && _vorbisPacket < 3)) { + int ret; + + // look for further theora headers + while (_theoraPacket && (_theoraPacket < 3) && (ret = ogg_stream_packetout(&_theoraOut, &_oggPacket))) { + if (ret < 0) + error("Error parsing Theora stream headers; corrupt stream?"); + + if (!th_decode_headerin(&_theoraInfo, &_theoraComment, &_theoraSetup, &_oggPacket)) + error("Error parsing Theora stream headers; corrupt stream?"); + + _theoraPacket++; + } + + // look for more vorbis header packets + while (_vorbisPacket && (_vorbisPacket < 3) && (ret = ogg_stream_packetout(&_vorbisOut, &_oggPacket))) { + if (ret < 0) + error("Error parsing Vorbis stream headers; corrupt stream?"); + + if (vorbis_synthesis_headerin(&_vorbisInfo, &_vorbisComment, &_oggPacket)) + error("Error parsing Vorbis stream headers; corrupt stream?"); + + _vorbisPacket++; + + if (_vorbisPacket == 3) + break; + } + + // The header pages/packets will arrive before anything else we + // care about, or the stream is not obeying spec + + if (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) { + queuePage(&_oggPage); // demux into the appropriate stream + } else { + ret = bufferData(); // someone needs more data + + if (ret == 0) + error("End of file while searching for codec headers."); + } + } + + // and now we have it all. initialize decoders + if (_theoraPacket) { + _theoraDecode = th_decode_alloc(&_theoraInfo, _theoraSetup); + debugN(1, "Ogg logical stream %lx is Theora %dx%d %.02f fps", + _theoraOut.serialno, _theoraInfo.pic_width, _theoraInfo.pic_height, + (double)_theoraInfo.fps_numerator / _theoraInfo.fps_denominator); + + switch (_theoraInfo.pixel_fmt) { + case TH_PF_420: + debug(1, " 4:2:0 video"); + break; + case TH_PF_422: + debug(1, " 4:2:2 video"); + break; + case TH_PF_444: + debug(1, " 4:4:4 video"); + break; + case TH_PF_RSVD: + default: + debug(1, " video\n (UNKNOWN Chroma sampling!)"); + break; + } + + if (_theoraInfo.pic_width != _theoraInfo.frame_width || _theoraInfo.pic_height != _theoraInfo.frame_height) + debug(1, " Frame content is %dx%d with offset (%d,%d).", + _theoraInfo.frame_width, _theoraInfo.frame_height, _theoraInfo.pic_x, _theoraInfo.pic_y); + + switch (_theoraInfo.colorspace){ + case TH_CS_UNSPECIFIED: + /* nothing to report */ + break; + case TH_CS_ITU_REC_470M: + debug(1, " encoder specified ITU Rec 470M (NTSC) color."); + break; + case TH_CS_ITU_REC_470BG: + debug(1, " encoder specified ITU Rec 470BG (PAL) color."); + break; + default: + debug(1, "warning: encoder specified unknown colorspace (%d).", _theoraInfo.colorspace); + break; + } + + debug(1, "Encoded by %s", _theoraComment.vendor); + if (_theoraComment.comments) { + debug(1, "theora comment header:"); + for (int i = 0; i < _theoraComment.comments; i++) { + if (_theoraComment.user_comments[i]) { + int len = _theoraComment.comment_lengths[i]; + char *value = (char *)malloc(len + 1); + if (value) { + memcpy(value, _theoraComment.user_comments[i], len); + value[len] = '\0'; + debug(1, "\t%s", value); + free(value); + } + } + } + } + + th_decode_ctl(_theoraDecode, TH_DECCTL_GET_PPLEVEL_MAX, &_ppLevelMax, sizeof(_ppLevelMax)); + _ppLevel = _ppLevelMax; + th_decode_ctl(_theoraDecode, TH_DECCTL_SET_PPLEVEL, &_ppLevel, sizeof(_ppLevel)); + _ppInc = 0; + } else { + // tear down the partial theora setup + th_info_clear(&_theoraInfo); + th_comment_clear(&_theoraComment); + } + + th_setup_free(_theoraSetup); + _theoraSetup = 0; + + if (_vorbisPacket) { + vorbis_synthesis_init(&_vorbisDSP, &_vorbisInfo); + vorbis_block_init(&_vorbisDSP, &_vorbisBlock); + debug(3, "Ogg logical stream %lx is Vorbis %d channel %ld Hz audio.", + _vorbisOut.serialno, _vorbisInfo.channels, _vorbisInfo.rate); + + _audStream = Audio::makeQueuingAudioStream(_vorbisInfo.rate, _vorbisInfo.channels); + + // Get enough audio data to start us off + while (_audStream->numQueuedStreams() == 0) { + // Queue more data + bufferData(); + while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) + queuePage(&_oggPage); + + queueAudio(); + } + + if (_audStream) + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, _audHandle, _audStream, -1, getVolume(), getBalance()); + } else { + // tear down the partial vorbis setup + vorbis_info_clear(&_vorbisInfo); + vorbis_comment_clear(&_vorbisComment); + _endOfAudio = true; + } + + _surface.create(_theoraInfo.frame_width, _theoraInfo.frame_height, g_system->getScreenFormat()); + + // Set up a display surface + _displaySurface.pixels = _surface.getBasePtr(_theoraInfo.pic_x, _theoraInfo.pic_y); + _displaySurface.w = _theoraInfo.pic_width; + _displaySurface.h = _theoraInfo.pic_height; + _displaySurface.format = _surface.format; + _displaySurface.pitch = _surface.pitch; + + // Set the frame rate + _frameRate = Common::Rational(_theoraInfo.fps_numerator, _theoraInfo.fps_denominator); + + return true; +} + +void TheoraDecoder::close() { + if (_vorbisPacket) { + ogg_stream_clear(&_vorbisOut); + vorbis_block_clear(&_vorbisBlock); + vorbis_dsp_clear(&_vorbisDSP); + vorbis_comment_clear(&_vorbisComment); + vorbis_info_clear(&_vorbisInfo); + + g_system->getMixer()->stopHandle(*_audHandle); + + _audStream = 0; + _vorbisPacket = false; + } + if (_theoraPacket) { + ogg_stream_clear(&_theoraOut); + th_decode_free(_theoraDecode); + th_comment_clear(&_theoraComment); + th_info_clear(&_theoraInfo); + _theoraDecode = 0; + _theoraPacket = false; + } + + if (!_fileStream) + return; + + ogg_sync_clear(&_oggSync); + + delete _fileStream; + _fileStream = 0; + + _surface.free(); + _displaySurface.pixels = 0; + _displaySurface.free(); + + reset(); +} + +const Graphics::Surface *TheoraDecoder::decodeNextFrame() { + // First, let's get our frame + while (_theoraPacket) { + // theora is one in, one out... + if (ogg_stream_packetout(&_theoraOut, &_oggPacket) > 0) { + + if (_ppInc) { + _ppLevel += _ppInc; + th_decode_ctl(_theoraDecode, TH_DECCTL_SET_PPLEVEL, &_ppLevel, sizeof(_ppLevel)); + _ppInc = 0; + } + + if (th_decode_packetin(_theoraDecode, &_oggPacket, NULL) == 0) { + _curFrame++; + + // Convert YUV data to RGB data + th_ycbcr_buffer yuv; + th_decode_ycbcr_out(_theoraDecode, yuv); + translateYUVtoRGBA(yuv); + + if (_curFrame == 0) + _startTime = g_system->getMillis(); + + double time = th_granule_time(_theoraDecode, _oggPacket.granulepos); + + // We need to calculate when the next frame should be shown + // This is all in floating point because that's what the Ogg code gives us + // Ogg is a lossy container format, so it doesn't always list the time to the + // next frame. In such cases, we need to calculate it ourselves. + if (time == -1.0) + _nextFrameStartTime += _frameRate.getInverse().toDouble(); + else + _nextFrameStartTime = time; + + // break out + break; + } + } else { + // If we can't get any more frames, we're done. + if (_theoraOut.e_o_s || _fileStream->eos()) { + _endOfVideo = true; + break; + } + + // Queue more data + bufferData(); + while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) + queuePage(&_oggPage); + } + + // Update audio if we can + queueAudio(); + } + + // Force at least some audio to be buffered + // TODO: 5 is very arbitrary. We probably should do something like QuickTime does. + while (!_endOfAudio && _audStream->numQueuedStreams() < 5) { + bufferData(); + while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) + queuePage(&_oggPage); + + bool queuedAudio = queueAudio(); + if ((_vorbisOut.e_o_s || _fileStream->eos()) && !queuedAudio) { + _endOfAudio = true; + break; + } + } + + return &_displaySurface; +} + +bool TheoraDecoder::queueAudio() { + if (!_audStream) + return false; + + // An audio buffer should have been allocated (either in the constructor or after queuing the current buffer) + if (!_audiobuf) { + warning("[TheoraDecoder::queueAudio] Invalid audio buffer"); + return false; + } + + bool queuedAudio = false; + + for (;;) { + float **pcm; + + // if there's pending, decoded audio, grab it + int ret = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm); + if (ret > 0) { + int count = _audiobufFill / 2; + int maxsamples = ((AUDIOFD_FRAGSIZE - _audiobufFill) / _vorbisInfo.channels) >> 1; + int i; + for (i = 0; i < ret && i < maxsamples; i++) + for (int j = 0; j < _vorbisInfo.channels; j++) { + int val = CLIP((int)rint(pcm[j][i] * 32767.f), -32768, 32767); + _audiobuf[count++] = val; + } + + vorbis_synthesis_read(&_vorbisDSP, i); + _audiobufFill += (i * _vorbisInfo.channels) << 1; + + if (_audiobufFill == AUDIOFD_FRAGSIZE) { + byte flags = Audio::FLAG_16BITS | Audio::FLAG_STEREO; +#ifdef SCUMM_LITTLE_ENDIAN + flags |= Audio::FLAG_LITTLE_ENDIAN; +#endif + _audStream->queueBuffer((byte *)_audiobuf, AUDIOFD_FRAGSIZE, DisposeAfterUse::YES, flags); + + // The audio mixer is now responsible for the old audio buffer. + // We need to create a new one. + _audiobuf = (ogg_int16_t *)malloc(AUDIOFD_FRAGSIZE * sizeof(ogg_int16_t)); + if (!_audiobuf) { + warning("[TheoraDecoder::queueAudio] Cannot allocate memory for audio buffer"); + return false; + } + + _audiobufFill = 0; + queuedAudio = true; + } + } else { + // no pending audio; is there a pending packet to decode? + if (ogg_stream_packetout(&_vorbisOut, &_oggPacket) > 0) { + if (vorbis_synthesis(&_vorbisBlock, &_oggPacket) == 0) // test for success! + vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock); + } else // we've buffered all we have, break out for now + return queuedAudio; + } + } + + // Unreachable + return false; +} + +void TheoraDecoder::reset() { + VideoDecoder::reset(); + + // FIXME: This does a rewind() instead of a reset()! + + if (_fileStream) + _fileStream->seek(0); + + _audiobufFill = 0; + _audiobufReady = false; + + _curFrame = -1; + + _theoraPacket = 0; + _vorbisPacket = 0; +} + +bool TheoraDecoder::endOfVideo() const { + return !isVideoLoaded() || (_endOfVideo && (!_audStream || (_audStream->endOfData() && _endOfAudio))); +} + +uint32 TheoraDecoder::getTimeToNextFrame() const { + if (endOfVideo() || _curFrame < 0) + return 0; + + uint32 elapsedTime = getTime(); + uint32 nextFrameStartTime = (uint32)(_nextFrameStartTime * 1000); + + if (nextFrameStartTime <= elapsedTime) + return 0; + + return nextFrameStartTime - elapsedTime; +} + +uint32 TheoraDecoder::getTime() const { + if (_audStream) + return g_system->getMixer()->getSoundElapsedTime(*_audHandle); + + return VideoDecoder::getTime(); +} + +void TheoraDecoder::pauseVideoIntern(bool pause) { + if (_audStream) + g_system->getMixer()->pauseHandle(*_audHandle, pause); +} + +enum TheoraYUVBuffers { + kBufferY = 0, + kBufferU = 1, + kBufferV = 2 +}; + +void TheoraDecoder::translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer) { + // Width and height of all buffers have to be divisible by 2. + assert((YUVBuffer[kBufferY].width & 1) == 0); + assert((YUVBuffer[kBufferY].height & 1) == 0); + assert((YUVBuffer[kBufferU].width & 1) == 0); + assert((YUVBuffer[kBufferV].width & 1) == 0); + + // UV images have to have a quarter of the Y image resolution + assert(YUVBuffer[kBufferU].width == YUVBuffer[kBufferY].width >> 1); + assert(YUVBuffer[kBufferV].width == YUVBuffer[kBufferY].width >> 1); + assert(YUVBuffer[kBufferU].height == YUVBuffer[kBufferY].height >> 1); + assert(YUVBuffer[kBufferV].height == YUVBuffer[kBufferY].height >> 1); + + Graphics::convertYUV420ToRGB(&_surface, YUVBuffer[kBufferY].data, YUVBuffer[kBufferU].data, YUVBuffer[kBufferV].data, YUVBuffer[kBufferY].width, YUVBuffer[kBufferY].height, YUVBuffer[kBufferY].stride, YUVBuffer[kBufferU].stride); +} + +void TheoraDecoder::updateVolume() { + if (g_system->getMixer()->isSoundHandleActive(*_audHandle)) + g_system->getMixer()->setChannelVolume(*_audHandle, getVolume()); +} + +void TheoraDecoder::updateBalance() { + if (g_system->getMixer()->isSoundHandleActive(*_audHandle)) + g_system->getMixer()->setChannelBalance(*_audHandle, getBalance()); +} + +void TheoraDecoder::rewind() { + reset(); +} + +} // End of namespace Sword25 + +#endif diff --git a/engines/wintermute/video/decoders/theora_decoder.h b/engines/wintermute/video/decoders/theora_decoder.h new file mode 100644 index 0000000000..fd94f52142 --- /dev/null +++ b/engines/wintermute/video/decoders/theora_decoder.h @@ -0,0 +1,147 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef WINTERMUTE_THEORADECODER_H +#define WINTERMUTE_THEORADECODER_H + +#include "common/scummsys.h" // for USE_THEORADEC + +#ifdef USE_THEORADEC + +#include "common/rational.h" +#include "video/video_decoder.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "graphics/pixelformat.h" +#include "graphics/surface.h" + +#include <theora/theoradec.h> +#include <vorbis/codec.h> + +namespace Common { +class SeekableReadStream; +} + +namespace Wintermute { + +/** + * + * Decoder for Theora videos. + * Video decoder used in engines: + * - sword25 + * - wintermute + */ +class TheoraDecoder : public Video::RewindableVideoDecoder { +public: + TheoraDecoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kMusicSoundType); + virtual ~TheoraDecoder(); + + /** + * Load a video file + * @param stream the stream to load + */ + bool loadStream(Common::SeekableReadStream *stream); + void close(); + void reset(); + + /** + * Decode the next frame and return the frame's surface + * @note the return surface should *not* be freed + * @note this may return 0, in which case the last frame should be kept on screen + */ + const Graphics::Surface *decodeNextFrame(); + + bool isVideoLoaded() const { return _fileStream != 0; } + uint16 getWidth() const { return _displaySurface.w; } + uint16 getHeight() const { return _displaySurface.h; } + + uint32 getFrameCount() const { + // It is not possible to get frame count easily + // I.e. seeking is required + assert(0); + return 0; + } + + Graphics::PixelFormat getPixelFormat() const { return _displaySurface.format; } + uint32 getTime() const; + uint32 getTimeToNextFrame() const; + + bool endOfVideo() const; + + void rewind(); + +protected: + // VideoDecoder API + void updateVolume(); + void updateBalance(); + void pauseVideoIntern(bool pause); + +private: + void queuePage(ogg_page *page); + bool queueAudio(); + int bufferData(); + void translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer); + + Common::SeekableReadStream *_fileStream; + Graphics::Surface _surface; + Graphics::Surface _displaySurface; + Common::Rational _frameRate; + double _nextFrameStartTime; + bool _endOfVideo; + bool _endOfAudio; + + Audio::Mixer::SoundType _soundType; + Audio::SoundHandle *_audHandle; + Audio::QueuingAudioStream *_audStream; + + ogg_sync_state _oggSync; + ogg_page _oggPage; + ogg_packet _oggPacket; + ogg_stream_state _vorbisOut; + ogg_stream_state _theoraOut; + th_info _theoraInfo; + th_comment _theoraComment; + th_dec_ctx *_theoraDecode; + th_setup_info *_theoraSetup; + vorbis_info _vorbisInfo; + vorbis_dsp_state _vorbisDSP; + vorbis_block _vorbisBlock; + vorbis_comment _vorbisComment; + + int _theoraPacket; + int _vorbisPacket; + + int _ppLevelMax; + int _ppLevel; + int _ppInc; + + // single audio fragment audio buffering + int _audiobufFill; + bool _audiobufReady; + ogg_int16_t *_audiobuf; +}; + +} // End of namespace Sword25 + +#endif + +#endif diff --git a/engines/wintermute/video/video_player.cpp b/engines/wintermute/video/video_player.cpp new file mode 100644 index 0000000000..2577b8aedc --- /dev/null +++ b/engines/wintermute/video/video_player.cpp @@ -0,0 +1,109 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This file is based on WME Lite. + * http://dead-code.org/redir.php?target=wmelite + * Copyright (c) 2011 Jan Nedoma + */ + + +#include "engines/wintermute/video/video_player.h" + +namespace Wintermute { + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +VideoPlayer::VideoPlayer(BaseGame *inGame) : BaseClass(inGame) { + setDefaults(); +} + +////////////////////////////////////////////////////////////////////////// +bool VideoPlayer::setDefaults() { + _playing = false; + _videoEndTime = 0; + _soundAvailable = false; + _startTime = 0; + _totalVideoTime = 0; + _playPosX = _playPosY = 0; + _playZoom = 0.0f; + + _filename = NULL; + + _slowRendering = false; + + _currentSubtitle = 0; + _showSubtitle = false; + + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +VideoPlayer::~VideoPlayer() { + cleanup(); +} + +////////////////////////////////////////////////////////////////////////// +bool VideoPlayer::cleanup() { + return 0; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoPlayer::initialize(const char *inFilename, const char *subtitleFile) { + warning("VideoPlayer: %s %s - Not implemented yet", inFilename, subtitleFile); + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoPlayer::update() { + return 0; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoPlayer::display() { + return 0; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoPlayer::play(TVideoPlayback type, int x, int y, bool freezeMusic) { + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoPlayer::stop() { + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoPlayer::isPlaying() { + return _playing; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoPlayer::loadSubtitles(const char *filename, const char *subtitleFile) { + return STATUS_OK; +} + +} // end of namespace Wintermute diff --git a/engines/wintermute/video/video_player.h b/engines/wintermute/video/video_player.h new file mode 100644 index 0000000000..d5466da679 --- /dev/null +++ b/engines/wintermute/video/video_player.h @@ -0,0 +1,90 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This file is based on WME Lite. + * http://dead-code.org/redir.php?target=wmelite + * Copyright (c) 2011 Jan Nedoma + */ + +#ifndef WINTERMUTE_VIDPLAYER_H +#define WINTERMUTE_VIDPLAYER_H + +#include "engines/wintermute/dctypes.h" // Added by ClassView +#include "engines/wintermute/base/base.h" + +#define MAX_AUDIO_STREAMS 5 +#define MAX_VIDEO_STREAMS 5 + + +namespace Wintermute { + +// AVI-Video-player, currently fully stubbed +class VideoPlayer : public BaseClass { +public: + bool _showSubtitle; + int _currentSubtitle; + bool loadSubtitles(const char *filename, const char *subtitleFile); + bool _slowRendering; + bool isPlaying(); + char *_filename; + bool stop(); + bool play(TVideoPlayback Type = VID_PLAY_CENTER, int x = 0, int y = 0, bool freezeMusic = true); + uint32 _totalVideoTime; + uint32 _startTime; + //CVidRenderer *_vidRenderer; + //BaseSoundAVI *_sound; + bool _soundAvailable; + bool setDefaults(); + bool _playing; + bool display(); + bool update(); + bool initialize(const char *inFilename, const char *subtitleFile = NULL); + bool cleanup(); + VideoPlayer(BaseGame *inGame); + virtual ~VideoPlayer(); + + /*PAVIFILE _aviFile; + + LONG _lastSample; + + PAVISTREAM _audioStream; + PAVISTREAM _videoStream; + + LPWAVEFORMAT _audioFormat; + + LPBITMAPINFO _videoFormat; + PGETFRAME _videoPGF;*/ + uint32 _videoEndTime; + + int _playPosX; + int _playPosY; + float _playZoom; + + /* LPBITMAPV4HEADER _targetFormat; + + BaseArray<CVidSubtitle *, CVidSubtitle *> _subtitles;*/ +}; + +} // end of namespace Wintermute + +#endif diff --git a/engines/wintermute/video/video_theora_player.cpp b/engines/wintermute/video/video_theora_player.cpp new file mode 100644 index 0000000000..d92a74610d --- /dev/null +++ b/engines/wintermute/video/video_theora_player.cpp @@ -0,0 +1,500 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This file is based on WME Lite. + * http://dead-code.org/redir.php?target=wmelite + * Copyright (c) 2011 Jan Nedoma + */ + + +#include "engines/wintermute/video/video_theora_player.h" +#include "engines/wintermute/base/base_game.h" +#include "engines/wintermute/base/base_file_manager.h" +#include "engines/wintermute/base/gfx/osystem/base_surface_osystem.h" +#include "engines/wintermute/base/gfx/base_image.h" +#include "engines/wintermute/base/sound/base_sound_manager.h" +#include "engines/wintermute/utils/utils.h" +#include "engines/wintermute/platform_osystem.h" +#include "engines/wintermute/video/decoders/theora_decoder.h" +#include "engines/wintermute/wintermute.h" +#include "common/system.h" + +namespace Wintermute { + +IMPLEMENT_PERSISTENT(VideoTheoraPlayer, false) + +////////////////////////////////////////////////////////////////////////// +VideoTheoraPlayer::VideoTheoraPlayer(BaseGame *inGame) : BaseClass(inGame) { + SetDefaults(); +} + +////////////////////////////////////////////////////////////////////////// +void VideoTheoraPlayer::SetDefaults() { + + _file = NULL; + _filename = ""; + _startTime = 0; + _looping = false; + + _freezeGame = false; + _currentTime = 0; + + _state = THEORA_STATE_NONE; + + _videoFrameReady = false; + _audioFrameReady = false; + _videobufTime = 0; + + _playbackStarted = false; + _dontDropFrames = false; + + _texture = NULL; + _alphaImage = NULL; + _alphaFilename = ""; + + _frameRendered = false; + + _seekingKeyframe = false; + _timeOffset = 0.0f; + + _posX = _posY = 0; + _playbackType = VID_PLAY_CENTER; + _playZoom = 0.0f; + + _savedState = THEORA_STATE_NONE; + _savedPos = 0; + _volume = 100; + _theoraDecoder = NULL; + + // TODO: Add subtitles-support + //_subtitler = NULL; +} + +////////////////////////////////////////////////////////////////////////// +VideoTheoraPlayer::~VideoTheoraPlayer(void) { + cleanup(); +// SAFE_DELETE(_subtitler); +} + +////////////////////////////////////////////////////////////////////////// +void VideoTheoraPlayer::cleanup() { + if (_file) { + BaseFileManager::getEngineInstance()->closeFile(_file); + _file = NULL; + } + + _surface.free(); + if (_theoraDecoder) { + _theoraDecoder->close(); + } + delete _theoraDecoder; + _theoraDecoder = NULL; + delete _alphaImage; + _alphaImage = NULL; + delete _texture; + _texture = NULL; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::initialize(const Common::String &filename, const Common::String &subtitleFile) { + cleanup(); + + _filename = filename; + _file = BaseFileManager::getEngineInstance()->openFile(filename, true, false); + if (!_file) { + return STATUS_FAILED; + } + +#if defined (USE_THEORADEC) + _theoraDecoder = new TheoraDecoder(); +#else + return STATUS_FAILED; +#endif + _theoraDecoder->loadStream(_file); + + if (!_theoraDecoder->isVideoLoaded()) { + return STATUS_FAILED; + } + + _state = THEORA_STATE_PAUSED; + + // Additional setup. + _surface.create(_theoraDecoder->getWidth(), _theoraDecoder->getHeight(), _theoraDecoder->getPixelFormat()); + _texture = new BaseSurfaceOSystem(_gameRef); + _texture->create(_theoraDecoder->getWidth(), _theoraDecoder->getHeight()); + _state = THEORA_STATE_PLAYING; + _playZoom = 100; + + return STATUS_OK; +} + + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::resetStream() { + warning("VidTheoraPlayer::resetStream - stubbed"); +#if 0 // Stubbed for now, as theora isn't seekable + if (_sound) { + _sound->Stop(); + } + + m_TimeOffset = 0.0f; + Initialize(m_Filename); + Play(m_PlaybackType, m_PosX, m_PosY, false, false, m_Looping, 0, m_PlayZoom); +#endif + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::play(TVideoPlayback type, int x, int y, bool freezeGame, bool freezeMusic, bool looping, uint32 startTime, float forceZoom, int volume) { + if (forceZoom < 0.0f) { + forceZoom = 100.0f; + } + if (volume < 0) { + _volume = _gameRef->_soundMgr->getVolumePercent(Audio::Mixer::kSFXSoundType); + } else { + _volume = volume; + } + + _freezeGame = freezeGame; + + if (!_playbackStarted && _freezeGame) { + _gameRef->freeze(freezeMusic); + } + + _playbackStarted = false; + float width, height; + if (_theoraDecoder) { + _surface.free(); + _surface.copyFrom(*_theoraDecoder->decodeNextFrame()); + _state = THEORA_STATE_PLAYING; + _looping = looping; + _playbackType = type; + + _startTime = startTime; + _volume = volume; + _posX = x; + _posY = y; + _playZoom = forceZoom; + + width = (float)_theoraDecoder->getWidth(); + height = (float)_theoraDecoder->getHeight(); + } else { + width = (float)_gameRef->_renderer->_width; + height = (float)_gameRef->_renderer->_height; + } + + switch (type) { + case VID_PLAY_POS: + _playZoom = forceZoom; + _posX = x; + _posY = y; + break; + + case VID_PLAY_STRETCH: { + float zoomX = (float)((float)_gameRef->_renderer->_width / width * 100); + float zoomY = (float)((float)_gameRef->_renderer->_height / height * 100); + _playZoom = MIN(zoomX, zoomY); + _posX = (int)((_gameRef->_renderer->_width - width * (_playZoom / 100)) / 2); + _posY = (int)((_gameRef->_renderer->_height - height * (_playZoom / 100)) / 2); + } + break; + + case VID_PLAY_CENTER: + _playZoom = 100.0f; + _posX = (int)((_gameRef->_renderer->_width - width) / 2); + _posY = (int)((_gameRef->_renderer->_height - height) / 2); + break; + } + return STATUS_OK; +#if 0 // Stubbed for now as theora isn't seekable + if (StartTime) SeekToTime(StartTime); + + Update(); +#endif + return STATUS_FAILED; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::stop() { + _theoraDecoder->close(); + _state = THEORA_STATE_FINISHED; + if (_freezeGame) { + _gameRef->unfreeze(); + } + + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::update() { + _currentTime = _freezeGame ? _gameRef->_liveTimer : _gameRef->_timer; + + if (!isPlaying()) { + return STATUS_OK; + } + + if (_playbackStarted /*&& m_Sound && !m_Sound->IsPlaying()*/) { + return STATUS_OK; + } + + if (_playbackStarted && !_freezeGame && _gameRef->_state == GAME_FROZEN) { + return STATUS_OK; + } + + if (_theoraDecoder) { + if (_theoraDecoder->endOfVideo() && _looping) { + warning("Should loop movie %s", _filename.c_str()); + _theoraDecoder->rewind(); + } else if (_theoraDecoder->endOfVideo() && !_looping) { + debugC(kWintermuteDebugLog, "Finished movie %s", _filename.c_str()); + _state = THEORA_STATE_FINISHED; + _playbackStarted = false; + if (_freezeGame) { + _gameRef->unfreeze(); + } + } + if (_state == THEORA_STATE_PLAYING) { + if (_theoraDecoder->getTimeToNextFrame() == 0) { + _surface.free(); + _surface.copyFrom(*_theoraDecoder->decodeNextFrame()); + if (_texture) { + writeVideo(); + } + } + return STATUS_OK; + } + } + // Skip the busy-loop? + if ((!_texture || !_videoFrameReady) && !_theoraDecoder->endOfVideo()) { + // end playback + if (!_looping) { + _state = THEORA_STATE_FINISHED; + if (_freezeGame) { + _gameRef->unfreeze(); + } + return STATUS_OK; + } else { + resetStream(); + return STATUS_OK; + } + } + + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +uint32 VideoTheoraPlayer::getMovieTime() { + if (!_playbackStarted) { + return 0; + } else { + return _theoraDecoder->getTime(); + } +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::writeVideo() { + if (!_texture) { + return STATUS_FAILED; + } + + _texture->startPixelOp(); + + writeAlpha(); + if (_alphaImage) { + _texture->putSurface(_surface, true); + } else { + _texture->putSurface(_surface, false); + } + + //RenderFrame(_texture, &yuv); + + _texture->endPixelOp(); + _videoFrameReady = true; + return STATUS_OK; +} + +void VideoTheoraPlayer::writeAlpha() { // TODO: Endian-fix. + if (_alphaImage && _surface.w == _alphaImage->getSurface()->w && _surface.h == _alphaImage->getSurface()->h) { + assert(_alphaImage->getSurface()->format.bytesPerPixel == 4); + assert(_surface.format.bytesPerPixel == 4); + const byte *alphaData = (byte *)_alphaImage->getSurface()->getBasePtr(0, 0); +#ifdef SCUMM_LITTLE_ENDIAN + int alphaPlace = (_alphaImage->getSurface()->format.aShift / 8); +#else + int alphaPlace = 3 - (_alphaImage->getSurface()->format.aShift / 8); +#endif + alphaData += alphaPlace; + byte *imgData = (byte *)_surface.getBasePtr(0, 0); +#ifdef SCUMM_LITTLE_ENDIAN + imgData += (_surface.format.aShift / 8); +#else + imgData += 3 - (_surface.format.aShift / 8); +#endif + for (int i = 0; i < _surface.w * _surface.h; i++) { + *imgData = *alphaData; + alphaData += 4; + imgData += 4; + } + } +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::display(uint32 alpha) { + Rect32 rc; + bool res; + + if (_texture && _videoFrameReady) { + BasePlatform::setRect(&rc, 0, 0, _texture->getWidth(), _texture->getHeight()); + if (_playZoom == 100.0f) { + res = _texture->displayTrans(_posX, _posY, rc, alpha); + } else { + res = _texture->displayTransZoom(_posX, _posY, rc, _playZoom, _playZoom, alpha); + } + } else { + res = STATUS_FAILED; + } + // TODO: Add subtitles-support +/* if (m_Subtitler && _gameRef->m_VideoSubtitles) { + m_Subtitler->display(); + }*/ + + return res; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::setAlphaImage(const Common::String &filename) { + delete _alphaImage; + _alphaImage = new BaseImage(); + if (!_alphaImage || DID_FAIL(_alphaImage->loadFile(filename))) { + delete _alphaImage; + _alphaImage = NULL; + _alphaFilename = ""; + return STATUS_FAILED; + } + + if (_alphaFilename != filename) { + _alphaFilename = filename; + } + //TODO: Conversion. + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +byte VideoTheoraPlayer::getAlphaAt(int x, int y) { + if (_alphaImage) { + return _alphaImage->getAlphaAt(x, y); + } else { + return 0xFF; + } +} + + +////////////////////////////////////////////////////////////////////////// +inline int intlog(int num) { + int r = 0; + while (num > 0) { + num = num / 2; + r = r + 1; + } + + return r; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::seekToTime(uint32 time) { + warning("VideoTheoraPlayer::SeekToTime(%d) - not supported", time); + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::pause() { + if (_state == THEORA_STATE_PLAYING) { + _state = THEORA_STATE_PAUSED; + _theoraDecoder->pauseVideo(true); + return STATUS_OK; + } else { + return STATUS_FAILED; + } +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::resume() { + if (_state == THEORA_STATE_PAUSED) { + _state = THEORA_STATE_PLAYING; + _theoraDecoder->pauseVideo(false); + return STATUS_OK; + } else { + return STATUS_FAILED; + } +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::persist(BasePersistenceManager *persistMgr) { + //BaseClass::persist(persistMgr); + + if (persistMgr->getIsSaving()) { + _savedPos = getMovieTime() * 1000; + _savedState = _state; + } else { + SetDefaults(); + } + + persistMgr->transfer(TMEMBER(_gameRef)); + persistMgr->transfer(TMEMBER(_savedPos)); + persistMgr->transfer(TMEMBER(_savedState)); + persistMgr->transfer(TMEMBER(_filename)); + persistMgr->transfer(TMEMBER(_alphaFilename)); + persistMgr->transfer(TMEMBER(_posX)); + persistMgr->transfer(TMEMBER(_posY)); + persistMgr->transfer(TMEMBER(_playZoom)); + persistMgr->transfer(TMEMBER_INT(_playbackType)); + persistMgr->transfer(TMEMBER(_looping)); + persistMgr->transfer(TMEMBER(_volume)); + + if (!persistMgr->getIsSaving() && (_savedState != THEORA_STATE_NONE)) { + initializeSimple(); + } + + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +bool VideoTheoraPlayer::initializeSimple() { + if (DID_SUCCEED(initialize(_filename))) { + if (_alphaFilename != "") { + setAlphaImage(_alphaFilename); + } + play(_playbackType, _posX, _posY, false, false, _looping, _savedPos, _playZoom); + } else { + _state = THEORA_STATE_FINISHED; + } + + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +BaseSurface *VideoTheoraPlayer::getTexture() { + return _texture; +} + +} // end of namespace Wintermute diff --git a/engines/wintermute/video/video_theora_player.h b/engines/wintermute/video/video_theora_player.h new file mode 100644 index 0000000000..41b45d2289 --- /dev/null +++ b/engines/wintermute/video/video_theora_player.h @@ -0,0 +1,148 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This file is based on WME Lite. + * http://dead-code.org/redir.php?target=wmelite + * Copyright (c) 2011 Jan Nedoma + */ + +#ifndef WINTERMUTE_VIDTHEORAPLAYER_H +#define WINTERMUTE_VIDTHEORAPLAYER_H + +#include "engines/wintermute/base/base.h" +#include "engines/wintermute/persistent.h" +#include "video/video_decoder.h" +#include "common/stream.h" +#include "graphics/surface.h" + +namespace Wintermute { +class BaseSurface; +class BaseImage; +class VideoTheoraPlayer : public BaseClass { +private: + enum { + THEORA_STATE_NONE = 0, + THEORA_STATE_PLAYING = 1, + THEORA_STATE_PAUSED = 2, + THEORA_STATE_FINISHED = 3 + }; + Video::RewindableVideoDecoder *_theoraDecoder; + Graphics::Surface _surface; +public: + DECLARE_PERSISTENT(VideoTheoraPlayer, BaseClass) + + VideoTheoraPlayer(BaseGame *inGame); + virtual ~VideoTheoraPlayer(void); + + // external objects + Common::SeekableReadStream *_file; + Common::String _filename; + + BaseSurface *_texture; + //CVidSubtitler *_subtitler; + + // control methods + bool initialize(const Common::String &filename, const Common::String &subtitleFile = NULL); + bool initializeSimple(); + bool update(); + bool play(TVideoPlayback type = VID_PLAY_CENTER, int x = 0, int y = 0, bool freezeGame = false, bool freezeMusic = true, bool looping = false, uint32 startTime = 0, float forceZoom = -1.0f, int volume = -1); + bool stop(); + bool display(uint32 alpha = 0xFFFFFFFF); + + bool pause(); + bool resume(); + + bool isPlaying() { + return _state == THEORA_STATE_PLAYING; + }; + bool isFinished() { + return _state == THEORA_STATE_FINISHED; + }; + bool isPaused() { + return _state == THEORA_STATE_PAUSED; + }; + + uint32 getMovieTime(); + + BaseSurface *getTexture(); + + int _state; + uint32 _startTime; + + int _savedState; + uint32 _savedPos; + + + // alpha related + BaseImage *_alphaImage; + Common::String _alphaFilename; + bool setAlphaImage(const Common::String &filename); + __inline byte getAlphaAt(int x, int y); + void writeAlpha(); + + bool seekToTime(uint32 Time); + + + void cleanup(); + bool resetStream(); + + // video properties + TVideoPlayback _playbackType; + int _posX; + int _posY; + float _playZoom; + int _volume; + + bool _looping; + bool _dontDropFrames; + bool _freezeGame; + uint32 _currentTime; + + +private: + // seeking support + bool _seekingKeyframe; + float _timeOffset; + + bool _frameRendered; + + bool getIsFrameReady() { + return _videoFrameReady; + } +private: + bool _audioFrameReady; + bool _videoFrameReady; + float _videobufTime; + + bool writeVideo(); + + bool _playbackStarted; + + // helpers + void SetDefaults(); + +}; + +} // end of namespace Wintermute + +#endif |