https://github.com/scummvm/scummvm
Raw File
Tip revision: 89fce5a98fba5fa2761f9905370ee7b235273a2b authored by Eugene Sandulenko on 26 September 2016, 18:55:58 UTC
RELEASE: This is 1.9.0pre
Tip revision: 89fce5a
video.cpp
/* 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 "mohawk/mohawk.h"
#include "mohawk/resource.h"
#include "mohawk/video.h"

#include "common/algorithm.h"
#include "common/debug.h"
#include "common/events.h"
#include "common/textconsole.h"
#include "common/system.h"

#include "graphics/palette.h"
#include "graphics/surface.h"

#include "video/qt_decoder.h"


namespace Mohawk {

VideoEntry::VideoEntry() : _video(0), _id(-1), _x(0), _y(0), _loop(false), _enabled(true) {
}

VideoEntry::VideoEntry(Video::VideoDecoder *video, const Common::String &fileName) : _video(video), _fileName(fileName), _id(-1), _x(0), _y(0), _loop(false), _enabled(true) {
}

VideoEntry::VideoEntry(Video::VideoDecoder *video, int id) : _video(video), _id(id), _x(0), _y(0), _loop(false), _enabled(true) {
}

VideoEntry::~VideoEntry() {
	close();
}

void VideoEntry::close() {
	delete _video;
	_video = 0;
}

bool VideoEntry::endOfVideo() const {
	return !isOpen() || _video->endOfVideo();
}

int VideoEntry::getCurFrame() const {
	assert(_video);
	return _video->getCurFrame();
}

uint32 VideoEntry::getFrameCount() const {
	assert(_video);
	return _video->getFrameCount();
}

uint32 VideoEntry::getTime() const {
	assert(_video);
	return _video->getTime();
}

Audio::Timestamp VideoEntry::getDuration() const {
	assert(_video);
	return _video->getDuration();
}

Common::Rational VideoEntry::getRate() const {
	assert(_video);
	return _video->getRate();
}

void VideoEntry::center() {
	assert(_video);
	_x = (g_system->getWidth() - _video->getWidth()) / 2;
	_y = (g_system->getHeight() - _video->getHeight()) / 2;
}

void VideoEntry::setBounds(const Audio::Timestamp &startTime, const Audio::Timestamp &endTime) {
	assert(_video);
	_start = startTime;
	_video->setEndTime(endTime);
	_video->seek(startTime);
}

void VideoEntry::seek(const Audio::Timestamp &time) {
	assert(_video);
	_video->seek(time);
}

void VideoEntry::setRate(const Common::Rational &rate) {
	assert(_video);
	_video->setRate(rate);
}

void VideoEntry::pause(bool isPaused) {
	assert(_video);
	_video->pauseVideo(isPaused);
}

void VideoEntry::start() {
	assert(_video);
	_video->start();
}

void VideoEntry::stop() {
	assert(_video);
	_video->stop();
}

bool VideoEntry::isPlaying() const {
	assert(_video);
	return _video->isPlaying();
}

int VideoEntry::getVolume() const {
	assert(_video);
	return _video->getVolume();
}

void VideoEntry::setVolume(int volume) {
	assert(_video);
	_video->setVolume(CLIP(volume, 0, 255));
}

VideoHandle::VideoHandle(VideoEntryPtr ptr) : _ptr(ptr) {
}

VideoHandle::VideoHandle(const VideoHandle &handle) : _ptr(handle._ptr) {
}

VideoManager::VideoManager(MohawkEngine* vm) : _vm(vm) {
	// Set dithering enabled, if required
	_enableDither = (_vm->getGameType() == GType_MYST || _vm->getGameType() == GType_MAKINGOF) && !(_vm->getFeatures() & GF_ME);
}

VideoManager::~VideoManager() {
	stopVideos();
}

void VideoManager::pauseVideos() {
	for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
		(*it)->pause(true);
}

void VideoManager::resumeVideos() {
	for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
		(*it)->pause(false);
}

void VideoManager::stopVideos() {
	for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
		(*it)->close();

	_videos.clear();
}

void VideoManager::playMovieBlocking(const Common::String &fileName, uint16 x, uint16 y, bool clearScreen) {
	VideoEntryPtr ptr = open(fileName);
	if (!ptr)
		return;

	ptr->moveTo(x, y);

	// Clear screen if requested
	if (clearScreen) {
		_vm->_system->fillScreen(_vm->_system->getScreenFormat().RGBToColor(0, 0, 0));
		_vm->_system->updateScreen();
	}

	ptr->start();
	waitUntilMovieEnds(VideoHandle(ptr));
}

void VideoManager::playMovieBlockingCentered(const Common::String &fileName, bool clearScreen) {
	VideoEntryPtr ptr = open(fileName);
	if (!ptr)
		return;

	// Clear screen if requested
	if (clearScreen) {
		_vm->_system->fillScreen(_vm->_system->getScreenFormat().RGBToColor(0, 0, 0));
		_vm->_system->updateScreen();
	}

	ptr->center();
	ptr->start();
	waitUntilMovieEnds(VideoHandle(ptr));
}

void VideoManager::waitUntilMovieEnds(VideoHandle videoHandle) {
	if (!videoHandle)
		return;

	// Sanity check
	if (videoHandle._ptr->isLooping())
		error("Called waitUntilMovieEnds() on a looping video");

	bool continuePlaying = true;

	while (!videoHandle->endOfVideo() && !_vm->shouldQuit() && continuePlaying) {
		if (updateMovies())
			_vm->_system->updateScreen();

		Common::Event event;
		while (_vm->_system->getEventManager()->pollEvent(event)) {
			switch (event.type) {
			case Common::EVENT_RTL:
			case Common::EVENT_QUIT:
				continuePlaying = false;
				break;
			case Common::EVENT_KEYDOWN:
				switch (event.kbd.keycode) {
				case Common::KEYCODE_SPACE:
					_vm->pauseGame();
					break;
				case Common::KEYCODE_ESCAPE:
					continuePlaying = false;
					_vm->doVideoTimer(videoHandle, true);
					break;
				default:
					break;
			}
			default:
				break;
			}
		}

		// Cut down on CPU usage
		_vm->_system->delayMillis(10);
	}

	// Ensure it's removed
	removeEntry(videoHandle._ptr);
}

void VideoManager::delayUntilMovieEnds(VideoHandle videoHandle) {
	// FIXME: Why is this separate from waitUntilMovieEnds?
	// It seems to only cut out the event loop (which is bad).

	if (!videoHandle)
		return;

	// Sanity check
	if (videoHandle._ptr->isLooping())
		error("Called delayUntilMovieEnds() on a looping video");

	while (!videoHandle->endOfVideo() && !_vm->shouldQuit()) {
		if (updateMovies())
			_vm->_system->updateScreen();

		// Cut down on CPU usage
		_vm->_system->delayMillis(10);
	}

	// Ensure it's removed
	removeEntry(videoHandle._ptr);
}

VideoHandle VideoManager::playMovie(const Common::String &fileName) {
	VideoEntryPtr ptr = open(fileName);
	if (!ptr)
		return VideoHandle();

	ptr->start();
	return VideoHandle(ptr);
}

VideoHandle VideoManager::playMovie(uint16 id) {
	VideoEntryPtr ptr = open(id);
	if (!ptr)
		return VideoHandle();

	ptr->start();
	return VideoHandle(ptr);
}

bool VideoManager::updateMovies() {
	bool updateScreen = false;

	for (VideoList::iterator it = _videos.begin(); it != _videos.end(); ) {
		// Check of the video has reached the end
		if ((*it)->endOfVideo()) {
			if ((*it)->isLooping()) {
				// Seek back if looping
				(*it)->seek((*it)->getStart());
			} else {
				// Done; close and continue on
				(*it)->close();
				it = _videos.erase(it);
				continue;
			}
		}

		Video::VideoDecoder *video = (*it)->_video;

		// Ignore paused videos
		if (video->isPaused()) {
			it++;
			continue;
		}

		// Check if we need to draw a frame
		if (video->needsUpdate()) {
			if (drawNextFrame(*it)) {
				updateScreen = true;
			}
		}

		// Check the video time
		_vm->doVideoTimer(VideoHandle(*it), false);

		// Remember to increase the iterator
		it++;
	}

	// Return true if we need to update the screen
	return updateScreen;
}

bool VideoManager::drawNextFrame(VideoEntryPtr videoEntry) {
	Video::VideoDecoder *video = videoEntry->_video;
	const Graphics::Surface *frame = video->decodeNextFrame();

	if (!frame || !videoEntry->isEnabled()) {
		return false;
	}

	Graphics::Surface *convertedFrame = 0;
	Graphics::PixelFormat pixelFormat = _vm->_system->getScreenFormat();

	if (frame->format != pixelFormat) {
		// We don't support downconverting to 8bpp without having
		// support in the codec. Set _enableDither if shows up.
		if (pixelFormat.bytesPerPixel == 1) {
			warning("Cannot convert high color video frame to 8bpp");
			return false;
		}

		// Convert to the current screen format
		convertedFrame = frame->convertTo(pixelFormat, video->getPalette());
		frame = convertedFrame;
	} else if (pixelFormat.bytesPerPixel == 1 && video->hasDirtyPalette()) {
		// Set the palette when running in 8bpp mode only
		// Don't do this for Myst, which has its own per-stack handling
		if (_vm->getGameType() != GType_MYST)
			_vm->_system->getPaletteManager()->setPalette(video->getPalette(), 0, 256);
	}

	// Clip the video to make sure it stays on the screen (Myst does this a few times)
	Common::Rect targetRect = Common::Rect(video->getWidth(), video->getHeight());
	targetRect.translate(videoEntry->getX(), videoEntry->getY());

	Common::Rect frameRect = Common::Rect(video->getWidth(), video->getHeight());

	if (targetRect.left < 0) {
		frameRect.left -= targetRect.left;
		targetRect.left = 0;
	}

	if (targetRect.top < 0) {
		frameRect.top -= targetRect.top;
		targetRect.top = 0;
	}

	if (targetRect.right > _vm->_system->getWidth()) {
		frameRect.right -= targetRect.right - _vm->_system->getWidth();
		targetRect.right = _vm->_system->getWidth();
	}

	if (targetRect.bottom > _vm->_system->getHeight()) {
		frameRect.bottom -= targetRect.bottom - _vm->_system->getHeight();
		targetRect.bottom = _vm->_system->getHeight();
	}

	_vm->_system->copyRectToScreen(frame->getBasePtr(frameRect.left, frameRect.top), frame->pitch,
	                               targetRect.left, targetRect.top, targetRect.width(), targetRect.height());

	// Delete 8bpp conversion surface
	if (convertedFrame) {
		convertedFrame->free();
		delete convertedFrame;
	}

	// We've drawn something to the screen, make sure we update it
	return true;
}

void VideoManager::activateMLST(uint16 mlstId, uint16 card) {
	Common::SeekableReadStream *mlstStream = _vm->getResource(ID_MLST, card);
	uint16 recordCount = mlstStream->readUint16BE();

	for (uint16 i = 0; i < recordCount; i++) {
		MLSTRecord mlstRecord;
		mlstRecord.index = mlstStream->readUint16BE();
		mlstRecord.movieID = mlstStream->readUint16BE();
		mlstRecord.code = mlstStream->readUint16BE();
		mlstRecord.left = mlstStream->readUint16BE();
		mlstRecord.top = mlstStream->readUint16BE();

		for (byte j = 0; j < 2; j++)
			if (mlstStream->readUint16BE() != 0)
				warning("u0[%d] in MLST non-zero", j);

		if (mlstStream->readUint16BE() != 0xFFFF)
			warning("u0[2] in MLST not 0xFFFF");

		mlstRecord.loop = mlstStream->readUint16BE();
		mlstRecord.volume = mlstStream->readUint16BE();
		mlstRecord.u1 = mlstStream->readUint16BE();

		if (mlstRecord.u1 != 1)
			warning("mlstRecord.u1 not 1");

		// We've found a match, add it
		if (mlstRecord.index == mlstId) {
			// Make sure we don't have any duplicates
			for (uint32 j = 0; j < _mlstRecords.size(); j++)
				if (_mlstRecords[j].index == mlstRecord.index || _mlstRecords[j].code == mlstRecord.code) {
					_mlstRecords.remove_at(j);
					j--;
				}

			_mlstRecords.push_back(mlstRecord);
			break;
		}
	}

	delete mlstStream;
}

void VideoManager::clearMLST() {
	_mlstRecords.clear();
}

VideoHandle VideoManager::playMovieRiven(uint16 id) {
	for (uint16 i = 0; i < _mlstRecords.size(); i++) {
		if (_mlstRecords[i].code == id) {
			debug(1, "Play tMOV %d (non-blocking) at (%d, %d) %s, Volume = %d", _mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, _mlstRecords[i].loop != 0 ? "looping" : "non-looping", _mlstRecords[i].volume);

			VideoEntryPtr ptr = open(_mlstRecords[i].movieID);
			if (ptr) {
				ptr->moveTo(_mlstRecords[i].left, _mlstRecords[i].top);
				ptr->setLooping(_mlstRecords[i].loop != 0);
				ptr->setVolume(_mlstRecords[i].volume);
				ptr->start();
			}

			return VideoHandle(ptr);
		}
	}

	return VideoHandle();
}

void VideoManager::playMovieBlockingRiven(uint16 id) {
	for (uint16 i = 0; i < _mlstRecords.size(); i++) {
		if (_mlstRecords[i].code == id) {
			debug(1, "Play tMOV %d (blocking) at (%d, %d), Volume = %d", _mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, _mlstRecords[i].volume);
			VideoEntryPtr ptr = open(_mlstRecords[i].movieID);
			ptr->moveTo(_mlstRecords[i].left, _mlstRecords[i].top);
			ptr->setVolume(_mlstRecords[i].volume);
			ptr->start();
			waitUntilMovieEnds(VideoHandle(ptr));
			return;
		}
	}
}

void VideoManager::stopMovieRiven(uint16 id) {
	debug(2, "Stopping movie %d", id);
	VideoHandle handle = findVideoHandleRiven(id);
	if (handle)
		removeEntry(handle._ptr);
}

void VideoManager::disableAllMovies() {
	debug(2, "Disabling all movies");
	for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
		(*it)->setEnabled(false);
}

VideoEntryPtr VideoManager::open(uint16 id) {
	// If this video is already playing, return that handle
	VideoHandle oldHandle = findVideoHandle(id);
	if (oldHandle._ptr)
		return oldHandle._ptr;

	// Otherwise, create a new entry
	Video::QuickTimeDecoder *video = new Video::QuickTimeDecoder();
	video->setChunkBeginOffset(_vm->getResourceOffset(ID_TMOV, id));
	video->loadStream(_vm->getResource(ID_TMOV, id));

	// Create the entry
	VideoEntryPtr entry(new VideoEntry(video, id));

	// Enable dither if necessary
	checkEnableDither(entry);

	// Add it to the video list
	_videos.push_back(entry);

	return entry;
}

VideoEntryPtr VideoManager::open(const Common::String &fileName) {
	// If this video is already playing, return that entry
	VideoHandle oldHandle = findVideoHandle(fileName);
	if (oldHandle._ptr)
		return oldHandle._ptr;

	// Otherwise, create a new entry
	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName);
	if (!stream)
		return VideoEntryPtr();

	Video::VideoDecoder *video = new Video::QuickTimeDecoder();
	if (!video->loadStream(stream)) {
		// FIXME: Better error handling
		delete video;
		return VideoEntryPtr();
	}

	// Create the entry
	VideoEntryPtr entry(new VideoEntry(video, fileName));

	// Enable dither if necessary
	checkEnableDither(entry);

	// Add it to the video list
	_videos.push_back(entry);

	return entry;
}

VideoHandle VideoManager::findVideoHandleRiven(uint16 id) {
	for (uint16 i = 0; i < _mlstRecords.size(); i++)
		if (_mlstRecords[i].code == id)
			for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
				if ((*it)->getID() == _mlstRecords[i].movieID)
					return VideoHandle(*it);

	return VideoHandle();
}

VideoHandle VideoManager::findVideoHandle(uint16 id) {
	if (id == 0)
		return VideoHandle();

	for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
		if ((*it)->getID() == id)
			return VideoHandle(*it);

	return VideoHandle();
}

VideoHandle VideoManager::findVideoHandle(const Common::String &fileName) {
	if (fileName.empty())
		return VideoHandle();

	for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
		if ((*it)->getFileName().equalsIgnoreCase(fileName))
			return VideoHandle(*it);

	return VideoHandle();
}

bool VideoManager::isVideoPlaying() {
	for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++)
		if (!(*it)->endOfVideo())
			return true;

	return false;
}

void VideoManager::drawVideoFrame(VideoHandle handle, const Audio::Timestamp &time) {
	assert(handle);
	handle->seek(time);
	drawNextFrame(handle._ptr);
	handle->stop();
}

VideoManager::VideoList::iterator VideoManager::findEntry(VideoEntryPtr ptr) {
	return Common::find(_videos.begin(), _videos.end(), ptr);
}

void VideoManager::removeEntry(VideoEntryPtr ptr) {
	VideoManager::VideoList::iterator it = findEntry(ptr);
	if (it != _videos.end())
		_videos.erase(it);
}

void VideoManager::checkEnableDither(VideoEntryPtr &entry) {
	// If we're not dithering, bail out
	if (!_enableDither)
		return;

	// Set the palette
	byte palette[256 * 3];
	g_system->getPaletteManager()->grabPalette(palette, 0, 256);
	entry->_video->setDitheringPalette(palette);

	if (entry->_video->getPixelFormat().bytesPerPixel != 1) {
		if (entry->getFileName().empty())
			error("Failed to set dither for video tMOV %d", entry->getID());
		else
			error("Failed to set dither for video %s", entry->getFileName().c_str());
	}
}

} // End of namespace Mohawk
back to top