aboutsummaryrefslogtreecommitdiff
path: root/engines/titanic/support/avi_surface.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/titanic/support/avi_surface.cpp')
-rw-r--r--engines/titanic/support/avi_surface.cpp484
1 files changed, 484 insertions, 0 deletions
diff --git a/engines/titanic/support/avi_surface.cpp b/engines/titanic/support/avi_surface.cpp
new file mode 100644
index 0000000000..de7b9abe3e
--- /dev/null
+++ b/engines/titanic/support/avi_surface.cpp
@@ -0,0 +1,484 @@
+/* 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.
+ *
+ */
+
+#include "common/system.h"
+#include "graphics/pixelformat.h"
+#include "video/avi_decoder.h"
+#include "titanic/support/avi_surface.h"
+#include "titanic/support/screen_manager.h"
+#include "titanic/support/video_surface.h"
+#include "titanic/titanic.h"
+
+namespace Titanic {
+
+Video::AVIDecoder::AVIVideoTrack &AVIDecoder::getVideoTrack(uint idx) {
+ assert(idx < _videoTracks.size());
+ AVIVideoTrack *track = static_cast<AVIVideoTrack *>(_videoTracks[idx].track);
+ return *track;
+}
+
+AVISurface::AVISurface(const CResourceKey &key) : _movieName(key.getString()) {
+ _videoSurface = nullptr;
+ _streamCount = 0;
+ _movieFrameSurface[0] = _movieFrameSurface[1] = nullptr;
+ _framePixels = nullptr;
+ _priorFrameTime = 0;
+
+ // Reset current frame. We need to keep track of frames separately from the decoder,
+ // since it needs to be able to go beyond the frame count or to negative to allow
+ // correct detection of when range playbacks have finished
+ _currentFrame = -1;
+ _isReversed = false;
+
+ // Create a decoder
+ _decoder = new AVIDecoder(Audio::Mixer::kPlainSoundType);
+ if (!_decoder->loadFile(_movieName))
+ error("Could not open video - %s", key.getString().c_str());
+
+ _streamCount = _decoder->videoTrackCount();
+}
+
+AVISurface::~AVISurface() {
+ if (_videoSurface)
+ _videoSurface->_flipVertically = false;
+ delete _framePixels;
+ delete _movieFrameSurface[0];
+ delete _movieFrameSurface[1];
+ delete _decoder;
+}
+
+bool AVISurface::play(uint flags, CGameObject *obj) {
+ if (flags & MOVIE_REVERSE)
+ return play(_decoder->getFrameCount() - 1, 0, flags, obj);
+ else
+ return play(0, _decoder->getFrameCount() - 1, flags, obj);
+}
+
+bool AVISurface::play(int startFrame, int endFrame, uint flags, CGameObject *obj) {
+ if (flags & MOVIE_STOP_PREVIOUS)
+ stop();
+
+ return play(startFrame, endFrame, -1, flags, obj);
+}
+
+bool AVISurface::play(int startFrame, int endFrame, int initialFrame, uint flags, CGameObject *obj) {
+ CMovieRangeInfo *info = new CMovieRangeInfo();
+ info->_startFrame = startFrame;
+ info->_endFrame = endFrame;
+ info->_isReversed = endFrame < startFrame;
+ info->_initialFrame = 0;
+ info->_isRepeat = flags & MOVIE_REPEAT;
+
+ if (obj) {
+ CMovieEvent *me = new CMovieEvent();
+ me->_type = MET_MOVIE_END;
+ me->_startFrame = startFrame;
+ me->_endFrame = endFrame;
+ me->_initialFrame = 0;
+ me->_gameObject = obj;
+
+ info->addEvent(me);
+ }
+
+ _movieRangeInfo.push_back(info);
+
+ if (_movieRangeInfo.size() == 1) {
+ // First play call, so start the movie playing
+ setReversed(info->_isReversed);
+ return startAtFrame(initialFrame);
+ } else {
+ return true;
+ }
+}
+
+void AVISurface::stop() {
+ _decoder->stop();
+ _movieRangeInfo.destroyContents();
+}
+
+void AVISurface::pause() {
+ _decoder->pauseVideo(true);
+}
+
+void AVISurface::resume() {
+ if (_decoder->isPaused())
+ _decoder->pauseVideo(false);
+}
+
+bool AVISurface::startAtFrame(int frameNumber) {
+ if (isPlaying())
+ // If it's already playing, then don't allow it
+ return false;
+
+ if (frameNumber == -1)
+ // Default to starting frame of first movie range
+ frameNumber = _movieRangeInfo.front()->_startFrame;
+ if (_isReversed && frameNumber == (int)_decoder->getFrameCount())
+ --frameNumber;
+
+ // Start the playback
+ _decoder->start();
+
+ // Seek to the starting frame
+ seekToFrame(frameNumber);
+
+ // If we're in reverse playback, set the decoder to play in reverse
+ if (_isReversed)
+ _decoder->setRate(-1.0);
+
+ renderFrame();
+
+ return true;
+}
+
+void AVISurface::seekToFrame(uint frameNumber) {
+ if (_isReversed && frameNumber == _decoder->getFrameCount())
+ --frameNumber;
+
+ if ((int)frameNumber != getFrame()) {
+ _decoder->seekToFrame(frameNumber);
+ _currentFrame = (int)frameNumber;
+ }
+}
+
+void AVISurface::setReversed(bool isReversed) {
+ _isReversed = isReversed;
+}
+
+bool AVISurface::handleEvents(CMovieEventList &events) {
+ if (!isPlaying())
+ return true;
+
+ CMovieRangeInfo *info = _movieRangeInfo.front();
+ _currentFrame += _isReversed ? -1 : 1;
+
+ int newFrame = _currentFrame;
+ if ((info->_isReversed && newFrame < info->_endFrame) ||
+ (!info->_isReversed && newFrame > info->_endFrame)) {
+ if (info->_isRepeat) {
+ newFrame = info->_startFrame;
+ } else {
+ info->getMovieEnd(events);
+ _movieRangeInfo.remove(info);
+ delete info;
+
+ if (_movieRangeInfo.empty()) {
+ // No more ranges, so stop playback
+ stop();
+ } else {
+ // Not empty, so move onto new first one
+ info = _movieRangeInfo.front();
+ newFrame = info->_startFrame;
+ setReversed(info->_isReversed);
+ }
+ }
+ }
+
+ if (isPlaying()) {
+ if (newFrame != getFrame()) {
+ // The frame has been changed, so move to new position
+ seekToFrame(newFrame);
+ renderFrame();
+ }
+
+ // Get any events for the given position
+ info->getMovieFrame(events, newFrame);
+ return renderFrame();
+ } else {
+ return false;
+ }
+}
+
+void AVISurface::setVideoSurface(CVideoSurface *surface) {
+ _videoSurface = surface;
+
+ // Handling for secondary video stream
+ if (_streamCount == 2) {
+ const Common::String &streamName = _decoder->getVideoTrack(1).getName();
+
+ if (streamName == "mask0") {
+ _videoSurface->_transparencyMode = TRANS_MASK0;
+ } else if (streamName == "mask255") {
+ _videoSurface->_transparencyMode = TRANS_MASK255;
+ } else if (streamName == "alpha0") {
+ _videoSurface->_transparencyMode = TRANS_ALPHA0;
+ } else if (streamName == "alpha255") {
+ _videoSurface->_transparencyMode = TRANS_ALPHA255;
+ }
+ }
+
+ setupDecompressor();
+}
+
+void AVISurface::setupDecompressor() {
+ if (!_decoder)
+ return;
+
+ for (int idx = 0; idx < _streamCount; ++idx) {
+ Graphics::PixelFormat format = _decoder->getVideoTrack(idx).getPixelFormat();
+ int decoderPitch = _decoder->getWidth() * format.bytesPerPixel;
+ bool flag = false;
+
+ if (idx == 0 && _videoSurface && _videoSurface->getPitch() == decoderPitch) {
+ const uint bitCount = _decoder->getVideoTrack(0).getBitCount();
+ const int vDepth = _videoSurface->getPixelDepth();
+
+ switch (bitCount) {
+ case 15:
+ flag = vDepth == 1;
+ break;
+
+ case 16:
+ flag = vDepth == 1 || vDepth == 2;
+ break;
+
+ case 24:
+ flag = vDepth == 3;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (!flag) {
+ _framePixels = new Graphics::ManagedSurface(_decoder->getWidth(), _decoder->getHeight(),
+ _decoder->getVideoTrack(0).getPixelFormat());
+ } else if (idx == 0) {
+ // The original developers used a vertical flipped playback to indicate
+ // an incompatibility between source video and dest surface bit-depths,
+ // which would result in poor playback performance
+ _videoSurface->_flipVertically = true;
+ }
+ }
+}
+
+void AVISurface::copyMovieFrame(const Graphics::Surface &src, Graphics::ManagedSurface &dest) {
+ assert(src.w == dest.w && src.h == dest.h);
+
+ if (src.format.bytesPerPixel == 1) {
+ // Paletted 8-bit, so convert to 16-bit and copy over
+ Graphics::Surface *s = src.convertTo(dest.format, _decoder->getPalette());
+ dest.blitFrom(*s);
+ s->free();
+ delete s;
+ } else if (src.format.bytesPerPixel == 2) {
+ // Source is already 16-bit, with no alpha, so do a straight copy
+ dest.blitFrom(src);
+ } else {
+ // Source is 32-bit which may have transparent pixels. Copy over each
+ // pixel, replacing transparent pixels with the special transparency color
+ byte a, r, g, b;
+ assert(src.format.bytesPerPixel == 4 && dest.format.bytesPerPixel == 2);
+ uint16 transPixel = _videoSurface->getTransparencyColor();
+
+ for (uint y = 0; y < src.h; ++y) {
+ const uint32 *pSrc = (const uint32 *)src.getBasePtr(0, y);
+ uint16 *pDest = (uint16 *)dest.getBasePtr(0, y);
+
+ for (uint x = 0; x < src.w; ++x, ++pSrc, ++pDest) {
+ src.format.colorToARGB(*pSrc, a, r, g, b);
+ assert(a == 0 || a == 0xff);
+
+ *pDest = (a == 0) ? transPixel : dest.format.RGBToColor(r, g, b);
+ }
+ }
+ }
+}
+
+uint AVISurface::getWidth() const {
+ return _decoder->getWidth();
+}
+
+uint AVISurface::getHeight() const {
+ return _decoder->getHeight();
+}
+
+void AVISurface::setFrame(int frameNumber) {
+ // If playback was in process, stop it
+ if (isPlaying())
+ stop();
+
+ // Ensure the frame number is valid
+ if (frameNumber >= (int)_decoder->getFrameCount())
+ frameNumber = _decoder->getFrameCount() - 1;
+
+ seekToFrame(frameNumber);
+ renderFrame();
+}
+
+bool AVISurface::isNextFrame() {
+ if (!_decoder->endOfVideo())
+ return _decoder->getTimeToNextFrame() == 0;
+
+ // We're at the end of the video, so we need to manually
+ // keep track of frame delays. Hardcoded at the moment for 15FPS
+ const uint FRAME_TIME = 1000 / 15;
+ uint32 currTime = g_system->getMillis();
+ if (currTime >= (_priorFrameTime + FRAME_TIME)) {
+ _priorFrameTime = currTime;
+ return true;
+ }
+
+ return false;
+}
+
+bool AVISurface::renderFrame() {
+ // Check there's a frame ready for display
+ if (!_decoder->needsUpdate())
+ return false;
+
+ // Make a copy of each decoder's video frame
+ for (int idx = 0; idx < _streamCount; ++idx) {
+ const Graphics::Surface *frame;
+
+ if (idx == 0) {
+ frame = _decoder->decodeNextFrame();
+ if (!_movieFrameSurface[0])
+ _movieFrameSurface[0] = new Graphics::ManagedSurface(_decoder->getWidth(), _decoder->getHeight(),
+ g_system->getScreenFormat());
+
+ copyMovieFrame(*frame, *_movieFrameSurface[0]);
+ } else {
+ frame = _decoder->decodeNextTransparency();
+ if (!_movieFrameSurface[1])
+ _movieFrameSurface[1] = new Graphics::ManagedSurface(_decoder->getWidth(), _decoder->getHeight(),
+ Graphics::PixelFormat::createFormatCLUT8());
+
+ _movieFrameSurface[1]->blitFrom(*frame);
+ }
+ }
+
+ if (!_framePixels) {
+ if (_videoSurface->lock()) {
+ // Blit the frame directly to the video surface
+ assert(_streamCount == 1);
+ _videoSurface->blitFrom(Point(0, 0), &_movieFrameSurface[0]->rawSurface());
+
+ _videoSurface->unlock();
+ }
+ } else {
+ const Graphics::Surface &frameSurface = _movieFrameSurface[0]->rawSurface();
+ _videoSurface->lock();
+
+ if (frameSurface.format.bytesPerPixel == 1) {
+ // For paletted 8-bit surfaces, we need to convert it to 16-bit,
+ // since the blitting method we're using doesn't support palettes
+ Graphics::Surface *s = frameSurface.convertTo(g_system->getScreenFormat(),
+ _decoder->getPalette());
+
+ _videoSurface->getRawSurface()->blitFrom(*s);
+ s->free();
+ delete s;
+ } else {
+ _videoSurface->getRawSurface()->blitFrom(frameSurface);
+ }
+
+ _videoSurface->unlock();
+ }
+
+ return false;
+}
+
+bool AVISurface::addEvent(int frameNumber, CGameObject *obj) {
+ if (!_movieRangeInfo.empty()) {
+ CMovieRangeInfo *tail = _movieRangeInfo.back();
+ if (frameNumber == -1)
+ frameNumber = tail->_startFrame;
+
+ CMovieEvent *me = new CMovieEvent();
+ me->_type = MET_FRAME;
+ me->_startFrame = 0;
+ me->_endFrame = 0;
+ me->_initialFrame = frameNumber;
+ me->_gameObject = obj;
+ tail->addEvent(me);
+
+ return _movieRangeInfo.size() == 1 && frameNumber == getFrame();
+ }
+
+ return false;
+}
+
+void AVISurface::setFrameRate(double rate) {
+ // Convert rate from fps to relative to 1.0 (normal speed)
+ const int PRECISION = 10000;
+ double playRate = rate / 15.0; // Standard 15 FPS
+ Common::Rational pRate(playRate * PRECISION, PRECISION);
+
+ _decoder->setRate(pRate);
+}
+
+Graphics::ManagedSurface *AVISurface::getSecondarySurface() {
+ return _streamCount <= 1 ? nullptr : _movieFrameSurface[1];
+}
+
+Graphics::ManagedSurface *AVISurface::duplicateTransparency() const {
+ if (_streamCount <= 1) {
+ return nullptr;
+ } else {
+ Graphics::ManagedSurface *dest = new Graphics::ManagedSurface(_movieFrameSurface[1]->w,
+ _movieFrameSurface[1]->h, Graphics::PixelFormat::createFormatCLUT8());
+ dest->blitFrom(*_movieFrameSurface[1]);
+ return dest;
+ }
+}
+
+void AVISurface::playCutscene(const Rect &r, uint startFrame, uint endFrame) {
+ bool isDifferent = _movieFrameSurface[0]->w != r.width() ||
+ _movieFrameSurface[0]->h != r.height();
+
+ startAtFrame(startFrame);
+ _currentFrame = startFrame;
+
+ while (_currentFrame < (int)endFrame && !g_vm->shouldQuit()) {
+ if (isNextFrame()) {
+ renderFrame();
+ ++_currentFrame;
+
+ if (isDifferent) {
+ // Clear the destination area, and use the transBlitFrom method,
+ // which supports arbitrary scaling, to reduce to the desired size
+ g_vm->_screen->fillRect(r, 0);
+ g_vm->_screen->transBlitFrom(*_movieFrameSurface[0],
+ Common::Rect(0, 0, _movieFrameSurface[0]->w, _movieFrameSurface[0]->h), r);
+ } else {
+ g_vm->_screen->blitFrom(*_movieFrameSurface[0], Common::Point(r.left, r.top));
+ }
+
+ g_vm->_screen->update();
+ g_vm->_events->pollEvents();
+ }
+
+ // Brief wait, and check at the same time for clicks to abort the clip
+ if (g_vm->_events->waitForPress(10))
+ break;
+ }
+
+ stop();
+}
+
+uint AVISurface::getBitDepth() const {
+ return _decoder->getVideoTrack(0).getBitCount();
+}
+
+} // End of namespace Titanic