diff --git a/CMakeLists.txt b/CMakeLists.txt index 766167672..208d348fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,8 +37,8 @@ option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest ang GMock frameworks" OFF) # Sound source selection -option(USE_FFMPEG "use ffmpeg for sound" OFF) -option(USE_AUDIERE "use audiere for sound" OFF) +option(USE_FFMPEG "use ffmpeg for sound" ON) +option(USE_AUDIERE "use audiere for sound" ON) option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) # OS X deployment @@ -137,30 +137,54 @@ set(OPENMW_LIBS ${OENGINE_ALL}) set(OPENMW_LIBS_HEADER) # Sound setup +set(GOT_SOUND_INPUT 0) set(SOUND_INPUT_INCLUDES "") set(SOUND_INPUT_LIBRARY "") set(SOUND_DEFINE "") if (USE_FFMPEG) - find_package(FFMPEG REQUIRED) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${FFMPEG_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${FFMPEG_LIBRARIES}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_FFMPEG) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) + find_package(FFmpeg) + if (FFMPEG_FOUND) + set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${FFMPEG_INCLUDE_DIRS}) + set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES}) + set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_FFMPEG) + set(GOT_SOUND_INPUT 1) + endif (FFMPEG_FOUND) endif (USE_FFMPEG) -if (USE_AUDIERE) - find_package(Audiere REQUIRED) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${AUDIERE_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${AUDIERE_LIBRARY}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_AUDIERE) -endif (USE_AUDIERE) - -if (USE_MPG123) +if (USE_AUDIERE AND NOT GOT_SOUND_INPUT) + find_package(Audiere) + if (AUDIERE_FOUND) + set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${AUDIERE_INCLUDE_DIR}) + set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${AUDIERE_LIBRARY}) + set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_AUDIERE) + set(GOT_SOUND_INPUT 1) + endif (AUDIERE_FOUND) +endif (USE_AUDIERE AND NOT GOT_SOUND_INPUT) + +if (USE_MPG123 AND NOT GOT_SOUND_INPUT) find_package(MPG123 REQUIRED) find_package(SNDFILE REQUIRED) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${MPG123_LIBRARY} ${SNDFILE_LIBRARY}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_MPG123) -endif (USE_MPG123) + if (MPG123_FOUND AND SNDFILE_FOUND) + set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR}) + set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${MPG123_LIBRARY} ${SNDFILE_LIBRARY}) + set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_MPG123) + set(GOT_SOUND_INPUT 1) + endif (MPG123_FOUND AND SNDFILE_FOUND) +endif (USE_MPG123 AND NOT GOT_SOUND_INPUT) + +if (NOT GOT_SOUND_INPUT) + message(WARNING "--------------------") + message(WARNING "Failed to find any sound input packages") + message(WARNING "--------------------") +endif (NOT GOT_SOUND_INPUT) + +if (NOT FFMPEG_FOUND) + message(WARNING "--------------------") + message(WARNING "FFmpeg not found, video playback will be disabled") + message(WARNING "--------------------") +endif (NOT FFMPEG_FOUND) + # Platform specific if (WIN32) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 56147b500..482007090 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -16,7 +16,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky player animation npcanimation creatureanimation actors objects renderinginterface localmap occlusionquery terrain terrainmaterial water shadows - compositors characterpreview externalrendering globalmap + compositors characterpreview externalrendering globalmap videoplayer ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 9aaa5af85..5a13a50ec 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -128,12 +128,6 @@ float MWBase::Environment::getFrameDuration() const void MWBase::Environment::cleanup() { - delete mInputManager; - mInputManager = 0; - - delete mSoundManager; - mSoundManager = 0; - delete mMechanicsManager; mMechanicsManager = 0; @@ -146,11 +140,17 @@ void MWBase::Environment::cleanup() delete mScriptManager; mScriptManager = 0; + delete mWorld; + mWorld = 0; + + delete mSoundManager; + mSoundManager = 0; + delete mWindowManager; mWindowManager = 0; - delete mWorld; - mWorld = 0; + delete mInputManager; + mInputManager = 0; } const MWBase::Environment& MWBase::Environment::get() diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 92c177ff3..0c706ab49 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -22,6 +22,8 @@ namespace MWWorld namespace MWSound { class Sound; + class Sound_Decoder; + typedef boost::shared_ptr DecoderPtr; } namespace MWBase @@ -32,7 +34,7 @@ namespace MWBase class SoundManager { public: - + /* These must all fit together */ enum PlayMode { Play_Normal = 0, /* tracked, non-looping, multi-instance, environment */ Play_Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ @@ -41,6 +43,13 @@ namespace MWBase * but do not keep it updated (the sound will not move with * the object and will not stop when the object is deleted. */ }; + enum PlayType { + Play_TypeSfx = 1<<3, /* Normal SFX sound */ + Play_TypeVoice = 1<<4, /* Voice sound */ + Play_TypeMusic = 1<<5, /* Music track */ + Play_TypeMovie = 1<<6, /* Movie audio track */ + Play_TypeMask = Play_TypeSfx|Play_TypeVoice|Play_TypeMusic|Play_TypeMovie + }; private: @@ -89,12 +98,15 @@ namespace MWBase virtual void stopSay(MWWorld::Ptr reference=MWWorld::Ptr()) = 0; ///< Stop an actor speaking + virtual SoundPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0; + ///< Play a 2D audio track, using a custom decoder + virtual SoundPtr playSound(const std::string& soundId, float volume, float pitch, - int mode=Play_Normal) = 0; + PlayMode mode=Play_Normal) = 0; ///< Play a sound, independently of 3D-position virtual SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId, - float volume, float pitch, int mode=Play_Normal) = 0; + float volume, float pitch, PlayMode mode=Play_Normal) = 0; ///< Play a sound from an object virtual void stopSound3D(MWWorld::Ptr reference, const std::string& soundId) = 0; @@ -112,15 +124,16 @@ namespace MWBase virtual bool getSoundPlaying(MWWorld::Ptr reference, const std::string& soundId) const = 0; ///< Is the given sound currently playing on the given object? + virtual void pauseSounds(int types=Play_TypeMask) = 0; + ///< Pauses all currently playing sounds, including music. + + virtual void resumeSounds(int types=Play_TypeMask) = 0; + ///< Resumes all previously paused sounds. + virtual void update(float duration) = 0; virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) = 0; }; - - inline int operator|(SoundManager::PlayMode a, SoundManager::PlayMode b) - { return static_cast (a) | static_cast (b); } - inline int operator&(SoundManager::PlayMode a, SoundManager::PlayMode b) - { return static_cast (a) & static_cast (b); } } #endif diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index cc625b306..25d999c93 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -306,6 +306,11 @@ namespace MWBase /// 1 - only waiting \n /// 2 - player is underwater \n /// 3 - enemies are nearby (not implemented) + + + /// \todo this does not belong here + virtual void playVideo(const std::string& name, bool allowSkipping) = 0; + virtual void stopVideo() = 0; }; } diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index d791a1c99..319537297 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -45,7 +45,9 @@ namespace MWGui GM_Loading, GM_LoadingWallpaper, - GM_QuickKeysMenu + GM_QuickKeysMenu, + + GM_Video }; // Windows shown in inventory mode diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 099dafa6b..8ec495550 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -230,6 +230,8 @@ WindowManager::~WindowManager() delete mSpellCreationDialog; delete mEnchantingDialog; delete mTrainingWindow; + delete mCountDialog; + delete mQuickKeysMenu; cleanupGarbage(); @@ -407,6 +409,10 @@ void WindowManager::updateVisible() case GM_Loading: MyGUI::PointerManager::getInstance().setVisible(false); break; + case GM_Video: + MyGUI::PointerManager::getInstance().setVisible(false); + mHud->setVisible(false); + break; default: // Unsupported mode, switch back to game break; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 995da8f34..1f270df8b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -515,6 +515,8 @@ namespace MWInput { if (mWindows.isGuiMode () && (mWindows.getMode () == MWGui::GM_MainMenu || mWindows.getMode () == MWGui::GM_Settings)) mWindows.popGuiMode(); + else if (mWindows.isGuiMode () && mWindows.getMode () == MWGui::GM_Video) + MWBase::Environment::get().getWorld ()->stopVideo (); else mWindows.pushGuiMode (MWGui::GM_MainMenu); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8cb8e9fa8..ae7d6612b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -37,6 +37,7 @@ #include "npcanimation.hpp" #include "externalrendering.hpp" #include "globalmap.hpp" +#include "videoplayer.hpp" using namespace MWRender; using namespace Ogre; @@ -160,6 +161,9 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); + mVideoPlayer = new VideoPlayer(mRendering.getScene ()); + mVideoPlayer->setResolution (Settings::Manager::getInt ("resolution x", "Video"), Settings::Manager::getInt ("resolution y", "Video")); + mSun = 0; mDebugging = new Debugging(mMwRoot, engine); @@ -181,7 +185,7 @@ RenderingManager::~RenderingManager () delete mOcclusionQuery; delete mCompositors; delete mWater; - + delete mVideoPlayer; delete mFactory; } @@ -332,6 +336,8 @@ void RenderingManager::update (float duration, bool paused) } mOcclusionQuery->update(duration); + mVideoPlayer->update (); + mRendering.update(duration); if(paused) @@ -838,6 +844,8 @@ void RenderingManager::windowResized(Ogre::RenderWindow* rw) mCompositors->recreate(); mWater->assignTextures(); + mVideoPlayer->setResolution (rw->getWidth(), rw->getHeight()); + const Settings::CategorySettingVector& changed = Settings::Manager::apply(); MWBase::Environment::get().getInputManager()->processChangedSettings(changed); //FIXME MWBase::Environment::get().getWindowManager()->processChangedSettings(changed); // FIXME @@ -921,4 +929,14 @@ void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rend rendering.setup (mRendering.getScene()); } +void RenderingManager::playVideo(const std::string& name, bool allowSkipping) +{ + mVideoPlayer->playVideo ("video/" + name, allowSkipping); +} + +void RenderingManager::stopVideo() +{ + mVideoPlayer->stopVideo (); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 86346d1d6..68f2d79c3 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -45,6 +45,7 @@ namespace MWRender class Compositors; class ExternalRendering; class GlobalMap; + class VideoPlayer; class RenderingManager: private RenderingInterface, public Ogre::WindowEventListener { @@ -195,6 +196,9 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList void setupExternalRendering (MWRender::ExternalRendering& rendering); + void playVideo(const std::string& name, bool allowSkipping); + void stopVideo(); + protected: virtual void windowResized(Ogre::RenderWindow* rw); virtual void windowClosed(Ogre::RenderWindow* rw); @@ -248,6 +252,8 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList MWRender::Shadows* mShadows; MWRender::Compositors* mCompositors; + + VideoPlayer* mVideoPlayer; }; } diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp new file mode 100644 index 000000000..baec2f740 --- /dev/null +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -0,0 +1,1151 @@ +#include "videoplayer.hpp" + +#define __STDC_CONSTANT_MACROS +#include + +#include +#include + +#include +#include + +#include + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwsound/sound_decoder.hpp" +#include "../mwsound/sound.hpp" + + +namespace MWRender +{ + +#ifdef OPENMW_USE_FFMPEG + +extern "C" +{ +#include +#include +#include +} + +#define MAX_AUDIOQ_SIZE (5 * 16 * 1024) +#define MAX_VIDEOQ_SIZE (5 * 256 * 1024) +#define AV_SYNC_THRESHOLD 0.01 +#define AUDIO_DIFF_AVG_NB 20 +#define VIDEO_PICTURE_QUEUE_SIZE 1 + +enum { + AV_SYNC_AUDIO_MASTER, + AV_SYNC_VIDEO_MASTER, + AV_SYNC_EXTERNAL_MASTER, + + AV_SYNC_DEFAULT = AV_SYNC_EXTERNAL_MASTER +}; + + +struct PacketQueue { + PacketQueue() + : first_pkt(NULL), last_pkt(NULL), flushing(false), nb_packets(0), size(0) + { } + ~PacketQueue() + { clear(); } + + AVPacketList *first_pkt, *last_pkt; + volatile bool flushing; + int nb_packets; + int size; + + boost::mutex mutex; + boost::condition_variable cond; + + void put(AVPacket *pkt); + int get(AVPacket *pkt, VideoState *is); + + void flush(); + void clear(); +}; + +struct VideoPicture { + VideoPicture() : pts(0.0) + { } + + std::vector data; + double pts; +}; + +struct VideoState { + VideoState() + : format_ctx(NULL), av_sync_type(AV_SYNC_DEFAULT) + , external_clock_base(0.0) + , audio_st(NULL) + , video_st(NULL), frame_last_pts(0.0), frame_last_delay(0.0), + video_clock(0.0), sws_context(NULL), rgbaFrame(NULL), pictq_size(0), + pictq_rindex(0), pictq_windex(0) + , refresh_rate_ms(10), refresh(false), quit(false), display_ready(false) + { + // Register all formats and codecs + av_register_all(); + } + + ~VideoState() + { deinit(); } + + void init(const std::string& resourceName); + void deinit(); + + int stream_open(int stream_index, AVFormatContext *pFormatCtx); + + bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height); + + static void video_thread_loop(VideoState *is); + static void decode_thread_loop(VideoState *is); + + void video_display(); + void video_refresh_timer(); + + int queue_picture(AVFrame *pFrame, double pts); + double synchronize_video(AVFrame *src_frame, double pts); + + static void video_refresh(VideoState *is); + + + double get_audio_clock() + { return this->AudioTrack->getTimeOffset(); } + + double get_video_clock() + { return this->frame_last_pts; } + + double get_external_clock() + { return ((uint64_t)av_gettime()-this->external_clock_base) / 1000000.0; } + + double get_master_clock() + { + if(this->av_sync_type == AV_SYNC_VIDEO_MASTER) + return this->get_video_clock(); + if(this->av_sync_type == AV_SYNC_AUDIO_MASTER) + return this->get_audio_clock(); + return this->get_external_clock(); + } + + + static int OgreResource_Read(void *user_data, uint8_t *buf, int buf_size); + static int OgreResource_Write(void *user_data, uint8_t *buf, int buf_size); + static int64_t OgreResource_Seek(void *user_data, int64_t offset, int whence); + + + Ogre::DataStreamPtr stream; + AVFormatContext* format_ctx; + + int av_sync_type; + uint64_t external_clock_base; + + AVStream** audio_st; + PacketQueue audioq; + MWBase::SoundPtr AudioTrack; + + AVStream** video_st; + double frame_last_pts; + double frame_last_delay; + double video_clock; ///pkt = *pkt; + pkt1->next = NULL; + + if(pkt1->pkt.destruct == NULL) + { + if(av_dup_packet(&pkt1->pkt) < 0) + { + av_free(pkt1); + throw std::runtime_error("Failed to duplicate packet"); + } + av_free_packet(pkt); + } + + this->mutex.lock (); + + if(!last_pkt) + this->first_pkt = pkt1; + else + this->last_pkt->next = pkt1; + this->last_pkt = pkt1; + this->nb_packets++; + this->size += pkt1->pkt.size; + this->cond.notify_one(); + + this->mutex.unlock(); +} + +int PacketQueue::get(AVPacket *pkt, VideoState *is) +{ + boost::unique_lock lock(this->mutex); + while(!is->quit) + { + AVPacketList *pkt1 = this->first_pkt; + if(pkt1) + { + this->first_pkt = pkt1->next; + if(!this->first_pkt) + this->last_pkt = NULL; + this->nb_packets--; + this->size -= pkt1->pkt.size; + + *pkt = pkt1->pkt; + av_free(pkt1); + + return 1; + } + + if(this->flushing) + break; + this->cond.wait(lock); + } + + return -1; +} + +void PacketQueue::flush() +{ + this->flushing = true; + this->cond.notify_one(); +} + +void PacketQueue::clear() +{ + AVPacketList *pkt, *pkt1; + + this->mutex.lock(); + for(pkt = this->first_pkt; pkt != NULL; pkt = pkt1) + { + pkt1 = pkt->next; + av_free_packet(&pkt->pkt); + av_freep(&pkt); + } + this->last_pkt = NULL; + this->first_pkt = NULL; + this->nb_packets = 0; + this->size = 0; + this->mutex.unlock (); +} + + +class MovieAudioDecoder : public MWSound::Sound_Decoder +{ + static void fail(const std::string &str) + { + throw std::runtime_error(str); + } + + struct AutoAVPacket : public AVPacket { + AutoAVPacket(int size=0) + { + if(av_new_packet(this, size) < 0) + throw std::bad_alloc(); + } + ~AutoAVPacket() + { av_free_packet(this); } + }; + + VideoState *mVideoState; + AVStream *mAVStream; + + AutoAVPacket mPacket; + AVFrame *mFrame; + ssize_t mFramePos; + ssize_t mFrameSize; + + double mAudioClock; + + /* averaging filter for audio sync */ + double mAudioDiffAccum; + const double mAudioDiffAvgCoef; + const double mAudioDiffThreshold; + int mAudioDiffAvgCount; + + /* Add or subtract samples to get a better sync, return number of bytes to + * skip (negative means to duplicate). */ + int synchronize_audio() + { + if(mVideoState->av_sync_type == AV_SYNC_AUDIO_MASTER) + return 0; + + int sample_skip = 0; + + // accumulate the clock difference + double diff = mVideoState->get_master_clock() - mVideoState->get_audio_clock(); + mAudioDiffAccum = diff + mAudioDiffAvgCoef * mAudioDiffAccum; + if(mAudioDiffAvgCount < AUDIO_DIFF_AVG_NB) + mAudioDiffAvgCount++; + else + { + double avg_diff = mAudioDiffAccum * (1.0 - mAudioDiffAvgCoef); + if(fabs(avg_diff) >= mAudioDiffThreshold) + { + int n = av_get_bytes_per_sample(mAVStream->codec->sample_fmt) * + mAVStream->codec->channels; + sample_skip = ((int)(diff * mAVStream->codec->sample_rate) * n); + } + } + + return sample_skip; + } + + int audio_decode_frame(AVFrame *frame) + { + AVPacket *pkt = &mPacket; + + for(;;) + { + while(pkt->size > 0) + { + int len1, got_frame; + + len1 = avcodec_decode_audio4(mAVStream->codec, frame, &got_frame, pkt); + if(len1 < 0) break; + + if(len1 <= pkt->size) + { + /* Move the unread data to the front and clear the end bits */ + int remaining = pkt->size - len1; + memmove(pkt->data, &pkt->data[len1], remaining); + av_shrink_packet(pkt, remaining); + } + + /* No data yet? Look for more frames */ + if(!got_frame || frame->nb_samples <= 0) + continue; + + mAudioClock += (double)frame->nb_samples / + (double)mAVStream->codec->sample_rate; + + /* We have data, return it and come back for more later */ + return frame->nb_samples * mAVStream->codec->channels * + av_get_bytes_per_sample(mAVStream->codec->sample_fmt); + } + av_free_packet(pkt); + + /* next packet */ + if(mVideoState->audioq.get(pkt, mVideoState) < 0) + return -1; + + /* if update, update the audio clock w/pts */ + if((uint64_t)pkt->pts != AV_NOPTS_VALUE) + mAudioClock = av_q2d(mAVStream->time_base)*pkt->pts; + } + } + + void open(const std::string&) + { fail(std::string("Invalid call to ")+__PRETTY_FUNCTION__); } + + void close() { } + + std::string getName() + { return mVideoState->stream->getName(); } + + void rewind() { } + +public: + MovieAudioDecoder(VideoState *is) + : mVideoState(is) + , mAVStream(*is->audio_st) + , mFrame(avcodec_alloc_frame()) + , mFramePos(0) + , mFrameSize(0) + , mAudioClock(0.0) + , mAudioDiffAccum(0.0) + , mAudioDiffAvgCoef(exp(log(0.01 / AUDIO_DIFF_AVG_NB))) + /* Correct audio only if larger error than this */ + , mAudioDiffThreshold(2.0 * 0.050/* 50 ms */) + , mAudioDiffAvgCount(0) + { } + virtual ~MovieAudioDecoder() + { + av_freep(&mFrame); + } + + void getInfo(int *samplerate, MWSound::ChannelConfig *chans, MWSound::SampleType * type) + { + if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_U8) + *type = MWSound::SampleType_UInt8; + else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_S16) + *type = MWSound::SampleType_Int16; + else + fail(std::string("Unsupported sample format: ")+ + av_get_sample_fmt_name(mAVStream->codec->sample_fmt)); + + if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_MONO) + *chans = MWSound::ChannelConfig_Mono; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_STEREO) + *chans = MWSound::ChannelConfig_Stereo; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_QUAD) + *chans = MWSound::ChannelConfig_Quad; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_5POINT1) + *chans = MWSound::ChannelConfig_5point1; + else if(mAVStream->codec->channel_layout == AV_CH_LAYOUT_7POINT1) + *chans = MWSound::ChannelConfig_7point1; + else if(mAVStream->codec->channel_layout == 0) + { + /* Unknown channel layout. Try to guess. */ + if(mAVStream->codec->channels == 1) + *chans = MWSound::ChannelConfig_Mono; + else if(mAVStream->codec->channels == 2) + *chans = MWSound::ChannelConfig_Stereo; + else + { + std::stringstream sstr("Unsupported raw channel count: "); + sstr << mAVStream->codec->channels; + fail(sstr.str()); + } + } + else + { + char str[1024]; + av_get_channel_layout_string(str, sizeof(str), mAVStream->codec->channels, + mAVStream->codec->channel_layout); + fail(std::string("Unsupported channel layout: ")+str); + } + + *samplerate = mAVStream->codec->sample_rate; + } + + size_t read(char *stream, size_t len) + { + int sample_skip = synchronize_audio(); + size_t total = 0; + + while(total < len) + { + if(mFramePos >= mFrameSize) + { + /* We have already sent all our data; get more */ + mFrameSize = audio_decode_frame(mFrame); + if(mFrameSize < 0) + { + /* If error, we're done */ + break; + } + + mFramePos = std::min(mFrameSize, sample_skip); + sample_skip -= mFramePos; + continue; + } + + size_t len1 = len - total; + if(mFramePos >= 0) + { + len1 = std::min(len1, mFrameSize-mFramePos); + memcpy(stream, mFrame->data[0]+mFramePos, len1); + } + else + { + len1 = std::min(len1, -mFramePos); + + int n = av_get_bytes_per_sample(mAVStream->codec->sample_fmt) * + mAVStream->codec->channels; + + /* add samples by copying the first sample*/ + if(n == 1) + memset(stream, *mFrame->data[0], len1); + else if(n == 2) + { + const int16_t val = *((int16_t*)mFrame->data[0]); + for(size_t nb = 0;nb < len1;nb += n) + *((int16_t*)(stream+nb)) = val; + } + else if(n == 4) + { + const int32_t val = *((int32_t*)mFrame->data[0]); + for(size_t nb = 0;nb < len1;nb += n) + *((int32_t*)(stream+nb)) = val; + } + else if(n == 8) + { + const int64_t val = *((int64_t*)mFrame->data[0]); + for(size_t nb = 0;nb < len1;nb += n) + *((int64_t*)(stream+nb)) = val; + } + else + { + for(size_t nb = 0;nb < len1;nb += n) + memcpy(stream+nb, mFrame->data[0], n); + } + } + + total += len1; + stream += len1; + mFramePos += len1; + } + + return total; + } + + size_t getSampleOffset() + { + ssize_t clock_delay = (mFrameSize-mFramePos) / mAVStream->codec->channels / + av_get_bytes_per_sample(mAVStream->codec->sample_fmt); + return (size_t)(mAudioClock*mAVStream->codec->sample_rate) - clock_delay; + } +}; + + +int VideoState::OgreResource_Read(void *user_data, uint8_t *buf, int buf_size) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->stream; + return stream->read(buf, buf_size); +} + +int VideoState::OgreResource_Write(void *user_data, uint8_t *buf, int buf_size) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->stream; + return stream->write(buf, buf_size); +} + +int64_t VideoState::OgreResource_Seek(void *user_data, int64_t offset, int whence) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->stream; + + whence &= ~AVSEEK_FORCE; + if(whence == AVSEEK_SIZE) + return stream->size(); + if(whence == SEEK_SET) + stream->seek(offset); + else if(whence == SEEK_CUR) + stream->seek(stream->tell()+offset); + else if(whence == SEEK_END) + stream->seek(stream->size()+offset); + else + return -1; + + return stream->tell(); +} + + +void VideoState::video_refresh(VideoState* is) +{ + boost::system_time t = boost::get_system_time(); + while(!is->quit) + { + t += boost::posix_time::milliseconds(is->refresh_rate_ms); + boost::this_thread::sleep(t); + is->refresh = true; + } +} + + +void VideoState::video_display() +{ + VideoPicture *vp = &this->pictq[this->pictq_rindex]; + + if((*this->video_st)->codec->width != 0 && (*this->video_st)->codec->height != 0) + { + Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName("VideoTexture"); + if(texture.isNull() || static_cast(texture->getWidth()) != (*this->video_st)->codec->width + || static_cast(texture->getHeight()) != (*this->video_st)->codec->height) + { + Ogre::TextureManager::getSingleton ().remove ("VideoTexture"); + texture = Ogre::TextureManager::getSingleton().createManual( + "VideoTexture", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + (*this->video_st)->codec->width, (*this->video_st)->codec->height, + 0, + Ogre::PF_BYTE_RGBA, + Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); + } + Ogre::PixelBox pb((*this->video_st)->codec->width, (*this->video_st)->codec->height, 1, Ogre::PF_BYTE_RGBA, &vp->data[0]); + Ogre::HardwarePixelBufferSharedPtr buffer = texture->getBuffer(); + buffer->blitFromMemory(pb); + this->display_ready = true; + } +} + +void VideoState::video_refresh_timer() +{ + VideoPicture *vp; + double delay; + + if(this->pictq_size == 0) + return; + + vp = &this->pictq[this->pictq_rindex]; + + delay = vp->pts - this->frame_last_pts; /* the pts from last time */ + if(delay <= 0 || delay >= 1.0) { + /* if incorrect delay, use previous one */ + delay = this->frame_last_delay; + } + /* save for next time */ + this->frame_last_delay = delay; + this->frame_last_pts = vp->pts; + + /* FIXME: Syncing should be done in the decoding stage, where frames can be + * skipped or duplicated as needed. */ + /* update delay to sync to audio if not master source */ + if(this->av_sync_type != AV_SYNC_VIDEO_MASTER) + { + double diff = this->get_video_clock() - this->get_master_clock(); + + /* Skip or repeat the frame. Take delay into account + * FFPlay still doesn't "know if this is the best guess." */ + double sync_threshold = std::max(delay, AV_SYNC_THRESHOLD); + if(diff <= -sync_threshold) + delay = 0; + else if(diff >= sync_threshold) + delay = 2 * delay; + } + + this->refresh_rate_ms = std::max(1, (int)(delay*1000.0)); + /* show the picture! */ + this->video_display(); + + /* update queue for next picture! */ + this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE; + this->pictq_mutex.lock(); + this->pictq_size--; + this->pictq_cond.notify_one(); + this->pictq_mutex.unlock(); +} + + +int VideoState::queue_picture(AVFrame *pFrame, double pts) +{ + VideoPicture *vp; + + /* wait until we have a new pic */ + { + boost::unique_lock lock(this->pictq_mutex); + while(this->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE && !this->quit) + this->pictq_cond.timed_wait(lock, boost::posix_time::milliseconds(1)); + } + if(this->quit) + return -1; + + // windex is set to 0 initially + vp = &this->pictq[this->pictq_windex]; + + // Convert the image into RGBA format for Ogre + if(this->sws_context == NULL) + { + int w = (*this->video_st)->codec->width; + int h = (*this->video_st)->codec->height; + this->sws_context = sws_getContext(w, h, (*this->video_st)->codec->pix_fmt, + w, h, PIX_FMT_RGBA, SWS_BICUBIC, + NULL, NULL, NULL); + if(this->sws_context == NULL) + throw std::runtime_error("Cannot initialize the conversion context!\n"); + } + + vp->pts = pts; + vp->data.resize((*this->video_st)->codec->width * (*this->video_st)->codec->height * 4); + + uint8_t *dst = &vp->data[0]; + sws_scale(this->sws_context, pFrame->data, pFrame->linesize, + 0, (*this->video_st)->codec->height, &dst, this->rgbaFrame->linesize); + + // now we inform our display thread that we have a pic ready + this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_QUEUE_SIZE; + this->pictq_mutex.lock(); + this->pictq_size++; + this->pictq_mutex.unlock(); + + return 0; +} + +double VideoState::synchronize_video(AVFrame *src_frame, double pts) +{ + double frame_delay; + + /* if we have pts, set video clock to it */ + if(pts != 0) + this->video_clock = pts; + else + pts = this->video_clock; + + /* update the video clock */ + frame_delay = av_q2d((*this->video_st)->codec->time_base); + + /* if we are repeating a frame, adjust clock accordingly */ + frame_delay += src_frame->repeat_pict * (frame_delay * 0.5); + this->video_clock += frame_delay; + + return pts; +} + + +/* These are called whenever we allocate a frame + * buffer. We use this to store the global_pts in + * a frame at the time it is allocated. + */ +static uint64_t global_video_pkt_pts = AV_NOPTS_VALUE; +static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic) +{ + int ret = avcodec_default_get_buffer(c, pic); + uint64_t *pts = (uint64_t*)av_malloc(sizeof(uint64_t)); + *pts = global_video_pkt_pts; + pic->opaque = pts; + return ret; +} +static void our_release_buffer(struct AVCodecContext *c, AVFrame *pic) +{ + if(pic) av_freep(&pic->opaque); + avcodec_default_release_buffer(c, pic); +} + + +void VideoState::video_thread_loop(VideoState *self) +{ + AVPacket pkt1, *packet = &pkt1; + int frameFinished; + AVFrame *pFrame; + double pts; + + pFrame = avcodec_alloc_frame(); + + self->rgbaFrame = avcodec_alloc_frame(); + avpicture_alloc((AVPicture*)self->rgbaFrame, PIX_FMT_RGBA, (*self->video_st)->codec->width, (*self->video_st)->codec->height); + + while(self->videoq.get(packet, self) >= 0) + { + // Save global pts to be stored in pFrame + global_video_pkt_pts = packet->pts; + // Decode video frame + if(avcodec_decode_video2((*self->video_st)->codec, pFrame, &frameFinished, packet) < 0) + throw std::runtime_error("Error decoding video frame"); + + pts = 0; + if((uint64_t)packet->dts != AV_NOPTS_VALUE) + pts = packet->dts; + else if(pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE) + pts = *(uint64_t*)pFrame->opaque; + pts *= av_q2d((*self->video_st)->time_base); + + av_free_packet(packet); + + // Did we get a video frame? + if(frameFinished) + { + pts = self->synchronize_video(pFrame, pts); + if(self->queue_picture(pFrame, pts) < 0) + break; + } + } + + av_free(pFrame); + + avpicture_free((AVPicture*)self->rgbaFrame); + av_free(self->rgbaFrame); +} + +void VideoState::decode_thread_loop(VideoState *self) +{ + AVFormatContext *pFormatCtx = self->format_ctx; + AVPacket pkt1, *packet = &pkt1; + + try + { + if(!self->video_st && !self->audio_st) + throw std::runtime_error("No streams to decode"); + + // main decode loop + while(!self->quit) + { + if((self->audio_st >= 0 && self->audioq.size > MAX_AUDIOQ_SIZE) || + (self->video_st >= 0 && self->videoq.size > MAX_VIDEOQ_SIZE)) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + continue; + } + + if(av_read_frame(pFormatCtx, packet) < 0) + break; + + // Is this a packet from the video stream? + if(self->video_st && packet->stream_index == self->video_st-pFormatCtx->streams) + self->videoq.put(packet); + else if(self->audio_st && packet->stream_index == self->audio_st-pFormatCtx->streams) + self->audioq.put(packet); + else + av_free_packet(packet); + } + + /* all done - wait for it */ + self->videoq.flush(); + self->audioq.flush(); + while(!self->quit) + { + // EOF reached, all packets processed, we can exit now + if(self->audioq.nb_packets == 0 && self->videoq.nb_packets == 0) + break; + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + } + } + catch(std::runtime_error& e) { + std::cerr << "An error occured playing the video: " << e.what () << std::endl; + } + catch(Ogre::Exception& e) { + std::cerr << "An error occured playing the video: " << e.getFullDescription () << std::endl; + } + + self->quit = true; +} + + +bool VideoState::update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height) +{ + if(this->quit) + return false; + + if(this->refresh) + { + this->refresh = false; + this->video_refresh_timer(); + // Would be nice not to do this all the time... + if(this->display_ready) + mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("VideoTexture"); + + // Correct aspect ratio by adding black bars + double videoaspect = av_q2d((*this->video_st)->codec->sample_aspect_ratio); + if(videoaspect == 0.0) + videoaspect = 1.0; + videoaspect *= static_cast((*this->video_st)->codec->width) / (*this->video_st)->codec->height; + + double screenaspect = static_cast(screen_width) / screen_height; + double aspect_correction = videoaspect / screenaspect; + + rect->setCorners(std::max(-1.0, -1.0 * aspect_correction), std::min( 1.0, 1.0 / aspect_correction), + std::min( 1.0, 1.0 * aspect_correction), std::max(-1.0, -1.0 / aspect_correction)); + } + return true; +} + + +int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) +{ + MWSound::DecoderPtr decoder; + AVCodecContext *codecCtx; + AVCodec *codec; + + if(stream_index < 0 || stream_index >= static_cast(pFormatCtx->nb_streams)) + return -1; + + // Get a pointer to the codec context for the video stream + codecCtx = pFormatCtx->streams[stream_index]->codec; + codec = avcodec_find_decoder(codecCtx->codec_id); + if(!codec || (avcodec_open2(codecCtx, codec, NULL) < 0)) + { + fprintf(stderr, "Unsupported codec!\n"); + return -1; + } + + switch(codecCtx->codec_type) + { + case AVMEDIA_TYPE_AUDIO: + this->audio_st = pFormatCtx->streams + stream_index; + + decoder.reset(new MovieAudioDecoder(this)); + this->AudioTrack = MWBase::Environment::get().getSoundManager()->playTrack(decoder, MWBase::SoundManager::Play_TypeMovie); + if(!this->AudioTrack) + { + avcodec_close((*this->audio_st)->codec); + this->audio_st = NULL; + return -1; + } + break; + + case AVMEDIA_TYPE_VIDEO: + this->video_st = pFormatCtx->streams + stream_index; + + this->frame_last_delay = 40e-3; + + codecCtx->get_buffer = our_get_buffer; + codecCtx->release_buffer = our_release_buffer; + this->video_thread = boost::thread(video_thread_loop, this); + this->refresh_thread = boost::thread(video_refresh, this); + break; + + default: + break; + } + + return 0; +} + +void VideoState::init(const std::string& resourceName) +{ + int video_index = -1; + int audio_index = -1; + unsigned int i; + + this->av_sync_type = AV_SYNC_DEFAULT; + this->refresh_rate_ms = 10; + this->refresh = false; + this->quit = false; + + this->stream = Ogre::ResourceGroupManager::getSingleton().openResource(resourceName); + if(this->stream.isNull()) + throw std::runtime_error("Failed to open video resource"); + + AVIOContext *ioCtx = avio_alloc_context(NULL, 0, 0, this, OgreResource_Read, OgreResource_Write, OgreResource_Seek); + if(!ioCtx) throw std::runtime_error("Failed to allocate AVIOContext"); + + this->format_ctx = avformat_alloc_context(); + if(this->format_ctx) + this->format_ctx->pb = ioCtx; + + // Open video file + /// \todo leak here, ffmpeg or valgrind bug ? + if(!this->format_ctx || avformat_open_input(&this->format_ctx, resourceName.c_str(), NULL, NULL)) + { + // "Note that a user-supplied AVFormatContext will be freed on failure." + this->format_ctx = NULL; + av_free(ioCtx); + throw std::runtime_error("Failed to open video input"); + } + + // Retrieve stream information + if(avformat_find_stream_info(this->format_ctx, NULL) < 0) + throw std::runtime_error("Failed to retrieve stream information"); + + // Dump information about file onto standard error + av_dump_format(this->format_ctx, 0, resourceName.c_str(), 0); + + for(i = 0;i < this->format_ctx->nb_streams;i++) + { + if(this->format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0) + video_index = i; + if(this->format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0) + audio_index = i; + } + + this->external_clock_base = av_gettime(); + if(audio_index >= 0) + this->stream_open(audio_index, this->format_ctx); + if(video_index >= 0) + this->stream_open(video_index, this->format_ctx); + + this->parse_thread = boost::thread(decode_thread_loop, this); +} + +void VideoState::deinit() +{ + this->quit = true; + + this->audioq.cond.notify_one(); + this->videoq.cond.notify_one(); + + this->parse_thread.join(); + this->video_thread.join(); + this->refresh_thread.join(); + + if(this->audio_st) + avcodec_close((*this->audio_st)->codec); + this->audio_st = NULL; + if(this->video_st) + avcodec_close((*this->video_st)->codec); + this->video_st = NULL; + + if(this->sws_context) + sws_freeContext(this->sws_context); + this->sws_context = NULL; + + if(this->format_ctx) + { + AVIOContext *ioContext = this->format_ctx->pb; + avformat_close_input(&this->format_ctx); + av_free(ioContext); + } +} + +#else // defined OPENMW_USE_FFMPEG + +class VideoState +{ +public: + VideoState() { } + + void init(const std::string& resourceName) + { + throw std::runtime_error("FFmpeg not supported, cannot play \""+resourceName+"\""); + } + void deinit() { } + + void close() { } + + bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height) + { return false; } +}; + +#endif // defined OPENMW_USE_FFMPEG + + +VideoPlayer::VideoPlayer(Ogre::SceneManager* sceneMgr) + : mState(NULL) + , mSceneMgr(sceneMgr) + , mVideoMaterial(NULL) + , mRectangle(NULL) + , mNode(NULL) + , mAllowSkipping(false) +{ + mVideoMaterial = Ogre::MaterialManager::getSingleton().getByName("VideoMaterial", "General"); + if (mVideoMaterial.isNull ()) + { + mVideoMaterial = Ogre::MaterialManager::getSingleton().create("VideoMaterial", "General"); + mVideoMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); + mVideoMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); + mVideoMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); + mVideoMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(); + mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + } + mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("black.png"); + + Ogre::MaterialPtr blackMaterial = Ogre::MaterialManager::getSingleton().getByName("BlackBarsMaterial", "General"); + if (blackMaterial.isNull ()) + { + blackMaterial = Ogre::MaterialManager::getSingleton().create("BlackBarsMaterial", "General"); + blackMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); + blackMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); + blackMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); + blackMaterial->getTechnique(0)->getPass(0)->createTextureUnitState()->setTextureName("black.png"); + } + + mRectangle = new Ogre::Rectangle2D(true); + mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0); + mRectangle->setMaterial("VideoMaterial"); + mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY+2); + mBackgroundRectangle = new Ogre::Rectangle2D(true); + mBackgroundRectangle->setCorners(-1.0, 1.0, 1.0, -1.0); + mBackgroundRectangle->setMaterial("BlackBarsMaterial"); + mBackgroundRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY+1); + + // Use infinite AAB to always stay visible + Ogre::AxisAlignedBox aabInf; + aabInf.setInfinite(); + mRectangle->setBoundingBox(aabInf); + mBackgroundRectangle->setBoundingBox(aabInf); + + // Attach background to the scene + mNode = sceneMgr->getRootSceneNode()->createChildSceneNode(); + mNode->attachObject(mRectangle); + mBackgroundNode = sceneMgr->getRootSceneNode()->createChildSceneNode(); + mBackgroundNode->attachObject(mBackgroundRectangle); + + mRectangle->setVisible(false); + mRectangle->setVisibilityFlags(0x1); + mBackgroundRectangle->setVisible(false); + mBackgroundRectangle->setVisibilityFlags(0x1); +} + +VideoPlayer::~VideoPlayer() +{ + if(mState) + close(); + + mSceneMgr->destroySceneNode(mNode); + mSceneMgr->destroySceneNode(mBackgroundNode); + + delete mRectangle; + delete mBackgroundRectangle; +} + +void VideoPlayer::playVideo(const std::string &resourceName, bool allowSkipping) +{ + mAllowSkipping = allowSkipping; + + if(mState) + close(); + + mRectangle->setVisible(true); + mBackgroundRectangle->setVisible(true); + mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("black.png"); + + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Video); + + // Turn off rendering except the GUI + mSceneMgr->clearSpecialCaseRenderQueues(); + // SCRQM_INCLUDE with RENDER_QUEUE_OVERLAY does not work. + for(int i = 0;i < Ogre::RENDER_QUEUE_MAX;++i) + { + if(i > 0 && i < 96) + mSceneMgr->addSpecialCaseRenderQueue(i); + } + mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); + + MWBase::Environment::get().getSoundManager()->pauseSounds(); + + try { + mState = new VideoState; + mState->init(resourceName); + } + catch(std::exception& e) { + std::cerr<< "Failed to play video: "<update(mVideoMaterial, mRectangle, mWidth, mHeight)) + close(); + } +} + +void VideoPlayer::stopVideo () +{ + if (mAllowSkipping) + close(); +} + +void VideoPlayer::close() +{ + if(mState) + { + mState->deinit(); + + delete mState; + mState = NULL; + } + + MWBase::Environment::get().getSoundManager()->resumeSounds(); + + mRectangle->setVisible(false); + mBackgroundRectangle->setVisible(false); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Video); + + mSceneMgr->clearSpecialCaseRenderQueues(); + mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); +} + +bool VideoPlayer::isPlaying () +{ + return mState != NULL; +} + +} diff --git a/apps/openmw/mwrender/videoplayer.hpp b/apps/openmw/mwrender/videoplayer.hpp new file mode 100644 index 000000000..5e9d18ba4 --- /dev/null +++ b/apps/openmw/mwrender/videoplayer.hpp @@ -0,0 +1,52 @@ +#ifndef VIDEOPLAYER_H +#define VIDEOPLAYER_H + +#include + +namespace Ogre +{ + class SceneManager; + class SceneNode; + class Rectangle2D; +} + +namespace MWRender +{ + struct VideoState; + + class VideoPlayer + { + public: + VideoPlayer(Ogre::SceneManager* sceneMgr); + ~VideoPlayer(); + + void playVideo (const std::string& resourceName, bool allowSkipping); + + void update(); + + void close(); + void stopVideo(); + + bool isPlaying(); + + void setResolution (int w, int h) { mWidth = w; mHeight = h; } + + + private: + VideoState* mState; + + bool mAllowSkipping; + + Ogre::SceneManager* mSceneMgr; + Ogre::MaterialPtr mVideoMaterial; + Ogre::Rectangle2D* mRectangle; + Ogre::Rectangle2D* mBackgroundRectangle; + Ogre::SceneNode* mNode; + Ogre::SceneNode* mBackgroundNode; + + int mWidth; + int mHeight; + }; +} + +#endif diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 66be100e5..7c009f095 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -310,5 +310,5 @@ op 0x20001f3: AddSoulGem op 0x20001f4: AddSoulGem, explicit reference op 0x20001f5: RemoveSoulGem op 0x20001f6: RemoveSoulGem, explicit reference - -opcodes 0x20001f7-0x3ffffff unused +op 0x20001f7: PlayBink +opcodes 0x20001f8-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 9ca3503cf..a348a0a49 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -27,6 +27,22 @@ namespace MWScript { namespace Misc { + class OpPlayBink : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + std::string name = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + bool allowSkipping = runtime[0].mInteger; + runtime.pop(); + + MWBase::Environment::get().getWorld ()->playVideo (name, allowSkipping); + } + }; + class OpGetPcSleep : public Interpreter::Opcode0 { public: @@ -257,7 +273,7 @@ namespace MWScript static bool sActivate; public: - + virtual void execute(Interpreter::Runtime &runtime) { InterpreterContext& context = @@ -307,51 +323,51 @@ namespace MWScript MWMechanics::EffectKey(key)).mMagnitude > 0); } }; - + template class OpAddSoulGem : public Interpreter::Opcode0 - { + { public: virtual void execute (Interpreter::Runtime& runtime) - { + { MWWorld::Ptr ptr = R()(runtime); std::string creature = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - + std::string gem = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); store.get().find(creature); // This line throws an exception if it can't find the creature MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), gem); ref.getPtr().getRefData().setCount (1); - + ref.getPtr().getCellRef().mSoul = creature; MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr()); - } + } }; template class OpRemoveSoulGem : public Interpreter::Opcode0 - { + { public: virtual void execute (Interpreter::Runtime& runtime) - { - + { + MWWorld::Ptr ptr = R()(runtime); std::string soul = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); - + for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { @@ -364,7 +380,7 @@ namespace MWScript break; } } - } + } }; template @@ -490,6 +506,8 @@ namespace MWScript const int opcodeSetDeleteExplicit = 0x20001e6; const int opcodeGetSquareRoot = 0x20001e7; + const int opcodePlayBink = 0x20001f7; + void registerExtensions (Compiler::Extensions& extensions) { extensions.registerFunction ("xbox", 'l', "", opcodeXBox); @@ -515,6 +533,7 @@ namespace MWScript extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode); extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep); extensions.registerInstruction ("wakeuppc", "", opcodeWakeUpPc); + extensions.registerInstruction ("playbink", "Sl", opcodePlayBink); extensions.registerFunction ("getlocked", 'l', "", opcodeGetLocked, opcodeGetLockedExplicit); extensions.registerFunction ("geteffect", 'l', "l", opcodeGetEffect, opcodeGetEffectExplicit); extensions.registerInstruction ("addsoulgem", "cc", opcodeAddSoulGem, opcodeAddSoulGemExplicit); @@ -548,6 +567,7 @@ namespace MWScript interpreter.installSegment5 (opcodeToggleVanityMode, new OpToggleVanityMode); interpreter.installSegment5 (opcodeGetPcSleep, new OpGetPcSleep); interpreter.installSegment5 (opcodeWakeUpPc, new OpWakeUpPc); + interpreter.installSegment5 (opcodePlayBink, new OpPlayBink); interpreter.installSegment5 (opcodeGetLocked, new OpGetLocked); interpreter.installSegment5 (opcodeGetLockedExplicit, new OpGetLocked); interpreter.installSegment5 (opcodeGetEffect, new OpGetEffect); diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 35e66241e..408a6f29d 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -118,7 +118,8 @@ namespace MWScript std::string sound = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, 1.0, 1.0, mLoop ? MWBase::SoundManager::Play_Loop : 0); + MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, 1.0, 1.0, mLoop ? MWBase::SoundManager::Play_Loop : + MWBase::SoundManager::Play_Normal); } }; @@ -144,7 +145,8 @@ namespace MWScript Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, volume, pitch, mLoop ? MWBase::SoundManager::Play_Loop : 0); + MWBase::Environment::get().getSoundManager()->playSound3D (ptr, sound, volume, pitch, mLoop ? MWBase::SoundManager::Play_Loop : + MWBase::SoundManager::Play_Normal); } }; diff --git a/apps/openmw/mwsound/audiere_decoder.cpp b/apps/openmw/mwsound/audiere_decoder.cpp index 4e73573a7..788d5ae40 100644 --- a/apps/openmw/mwsound/audiere_decoder.cpp +++ b/apps/openmw/mwsound/audiere_decoder.cpp @@ -53,6 +53,9 @@ public: : mStream(stream), refs(1) { } virtual ~OgreFile() { } + + Ogre::String getName() + { return mStream->getName(); } }; @@ -60,7 +63,7 @@ void Audiere_Decoder::open(const std::string &fname) { close(); - audiere::FilePtr file(new OgreFile(mResourceMgr.openResource(fname))); + mSoundFile = audiere::FilePtr(new OgreFile(mResourceMgr.openResource(fname))); mSoundSource = audiere::OpenSampleSource(file); int channels, srate; @@ -86,9 +89,15 @@ void Audiere_Decoder::open(const std::string &fname) void Audiere_Decoder::close() { + mSoundFile = NULL; mSoundSource = NULL; } +std::string Audiere_Decoder::getName() +{ + return mSoundFile->getName(); +} + void Audiere_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { *samplerate = mSampleRate; @@ -108,6 +117,11 @@ void Audiere_Decoder::rewind() mSoundSource->reset(); } +size_t Audiere_Decoder::getSampleOffset() +{ + return 0; +} + Audiere_Decoder::Audiere_Decoder() { } diff --git a/apps/openmw/mwsound/audiere_decoder.hpp b/apps/openmw/mwsound/audiere_decoder.hpp index 0ad026d51..91c07ccac 100644 --- a/apps/openmw/mwsound/audiere_decoder.hpp +++ b/apps/openmw/mwsound/audiere_decoder.hpp @@ -12,6 +12,7 @@ namespace MWSound { class Audiere_Decoder : public Sound_Decoder { + audiere::FilePtr mSoundFile; audiere::SampleSourcePtr mSoundSource; int mSampleRate; SampleType mSampleType; @@ -20,10 +21,12 @@ namespace MWSound virtual void open(const std::string &fname); virtual void close(); + virtual std::string getName(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); virtual void rewind(); + virtual size_t getSampleOffset(); Audiere_Decoder& operator=(const Audiere_Decoder &rhs); Audiere_Decoder(const Audiere_Decoder &rhs); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 5f61ab8f0..261a86ca6 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -15,28 +15,6 @@ static void fail(const std::string &msg) { throw std::runtime_error("FFmpeg exception: "+msg); } -struct PacketList { - AVPacket pkt; - PacketList *next; -}; - -struct FFmpeg_Decoder::MyStream { - AVCodecContext *mCodecCtx; - int mStreamIdx; - - PacketList *mPackets; - - char *mDecodedData; - size_t mDecodedDataSize; - - FFmpeg_Decoder *mParent; - - void clearPackets(); - void *getAVAudioData(size_t *length); - size_t readAVAudioData(void *data, size_t length); -}; - - int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) { Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; @@ -72,166 +50,84 @@ int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence) /* Used by getAV*Data to search for more compressed data, and buffer it in the * correct stream. It won't buffer data for streams that the app doesn't have a * handle for. */ -bool FFmpeg_Decoder::getNextPacket(int streamidx) +bool FFmpeg_Decoder::getNextPacket() { - PacketList *packet; + if(!mStream) + return false; - packet = (PacketList*)av_malloc(sizeof(*packet)); - packet->next = NULL; - -next_packet: - while(av_read_frame(mFormatCtx, &packet->pkt) >= 0) + int stream_idx = mStream - mFormatCtx->streams; + while(av_read_frame(mFormatCtx, &mPacket) >= 0) { - std::vector::iterator iter = mStreams.begin(); - - /* Check each stream the user has a handle for, looking for the one - * this packet belongs to */ - while(iter != mStreams.end()) + /* Check if the packet belongs to this stream */ + if(stream_idx == mPacket.stream_index) { - if((*iter)->mStreamIdx == packet->pkt.stream_index) - { - PacketList **last; - - last = &(*iter)->mPackets; - while(*last != NULL) - last = &(*last)->next; - - *last = packet; - if((*iter)->mStreamIdx == streamidx) - return true; - - packet = (PacketList*)av_malloc(sizeof(*packet)); - packet->next = NULL; - goto next_packet; - } - iter++; + if((uint64_t)mPacket.pts != AV_NOPTS_VALUE) + mNextPts = av_q2d((*mStream)->time_base)*mPacket.pts; + return true; } + /* Free the packet and look for another */ - av_free_packet(&packet->pkt); + av_free_packet(&mPacket); } - av_free(packet); return false; } -void FFmpeg_Decoder::MyStream::clearPackets() +bool FFmpeg_Decoder::getAVAudioData() { - while(mPackets) - { - PacketList *self = mPackets; - mPackets = self->next; + int got_frame, len; - av_free_packet(&self->pkt); - av_free(self); - } -} - -void *FFmpeg_Decoder::MyStream::getAVAudioData(size_t *length) -{ - int size; - int len; - - if(length) *length = 0; - if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) - return NULL; - - mDecodedDataSize = 0; - -next_packet: - if(!mPackets && !mParent->getNextPacket(mStreamIdx)) - return NULL; - - /* Decode some data, and check for errors */ - size = AVCODEC_MAX_AUDIO_FRAME_SIZE; - while((len=avcodec_decode_audio3(mCodecCtx, (int16_t*)mDecodedData, &size, - &mPackets->pkt)) == 0) - { - PacketList *self; + if((*mStream)->codec->codec_type != AVMEDIA_TYPE_AUDIO) + return false; - if(size > 0) - break; + do { + if(mPacket.size == 0 && !getNextPacket()) + return false; - /* Packet went unread and no data was given? Drop it and try the next, - * I guess... */ - self = mPackets; - mPackets = self->next; + /* Decode some data, and check for errors */ + if((len=avcodec_decode_audio4((*mStream)->codec, mFrame, &got_frame, &mPacket)) < 0) + return false; - av_free_packet(&self->pkt); - av_free(self); - - if(!mPackets) - goto next_packet; - - size = AVCODEC_MAX_AUDIO_FRAME_SIZE; - } - - if(len < 0) - return NULL; - - if(len < mPackets->pkt.size) - { /* Move the unread data to the front and clear the end bits */ - int remaining = mPackets->pkt.size - len; - memmove(mPackets->pkt.data, &mPackets->pkt.data[len], remaining); - memset(&mPackets->pkt.data[remaining], 0, mPackets->pkt.size - remaining); - mPackets->pkt.size -= len; - } - else - { - PacketList *self; - - self = mPackets; - mPackets = self->next; - - av_free_packet(&self->pkt); - av_free(self); - } - - if(size == 0) - goto next_packet; - - /* Set the output buffer size */ - mDecodedDataSize = size; - if(length) *length = mDecodedDataSize; + int remaining = mPacket.size - len; + if(remaining <= 0) + av_free_packet(&mPacket); + else + { + memmove(mPacket.data, &mPacket.data[len], remaining); + av_shrink_packet(&mPacket, remaining); + } + } while(got_frame == 0 || mFrame->nb_samples == 0); + mNextPts += (double)mFrame->nb_samples / (double)(*mStream)->codec->sample_rate; - return mDecodedData; + return true; } -size_t FFmpeg_Decoder::MyStream::readAVAudioData(void *data, size_t length) +size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length) { size_t dec = 0; while(dec < length) { /* If there's no decoded data, find some */ - if(mDecodedDataSize == 0) + if(mFramePos >= mFrameSize) { - if(getAVAudioData(NULL) == NULL) + if(!getAVAudioData()) break; + mFramePos = 0; + mFrameSize = mFrame->nb_samples * (*mStream)->codec->channels * + av_get_bytes_per_sample((*mStream)->codec->sample_fmt); } - if(mDecodedDataSize > 0) - { - /* Get the amount of bytes remaining to be written, and clamp to - * the amount of decoded data we have */ - size_t rem = length-dec; - if(rem > mDecodedDataSize) - rem = mDecodedDataSize; - - /* Copy the data to the app's buffer and increment */ - if(data != NULL) - { - memcpy(data, mDecodedData, rem); - data = (char*)data + rem; - } - dec += rem; + /* Get the amount of bytes remaining to be written, and clamp to + * the amount of decoded data we have */ + size_t rem = std::min(length-dec, mFrameSize-mFramePos); - /* If there's any decoded data left, move it to the front of the - * buffer for next time */ - if(rem < mDecodedDataSize) - memmove(mDecodedData, &mDecodedData[rem], mDecodedDataSize - rem); - mDecodedDataSize -= rem; - } + /* Copy the data to the app's buffer and increment */ + memcpy(data, mFrame->data[0]+mFramePos, rem); + data = (char*)data + rem; + dec += rem; + mFramePos += rem; } /* Return the number of bytes we were able to get */ @@ -239,7 +135,6 @@ size_t FFmpeg_Decoder::MyStream::readAVAudioData(void *data, size_t length) } - void FFmpeg_Decoder::open(const std::string &fname) { close(); @@ -265,140 +160,155 @@ void FFmpeg_Decoder::open(const std::string &fname) { if(mFormatCtx->streams[j]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - std::auto_ptr stream(new MyStream); - stream->mCodecCtx = mFormatCtx->streams[j]->codec; - stream->mStreamIdx = j; - stream->mPackets = NULL; - - AVCodec *codec = avcodec_find_decoder(stream->mCodecCtx->codec_id); - if(!codec) - { - std::stringstream ss("No codec found for id "); - ss << stream->mCodecCtx->codec_id; - fail(ss.str()); - } - if(avcodec_open(stream->mCodecCtx, codec) < 0) - fail("Failed to open audio codec " + std::string(codec->long_name)); - - stream->mDecodedData = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE); - stream->mDecodedDataSize = 0; - - stream->mParent = this; - mStreams.push_back(stream.release()); + mStream = &mFormatCtx->streams[j]; break; } } - if(mStreams.empty()) + if(!mStream) fail("No audio streams in "+fname); + + AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id); + if(!codec) + { + std::stringstream ss("No codec found for id "); + ss << (*mStream)->codec->codec_id; + fail(ss.str()); + } + if(avcodec_open2((*mStream)->codec, codec, NULL) < 0) + fail("Failed to open audio codec " + std::string(codec->long_name)); + + mFrame = avcodec_alloc_frame(); } catch(std::exception &e) { - av_close_input_file(mFormatCtx); - mFormatCtx = NULL; + avformat_close_input(&mFormatCtx); throw; } } void FFmpeg_Decoder::close() { - while(!mStreams.empty()) - { - MyStream *stream = mStreams.front(); + if(mStream) + avcodec_close((*mStream)->codec); + mStream = NULL; - stream->clearPackets(); - avcodec_close(stream->mCodecCtx); - av_free(stream->mDecodedData); - delete stream; + av_free_packet(&mPacket); + av_freep(&mFrame); - mStreams.erase(mStreams.begin()); - } if(mFormatCtx) { AVIOContext* context = mFormatCtx->pb; + avformat_close_input(&mFormatCtx); av_free(context); - mFormatCtx->pb = NULL; - av_close_input_file(mFormatCtx); } - mFormatCtx = NULL; mDataStream.setNull(); } +std::string FFmpeg_Decoder::getName() +{ + return mFormatCtx->filename; +} + void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { - if(mStreams.empty()) + if(!mStream) fail("No audio stream info"); - MyStream *stream = mStreams[0]; - if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8) + if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8) *type = SampleType_UInt8; - else if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16) + else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16) *type = SampleType_Int16; else fail(std::string("Unsupported sample format: ")+ - av_get_sample_fmt_name(stream->mCodecCtx->sample_fmt)); + av_get_sample_fmt_name((*mStream)->codec->sample_fmt)); - if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) + if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_MONO) *chans = ChannelConfig_Mono; - else if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_STEREO) + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_STEREO) *chans = ChannelConfig_Stereo; - else if(stream->mCodecCtx->channel_layout == 0) + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_QUAD) + *chans = ChannelConfig_Quad; + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_5POINT1) + *chans = ChannelConfig_5point1; + else if((*mStream)->codec->channel_layout == AV_CH_LAYOUT_7POINT1) + *chans = ChannelConfig_7point1; + else if((*mStream)->codec->channel_layout == 0) { /* Unknown channel layout. Try to guess. */ - if(stream->mCodecCtx->channels == 1) + if((*mStream)->codec->channels == 1) *chans = ChannelConfig_Mono; - else if(stream->mCodecCtx->channels == 2) + else if((*mStream)->codec->channels == 2) *chans = ChannelConfig_Stereo; else { std::stringstream sstr("Unsupported raw channel count: "); - sstr << stream->mCodecCtx->channels; + sstr << (*mStream)->codec->channels; fail(sstr.str()); } } else { char str[1024]; - av_get_channel_layout_string(str, sizeof(str), stream->mCodecCtx->channels, - stream->mCodecCtx->channel_layout); + av_get_channel_layout_string(str, sizeof(str), (*mStream)->codec->channels, + (*mStream)->codec->channel_layout); fail(std::string("Unsupported channel layout: ")+str); } - *samplerate = stream->mCodecCtx->sample_rate; + *samplerate = (*mStream)->codec->sample_rate; } size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) { - if(mStreams.empty()) - fail("No audio streams"); - - return mStreams.front()->readAVAudioData(buffer, bytes); + if(!mStream) + fail("No audio stream"); + return readAVAudioData(buffer, bytes); } void FFmpeg_Decoder::readAll(std::vector &output) { - if(mStreams.empty()) - fail("No audio streams"); - MyStream *stream = mStreams.front(); - char *inbuf; - size_t got; + if(!mStream) + fail("No audio stream"); - while((inbuf=(char*)stream->getAVAudioData(&got)) != NULL && got > 0) + while(getAVAudioData()) + { + size_t got = mFrame->nb_samples * (*mStream)->codec->channels * + av_get_bytes_per_sample((*mStream)->codec->sample_fmt); + const char *inbuf = reinterpret_cast(mFrame->data[0]); output.insert(output.end(), inbuf, inbuf+got); + } } void FFmpeg_Decoder::rewind() { - av_seek_frame(mFormatCtx, -1, 0, 0); - std::for_each(mStreams.begin(), mStreams.end(), std::mem_fun(&MyStream::clearPackets)); + int stream_idx = mStream - mFormatCtx->streams; + if(av_seek_frame(mFormatCtx, stream_idx, 0, 0) < 0) + fail("Failed to seek in audio stream"); + av_free_packet(&mPacket); + mFrameSize = mFramePos = 0; + mNextPts = 0.0; } -FFmpeg_Decoder::FFmpeg_Decoder() : mFormatCtx(NULL) +size_t FFmpeg_Decoder::getSampleOffset() { - static bool done_init = false; + int delay = (mFrameSize-mFramePos) / (*mStream)->codec->channels / + av_get_bytes_per_sample((*mStream)->codec->sample_fmt); + return (int)(mNextPts*(*mStream)->codec->sample_rate) - delay; +} + +FFmpeg_Decoder::FFmpeg_Decoder() + : mFormatCtx(NULL) + , mStream(NULL) + , mFrame(NULL) + , mFrameSize(0) + , mFramePos(0) + , mNextPts(0.0) +{ + memset(&mPacket, 0, sizeof(mPacket)); /* We need to make sure ffmpeg is initialized. Optionally silence warning * output from the lib */ + static bool done_init = false; if(!done_init) { av_register_all(); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 7b028e1d0..32b2797ed 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -10,8 +10,8 @@ #include extern "C" { -#include -#include +#include +#include } #include "sound_decoder.hpp" @@ -22,25 +22,36 @@ namespace MWSound class FFmpeg_Decoder : public Sound_Decoder { AVFormatContext *mFormatCtx; + AVStream **mStream; - struct MyStream; - std::vector mStreams; + AVPacket mPacket; + AVFrame *mFrame; - bool getNextPacket(int streamidx); + int mFrameSize; + int mFramePos; + + double mNextPts; + + bool getNextPacket(); Ogre::DataStreamPtr mDataStream; static int readPacket(void *user_data, uint8_t *buf, int buf_size); static int writePacket(void *user_data, uint8_t *buf, int buf_size); static int64_t seek(void *user_data, int64_t offset, int whence); + bool getAVAudioData(); + size_t readAVAudioData(void *data, size_t length); + virtual void open(const std::string &fname); virtual void close(); + virtual std::string getName(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); virtual void readAll(std::vector &output); virtual void rewind(); + virtual size_t getSampleOffset(); FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); FFmpeg_Decoder(const FFmpeg_Decoder &rhs); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index 7f7a84889..fb187f844 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -155,6 +155,11 @@ void MpgSnd_Decoder::close() mDataStream.setNull(); } +std::string MpgSnd_Decoder::getName() +{ + return mDataStream->getName(); +} + void MpgSnd_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { if(!mSndFile && !mMpgFile) @@ -213,6 +218,11 @@ void MpgSnd_Decoder::rewind() } } +size_t MpgSnd_Decoder::getSampleOffset() +{ + return 0; +} + MpgSnd_Decoder::MpgSnd_Decoder() : mSndInfo() , mSndFile(NULL) diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp index 09082c2f4..be52f6f49 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -34,11 +34,13 @@ namespace MWSound virtual void open(const std::string &fname); virtual void close(); + virtual std::string getName(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); virtual void readAll(std::vector &output); virtual void rewind(); + virtual size_t getSampleOffset(); MpgSnd_Decoder& operator=(const MpgSnd_Decoder &rhs); MpgSnd_Decoder(const MpgSnd_Decoder &rhs); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index e5169d878..67008e2bc 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -61,10 +61,50 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) if(fmtlist[i].chans == chans && fmtlist[i].type == type) return fmtlist[i].format; } + + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + static const struct { + char name[32]; + ChannelConfig chans; + SampleType type; + } mcfmtlist[] = { + { "AL_FORMAT_QUAD16", ChannelConfig_Quad, SampleType_Int16 }, + { "AL_FORMAT_QUAD8", ChannelConfig_Quad, SampleType_UInt8 }, + { "AL_FORMAT_51CHN16", ChannelConfig_5point1, SampleType_Int16 }, + { "AL_FORMAT_51CHN8", ChannelConfig_5point1, SampleType_UInt8 }, + { "AL_FORMAT_71CHN16", ChannelConfig_7point1, SampleType_Int16 }, + { "AL_FORMAT_71CHN8", ChannelConfig_7point1, SampleType_UInt8 }, + }; + static const size_t mcfmtlistsize = sizeof(mcfmtlist)/sizeof(mcfmtlist[0]); + + for(size_t i = 0;i < mcfmtlistsize;i++) + { + if(mcfmtlist[i].chans == chans && mcfmtlist[i].type == type) + { + ALenum format = alGetEnumValue(mcfmtlist[i].name); + if(format != 0 && format != -1) + return format; + } + } + } + fail(std::string("Unsupported sound format (")+getChannelConfigName(chans)+", "+getSampleTypeName(type)+")"); return AL_NONE; } +static ALint getBufferSampleCount(ALuint buf) +{ + ALint size, bits, channels; + + alGetBufferi(buf, AL_SIZE, &size); + alGetBufferi(buf, AL_BITS, &bits); + alGetBufferi(buf, AL_CHANNELS, &channels); + throwALerror(); + + return size / channels * 8 / bits; +} + // // A streaming OpenAL sound. // @@ -82,19 +122,26 @@ class OpenAL_SoundStream : public Sound ALsizei mSampleRate; ALuint mBufferSize; + ALuint mSamplesQueued; + DecoderPtr mDecoder; volatile bool mIsFinished; + void updateAll(bool local); + OpenAL_SoundStream(const OpenAL_SoundStream &rhs); OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs); + friend class OpenAL_Output; + public: - OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder); + OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags); virtual ~OpenAL_SoundStream(); virtual void stop(); virtual bool isPlaying(); + virtual double getTimeOffset(); virtual void update(); void play(); @@ -109,7 +156,7 @@ const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; struct OpenAL_Output::StreamThread { typedef std::vector StreamVec; StreamVec mStreams; - boost::mutex mMutex; + boost::recursive_mutex mMutex; boost::thread mThread; StreamThread() @@ -170,8 +217,9 @@ private: }; -OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder) - : mOutput(output), mSource(src), mDecoder(decoder), mIsFinished(true) +OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags) + : Sound(Ogre::Vector3(0.0f), 1.0f, basevol, pitch, 1.0f, 1000.0f, flags) + , mOutput(output), mSource(src), mSamplesQueued(0), mDecoder(decoder), mIsFinished(true) { throwALerror(); @@ -189,6 +237,8 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode mBufferSize = static_cast(sBufferLength*srate); mBufferSize = framesToBytes(mBufferSize, chans, type); + + mOutput.mActiveSounds.push_back(this); } catch(std::exception &e) { @@ -209,22 +259,20 @@ OpenAL_SoundStream::~OpenAL_SoundStream() alGetError(); mDecoder->close(); + + mOutput.mActiveSounds.erase(std::find(mOutput.mActiveSounds.begin(), + mOutput.mActiveSounds.end(), this)); } void OpenAL_SoundStream::play() { - std::vector data(mBufferSize); - alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); throwALerror(); + mSamplesQueued = 0; for(ALuint i = 0;i < sNumBuffers;i++) - { - size_t got; - got = mDecoder->read(&data[0], data.size()); - alBufferData(mBuffers[i], mFormat, &data[0], got, mSampleRate); - } + alBufferData(mBuffers[i], mFormat, this, 0, mSampleRate); throwALerror(); alSourceQueueBuffers(mSource, sNumBuffers, mBuffers); @@ -243,6 +291,7 @@ void OpenAL_SoundStream::stop() alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); throwALerror(); + mSamplesQueued = 0; mDecoder->rewind(); } @@ -254,11 +303,49 @@ bool OpenAL_SoundStream::isPlaying() alGetSourcei(mSource, AL_SOURCE_STATE, &state); throwALerror(); - if(state == AL_PLAYING) + if(state == AL_PLAYING || state == AL_PAUSED) return true; return !mIsFinished; } +double OpenAL_SoundStream::getTimeOffset() +{ + ALint state = AL_STOPPED; + ALfloat offset = 0.0f; + double t; + + mOutput.mStreamThread->mMutex.lock(); + alGetSourcef(mSource, AL_SEC_OFFSET, &offset); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if(state == AL_PLAYING || state == AL_PAUSED) + t = (double)(mDecoder->getSampleOffset() - mSamplesQueued)/(double)mSampleRate + offset; + else + t = (double)mDecoder->getSampleOffset() / (double)mSampleRate; + mOutput.mStreamThread->mMutex.unlock(); + + throwALerror(); + return t; +} + +void OpenAL_SoundStream::updateAll(bool local) +{ + alSourcef(mSource, AL_REFERENCE_DISTANCE, mMinDistance); + alSourcef(mSource, AL_MAX_DISTANCE, mMaxDistance); + if(local) + { + alSourcef(mSource, AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(mSource, AL_SOURCE_RELATIVE, AL_TRUE); + } + else + { + alSourcef(mSource, AL_ROLLOFF_FACTOR, 1.0f); + alSourcei(mSource, AL_SOURCE_RELATIVE, AL_FALSE); + } + alSourcei(mSource, AL_LOOPING, AL_FALSE); + + update(); +} + void OpenAL_SoundStream::update() { ALfloat gain = mVolume*mBaseVolume; @@ -279,52 +366,58 @@ void OpenAL_SoundStream::update() bool OpenAL_SoundStream::process() { - bool finished = mIsFinished; - ALint processed, state; - - alGetSourcei(mSource, AL_SOURCE_STATE, &state); - alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); - throwALerror(); - - if(processed > 0) - { - std::vector data(mBufferSize); - do { - ALuint bufid; - size_t got; + try { + bool finished = mIsFinished; + ALint processed, state; - alSourceUnqueueBuffers(mSource, 1, &bufid); - processed--; - - if(finished) - continue; - - got = mDecoder->read(&data[0], data.size()); - finished = (got < data.size()); - if(got > 0) - { - alBufferData(bufid, mFormat, &data[0], got, mSampleRate); - alSourceQueueBuffers(mSource, 1, &bufid); - } - } while(processed > 0); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); throwALerror(); - } - if(state != AL_PLAYING && state != AL_PAUSED) - { - ALint queued; + if(processed > 0) + { + std::vector data(mBufferSize); + do { + ALuint bufid = 0; + size_t got; + + alSourceUnqueueBuffers(mSource, 1, &bufid); + mSamplesQueued -= getBufferSampleCount(bufid); + processed--; + + if(finished) + continue; + + got = mDecoder->read(&data[0], data.size()); + finished = (got < data.size()); + if(got > 0) + { + alBufferData(bufid, mFormat, &data[0], got, mSampleRate); + alSourceQueueBuffers(mSource, 1, &bufid); + mSamplesQueued += getBufferSampleCount(bufid); + } + } while(processed > 0); + throwALerror(); + } - alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); - throwALerror(); - if(queued > 0) + if(state != AL_PLAYING && state != AL_PAUSED) { - alSourcePlay(mSource); + ALint queued = 0; + + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + if(queued > 0) + alSourcePlay(mSource); throwALerror(); } - } - mIsFinished = finished; - return !finished; + mIsFinished = finished; + } + catch(std::exception &e) { + std::cout<< "Error updating stream \""<getName()<<"\"" <removeAll(); - while(!mFreeSources.empty()) - { - alDeleteSources(1, &mFreeSources.front()); - mFreeSources.pop_front(); - } + for(size_t i = 0;i < mFreeSources.size();i++) + alDeleteSources(1, &mFreeSources[i]); + mFreeSources.clear(); mBufferRefs.clear(); mUnusedBuffers.clear(); @@ -642,7 +773,7 @@ void OpenAL_Output::bufferFinished(ALuint buf) } -MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, int flags) +MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, float basevol, float pitch, int flags) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -655,7 +786,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf)); + sound.reset(new OpenAL_Sound(*this, src, buf, Ogre::Vector3(0.0f), vol, basevol, pitch, 1.0f, 1000.0f, flags)); } catch(std::exception &e) { @@ -666,25 +797,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume throw; } - alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f); - alSourcef(src, AL_MAX_DISTANCE, 1000.0f); - alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); - - if(!(flags&MWBase::SoundManager::Play_NoEnv) && mLastEnvironment == Env_Underwater) - { - volume *= 0.9f; - pitch *= 0.7f; - } - alSourcef(src, AL_GAIN, volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(src, AL_LOOPING, (flags&MWBase::SoundManager::Play_Loop) ? AL_TRUE : AL_FALSE); - throwALerror(); + sound->updateAll(true); alSourcei(src, AL_BUFFER, buf); alSourcePlay(src); @@ -693,8 +806,8 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume return sound; } -MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, - float min, float max, int flags) +MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float vol, float basevol, float pitch, + float min, float max, int flags) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -707,7 +820,7 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound3D(*this, src, buf)); + sound.reset(new OpenAL_Sound3D(*this, src, buf, pos, vol, basevol, pitch, min, max, flags)); } catch(std::exception &e) { @@ -718,26 +831,7 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre throw; } - alSource3f(src, AL_POSITION, pos.x, pos.z, -pos.y); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, min); - alSourcef(src, AL_MAX_DISTANCE, max); - alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); - - if(!(flags&MWBase::SoundManager::Play_NoEnv) && mLastEnvironment == Env_Underwater) - { - volume *= 0.9f; - pitch *= 0.7f; - } - alSourcef(src, AL_GAIN, (pos.squaredDistance(mPos) > max*max) ? - 0.0f : volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); - alSourcei(src, AL_LOOPING, (flags&MWBase::SoundManager::Play_Loop) ? AL_TRUE : AL_FALSE); - throwALerror(); + sound->updateAll(false); alSourcei(src, AL_BUFFER, buf); alSourcePlay(src); @@ -747,7 +841,7 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre } -MWBase::SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch, int flags) +MWBase::SoundPtr OpenAL_Output::streamSound(DecoderPtr decoder, float volume, float pitch, int flags) { boost::shared_ptr sound; ALuint src; @@ -757,13 +851,11 @@ MWBase::SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volu src = mFreeSources.front(); mFreeSources.pop_front(); + if((flags&MWBase::SoundManager::Play_Loop)) + std::cout <<"Warning: cannot loop stream \""<getName()<<"\""<< std::endl; try { - if((flags&MWBase::SoundManager::Play_Loop)) - std::cout <<"Warning: cannot loop stream "<open(fname); - sound.reset(new OpenAL_SoundStream(*this, src, decoder)); + sound.reset(new OpenAL_SoundStream(*this, src, decoder, volume, pitch, flags)); } catch(std::exception &e) { @@ -771,25 +863,7 @@ MWBase::SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volu throw; } - alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f); - alSourcef(src, AL_MAX_DISTANCE, 1000.0f); - alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); - - if(!(flags&MWBase::SoundManager::Play_NoEnv) && mLastEnvironment == Env_Underwater) - { - volume *= 0.9f; - pitch *= 0.7f; - } - alSourcef(src, AL_GAIN, volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(src, AL_LOOPING, AL_FALSE); - throwALerror(); + sound->updateAll(true); sound->play(); return sound; @@ -814,6 +888,61 @@ void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 } +void OpenAL_Output::pauseSounds(int types) +{ + std::vector sources; + SoundVec::const_iterator iter = mActiveSounds.begin(); + while(iter != mActiveSounds.end()) + { + const OpenAL_SoundStream *stream = dynamic_cast(*iter); + if(stream) + { + if(stream->mSource && (stream->getPlayType()&types)) + sources.push_back(stream->mSource); + } + else + { + const OpenAL_Sound *sound = dynamic_cast(*iter); + if(sound && sound->mSource && (sound->getPlayType()&types)) + sources.push_back(sound->mSource); + } + iter++; + } + if(sources.size() > 0) + { + alSourcePausev(sources.size(), &sources[0]); + throwALerror(); + } +} + +void OpenAL_Output::resumeSounds(int types) +{ + std::vector sources; + SoundVec::const_iterator iter = mActiveSounds.begin(); + while(iter != mActiveSounds.end()) + { + const OpenAL_SoundStream *stream = dynamic_cast(*iter); + if(stream) + { + if(stream->mSource && (stream->getPlayType()&types)) + sources.push_back(stream->mSource); + } + else + { + const OpenAL_Sound *sound = dynamic_cast(*iter); + if(sound && sound->mSource && (sound->getPlayType()&types)) + sources.push_back(sound->mSource); + } + iter++; + } + if(sources.size() > 0) + { + alSourcePlayv(sources.size(), &sources[0]); + throwALerror(); + } +} + + OpenAL_Output::OpenAL_Output(SoundManager &mgr) : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0), mLastEnvironment(Env_Normal), mStreamThread(new StreamThread) diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index fecffa575..02706b50c 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -33,6 +33,9 @@ namespace MWSound uint64_t mBufferCacheMemSize; + typedef std::vector SoundVec; + SoundVec mActiveSounds; + ALuint getBuffer(const std::string &fname); void bufferFinished(ALuint buffer); @@ -42,13 +45,16 @@ namespace MWSound virtual void init(const std::string &devname=""); virtual void deinit(); - virtual MWBase::SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags); + virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags); virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float volume, float pitch, float min, float max, int flags); - virtual MWBase::SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags); + float vol, float basevol, float pitch, float min, float max, int flags); + virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags); virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env); + virtual void pauseSounds(int types); + virtual void resumeSounds(int types); + OpenAL_Output& operator=(const OpenAL_Output &rhs); OpenAL_Output(const OpenAL_Output &rhs); @@ -64,7 +70,7 @@ namespace MWSound friend class SoundManager; }; #ifndef DEFAULT_OUTPUT -#define DEFAULT_OUTPUT (::MWSound::OpenAL_Output) +#define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x)) #endif } diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 729147f75..8deaf26a6 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -26,16 +26,22 @@ namespace MWSound public: virtual void stop() = 0; virtual bool isPlaying() = 0; + virtual double getTimeOffset() = 0; void setPosition(const Ogre::Vector3 &pos) { mPos = pos; } void setVolume(float volume) { mVolume = volume; } - Sound() : mPos(0.0f, 0.0f, 0.0f) - , mVolume(1.0f) - , mBaseVolume(1.0f) - , mPitch(1.0f) - , mMinDistance(20.0f) /* 1 * min_range_scale */ - , mMaxDistance(12750.0f) /* 255 * max_range_scale */ - , mFlags(MWBase::SoundManager::Play_Normal) + MWBase::SoundManager::PlayType getPlayType() const + { return (MWBase::SoundManager::PlayType)(mFlags&MWBase::SoundManager::Play_TypeMask); } + + + Sound(const Ogre::Vector3& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags) + : mPos(pos) + , mVolume(vol) + , mBaseVolume(basevol) + , mPitch(pitch) + , mMinDistance(mindist) + , mMaxDistance(maxdist) + , mFlags(flags) { } virtual ~Sound() { } diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 9c28d5ff5..29d99e8fa 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -15,7 +15,10 @@ namespace MWSound enum ChannelConfig { ChannelConfig_Mono, - ChannelConfig_Stereo + ChannelConfig_Stereo, + ChannelConfig_Quad, + ChannelConfig_5point1, + ChannelConfig_7point1 }; const char *getChannelConfigName(ChannelConfig config); @@ -29,11 +32,13 @@ namespace MWSound virtual void open(const std::string &fname) = 0; virtual void close() = 0; + virtual std::string getName() = 0; virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0; virtual size_t read(char *buffer, size_t bytes) = 0; virtual void readAll(std::vector &output); virtual void rewind() = 0; + virtual size_t getSampleOffset() = 0; Sound_Decoder() : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) { } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 2680ec1db..b5ccc946a 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -24,13 +24,16 @@ namespace MWSound virtual void init(const std::string &devname="") = 0; virtual void deinit() = 0; - virtual MWBase::SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags) = 0; + virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags) = 0; virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float volume, float pitch, float min, float max, int flags) = 0; - virtual MWBase::SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags) = 0; + float vol, float basevol, float pitch, float min, float max, int flags) = 0; + virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags) = 0; virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) = 0; + virtual void pauseSounds(int types) = 0; + virtual void resumeSounds(int types) = 0; + Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 8c4798c9d..8a69fc96b 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -52,6 +52,7 @@ namespace MWSound , mMusicVolume(1.0f) , mFootstepsVolume(1.0f) , mVoiceVolume(1.0f) + , mPausedSoundTypes(0) { if(!useSound) return; @@ -86,7 +87,7 @@ namespace MWSound { if(devname.empty()) throw; - std::cout <<"Failed to open device \""<init(); Settings::Manager::setString("device", "Sound", ""); } @@ -137,6 +138,27 @@ namespace MWSound return "Sound/"+snd->mSound; } + // Gets the combined volume settings for the given sound type + float SoundManager::volumeFromType(PlayType type) const + { + float volume = mMasterVolume; + switch(type) + { + case Play_TypeSfx: + volume *= mSFXVolume; + break; + case Play_TypeVoice: + volume *= mVoiceVolume; + break; + case Play_TypeMusic: + case Play_TypeMovie: + volume *= mMusicVolume; + break; + case Play_TypeMask: + break; + } + return volume; + } bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const { @@ -165,11 +187,13 @@ namespace MWSound std::cout <<"Playing "<streamSound(filename, basevol, 1.0f, Play_NoEnv); - mMusic->mBaseVolume = basevol; - mMusic->mFlags = Play_NoEnv; + + DecoderPtr decoder = getDecoder(); + decoder->open(filename); + + mMusic = mOutput->streamSound(decoder, volumeFromType(Play_TypeMusic), + 1.0f, Play_NoEnv|Play_TypeMusic); } catch(std::exception &e) { @@ -212,16 +236,13 @@ namespace MWSound try { // The range values are not tested - float basevol = mMasterVolume * mVoiceVolume; + float basevol = volumeFromType(Play_TypeVoice); std::string filePath = "Sound/"+filename; const ESM::Position &pos = ptr.getRefData().getPosition(); const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, basevol, 1.0f, - 20.0f, 12750.0f, Play_Normal); - sound->mPos = objpos; - sound->mBaseVolume = basevol; - + MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, + 20.0f, 12750.0f, Play_Normal|Play_TypeVoice); mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) @@ -236,12 +257,10 @@ namespace MWSound return; try { - float basevol = mMasterVolume * mVoiceVolume; + float basevol = volumeFromType(Play_TypeVoice); std::string filePath = "Sound/"+filename; - MWBase::SoundPtr sound = mOutput->playSound(filePath, basevol, 1.0f, Play_Normal); - sound->mBaseVolume = basevol; - + MWBase::SoundPtr sound = mOutput->playSound(filePath, 1.0f, basevol, 1.0f, Play_Normal|Play_TypeVoice); mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), std::string("_say_sound")); } catch(std::exception &e) @@ -271,26 +290,35 @@ namespace MWSound } + MWBase::SoundPtr SoundManager::playTrack(const DecoderPtr& decoder, PlayType type) + { + MWBase::SoundPtr track; + if(!mOutput->isInitialized()) + return track; + try + { + track = mOutput->streamSound(decoder, volumeFromType(type), 1.0f, Play_NoEnv|type); + } + catch(std::exception &e) + { + std::cout <<"Sound Error: "<isInitialized()) return sound; try { - float basevol = mMasterVolume * mSFXVolume; + float basevol = volumeFromType(Play_TypeSfx); float min, max; - std::string file = lookup(soundId, basevol, min, max); - - sound = mOutput->playSound(file, volume*basevol, pitch, mode); - sound->mVolume = volume; - sound->mBaseVolume = basevol; - sound->mPitch = pitch; - sound->mMinDistance = min; - sound->mMaxDistance = max; - sound->mFlags = mode; + std::string file = lookup(soundId, volume, min, max); + sound = mOutput->playSound(file, volume, basevol, pitch, mode|Play_TypeSfx); mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } catch(std::exception &e) @@ -301,7 +329,7 @@ namespace MWSound } MWBase::SoundPtr SoundManager::playSound3D(MWWorld::Ptr ptr, const std::string& soundId, - float volume, float pitch, int mode) + float volume, float pitch, PlayMode mode) { MWBase::SoundPtr sound; if(!mOutput->isInitialized()) @@ -309,21 +337,13 @@ namespace MWSound try { // Look up the sound in the ESM data - float basevol = mMasterVolume * mSFXVolume; + float basevol = volumeFromType(Play_TypeSfx); float min, max; - std::string file = lookup(soundId, basevol, min, max); + std::string file = lookup(soundId, volume, min, max); const ESM::Position &pos = ptr.getRefData().getPosition();; const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode); - sound->mPos = objpos; - sound->mVolume = volume; - sound->mBaseVolume = basevol; - sound->mPitch = pitch; - sound->mMinDistance = min; - sound->mMaxDistance = max; - sound->mFlags = mode; - + sound = mOutput->playSound3D(file, objpos, volume, basevol, pitch, min, max, mode|Play_TypeSfx); if((mode&Play_NoTrack)) mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); else @@ -404,6 +424,27 @@ namespace MWSound } + void SoundManager::pauseSounds(int types) + { + if(mOutput->isInitialized()) + { + types &= Play_TypeMask; + mOutput->pauseSounds(types); + mPausedSoundTypes |= types; + } + } + + void SoundManager::resumeSounds(int types) + { + if(mOutput->isInitialized()) + { + types &= types&Play_TypeMask&mPausedSoundTypes; + mOutput->resumeSounds(types); + mPausedSoundTypes &= ~types; + } + } + + void SoundManager::updateRegionSound(float duration) { MWWorld::Ptr::CellStore *current = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell(); @@ -525,24 +566,13 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(snditer->second.second != "_say_sound") - { - float basevol = mMasterVolume * mSFXVolume; - float min, max; - lookup(snditer->second.second, basevol, min, max); - snditer->first->mBaseVolume = basevol; - } - else - { - float basevol = mMasterVolume * mVoiceVolume; - snditer->first->mBaseVolume = basevol; - } + snditer->first->mBaseVolume = volumeFromType(snditer->first->getPlayType()); snditer->first->update(); snditer++; } if(mMusic) { - mMusic->mBaseVolume = mMasterVolume * mMusicVolume; + mMusic->mBaseVolume = volumeFromType(mMusic->getPlayType()); mMusic->update(); } } @@ -585,8 +615,11 @@ namespace MWSound { switch(config) { - case ChannelConfig_Mono: return "Mono"; - case ChannelConfig_Stereo: return "Stereo"; + case ChannelConfig_Mono: return "Mono"; + case ChannelConfig_Stereo: return "Stereo"; + case ChannelConfig_Quad: return "Quad"; + case ChannelConfig_5point1: return "5.1 Surround"; + case ChannelConfig_7point1: return "7.1 Surround"; } return "(unknown channel config)"; } @@ -595,8 +628,11 @@ namespace MWSound { switch(config) { - case ChannelConfig_Mono: frames *= 1; break; - case ChannelConfig_Stereo: frames *= 2; break; + case ChannelConfig_Mono: frames *= 1; break; + case ChannelConfig_Stereo: frames *= 2; break; + case ChannelConfig_Quad: frames *= 4; break; + case ChannelConfig_5point1: frames *= 6; break; + case ChannelConfig_7point1: frames *= 8; break; } switch(type) { diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index a84aa3b9a..b475449d9 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -22,8 +22,6 @@ namespace MWSound struct Sound_Decoder; class Sound; - typedef boost::shared_ptr DecoderPtr; - enum Environment { Env_Normal, Env_Underwater @@ -54,6 +52,8 @@ namespace MWSound Ogre::Vector3 mListenerDir; Ogre::Vector3 mListenerUp; + int mPausedSoundTypes; + std::string lookup(const std::string &soundId, float &volume, float &min, float &max); void streamMusicFull(const std::string& filename); @@ -61,6 +61,8 @@ namespace MWSound void updateSounds(float duration); void updateRegionSound(float duration); + float volumeFromType(PlayType type) const; + SoundManager(const SoundManager &rhs); SoundManager& operator=(const SoundManager &rhs); @@ -105,11 +107,14 @@ namespace MWSound virtual void stopSay(MWWorld::Ptr reference=MWWorld::Ptr()); ///< Stop an actor speaking - virtual MWBase::SoundPtr playSound(const std::string& soundId, float volume, float pitch, int mode=Play_Normal); + virtual MWBase::SoundPtr playTrack(const DecoderPtr& decoder, PlayType type); + ///< Play a 2D audio track, using a custom decoder + + virtual MWBase::SoundPtr playSound(const std::string& soundId, float volume, float pitch, PlayMode mode=Play_Normal); ///< Play a sound, independently of 3D-position virtual MWBase::SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId, - float volume, float pitch, int mode=Play_Normal); + float volume, float pitch, PlayMode mode=Play_Normal); ///< Play a sound from an object virtual void stopSound3D(MWWorld::Ptr reference, const std::string& soundId); @@ -127,6 +132,12 @@ namespace MWSound virtual bool getSoundPlaying(MWWorld::Ptr reference, const std::string& soundId) const; ///< Is the given sound currently playing on the given object? + virtual void pauseSounds(int types=Play_TypeMask); + ///< Pauses all currently playing sounds, including music. + + virtual void resumeSounds(int types=Play_TypeMask); + ///< Resumes all previously paused sounds. + virtual void update(float duration); virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 009b325c0..19bc881f7 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -738,7 +738,7 @@ void WeatherManager::update(float duration) if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), ambientSnd) == mSoundsPlaying.end()) { mSoundsPlaying.push_back(ambientSnd); - MWBase::Environment::get().getSoundManager()->playSound(ambientSnd, 1.0, 1.0, true); + MWBase::Environment::get().getSoundManager()->playSound(ambientSnd, 1.0, 1.0, MWBase::SoundManager::Play_Loop); } } @@ -749,7 +749,7 @@ void WeatherManager::update(float duration) if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), rainSnd) == mSoundsPlaying.end()) { mSoundsPlaying.push_back(rainSnd); - MWBase::Environment::get().getSoundManager()->playSound(rainSnd, 1.0, 1.0, true); + MWBase::Environment::get().getSoundManager()->playSound(rainSnd, 1.0, 1.0, MWBase::SoundManager::Play_Loop); } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4f60f6ef4..d59623ae5 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1300,4 +1300,14 @@ namespace MWWorld return 0; } + + void World::playVideo (const std::string &name, bool allowSkipping) + { + mRendering->playVideo(name, allowSkipping); + } + + void World::stopVideo () + { + mRendering->stopVideo(); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index d29adebf4..35652f01d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -336,6 +336,10 @@ namespace MWWorld /// 1 - only waiting \n /// 2 - player is underwater \n /// 3 - enemies are nearby (not implemented) + + /// \todo this does not belong here + virtual void playVideo(const std::string& name, bool allowSkipping); + virtual void stopVideo(); }; } diff --git a/cmake/FindFFMPEG.cmake b/cmake/FindFFMPEG.cmake deleted file mode 100644 index 2e755d047..000000000 --- a/cmake/FindFFMPEG.cmake +++ /dev/null @@ -1,105 +0,0 @@ -# Find the FFmpeg library -# -# Sets -# FFMPEG_FOUND. If false, don't try to use ffmpeg -# FFMPEG_INCLUDE_DIR -# FFMPEG_LIBRARIES -# -# Modified by Nicolay Korslund for OpenMW - -SET( FFMPEG_FOUND "NO" ) - -FIND_PATH( FFMPEG_general_INCLUDE_DIR libavcodec/avcodec.h libavformat/avformat.h - HINTS - PATHS - /usr/include - /usr/local/include - /usr/include/ffmpeg - /usr/local/include/ffmpeg - /usr/include/ffmpeg/libavcodec - /usr/local/include/ffmpeg/libavcodec - /usr/include/libavcodec - /usr/local/include/libavcodec - ) - -FIND_PATH( FFMPEG_avcodec_INCLUDE_DIR avcodec.h - HINTS - PATHS - ${FFMPEG_general_INCLUDE_DIR}/libavcodec - /usr/include - /usr/local/include - /usr/include/ffmpeg - /usr/local/include/ffmpeg - /usr/include/ffmpeg/libavcodec - /usr/local/include/ffmpeg/libavcodec - /usr/include/libavcodec - /usr/local/include/libavcodec -) - -FIND_PATH( FFMPEG_avformat_INCLUDE_DIR avformat.h - HINTS - PATHS - ${FFMPEG_general_INCLUDE_DIR}/libavformat - /usr/include - /usr/local/include - /usr/include/ffmpeg - /usr/local/include/ffmpeg - /usr/include/ffmpeg/libavformat - /usr/local/include/ffmpeg/libavformat - /usr/include/libavformat - /usr/local/include/libavformat -) - -set(FFMPEG_INCLUDE_DIR ${FFMPEG_general_INCLUDE_DIR} ${FFMPEG_avcodec_INCLUDE_DIR} ${FFMPEG_avformat_INCLUDE_DIR}) - -IF( FFMPEG_INCLUDE_DIR ) - -FIND_PROGRAM( FFMPEG_CONFIG ffmpeg-config - /usr/bin - /usr/local/bin - ${HOME}/bin -) - -IF( FFMPEG_CONFIG ) - EXEC_PROGRAM( ${FFMPEG_CONFIG} ARGS "--libs avformat" OUTPUT_VARIABLE FFMPEG_LIBS ) - SET( FFMPEG_FOUND "YES" ) - SET( FFMPEG_LIBRARIES "${FFMPEG_LIBS}" ) - -ELSE( FFMPEG_CONFIG ) - - FIND_LIBRARY( FFMPEG_avcodec_LIBRARY avcodec - /usr/lib - /usr/local/lib - /usr/lib64 - /usr/local/lib64 - ) - - FIND_LIBRARY( FFMPEG_avformat_LIBRARY avformat - /usr/lib - /usr/local/lib - /usr/lib64 - /usr/local/lib64 - ) - - FIND_LIBRARY( FFMPEG_avutil_LIBRARY avutil - /usr/lib - /usr/local/lib - /usr/lib64 - /usr/local/lib64 - ) - - IF( FFMPEG_avcodec_LIBRARY ) - IF( FFMPEG_avformat_LIBRARY ) - - SET( FFMPEG_FOUND "YES" ) - SET( FFMPEG_LIBRARIES ${FFMPEG_avformat_LIBRARY} ${FFMPEG_avcodec_LIBRARY} ) - IF( FFMPEG_avutil_LIBRARY ) - SET( FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${FFMPEG_avutil_LIBRARY} ) - ENDIF( FFMPEG_avutil_LIBRARY ) - - ENDIF( FFMPEG_avformat_LIBRARY ) - ENDIF( FFMPEG_avcodec_LIBRARY ) - -ENDIF( FFMPEG_CONFIG ) - -ENDIF( FFMPEG_INCLUDE_DIR ) diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake new file mode 100644 index 000000000..c80203a25 --- /dev/null +++ b/cmake/FindFFmpeg.cmake @@ -0,0 +1,148 @@ +# vim: ts=2 sw=2 +# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) +# +# Once done this will define +# FFMPEG_FOUND - System has the all required components. +# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. +# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. +# +# For each of the components it will additionaly set. +# - AVCODEC +# - AVDEVICE +# - AVFORMAT +# - AVUTIL +# - POSTPROCESS +# - SWSCALE +# the following variables will be defined +# _FOUND - System has +# _INCLUDE_DIRS - Include directory necessary for using the headers +# _LIBRARIES - Link these to use +# _DEFINITIONS - Compiler switches required for using +# _VERSION - The components version +# +# Copyright (c) 2006, Matthias Kretz, +# Copyright (c) 2008, Alexander Neundorf, +# Copyright (c) 2011, Michael Jansen, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindPackageHandleStandardArgs) + +# The default components were taken from a survey over other FindFFMPEG.cmake files +if (NOT FFmpeg_FIND_COMPONENTS) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) +endif () + +# +### Macro: set_component_found +# +# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. +# +macro(set_component_found _component ) + if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) + # message(STATUS " - ${_component} found.") + set(${_component}_FOUND TRUE) + else () + # message(STATUS " - ${_component} not found.") + endif () +endmacro() + +# +### Macro: find_component +# +# Checks for the given component by invoking pkgconfig and then looking up the libraries and +# include directories. +# +macro(find_component _component _pkgconfig _library _header) + + if (NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(PC_${_component} ${_pkgconfig}) + endif () + endif (NOT WIN32) + + find_path(${_component}_INCLUDE_DIRS ${_header} + HINTS + ${PC_LIB${_component}_INCLUDEDIR} + ${PC_LIB${_component}_INCLUDE_DIRS} + PATH_SUFFIXES + ffmpeg + ) + + find_library(${_component}_LIBRARIES NAMES ${_library} + HINTS + ${PC_LIB${_component}_LIBDIR} + ${PC_LIB${_component}_LIBRARY_DIRS} + ) + + set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") + set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") + + set_component_found(${_component}) + + mark_as_advanced( + ${_component}_INCLUDE_DIRS + ${_component}_LIBRARIES + ${_component}_DEFINITIONS + ${_component}_VERSION) + +endmacro() + + +# Check for cached results. If there are skip the costly part. +if (NOT FFMPEG_LIBRARIES) + + # Check for all possible component. + find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) + find_component(AVFORMAT libavformat avformat libavformat/avformat.h) + find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) + find_component(AVUTIL libavutil avutil libavutil/avutil.h) + find_component(SWSCALE libswscale swscale libswscale/swscale.h) + find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) + + # Check if the required components were found and add their stuff to the FFMPEG_* vars. + foreach (_component ${FFmpeg_FIND_COMPONENTS}) + if (${_component}_FOUND) + # message(STATUS "Required component ${_component} present.") + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) + list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) + else () + # message(STATUS "Required component ${_component} missing.") + endif () + endforeach () + + # Build the include path with duplicates removed. + if (FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) + endif () + + # cache the vars. + set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) + + mark_as_advanced(FFMPEG_INCLUDE_DIRS + FFMPEG_LIBRARIES + FFMPEG_DEFINITIONS) + +endif () + +# Now set the noncached _FOUND vars for the components. +foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE) + set_component_found(${_component}) +endforeach () + +# Compile the list of required vars +set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) +foreach (_component ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) +endforeach () + +# Give a nice error message if some of the required vars are missing. +find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) diff --git a/cmake/FindSDL.cmake b/cmake/FindSDL.cmake new file mode 100644 index 000000000..0dc02f5b6 --- /dev/null +++ b/cmake/FindSDL.cmake @@ -0,0 +1,177 @@ +# Locate SDL library +# This module defines +# SDL_LIBRARY, the name of the library to link against +# SDL_FOUND, if false, do not try to link to SDL +# SDL_INCLUDE_DIR, where to find SDL.h +# +# This module responds to the the flag: +# SDL_BUILDING_LIBRARY +# If this is defined, then no SDL_main will be linked in because +# only applications need main(). +# Otherwise, it is assumed you are building an application and this +# module will attempt to locate and set the the proper link flags +# as part of the returned SDL_LIBRARY variable. +# +# Don't forget to include SDLmain.h and SDLmain.m your project for the +# OS X framework based version. (Other versions link to -lSDLmain which +# this module will try to find on your behalf.) Also for OS X, this +# module will automatically add the -framework Cocoa on your behalf. +# +# +# Additional Note: If you see an empty SDL_LIBRARY_TEMP in your configuration +# and no SDL_LIBRARY, it means CMake did not find your SDL library +# (SDL.dll, libsdl.so, SDL.framework, etc). +# Set SDL_LIBRARY_TEMP to point to your SDL library, and configure again. +# Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this value +# as appropriate. These values are used to generate the final SDL_LIBRARY +# variable, but when these values are unset, SDL_LIBRARY does not get created. +# +# +# $SDLDIR is an environment variable that would +# correspond to the ./configure --prefix=$SDLDIR +# used in building SDL. +# l.e.galup 9-20-02 +# +# Modified by Eric Wing. +# Added code to assist with automated building by using environmental variables +# and providing a more controlled/consistent search behavior. +# Added new modifications to recognize OS X frameworks and +# additional Unix paths (FreeBSD, etc). +# Also corrected the header search path to follow "proper" SDL guidelines. +# Added a search for SDLmain which is needed by some platforms. +# Added a search for threads which is needed by some platforms. +# Added needed compile switches for MinGW. +# +# On OSX, this will prefer the Framework version (if found) over others. +# People will have to manually change the cache values of +# SDL_LIBRARY to override this selection or set the CMake environment +# CMAKE_INCLUDE_PATH to modify the search paths. +# +# Note that the header path has changed from SDL/SDL.h to just SDL.h +# This needed to change because "proper" SDL convention +# is #include "SDL.h", not . This is done for portability +# reasons because not all systems place things in SDL/ (see FreeBSD). + +#============================================================================= +# Copyright 2003-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +FIND_PATH(SDL_INCLUDE_DIR SDL.h + HINTS + $ENV{SDLDIR} + PATH_SUFFIXES include/SDL include + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local/include/SDL12 + /usr/local/include/SDL11 # FreeBSD ports + /usr/include/SDL12 + /usr/include/SDL11 + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) +#MESSAGE("SDL_INCLUDE_DIR is ${SDL_INCLUDE_DIR}") + +# SDL-1.1 is the name used by FreeBSD ports... +# don't confuse it for the version number. +FIND_LIBRARY(SDL_LIBRARY_TEMP + NAMES SDL SDL-1.1 + HINTS + $ENV{SDLDIR} + PATH_SUFFIXES lib64 lib + PATHS + /sw + /opt/local + /opt/csw + /opt +) + +#MESSAGE("SDL_LIBRARY_TEMP is ${SDL_LIBRARY_TEMP}") + +IF(NOT SDL_BUILDING_LIBRARY) + IF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework") + # Non-OS X framework versions expect you to also dynamically link to + # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms + # seem to provide SDLmain for compatibility even though they don't + # necessarily need it. + FIND_LIBRARY(SDLMAIN_LIBRARY + NAMES SDLmain SDLmain-1.1 + HINTS + $ENV{SDLDIR} + PATH_SUFFIXES lib64 lib + PATHS + /sw + /opt/local + /opt/csw + /opt + ) + ENDIF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework") +ENDIF(NOT SDL_BUILDING_LIBRARY) + +# SDL may require threads on your system. +# The Apple build may not need an explicit flag because one of the +# frameworks may already provide it. +# But for non-OSX systems, I will use the CMake Threads package. +IF(NOT APPLE) + FIND_PACKAGE(Threads) +ENDIF(NOT APPLE) + +# MinGW needs an additional library, mwindows +# It's total link flags should look like -lmingw32 -lSDLmain -lSDL -lmwindows +# (Actually on second look, I think it only needs one of the m* libraries.) +IF(MINGW) + SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") +ENDIF(MINGW) + +SET(SDL_FOUND "NO") +IF(SDL_LIBRARY_TEMP) + # For SDLmain + IF(NOT SDL_BUILDING_LIBRARY) + IF(SDLMAIN_LIBRARY) + SET(SDL_LIBRARY_TEMP ${SDLMAIN_LIBRARY} ${SDL_LIBRARY_TEMP}) + ENDIF(SDLMAIN_LIBRARY) + ENDIF(NOT SDL_BUILDING_LIBRARY) + + # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa. + # CMake doesn't display the -framework Cocoa string in the UI even + # though it actually is there if I modify a pre-used variable. + # I think it has something to do with the CACHE STRING. + # So I use a temporary variable until the end so I can set the + # "real" variable in one-shot. + IF(APPLE) + SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} "-framework Cocoa") + ENDIF(APPLE) + + # For threads, as mentioned Apple doesn't need this. + # In fact, there seems to be a problem if I used the Threads package + # and try using this line, so I'm just skipping it entirely for OS X. + IF(NOT APPLE) + SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) + ENDIF(NOT APPLE) + + # For MinGW library + IF(MINGW) + SET(SDL_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL_LIBRARY_TEMP}) + ENDIF(MINGW) + + # Set the final string here so the GUI reflects the final state. + SET(SDL_LIBRARY ${SDL_LIBRARY_TEMP} CACHE STRING "Where the SDL Library can be found") + # Set the temp variable to INTERNAL so it is not seen in the CMake GUI + SET(SDL_LIBRARY_TEMP "${SDL_LIBRARY_TEMP}" CACHE INTERNAL "") + + SET(SDL_FOUND "YES") +ENDIF(SDL_LIBRARY_TEMP) + +#MESSAGE("SDL_LIBRARY is ${SDL_LIBRARY}") + diff --git a/files/gbuffer/gbuffer.compositor b/files/gbuffer/gbuffer.compositor index 04600ce9b..0a0675fa0 100644 --- a/files/gbuffer/gbuffer.compositor +++ b/files/gbuffer/gbuffer.compositor @@ -72,7 +72,7 @@ compositor gbufferFinalizer pass render_scene { first_render_queue 51 - last_render_queue 100 + last_render_queue 105 } } target_output