diff options
author | Einar Johan Trøan Sømåen | 2012-06-02 18:38:20 +0200 |
---|---|---|
committer | Einar Johan Trøan Sømåen | 2012-06-02 18:46:17 +0200 |
commit | cafdcd1c8a68fb23dd6e2841b427fb69f29dd62c (patch) | |
tree | 8a065b7ab2f0dd630c9912ffd3ece6c5007cee67 /engines/wintermute/video | |
parent | 74ff79e90bb2862e8c84867ff50f56675c1b4c7e (diff) | |
download | scummvm-rg350-cafdcd1c8a68fb23dd6e2841b427fb69f29dd62c.tar.gz scummvm-rg350-cafdcd1c8a68fb23dd6e2841b427fb69f29dd62c.tar.bz2 scummvm-rg350-cafdcd1c8a68fb23dd6e2841b427fb69f29dd62c.zip |
WINTERMUTE: Add in more of the missing Video-pieces, also, copy over the TheoraPlayer from SWORD25
Diffstat (limited to 'engines/wintermute/video')
-rw-r--r-- | engines/wintermute/video/VidPlayer.cpp | 18 | ||||
-rw-r--r-- | engines/wintermute/video/VidPlayer.h | 16 | ||||
-rw-r--r-- | engines/wintermute/video/VidTheoraPlayer.cpp | 20 | ||||
-rw-r--r-- | engines/wintermute/video/VidTheoraPlayer.h | 19 | ||||
-rw-r--r-- | engines/wintermute/video/decoders/theora_decoder.cpp | 566 | ||||
-rw-r--r-- | engines/wintermute/video/decoders/theora_decoder.h | 144 |
6 files changed, 747 insertions, 36 deletions
diff --git a/engines/wintermute/video/VidPlayer.cpp b/engines/wintermute/video/VidPlayer.cpp index ab20135595..cd693f0b3f 100644 --- a/engines/wintermute/video/VidPlayer.cpp +++ b/engines/wintermute/video/VidPlayer.cpp @@ -86,12 +86,12 @@ HRESULT CVidPlayer::SetDefaults() { ////////////////////////////////////////////////////////////////////////// CVidPlayer::~CVidPlayer() { - Cleanup(); + cleanup(); } ////////////////////////////////////////////////////////////////////////// -HRESULT CVidPlayer::Cleanup() { +HRESULT CVidPlayer::cleanup() { #if 0 if (_sound) _sound->Stop(); if (_videoPGF) AVIStreamGetFrameClose(_videoPGF); @@ -124,7 +124,7 @@ HRESULT CVidPlayer::Cleanup() { ////////////////////////////////////////////////////////////////////////// -HRESULT CVidPlayer::Initialize(char *inFilename, char *SubtitleFile) { +HRESULT CVidPlayer::initialize(const char *inFilename, const char *SubtitleFile) { #if 0 Cleanup(); @@ -204,7 +204,7 @@ HRESULT CVidPlayer::Initialize(char *inFilename, char *SubtitleFile) { ////////////////////////////////////////////////////////////////////////// -HRESULT CVidPlayer::Update() { +HRESULT CVidPlayer::update() { #if 0 if (!m_Playing) return S_OK; @@ -274,7 +274,7 @@ HRESULT CVidPlayer::Update() { ////////////////////////////////////////////////////////////////////////// -HRESULT CVidPlayer::Display() { +HRESULT CVidPlayer::display() { #if 0 if (!m_Playing) return S_OK; @@ -298,7 +298,7 @@ HRESULT CVidPlayer::Display() { ////////////////////////////////////////////////////////////////////////// -HRESULT CVidPlayer::Play(TVideoPlayback Type, int X, int Y, bool FreezeMusic) { +HRESULT CVidPlayer::play(TVideoPlayback Type, int X, int Y, bool FreezeMusic) { #if 0 if (!_videoStream || !_vidRenderer) return E_FAIL; @@ -359,7 +359,7 @@ HRESULT CVidPlayer::Play(TVideoPlayback Type, int X, int Y, bool FreezeMusic) { ////////////////////////////////////////////////////////////////////////// -HRESULT CVidPlayer::Stop() { +HRESULT CVidPlayer::stop() { #if 0 if (!_playing) return S_OK; @@ -372,13 +372,13 @@ HRESULT CVidPlayer::Stop() { ////////////////////////////////////////////////////////////////////////// -bool CVidPlayer::IsPlaying() { +bool CVidPlayer::isPlaying() { return _playing; } ////////////////////////////////////////////////////////////////////////// -HRESULT CVidPlayer::LoadSubtitles(char *Filename, char *SubtitleFile) { +HRESULT CVidPlayer::loadSubtitles(const char *Filename, const char *SubtitleFile) { #if 0 if (!Filename) return S_OK; diff --git a/engines/wintermute/video/VidPlayer.h b/engines/wintermute/video/VidPlayer.h index a1abf2ea46..eca9a96782 100644 --- a/engines/wintermute/video/VidPlayer.h +++ b/engines/wintermute/video/VidPlayer.h @@ -46,12 +46,12 @@ class CVidPlayer : public CBBase { public: bool _showSubtitle; int _currentSubtitle; - HRESULT LoadSubtitles(char *Filename, char *SubtitleFile); + HRESULT loadSubtitles(const char *Filename, const char *SubtitleFile); bool _slowRendering; - bool IsPlaying(); + bool isPlaying(); char *_filename; - HRESULT Stop(); - HRESULT Play(TVideoPlayback Type = VID_PLAY_CENTER, int X = 0, int Y = 0, bool FreezeMusic = true); + HRESULT stop(); + HRESULT play(TVideoPlayback Type = VID_PLAY_CENTER, int X = 0, int Y = 0, bool FreezeMusic = true); uint32 _totalVideoTime; uint32 _startTime; //CVidRenderer *_vidRenderer; @@ -59,10 +59,10 @@ public: bool _soundAvailable; HRESULT SetDefaults(); bool _playing; - HRESULT Display(); - HRESULT Update(); - HRESULT Initialize(char *inFilename, char *SubtitleFile = NULL); - HRESULT Cleanup(); + HRESULT display(); + HRESULT update(); + HRESULT initialize(const char *inFilename, const char *SubtitleFile = NULL); + HRESULT cleanup(); CVidPlayer(CBGame *inGame); virtual ~CVidPlayer(); diff --git a/engines/wintermute/video/VidTheoraPlayer.cpp b/engines/wintermute/video/VidTheoraPlayer.cpp index 01e1728732..56bdcc8a8a 100644 --- a/engines/wintermute/video/VidTheoraPlayer.cpp +++ b/engines/wintermute/video/VidTheoraPlayer.cpp @@ -159,7 +159,7 @@ void CVidTheoraPlayer::Cleanup() { }*/ ////////////////////////////////////////////////////////////////////////// -HRESULT CVidTheoraPlayer::Initialize(char *Filename, char *SubtitleFile) { +HRESULT CVidTheoraPlayer::initialize(const char *Filename, const char *SubtitleFile) { #if 0 Cleanup(); @@ -328,7 +328,7 @@ HRESULT CVidTheoraPlayer::Initialize(char *Filename, char *SubtitleFile) { return Res; #endif - return 0; + return E_FAIL; } @@ -345,7 +345,7 @@ HRESULT CVidTheoraPlayer::ResetStream() { } ////////////////////////////////////////////////////////////////////////// -HRESULT CVidTheoraPlayer::Play(TVideoPlayback Type, int X, int Y, bool FreezeGame, bool FreezeMusic, bool Looping, uint32 StartTime, float ForceZoom, int Volume) { +HRESULT CVidTheoraPlayer::play(TVideoPlayback Type, int X, int Y, bool FreezeGame, bool FreezeMusic, bool Looping, uint32 StartTime, float ForceZoom, int Volume) { #if 0 if (ForceZoom < 0.0f) ForceZoom = 100.0f; if (Volume < 0) m_Volume = Game->m_SoundMgr->GetVolumePercent(SOUND_SFX); @@ -397,11 +397,11 @@ HRESULT CVidTheoraPlayer::Play(TVideoPlayback Type, int X, int Y, bool FreezeGam Update(); #endif - return S_OK; + return E_FAIL; } ////////////////////////////////////////////////////////////////////////// -HRESULT CVidTheoraPlayer::Stop() { +HRESULT CVidTheoraPlayer::stop() { #if 0 if (m_Sound) m_Sound->Stop(); m_State = THEORA_STATE_FINISHED; @@ -411,7 +411,7 @@ HRESULT CVidTheoraPlayer::Stop() { } ////////////////////////////////////////////////////////////////////////// -HRESULT CVidTheoraPlayer::Update() { +HRESULT CVidTheoraPlayer::update() { #if 0 m_CurrentTime = m_FreezeGame ? Game->m_LiveTimer : Game->m_Timer; @@ -637,7 +637,7 @@ HRESULT CVidTheoraPlayer::WriteVideo() { } ////////////////////////////////////////////////////////////////////////// -HRESULT CVidTheoraPlayer::Display(uint32 Alpha) { +HRESULT CVidTheoraPlayer::display(uint32 Alpha) { RECT rc; HRESULT Res; @@ -890,7 +890,7 @@ finish: } ////////////////////////////////////////////////////////////////////////// -HRESULT CVidTheoraPlayer::Pause() { +HRESULT CVidTheoraPlayer::pause() { #if 0 if (m_State == THEORA_STATE_PLAYING) { m_State = THEORA_STATE_PAUSED; @@ -902,7 +902,7 @@ HRESULT CVidTheoraPlayer::Pause() { } ////////////////////////////////////////////////////////////////////////// -HRESULT CVidTheoraPlayer::Resume() { +HRESULT CVidTheoraPlayer::resume() { #if 0 if (_state == THEORA_STATE_PAUSED) { _state = THEORA_STATE_PLAYING; @@ -945,7 +945,7 @@ HRESULT CVidTheoraPlayer::Resume() { } */ ////////////////////////////////////////////////////////////////////////// -HRESULT CVidTheoraPlayer::InitializeSimple() { +HRESULT CVidTheoraPlayer::initializeSimple() { #if 0 if (SUCCEEDED(Initialize(m_Filename))) { if (m_AlphaFilename) SetAlphaImage(m_AlphaFilename); diff --git a/engines/wintermute/video/VidTheoraPlayer.h b/engines/wintermute/video/VidTheoraPlayer.h index 9766988656..74a094bf5c 100644 --- a/engines/wintermute/video/VidTheoraPlayer.h +++ b/engines/wintermute/video/VidTheoraPlayer.h @@ -71,21 +71,22 @@ public: //CVidSubtitler *_subtitler; // control methods - HRESULT Initialize(char *Filename, char *SubtitleFile = NULL); - HRESULT InitializeSimple(); - HRESULT Update(); - HRESULT 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); - HRESULT Stop(); - HRESULT Display(uint32 Alpha = 0xFFFFFFFF); + HRESULT initialize(const char *Filename, const char *SubtitleFile = NULL); + HRESULT initializeSimple(); + HRESULT update(); + HRESULT 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); + HRESULT stop(); + HRESULT display(uint32 Alpha = 0xFFFFFFFF); //HRESULT RenderFrame(CBSurface *Texture, yuv_buffer *yuv); - HRESULT Pause(); - HRESULT Resume(); + HRESULT pause(); + HRESULT resume(); - bool IsPlaying() { + bool isPlaying() { return _state == THEORA_STATE_PLAYING; }; bool IsFinished() { + return true; // HACK return _state == THEORA_STATE_FINISHED; }; bool IsPaused() { diff --git a/engines/wintermute/video/decoders/theora_decoder.cpp b/engines/wintermute/video/decoders/theora_decoder.cpp new file mode 100644 index 0000000000..7c766fe3c6 --- /dev/null +++ b/engines/wintermute/video/decoders/theora_decoder.cpp @@ -0,0 +1,566 @@ +/* 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::NO, 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()); +} + +} // 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..34a7aa32a4 --- /dev/null +++ b/engines/wintermute/video/decoders/theora_decoder.h @@ -0,0 +1,144 @@ +/* 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 + */ +class TheoraDecoder : public Video::VideoDecoder { +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; + +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 |