From 9b3e26e7a29756848395d7a12928efdb60b8ff21 Mon Sep 17 00:00:00 2001 From: Eugene Sandulenko Date: Wed, 4 Aug 2010 14:53:07 +0000 Subject: SWORD25: Start for VideoDecoder-compatible Theora player. svn-id: r53206 --- engines/sword25/fmv/movieplayer.cpp | 43 ++- engines/sword25/fmv/movieplayer.h | 22 +- engines/sword25/fmv/movieplayer_script.cpp | 196 ++++++------- engines/sword25/fmv/theora_decoder.cpp | 434 +++++++++++++++++++++++++++++ engines/sword25/fmv/theora_decoder.h | 120 ++++++++ engines/sword25/module.mk | 9 +- 6 files changed, 700 insertions(+), 124 deletions(-) create mode 100644 engines/sword25/fmv/theora_decoder.cpp create mode 100755 engines/sword25/fmv/theora_decoder.h diff --git a/engines/sword25/fmv/movieplayer.cpp b/engines/sword25/fmv/movieplayer.cpp index bfba6d1b7a..a031fc8283 100644 --- a/engines/sword25/fmv/movieplayer.cpp +++ b/engines/sword25/fmv/movieplayer.cpp @@ -38,11 +38,52 @@ namespace Sword25 { #define BS_LOG_PREFIX "MOVIEPLAYER" -BS_MoviePlayer::BS_MoviePlayer(BS_Kernel * pKernel) : BS_Service(pKernel) { +BS_Service *BS_OggTheora_CreateObject(BS_Kernel *pKernel) { return new BS_MoviePlayer(pKernel); } + +BS_MoviePlayer::BS_MoviePlayer(BS_Kernel *pKernel) : BS_Service(pKernel) { if (!_RegisterScriptBindings()) BS_LOG_ERRORLN("Script bindings could not be registered."); else BS_LOGLN("Script bindings registered."); } +bool BS_MoviePlayer::LoadMovie(const Common::String &Filename, unsigned int Z) { + return true; +} + +bool BS_MoviePlayer::UnloadMovie() { + return true; +} + +bool BS_MoviePlayer::Play() { + return true; +} + +bool BS_MoviePlayer::Pause() { + return true; +} + +void BS_MoviePlayer::Update() { +} + +bool BS_MoviePlayer::IsMovieLoaded() { + return true; +} + +bool BS_MoviePlayer::IsPaused() { + return true; +} + +float BS_MoviePlayer::GetScaleFactor() { + return 1.0f; +} + +void BS_MoviePlayer::SetScaleFactor(float ScaleFactor) { +} + +double BS_MoviePlayer::GetTime() { + return 1.0; +} + + } // End of namespace Sword25 diff --git a/engines/sword25/fmv/movieplayer.h b/engines/sword25/fmv/movieplayer.h index 9988b5e182..be841c51a9 100644 --- a/engines/sword25/fmv/movieplayer.h +++ b/engines/sword25/fmv/movieplayer.h @@ -55,7 +55,7 @@ public: // ----------------------------------------------------------------------------- BS_MoviePlayer(BS_Kernel *pKernel); - virtual ~BS_MoviePlayer() {}; + ~BS_MoviePlayer() {}; // ----------------------------------------------------------------------------- // Abstract interface must be implemented by each Movie Player @@ -71,14 +71,14 @@ public: * @param Z Z indicates the position of the film on the main graphics layer * @return Returns false if an error occured while loading, otherwise true. */ - virtual bool LoadMovie(const Common::String &Filename, unsigned int Z) = 0; + bool LoadMovie(const Common::String &Filename, unsigned int Z); /** * Unloads the currently loaded movie file. * @return Returns false if an error occurred while unloading, otherwise true. * @remark This method can only be called when IsMovieLoaded() returns true. */ - virtual bool UnloadMovie() = 0; + bool UnloadMovie(); /** * Plays the loaded movie. @@ -88,7 +88,7 @@ public: * @return Returns false if an error occurred while starting, otherwise true. * @remark This method can only be called when IsMovieLoaded() returns true. */ - virtual bool Play() = 0; + bool Play(); /** * Pauses movie playback. @@ -97,23 +97,23 @@ public: * @return Returns false if an error occurred while pausing, otherwise true. * @remark This method can only be called when IsMovieLoaded() returns true. */ - virtual bool Pause() = 0; + bool Pause(); /** * This function must be called once per frame. */ - virtual void Update() = 0; + void Update(); /** * Returns whether a film is loaded for playback. */ - virtual bool IsMovieLoaded() = 0; + bool IsMovieLoaded(); /** * Returns whether the movie playback is paused. * @remark This method can only be called when IsMovieLoaded() returns true. */ - virtual bool IsPaused() = 0; + bool IsPaused(); /** * Returns the scaling factor for the loaded film. @@ -123,20 +123,20 @@ public: * @return Returns the scaling factor of the film. * @remark This method can only be called when IsMovieLoaded() returns true. */ - virtual float GetScaleFactor() = 0; + float GetScaleFactor(); /** * Sets the factor by which the loaded film is to be scaled. * @param ScaleFactor The desired scale factor. * @remark This method can only be called when IsMovieLoaded() returns true. */ - virtual void SetScaleFactor(float ScaleFactor) = 0; + void SetScaleFactor(float ScaleFactor); /** * Returns the current playing position in seconds. * @remark This method can only be called when IsMovieLoaded() returns true. */ - virtual double GetTime() = 0; + double GetTime(); private: bool _RegisterScriptBindings(); diff --git a/engines/sword25/fmv/movieplayer_script.cpp b/engines/sword25/fmv/movieplayer_script.cpp index f95dc90b1d..523cc2d4f3 100644 --- a/engines/sword25/fmv/movieplayer_script.cpp +++ b/engines/sword25/fmv/movieplayer_script.cpp @@ -23,7 +23,7 @@ * */ -/* +/* * This code is based on Broken Sword 2.5 engine * * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer @@ -45,150 +45,138 @@ namespace Sword25 { - int LoadMovie(lua_State * L) - { - BS_MoviePlayer * FMVPtr = BS_Kernel::GetInstance()->GetFMV(); - BS_ASSERT(FMVPtr); +int LoadMovie(lua_State *L) { + BS_MoviePlayer *FMVPtr = BS_Kernel::GetInstance()->GetFMV(); + BS_ASSERT(FMVPtr); - lua_pushbooleancpp(L, FMVPtr->LoadMovie(luaL_checkstring(L, 1), lua_gettop(L) == 2 ? static_cast(luaL_checknumber(L, 2)) : 10)); + lua_pushbooleancpp(L, FMVPtr->LoadMovie(luaL_checkstring(L, 1), lua_gettop(L) == 2 ? static_cast(luaL_checknumber(L, 2)) : 10)); - return 1; - } - - // ------------------------------------------------------------------------- - - int UnloadMovie(lua_State * L) - { - BS_MoviePlayer * FMVPtr = BS_Kernel::GetInstance()->GetFMV(); - BS_ASSERT(FMVPtr); - - lua_pushbooleancpp(L, FMVPtr->UnloadMovie()); + return 1; +} - return 1; - } +// ------------------------------------------------------------------------- - // ------------------------------------------------------------------------- +int UnloadMovie(lua_State *L) { + BS_MoviePlayer *FMVPtr = BS_Kernel::GetInstance()->GetFMV(); + BS_ASSERT(FMVPtr); - int Play(lua_State * L) - { - BS_MoviePlayer * FMVPtr = BS_Kernel::GetInstance()->GetFMV(); - BS_ASSERT(FMVPtr); + lua_pushbooleancpp(L, FMVPtr->UnloadMovie()); - lua_pushbooleancpp(L, FMVPtr->Play()); + return 1; +} - return 1; - } +// ------------------------------------------------------------------------- - // ------------------------------------------------------------------------- +int Play(lua_State *L) { + BS_MoviePlayer *FMVPtr = BS_Kernel::GetInstance()->GetFMV(); + BS_ASSERT(FMVPtr); - int Pause(lua_State * L) - { - BS_MoviePlayer * FMVPtr = BS_Kernel::GetInstance()->GetFMV(); - BS_ASSERT(FMVPtr); + lua_pushbooleancpp(L, FMVPtr->Play()); - lua_pushbooleancpp(L, FMVPtr->Pause()); + return 1; +} - return 1; - } +// ------------------------------------------------------------------------- - // ------------------------------------------------------------------------- +int Pause(lua_State *L) { + BS_MoviePlayer *FMVPtr = BS_Kernel::GetInstance()->GetFMV(); + BS_ASSERT(FMVPtr); - int Update(lua_State * L) - { - BS_MoviePlayer * FMVPtr = BS_Kernel::GetInstance()->GetFMV(); - BS_ASSERT(FMVPtr); + lua_pushbooleancpp(L, FMVPtr->Pause()); - FMVPtr->Update(); + return 1; +} - return 0; - } +// ------------------------------------------------------------------------- - // ------------------------------------------------------------------------- +int Update(lua_State *L) { + BS_MoviePlayer *FMVPtr = BS_Kernel::GetInstance()->GetFMV(); + BS_ASSERT(FMVPtr); - int IsMovieLoaded(lua_State * L) - { - BS_MoviePlayer * FMVPtr = BS_Kernel::GetInstance()->GetFMV(); - BS_ASSERT(FMVPtr); + FMVPtr->Update(); - lua_pushbooleancpp(L, FMVPtr->IsMovieLoaded()); + return 0; +} - return 1; - } +// ------------------------------------------------------------------------- - // ------------------------------------------------------------------------- +int IsMovieLoaded(lua_State *L) { + BS_MoviePlayer *FMVPtr = BS_Kernel::GetInstance()->GetFMV(); + BS_ASSERT(FMVPtr); - int IsPaused(lua_State * L) - { - BS_MoviePlayer * FMVPtr = BS_Kernel::GetInstance()->GetFMV(); - BS_ASSERT(FMVPtr); + lua_pushbooleancpp(L, FMVPtr->IsMovieLoaded()); - lua_pushbooleancpp(L, FMVPtr->IsPaused()); + return 1; +} - return 1; - } +// ------------------------------------------------------------------------- - // ------------------------------------------------------------------------- +int IsPaused(lua_State *L) { + BS_MoviePlayer *FMVPtr = BS_Kernel::GetInstance()->GetFMV(); + BS_ASSERT(FMVPtr); - int GetScaleFactor(lua_State * L) - { - BS_MoviePlayer * FMVPtr = BS_Kernel::GetInstance()->GetFMV(); - BS_ASSERT(FMVPtr); + lua_pushbooleancpp(L, FMVPtr->IsPaused()); - lua_pushnumber(L, FMVPtr->GetScaleFactor()); + return 1; +} - return 1; - } +// ------------------------------------------------------------------------- - // ------------------------------------------------------------------------- +int GetScaleFactor(lua_State *L) { + BS_MoviePlayer *FMVPtr = BS_Kernel::GetInstance()->GetFMV(); + BS_ASSERT(FMVPtr); - int SetScaleFactor(lua_State * L) - { - BS_MoviePlayer * FMVPtr = BS_Kernel::GetInstance()->GetFMV(); - BS_ASSERT(FMVPtr); + lua_pushnumber(L, FMVPtr->GetScaleFactor()); - FMVPtr->SetScaleFactor(static_cast(luaL_checknumber(L, 1))); + return 1; +} - return 0; - } +// ------------------------------------------------------------------------- - // ------------------------------------------------------------------------- +int SetScaleFactor(lua_State *L) { + BS_MoviePlayer *FMVPtr = BS_Kernel::GetInstance()->GetFMV(); + BS_ASSERT(FMVPtr); - int GetTime(lua_State * L) - { - BS_MoviePlayer * FMVPtr = BS_Kernel::GetInstance()->GetFMV(); - BS_ASSERT(FMVPtr); + FMVPtr->SetScaleFactor(static_cast(luaL_checknumber(L, 1))); - lua_pushnumber(L, FMVPtr->GetTime()); + return 0; +} - return 1; - } +// ------------------------------------------------------------------------- - // ------------------------------------------------------------------------- +int GetTime(lua_State *L) { + BS_MoviePlayer *FMVPtr = BS_Kernel::GetInstance()->GetFMV(); + BS_ASSERT(FMVPtr); - const char * LIBRARY_NAME = "Movieplayer"; + lua_pushnumber(L, FMVPtr->GetTime()); - const luaL_reg LIBRARY_FUNCTIONS[] = - { - { "LoadMovie", LoadMovie }, - { "UnloadMovie", UnloadMovie }, - { "Play", Play }, - { "Pause", Pause }, - { "Update", Update }, - { "IsMovieLoaded", IsMovieLoaded }, - { "IsPaused", IsPaused }, - { "GetScaleFactor", GetScaleFactor }, - { "SetScaleFactor", SetScaleFactor }, - { "GetTime", GetTime }, - { 0, 0 } - }; + return 1; +} -bool BS_MoviePlayer::_RegisterScriptBindings() -{ - BS_Kernel * pKernel = BS_Kernel::GetInstance(); +// ------------------------------------------------------------------------- + +const char *LIBRARY_NAME = "Movieplayer"; + +const luaL_reg LIBRARY_FUNCTIONS[] = { + { "LoadMovie", LoadMovie }, + { "UnloadMovie", UnloadMovie }, + { "Play", Play }, + { "Pause", Pause }, + { "Update", Update }, + { "IsMovieLoaded", IsMovieLoaded }, + { "IsPaused", IsPaused }, + { "GetScaleFactor", GetScaleFactor }, + { "SetScaleFactor", SetScaleFactor }, + { "GetTime", GetTime }, + { 0, 0 } +}; + +bool BS_MoviePlayer::_RegisterScriptBindings() { + BS_Kernel *pKernel = BS_Kernel::GetInstance(); BS_ASSERT(pKernel); - BS_ScriptEngine * pScript = static_cast(pKernel->GetService("script")); + BS_ScriptEngine *pScript = static_cast(pKernel->GetService("script")); BS_ASSERT(pScript); - lua_State * L = static_cast(pScript->GetScriptObject()); + lua_State *L = static_cast(pScript->GetScriptObject()); BS_ASSERT(L); if (!BS_LuaBindhelper::AddFunctionsToLib(L, LIBRARY_NAME, LIBRARY_FUNCTIONS)) return false; diff --git a/engines/sword25/fmv/theora_decoder.cpp b/engines/sword25/fmv/theora_decoder.cpp new file mode 100644 index 0000000000..0a4de4e649 --- /dev/null +++ b/engines/sword25/fmv/theora_decoder.cpp @@ -0,0 +1,434 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +/* + * Source is based on the player example from libvorbis package + * + * 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 "sword25/fmv/theora_decoder.h" +#include "common/system.h" + +namespace Sword25 { + +TheoraDecoder::TheoraDecoder() { + _fileStream = 0; + _surface = 0; + + _theoraPacket = 0; + _vorbisPacket = 0; + _stateFlag = 0; +} + +TheoraDecoder::~TheoraDecoder() { + close(); +} + +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::load(Common::SeekableReadStream &stream) { + close(); + + _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 + while (!_stateFlag) { + 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); + _stateFlag = 1; + 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); + + 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); + + 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); + } else { + // tear down the partial vorbis setup + vorbis_info_clear(&_vorbisInfo); + vorbis_comment_clear(&_vorbisComment); + } + + // open audio + if (_vorbisPacket) + open_audio(); + + 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); + } + if (_theoraPacket) { + ogg_stream_clear(&_theoraOut); + th_decode_free(_theoraDecode); + th_comment_clear(&_theoraComment); + th_info_clear(&_theoraInfo); + } + ogg_sync_clear(&_oggSync); + + if (!_fileStream) + return; + + delete _fileStream; + _fileStream = 0; + + _surface->free(); + delete _surface; + _surface = 0; + + reset(); +} + +Graphics::Surface *TheoraDecoder::decodeNextFrame() { + int i, j; + + _stateFlag = 0; // playback has not begun + + // we want a video and audio frame ready to go at all times. If + // we have to buffer incoming, buffer the compressed data (ie, let + // ogg do the buffering) + while (_vorbisPacket && !_audiobufReady) { + int ret; + float **pcm; + + // if there's pending, decoded audio, grab it + if ((ret = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm)) > 0) { + int count = _audiobufFill / 2; + int maxsamples = (audiofd_fragsize - _audiobufFill) / 2 / _vorbisInfo.channels; + for (i = 0; i < ret && i < maxsamples; i++) + for (j = 0; j < _vorbisInfo.channels; j++) { + int val = CLIP(rint(pcm[j][i] * 32767.f), -32768, 32768); + _audiobuf[count++] = val; + } + + vorbis_synthesis_read(&_vorbisDSP, i); + _audiobufFill += i * _vorbisInfo.channels * 2; + + if (_audiobufFill == audiofd_fragsize) + _audiobufReady = 1; + + if (_vorbisDSP.granulepos >= 0) + _audiobufGranulePos = _vorbisDSP.granulepos - ret + i; + else + _audiobufGranulePos += i; + } 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 need more data; break out to suck in another page + break; + } + } + + while (_theoraPacket && !_videobufReady) { + // 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; + } + // HACK: This should be set after a seek or a gap, but we might not have + // a granulepos for the first packet (we only have them for the last + // packet on a page), so we just set it as often as we get it. + // To do this right, we should back-track from the last packet on the + // page and compute the correct granulepos for the first packet after + // a seek or a gap. + if (_oggPacket.granulepos >= 0) { + th_decode_ctl(_theoraDecode, TH_DECCTL_SET_GRANPOS, &_oggPacket.granulepos, sizeof(_oggPacket.granulepos)); + } + if (th_decode_packetin(_theoraDecode, &_oggPacket, &_videobufGranulePos) == 0) { + _videobufTime = th_granule_time(_theoraDecode, _videobufGranulePos); + _curFrame++; + + // is it already too old to be useful? This is only actually + // useful cosmetically after a SIGSTOP. Note that we have to + // decode the frame even if we don't show it (for now) due to + // keyframing. Soon enough libtheora will be able to deal + // with non-keyframe seeks. + + if (_videobufTime >= get_time()) + _videobufReady = 1; + else { + // If we are too slow, reduce the pp level. + _ppInc = _ppLevel > 0 ? -1 : 0; + dropped++; + } + } + } else + break; + } + + if (!_videobufReady && !_audiobufReady && _fileStream->eos()) + break; + + if (!_videobufReady || !_audiobufReady) { + // no data yet for somebody. Grab another page + bufferData(); + while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) { + queuePage(&_oggPage); + } + } + + // If playback has begun, top audio buffer off immediately. + if (_stateFlag) audio_write_nonblocking(); + + // are we at or past time for this video frame? + if (_stateFlag && _videobufReady && _videobufTime <= get_time()) { + video_write(); + _videobufReady = 0; + } + + if (_stateFlag && + (_audiobufReady || !_vorbisPacket) && + (_videobufReady || !_theoraPacket) && + !got_sigint) { + // we have an audio frame ready (which means the audio buffer is + // full), it's not time to play video, so wait until one of the + // audio buffer is ready or it's near time to play video + + // set up select wait on the audiobuffer and a timeout for video + struct timeval timeout; + fd_set writefs; + fd_set empty; + int n = 0; + + FD_ZERO(&writefs); + FD_ZERO(&empty); + if (audiofd >= 0) { + FD_SET(audiofd, &writefs); + n = audiofd + 1; + } + + if (_theoraPacket) { + double tdiff; + long milliseconds; + tdiff = _videobufTime - get_time(); + + // If we have lots of extra time, increase the post-processing level. + if (tdiff > _theoraInfo.fps_denominator * 0.25 / _theoraInfo.fps_numerator) { + _ppInc = _ppLevel < _ppLevelMax ? 1 : 0; + } else if (tdiff < _theoraInfo.fps_denominator * 0.05 / _theoraInfo.fps_numerator) { + _ppInc = _ppLevel > 0 ? -1 : 0; + } + milliseconds = tdiff * 1000 - 5; + if (milliseconds > 500) + milliseconds = 500; + if (milliseconds > 0) { + timeout.tv_sec = milliseconds / 1000; + timeout.tv_usec = (milliseconds % 1000) * 1000; + + n = select(n, &empty, &writefs, &empty, &timeout); + if (n) + audio_calibrate_timer(0); + } + } else { + select(n, &empty, &writefs, &empty, NULL); + } + } + + // if our buffers either don't exist or are ready to go, + // we can begin playback + if ((!_theoraPacket || _videobufReady) && + (!_vorbisPacket || _audiobufReady)) + _stateFlag = 1; + + // same if we've run out of input + if (_fileStream->eos()) + _stateFlag = 1; +} + +void TheoraDecoder::reset() { + VideoDecoder::reset(); + if (_fileStream) + _fileStream->seek(0); + + _videobufReady = 0; + _videobufGranulePos = -1; + _videobufTime = 0; + + _audiobufFill = 0; + _audiobufReady = 0; + _audiobufGranulePos = 0; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/fmv/theora_decoder.h b/engines/sword25/fmv/theora_decoder.h new file mode 100755 index 0000000000..cda1c8a6cb --- /dev/null +++ b/engines/sword25/fmv/theora_decoder.h @@ -0,0 +1,120 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SWORD25_THEORADECODER_H +#define SWORD25_THEORADECODER_H + +#include "graphics/video/video_decoder.h" + +#include +#include + +namespace Common { + class SeekableReadStream; +} + +namespace Sword25 { + +/** + * + * Decoder for Theora videos. + * Video decoder used in engines: + * - sword25 + */ +class TheoraDecoder : public Graphics::FixedRateVideoDecoder { +public: + TheoraDecoder(); + virtual ~TheoraDecoder(); + + /** + * Load a video file + * @param stream the stream to load + */ + bool load(Common::SeekableReadStream &stream); + void close(); + + /** + * 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 + */ + Graphics::Surface *decodeNextFrame(); + + bool isVideoLoaded() const { return _fileStream != 0; } + uint16 getWidth() const { return _surface->w; } + uint16 getHeight() const { return _surface->h; } + uint32 getFrameCount() const { return _frameCount; } + Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat(3, 8, 8, 8, 0, 0, 0, 0, 0); } + +protected: + Common::Rational getFrameRate() const { return _frameRate; } + +private: + void queuePage(ogg_page *page); + int bufferData(); + +private: + Common::SeekableReadStream *_fileStream; + Graphics::Surface *_surface; + Common::Rational _frameRate; + uint32 _frameCount; + + 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 _stateFlag; + + int _ppLevelMax; + int _ppLevel; + int _ppInc; + + // single frame video buffering + int _videobufReady; + ogg_int64_t _videobufGranulePos; + double _videobufTime; + + // single audio fragment audio buffering + int _audiobufFill; + int _audiobufReady; + ogg_int16_t *_audiobuf; + ogg_int64_t _audiobufGranulePos; // time position of last sample +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/module.mk b/engines/sword25/module.mk index e1320252b1..f61a86a262 100644 --- a/engines/sword25/module.mk +++ b/engines/sword25/module.mk @@ -5,14 +5,7 @@ MODULE_OBJS := \ sword25.o \ fmv/movieplayer.o \ fmv/movieplayer_script.o \ - fmv/oggtheora/audiobuffer.o \ - fmv/oggtheora/moviefile.o \ - fmv/oggtheora/oggstate.o \ - fmv/oggtheora/oggstreamstate.o \ - fmv/oggtheora/oggtheora.o \ - fmv/oggtheora/theorastate.o \ - fmv/oggtheora/vorbisstate.o \ - fmv/oggtheora/yuvtorgba.o \ + fmv/theora_decoder.o \ gfx/animation.o \ gfx/animationdescription.o \ gfx/animationresource.o \ -- cgit v1.2.3