mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-04 00:19:56 +00:00
577 lines
16 KiB
C++
577 lines
16 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 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 "titanic/support/avi_surface.h"
|
|
#include "titanic/support/screen_manager.h"
|
|
#include "titanic/support/video_surface.h"
|
|
#include "titanic/events.h"
|
|
#include "titanic/titanic.h"
|
|
#include "common/system.h"
|
|
#include "graphics/pixelformat.h"
|
|
#include "graphics/screen.h"
|
|
#include "video/avi_decoder.h"
|
|
#include "titanic/translation.h"
|
|
|
|
namespace Titanic {
|
|
|
|
#define DEFAULT_FPS 15.0
|
|
|
|
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 = false;
|
|
_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;
|
|
_priorFrame = -1;
|
|
|
|
// Create a decoder
|
|
_decoder = new AVIDecoder();
|
|
|
|
// Load the video into it
|
|
if (_movieName == TRANSLATE("y222.avi", "y237.avi")) {
|
|
// The y222.avi is the bells animation for the music room.
|
|
// It needs on the fly fixing for the video header
|
|
_decoder->loadStream(new y222());
|
|
} else if (!_decoder->loadFile(_movieName)) {
|
|
error("Could not open video - %s", key.getString().c_str());
|
|
}
|
|
|
|
_streamCount = _decoder->getTransparencyTrack() ? 2 : 1;
|
|
|
|
_soundManager = nullptr;
|
|
_hasAudio = false;
|
|
_frameRate = DEFAULT_FPS;
|
|
}
|
|
|
|
AVISurface::~AVISurface() {
|
|
if (_videoSurface)
|
|
_videoSurface->_flipVertically = false;
|
|
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
|
|
bool isReversePlayback = _movieRangeInfo.front()->_endFrame < _movieRangeInfo.front()->_startFrame;
|
|
|
|
if (isReversed() != isReversePlayback)
|
|
setFrameRate(isReversePlayback ? -DEFAULT_FPS : DEFAULT_FPS);
|
|
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 (frameNumber == (int)_decoder->getFrameCount())
|
|
--frameNumber;
|
|
|
|
// Start the playback
|
|
_decoder->start();
|
|
|
|
// Seek to the starting frame
|
|
_currentFrame = -1;
|
|
seekToFrame(frameNumber);
|
|
|
|
// If we're in reverse playback, set the decoder to play in reverse
|
|
if (isReversed())
|
|
_decoder->setReverse(true);
|
|
setFrameRate(_frameRate);
|
|
|
|
// Render the first frame
|
|
renderFrame();
|
|
|
|
return true;
|
|
}
|
|
|
|
void AVISurface::seekToFrame(uint frameNumber) {
|
|
if (isReversed() && frameNumber == _decoder->getFrameCount())
|
|
--frameNumber;
|
|
|
|
if ((int)frameNumber != _currentFrame) {
|
|
if (!isReversed() && frameNumber > 0) {
|
|
_decoder->seekToFrame(frameNumber - 1);
|
|
renderFrame();
|
|
}
|
|
|
|
_decoder->seekToFrame(frameNumber);
|
|
_currentFrame = _priorFrame = (int)frameNumber;
|
|
}
|
|
}
|
|
|
|
bool AVISurface::handleEvents(CMovieEventList &events) {
|
|
if (!isPlaying())
|
|
return true;
|
|
|
|
CMovieRangeInfo *info = _movieRangeInfo.front();
|
|
_priorFrame = _currentFrame;
|
|
_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;
|
|
bool reversed = info->_endFrame < info->_startFrame;
|
|
|
|
if (isReversed() != reversed)
|
|
// Direction is different, so force frame seek below
|
|
_priorFrame = -1;
|
|
|
|
// Set the next clip's direction
|
|
setFrameRate(reversed ? -DEFAULT_FPS : DEFAULT_FPS);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isPlaying()) {
|
|
if (newFrame != getFrame())
|
|
// The frame has been changed, so move to new position
|
|
seekToFrame(newFrame);
|
|
|
|
// 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->getTransparencyTrack()->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 = (idx == 0) ?
|
|
_decoder->getVideoTrack(0).getPixelFormat() :
|
|
_decoder->getTransparencyTrack()->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 = true;
|
|
} 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;
|
|
}
|
|
}
|
|
|
|
// Set a default frame rate
|
|
_frameRate = DEFAULT_FPS;
|
|
}
|
|
|
|
void AVISurface::copyMovieFrame(const Graphics::Surface &src, Graphics::ManagedSurface &dest) {
|
|
// WORKAROUND: Handle rare cases where frame sizes don't match the video size
|
|
Common::Rect copyRect(0, 0, MIN(src.w, dest.w), MIN(src.h, dest.h));
|
|
|
|
if (src.format.bytesPerPixel == 1) {
|
|
// Paletted 8-bit, so convert to 16-bit and copy over
|
|
const byte *palette = _decoder->getPalette();
|
|
if (palette) {
|
|
Graphics::Surface *s = src.convertTo(dest.format, palette);
|
|
dest.blitFrom(*s, copyRect, Common::Point(0, 0));
|
|
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, copyRect, Common::Point(0, 0));
|
|
} 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 < MIN(src.h, dest.h); ++y) {
|
|
const uint32 *pSrc = (const uint32 *)src.getBasePtr(0, y);
|
|
uint16 *pDest = (uint16 *)dest.getBasePtr(0, y);
|
|
|
|
for (uint x = 0; x < MIN(src.w, dest.w); ++x, ++pSrc, ++pDest) {
|
|
src.format.colorToARGB(*pSrc, a, r, g, b);
|
|
assert(a == 0 || a == 0xff);
|
|
|
|
*pDest = (a == 0 && _streamCount == 1) ? 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.
|
|
const uint FRAME_TIME = 1000 / DEFAULT_FPS;
|
|
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();
|
|
assert(frameNumber);
|
|
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) {
|
|
// Store the new frame rate
|
|
_frameRate = rate;
|
|
|
|
if (_decoder->isPlaying()) {
|
|
// Convert rate from fps to relative to 1.0 (normal speed)
|
|
const int PRECISION = 10000;
|
|
double playRate = rate / DEFAULT_FPS;
|
|
Common::Rational pRate((int)(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;
|
|
}
|
|
}
|
|
|
|
bool AVISurface::playCutscene(const Rect &r, uint startFrame, uint endFrame) {
|
|
if (g_vm->shouldQuit())
|
|
return false;
|
|
|
|
// TODO: Fixes slight "jumping back" when rotating in place in Top Of Well
|
|
// balcony between two elevators. Need a more generalized fix at some point
|
|
if (_movieName == "z48.avi")
|
|
_currentFrame = -1;
|
|
|
|
if (_currentFrame != ((int)startFrame - 1) || startFrame == 0) {
|
|
// Start video playback at the desired starting frame
|
|
if (startFrame > 0) {
|
|
// Give a chance for a key frame just prior to the start frame
|
|
// to be loaded first
|
|
setFrame(startFrame - 1);
|
|
}
|
|
|
|
setFrame(startFrame);
|
|
startAtFrame(startFrame);
|
|
_currentFrame = startFrame;
|
|
} else {
|
|
// Already in position, so pick up where we left off
|
|
_decoder->start();
|
|
}
|
|
|
|
bool isDifferent = _movieFrameSurface[0]->w != r.width() ||
|
|
_movieFrameSurface[0]->h != r.height();
|
|
|
|
bool isFinished = true;
|
|
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)) {
|
|
isFinished = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
stop();
|
|
return isFinished && !g_vm->shouldQuit();
|
|
}
|
|
|
|
uint AVISurface::getBitDepth() const {
|
|
return _decoder->getVideoTrack(0).getBitCount();
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
y222::y222() {
|
|
_innerStream = new File();
|
|
_innerStream->open(TRANSLATE("y222.avi", "y237.avi"));
|
|
}
|
|
|
|
y222::~y222() {
|
|
delete _innerStream;
|
|
}
|
|
|
|
uint32 y222::read(void *dataPtr, uint32 dataSize) {
|
|
int32 currPos = pos();
|
|
uint32 bytesRead = _innerStream->read(dataPtr, dataSize);
|
|
|
|
if (currPos <= 48 && (currPos + bytesRead) >= 52) {
|
|
byte *framesP = (byte *)dataPtr + (48 - currPos);
|
|
if (READ_LE_UINT32(framesP) == 1)
|
|
WRITE_LE_UINT32(framesP, 1085);
|
|
}
|
|
|
|
return bytesRead;
|
|
}
|
|
|
|
} // End of namespace Titanic
|