From eb1c24ffe68307c0caa9bc23db34f968c5a8005a Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 22 Oct 2014 22:11:03 +0200 Subject: [PATCH 1/4] Refactor video player engine to get rid of MWSound dependencies - Split video player to separate source files. - Move video player engine sources to extern/ (repository will be set up on github soon). - Audio is handled in a MovieAudioFactory, implemented by the user (here in MWSound subsystem). - Handle conversion of unsupported channel layouts via ffmpeg's swresample. --- CMakeLists.txt | 1 + apps/openmw/CMakeLists.txt | 5 +- apps/openmw/mwgui/videowidget.cpp | 8 + apps/openmw/mwgui/videowidget.hpp | 7 +- apps/openmw/mwgui/windowmanagerimp.cpp | 6 + apps/openmw/mwrender/videoplayer.cpp | 1184 ----------------- apps/openmw/mwrender/videoplayer.hpp | 37 - apps/openmw/mwsound/ffmpeg_decoder.cpp | 2 +- apps/openmw/mwsound/ffmpeg_decoder.hpp | 2 + apps/openmw/mwsound/libavwrapper.cpp | 4 +- apps/openmw/mwsound/movieaudiofactory.cpp | 173 +++ apps/openmw/mwsound/movieaudiofactory.hpp | 16 + extern/ogre-ffmpeg-videoplayer/CMakeLists.txt | 38 + extern/ogre-ffmpeg-videoplayer/License.txt | 9 + .../ogre-ffmpeg-videoplayer/audiodecoder.cpp | 307 +++++ .../ogre-ffmpeg-videoplayer/audiodecoder.hpp | 108 ++ .../ogre-ffmpeg-videoplayer/audiofactory.hpp | 20 + .../ogre-ffmpeg-videoplayer/libavwrapper.cpp | 113 ++ extern/ogre-ffmpeg-videoplayer/videodefs.hpp | 12 + .../ogre-ffmpeg-videoplayer/videoplayer.cpp | 95 ++ .../ogre-ffmpeg-videoplayer/videoplayer.hpp | 58 + extern/ogre-ffmpeg-videoplayer/videostate.cpp | 671 ++++++++++ extern/ogre-ffmpeg-videoplayer/videostate.hpp | 125 ++ 23 files changed, 1774 insertions(+), 1227 deletions(-) delete mode 100644 apps/openmw/mwrender/videoplayer.cpp delete mode 100644 apps/openmw/mwrender/videoplayer.hpp create mode 100644 apps/openmw/mwsound/movieaudiofactory.cpp create mode 100644 apps/openmw/mwsound/movieaudiofactory.hpp create mode 100644 extern/ogre-ffmpeg-videoplayer/CMakeLists.txt create mode 100644 extern/ogre-ffmpeg-videoplayer/License.txt create mode 100644 extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp create mode 100644 extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp create mode 100644 extern/ogre-ffmpeg-videoplayer/audiofactory.hpp create mode 100644 extern/ogre-ffmpeg-videoplayer/libavwrapper.cpp create mode 100644 extern/ogre-ffmpeg-videoplayer/videodefs.hpp create mode 100644 extern/ogre-ffmpeg-videoplayer/videoplayer.cpp create mode 100644 extern/ogre-ffmpeg-videoplayer/videoplayer.hpp create mode 100644 extern/ogre-ffmpeg-videoplayer/videostate.cpp create mode 100644 extern/ogre-ffmpeg-videoplayer/videostate.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 09b2b1621..37faeefb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -549,6 +549,7 @@ endif(WIN32) # Extern add_subdirectory (extern/shiny) +add_subdirectory (extern/ogre-ffmpeg-videoplayer) add_subdirectory (extern/oics) add_subdirectory (extern/sdl4ogre) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index e37cd1391..7315a81d3 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -21,7 +21,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows - characterpreview globalmap videoplayer ripplesimulation refraction + characterpreview globalmap ripplesimulation refraction terrainstorage renderconst effectmanager weaponanimation ) @@ -55,7 +55,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanagerimp openal_output ffmpeg_decoder sound sound_decoder sound_output loudness libavwrapper + soundmanagerimp openal_output ffmpeg_decoder sound sound_decoder sound_output loudness libavwrapper movieaudiofactory ) add_openmw_dir (mwworld @@ -132,6 +132,7 @@ target_link_libraries(openmw ${MYGUI_LIBRARIES} ${SDL2_LIBRARY} ${MYGUI_PLATFORM_LIBRARIES} + "ogre-ffmpeg-videoplayer" "oics" "sdl4ogre" components diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index cfd837a95..76fdd5287 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -1,5 +1,7 @@ #include "videowidget.hpp" +#include "../mwsound/movieaudiofactory.hpp" + namespace MWGui { @@ -10,6 +12,7 @@ VideoWidget::VideoWidget() void VideoWidget::playVideo(const std::string &video) { + mPlayer.setAudioFactory(new MWSound::MovieAudioFactory()); mPlayer.playVideo(video); setImageTexture(mPlayer.getTextureName()); @@ -36,4 +39,9 @@ void VideoWidget::stop() mPlayer.close(); } +bool VideoWidget::hasAudioStream() +{ + return mPlayer.hasAudioStream(); +} + } diff --git a/apps/openmw/mwgui/videowidget.hpp b/apps/openmw/mwgui/videowidget.hpp index ad3d4d5e3..79e1c26bb 100644 --- a/apps/openmw/mwgui/videowidget.hpp +++ b/apps/openmw/mwgui/videowidget.hpp @@ -3,7 +3,7 @@ #include -#include "../mwrender/videoplayer.hpp" +#include namespace MWGui { @@ -26,11 +26,14 @@ namespace MWGui /// @return Is the video still playing? bool update(); + /// Return true if a video is currently playing and it has an audio stream. + bool hasAudioStream(); + /// Stop video and free resources (done automatically on destruction) void stop(); private: - MWRender::VideoPlayer mPlayer; + Video::VideoPlayer mPlayer; }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 5a1b0b417..f431bc8f1 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1664,6 +1664,10 @@ namespace MWGui bool cursorWasVisible = mCursorVisible; setCursorVisible(false); + if (mVideoWidget->hasAudioStream()) + MWBase::Environment::get().getSoundManager()->pauseSounds( + MWBase::SoundManager::Play_TypeMask&(~MWBase::SoundManager::Play_TypeMovie)); + while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { MWBase::Environment::get().getInputManager()->update(0, true, false); @@ -1672,6 +1676,8 @@ namespace MWGui } mVideoWidget->stop(); + MWBase::Environment::get().getSoundManager()->resumeSounds(); + setCursorVisible(cursorWasVisible); // Restore normal rendering diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp deleted file mode 100644 index 16de1bd30..000000000 --- a/apps/openmw/mwrender/videoplayer.cpp +++ /dev/null @@ -1,1184 +0,0 @@ -#include "videoplayer.hpp" - -#define __STDC_CONSTANT_MACROS -#include - -#include -#include - -#include -#include -#include - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/soundmanager.hpp" -#include "../mwsound/sound_decoder.hpp" -#include "../mwsound/sound.hpp" - -#ifdef _WIN32 -#include - -typedef SSIZE_T ssize_t; -#endif - -namespace MWRender -{ - -extern "C" -{ - #include - #include - #include - - #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) - #define av_frame_alloc avcodec_alloc_frame - #endif - - // From libavformat version 55.0.100 and onward the declaration of av_gettime() is - // removed from libavformat/avformat.h and moved to libavutil/time.h - // https://github.com/FFmpeg/FFmpeg/commit/06a83505992d5f49846c18507a6c3eb8a47c650e - #if AV_VERSION_INT(55, 0, 100) <= AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ - LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO) - #include - #endif - - #if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ - LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) - #include - #endif - - #ifdef HAVE_LIBSWRESAMPLE - #include - #else - // FIXME: remove this section once libswresample is packaged for Debian - #include - #include - #define SwrContext AVAudioResampleContext - int swr_init(AVAudioResampleContext *avr); - void swr_free(AVAudioResampleContext **avr); - int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples); - AVAudioResampleContext * swr_alloc_set_opts( AVAudioResampleContext *avr, int64_t out_ch_layout, AVSampleFormat out_fmt, int out_rate, int64_t in_ch_layout, AVSampleFormat in_fmt, int in_rate, int o, void* l); - #endif -} - -#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 50 - -enum { - AV_SYNC_AUDIO_MASTER, // Play audio with no frame drops, sync video to audio - AV_SYNC_VIDEO_MASTER, // Play video with no frame drops, sync audio to video - AV_SYNC_EXTERNAL_MASTER, // Sync audio and video to an external clock - - 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) - , video_clock(0.0), sws_context(NULL), rgbaFrame(NULL), pictq_size(0) - , pictq_rindex(0), pictq_windex(0) - , quit(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(); - - static void video_thread_loop(VideoState *is); - static void decode_thread_loop(VideoState *is); - - void video_display(VideoPicture* vp); - void video_refresh(); - - int queue_picture(AVFrame *pFrame, double pts); - double synchronize_video(AVFrame *src_frame, double pts); - - 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::TexturePtr mTexture; - - 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 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; - - SwrContext *mSwr; - enum AVSampleFormat mOutputSampleFormat; - uint8_t *mDataBuf; - uint8_t **mFrameData; - int mDataBufLen; - - 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; - - if(mSwr) - { - if(!mDataBuf || mDataBufLen < frame->nb_samples) - { - av_freep(&mDataBuf); - if(av_samples_alloc(&mDataBuf, NULL, mAVStream->codec->channels, - frame->nb_samples, mOutputSampleFormat, 0) < 0) - break; - else - mDataBufLen = frame->nb_samples; - } - - if(swr_convert(mSwr, (uint8_t**)&mDataBuf, frame->nb_samples, - (const uint8_t**)frame->extended_data, frame->nb_samples) < 0) - { - break; - } - mFrameData = &mDataBuf; - } - else - mFrameData = &frame->data[0]; - - 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&) -#ifdef _WIN32 - { fail(std::string("Invalid call to ")+__FUNCSIG__); } -#else - { fail(std::string("Invalid call to ")+__PRETTY_FUNCTION__); } -#endif - - void close() { } - - std::string getName() - { return mVideoState->stream->getName(); } - - void rewind() { } - -public: - MovieAudioDecoder(VideoState *is) - : mVideoState(is) - , mAVStream(*is->audio_st) - , mFrame(av_frame_alloc()) - , 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) - , mSwr(0) - , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) - , mDataBuf(NULL) - , mFrameData(NULL) - , mDataBufLen(0) - { } - virtual ~MovieAudioDecoder() - { - av_freep(&mFrame); - swr_free(&mSwr); - av_freep(&mDataBuf); - } - - 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 if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLT) - *type = MWSound::SampleType_Float32; - else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_U8P) - *type = MWSound::SampleType_UInt8; - else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_S16P) - *type = MWSound::SampleType_Int16; - else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) - *type = MWSound::SampleType_Float32; - else - fail(std::string("Unsupported sample format: ")+ - av_get_sample_fmt_name(mAVStream->codec->sample_fmt)); - - int64_t ch_layout = mAVStream->codec->channel_layout; - - 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; - ch_layout = AV_CH_LAYOUT_MONO; - } - else if(mAVStream->codec->channels == 2) - { - *chans = MWSound::ChannelConfig_Stereo; - ch_layout = AV_CH_LAYOUT_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; - - if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_U8P) - mOutputSampleFormat = AV_SAMPLE_FMT_U8; - else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_S16P) - mOutputSampleFormat = AV_SAMPLE_FMT_S16; - else if(mAVStream->codec->sample_fmt == AV_SAMPLE_FMT_FLTP) - mOutputSampleFormat = AV_SAMPLE_FMT_FLT; - - if(mOutputSampleFormat != AV_SAMPLE_FMT_NONE) - { - mSwr = swr_alloc_set_opts(mSwr, // SwrContext - ch_layout, // output ch layout - mOutputSampleFormat, // output sample format - mAVStream->codec->sample_rate, // output sample rate - ch_layout, // input ch layout - mAVStream->codec->sample_fmt, // input sample format - mAVStream->codec->sample_rate, // input sample rate - 0, // logging level offset - NULL); // log context - if(!mSwr) - fail(std::string("Couldn't allocate SwrContext")); - if(swr_init(mSwr) < 0) - fail(std::string("Couldn't initialize SwrContext")); - } - } - - 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); - if(sample_skip > 0 || mFrameSize > -sample_skip) - sample_skip -= mFramePos; - continue; - } - - size_t len1 = len - total; - if(mFramePos >= 0) - { - len1 = std::min(len1, mFrameSize-mFramePos); - memcpy(stream, mFrameData[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, *mFrameData[0], len1); - else if(n == 2) - { - const int16_t val = *((int16_t*)mFrameData[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*)mFrameData[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*)mFrameData[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, mFrameData[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_display(VideoPicture *vp) -{ - if((*this->video_st)->codec->width != 0 && (*this->video_st)->codec->height != 0) - { - - if(static_cast(mTexture->getWidth()) != (*this->video_st)->codec->width || - static_cast(mTexture->getHeight()) != (*this->video_st)->codec->height) - { - mTexture->unload(); - mTexture->setWidth((*this->video_st)->codec->width); - mTexture->setHeight((*this->video_st)->codec->height); - mTexture->createInternalResources(); - } - Ogre::PixelBox pb((*this->video_st)->codec->width, (*this->video_st)->codec->height, 1, Ogre::PF_BYTE_RGBA, &vp->data[0]); - Ogre::HardwarePixelBufferSharedPtr buffer = mTexture->getBuffer(); - buffer->blitFromMemory(pb); - } -} - -void VideoState::video_refresh() -{ - if(this->pictq_size == 0) - return; - - if (this->av_sync_type == AV_SYNC_VIDEO_MASTER) - { - VideoPicture* vp = &this->pictq[this->pictq_rindex]; - this->video_display(vp); - this->pictq_rindex = (pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE; - this->frame_last_pts = vp->pts; - this->pictq_mutex.lock(); - this->pictq_size--; - this->pictq_cond.notify_one(); - this->pictq_mutex.unlock(); - } - else - { - const float threshold = 0.03; - if (this->pictq[pictq_rindex].pts > this->get_master_clock() + threshold) - return; // not ready yet to show this picture - - // TODO: the conversion to RGBA is done in the decoding thread, so if a picture is skipped here, then it was - // unnecessarily converted. But we may want to replace the conversion by a pixel shader anyway (see comment in queue_picture) - int i=0; - for (; ipictq_size-1; ++i) - { - if (this->pictq[pictq_rindex].pts + threshold <= this->get_master_clock()) - this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE; // not enough time to show this picture - else - break; - } - - VideoPicture* vp = &this->pictq[this->pictq_rindex]; - - this->video_display(vp); - - this->frame_last_pts = vp->pts; - - this->pictq_mutex.lock(); - this->pictq_size -= i; - // update queue for next picture - this->pictq_size--; - this->pictq_rindex++; - 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 - // TODO: we could do this in a pixel shader instead, if the source format - // matches a commonly used format (ie YUV420P) - 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 = static_cast(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; - - pFrame = av_frame_alloc(); - - self->rgbaFrame = av_frame_alloc(); - 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"); - - double 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 && self->audioq.size > MAX_AUDIOQ_SIZE) || - (self->video_st && 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 && self->pictq_size == 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() -{ - if(this->quit) - return false; - - this->video_refresh(); - 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; - - codecCtx->get_buffer = our_get_buffer; - codecCtx->release_buffer = our_release_buffer; - this->video_thread = boost::thread(video_thread_loop, 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->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 - /// - /// format_ctx->pb->buffer must be freed by hand, - /// if not, valgrind will show memleak, see: - /// - /// https://trac.ffmpeg.org/ticket/1357 - /// - if(!this->format_ctx || avformat_open_input(&this->format_ctx, resourceName.c_str(), NULL, NULL)) - { - if (this->format_ctx != NULL) - { - if (this->format_ctx->pb != NULL) - { - av_free(this->format_ctx->pb->buffer); - this->format_ctx->pb->buffer = NULL; - - av_free(this->format_ctx->pb); - this->format_ctx->pb = 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; - } - - if (audio_index != -1) - MWBase::Environment::get().getSoundManager()->pauseSounds(); - - 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); - - int width = (*this->video_st)->codec->width; - int height = (*this->video_st)->codec->height; - static int i = 0; - this->mTexture = Ogre::TextureManager::getSingleton().createManual( - "ffmpeg/VideoTexture" + Ogre::StringConverter::toString(++i), - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - width, height, - 0, - Ogre::PF_BYTE_RGBA, - Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); - - // initialize to (0,0,0,0) - std::vector buffer; - buffer.resize(width * height, 0); - Ogre::PixelBox pb(width, height, 1, Ogre::PF_BYTE_RGBA, &buffer[0]); - this->mTexture->getBuffer()->blitFromMemory(pb); - } - - - this->parse_thread = boost::thread(decode_thread_loop, this); -} - -void VideoState::deinit() -{ - this->quit = true; - - this->AudioTrack.reset(); - - this->audioq.cond.notify_one(); - this->videoq.cond.notify_one(); - - if (this->parse_thread.joinable()) - this->parse_thread.join(); - if (this->video_thread.joinable()) - this->video_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) - { - /// - /// format_ctx->pb->buffer must be freed by hand, - /// if not, valgrind will show memleak, see: - /// - /// https://trac.ffmpeg.org/ticket/1357 - /// - if (this->format_ctx->pb != NULL) - { - av_free(this->format_ctx->pb->buffer); - this->format_ctx->pb->buffer = NULL; - - av_free(this->format_ctx->pb); - this->format_ctx->pb = NULL; - } - avformat_close_input(&this->format_ctx); - } -} - - -VideoPlayer::VideoPlayer() - : mState(NULL) -{ - -} - -VideoPlayer::~VideoPlayer() -{ - if(mState) - close(); -} - -void VideoPlayer::playVideo(const std::string &resourceName) -{ - if(mState) - close(); - - try { - mState = new VideoState; - mState->init(resourceName); - } - catch(std::exception& e) { - std::cerr<< "Failed to play video: "<update()) - close(); - } -} - -std::string VideoPlayer::getTextureName() -{ - std::string name; - if (mState) - name = mState->mTexture->getName(); - return name; -} - -int VideoPlayer::getVideoWidth() -{ - int width=0; - if (mState) - width = mState->mTexture->getWidth(); - return width; -} - -int VideoPlayer::getVideoHeight() -{ - int height=0; - if (mState) - height = mState->mTexture->getHeight(); - return height; -} - -void VideoPlayer::close() -{ - if(mState) - { - mState->deinit(); - - delete mState; - mState = NULL; - } - - MWBase::Environment::get().getSoundManager()->resumeSounds(); -} - -bool VideoPlayer::isPlaying () -{ - return mState != NULL; -} - -} diff --git a/apps/openmw/mwrender/videoplayer.hpp b/apps/openmw/mwrender/videoplayer.hpp deleted file mode 100644 index 69b214870..000000000 --- a/apps/openmw/mwrender/videoplayer.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef VIDEOPLAYER_H -#define VIDEOPLAYER_H - -#include - -namespace MWRender -{ - struct VideoState; - - /** - * @brief Plays a video on an Ogre texture. - */ - class VideoPlayer - { - public: - VideoPlayer(); - ~VideoPlayer(); - - void playVideo (const std::string& resourceName); - - void update(); - - void close(); - - bool isPlaying(); - - std::string getTextureName(); - int getVideoWidth(); - int getVideoHeight(); - - - private: - VideoState* mState; - }; -} - -#endif diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 414e31d70..a2998ad03 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -7,7 +7,7 @@ extern "C" { #ifndef HAVE_LIBSWRESAMPLE -/* FIXME: remove this section once libswresample is available on all platforms */ +// FIXME: remove this section once libswresample is packaged for Debian int swr_init(AVAudioResampleContext *avr); void swr_free(AVAudioResampleContext **avr); int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index dc9937e81..796f17350 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -4,7 +4,9 @@ // FIXME: This can't be right? The headers refuse to build without UINT64_C, // which only gets defined in stdint.h in either C99 mode or with this macro // defined... +#ifndef __STDC_CONSTANT_MACROS #define __STDC_CONSTANT_MACROS +#endif #include extern "C" { diff --git a/apps/openmw/mwsound/libavwrapper.cpp b/apps/openmw/mwsound/libavwrapper.cpp index a12f412d1..a7a3245da 100644 --- a/apps/openmw/mwsound/libavwrapper.cpp +++ b/apps/openmw/mwsound/libavwrapper.cpp @@ -1,7 +1,9 @@ #ifndef HAVE_LIBSWRESAMPLE extern "C" { +#ifndef __STDC_CONSTANT_MACROS #define __STDC_CONSTANT_MACROS +#endif #include #include @@ -16,7 +18,7 @@ extern "C" #include #include -/* FIXME: delete this file once libswresample is available on all platforms */ +/* FIXME: delete this file once libswresample is packaged for Debian */ int swr_init(AVAudioResampleContext *avr) { return 1; } diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp new file mode 100644 index 000000000..bc7bb8023 --- /dev/null +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -0,0 +1,173 @@ +#include "movieaudiofactory.hpp" + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" + +#include "sound_decoder.hpp" +#include "sound.hpp" + +namespace MWSound +{ + + class MovieAudioDecoder; + class MWSoundDecoderBridge : public Sound_Decoder + { + public: + MWSoundDecoderBridge(MWSound::MovieAudioDecoder* decoder) + : mDecoder(decoder) + { + } + + private: + MWSound::MovieAudioDecoder* mDecoder; + + virtual void open(const std::string &fname); + virtual void close(); + virtual void rewind(); + virtual std::string getName(); + virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); + virtual size_t read(char *buffer, size_t bytes); + virtual size_t getSampleOffset(); + }; + + class MovieAudioDecoder : public Video::MovieAudioDecoder + { + public: + MovieAudioDecoder(Video::VideoState *videoState) + : Video::MovieAudioDecoder(videoState) + { + mDecoderBridge.reset(new MWSoundDecoderBridge(this)); + } + + 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; + } + + std::string getStreamName() + { + return mVideoState->stream->getName(); + } + + private: + // MovieAudioDecoder overrides + + virtual double getAudioClock() + { + return mAudioTrack->getTimeOffset(); + } + + virtual void adjustAudioSettings(AVSampleFormat& sampleFormat, uint64_t& channelLayout, int& sampleRate) + { + if (sampleFormat == AV_SAMPLE_FMT_U8P) + sampleFormat = AV_SAMPLE_FMT_U8; + else if (sampleFormat == AV_SAMPLE_FMT_S16P) + sampleFormat = AV_SAMPLE_FMT_S16; + else if (sampleFormat == AV_SAMPLE_FMT_FLTP) + sampleFormat = AV_SAMPLE_FMT_FLT; + else + sampleFormat = AV_SAMPLE_FMT_FLT; + + if (channelLayout != AV_CH_LAYOUT_MONO + && channelLayout != AV_CH_LAYOUT_5POINT1 + && channelLayout != AV_CH_LAYOUT_7POINT1 + && channelLayout != AV_CH_LAYOUT_STEREO + && channelLayout != AV_CH_LAYOUT_QUAD) + channelLayout = AV_CH_LAYOUT_STEREO; + } + + public: + ~MovieAudioDecoder() + { + mAudioTrack.reset(); + mDecoderBridge.reset(); + } + + MWBase::SoundPtr mAudioTrack; + boost::shared_ptr mDecoderBridge; + }; + + + void MWSoundDecoderBridge::open(const std::string &fname) + { + throw std::runtime_error("unimplemented"); + } + void MWSoundDecoderBridge::close() {} + void MWSoundDecoderBridge::rewind() {} + + std::string MWSoundDecoderBridge::getName() + { + return mDecoder->getStreamName(); + } + + void MWSoundDecoderBridge::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) + { + *samplerate = mDecoder->getOutputSampleRate(); + + uint64_t outputChannelLayout = mDecoder->getOutputChannelLayout(); + if (outputChannelLayout == AV_CH_LAYOUT_MONO) + *chans = ChannelConfig_Mono; + else if (outputChannelLayout == AV_CH_LAYOUT_5POINT1) + *chans = ChannelConfig_5point1; + else if (outputChannelLayout == AV_CH_LAYOUT_7POINT1) + *chans = ChannelConfig_7point1; + else if (outputChannelLayout == AV_CH_LAYOUT_STEREO) + *chans = ChannelConfig_Stereo; + else if (outputChannelLayout == AV_CH_LAYOUT_QUAD) + *chans = ChannelConfig_Quad; + else + { + std::stringstream error; + error << "Unsupported channel layout: " << outputChannelLayout; + throw std::runtime_error(error.str()); + } + + AVSampleFormat outputSampleFormat = mDecoder->getOutputSampleFormat(); + if (outputSampleFormat == AV_SAMPLE_FMT_U8) + *type = SampleType_UInt8; + else if (outputSampleFormat == AV_SAMPLE_FMT_FLT) + *type = SampleType_Float32; + else if (outputSampleFormat == AV_SAMPLE_FMT_S16) + *type = SampleType_Int16; + else + { + char str[1024]; + av_get_sample_fmt_string(str, sizeof(str), outputSampleFormat); + throw std::runtime_error(std::string("Unsupported sample format: ") + str); + } + } + + size_t MWSoundDecoderBridge::read(char *buffer, size_t bytes) + { + return mDecoder->read(buffer, bytes); + } + + size_t MWSoundDecoderBridge::getSampleOffset() + { + return mDecoder->getSampleOffset(); + } + + + + boost::shared_ptr MovieAudioFactory::createDecoder(Video::VideoState* videoState) + { + boost::shared_ptr decoder(new MWSound::MovieAudioDecoder(videoState)); + decoder->setupFormat(); + + MWBase::SoundPtr sound = MWBase::Environment::get().getSoundManager()->playTrack(decoder->mDecoderBridge, MWBase::SoundManager::Play_TypeMovie); + if (!sound.get()) + { + decoder.reset(); + return decoder; + } + + decoder->mAudioTrack = sound; + return decoder; + } + +} diff --git a/apps/openmw/mwsound/movieaudiofactory.hpp b/apps/openmw/mwsound/movieaudiofactory.hpp new file mode 100644 index 000000000..a3c602197 --- /dev/null +++ b/apps/openmw/mwsound/movieaudiofactory.hpp @@ -0,0 +1,16 @@ +#ifndef OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H +#define OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H + +#include + +namespace MWSound +{ + + class MovieAudioFactory : public Video::MovieAudioFactory + { + virtual boost::shared_ptr createDecoder(Video::VideoState* videoState); + }; + +} + +#endif diff --git a/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt b/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt new file mode 100644 index 000000000..a864659f7 --- /dev/null +++ b/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt @@ -0,0 +1,38 @@ +set(OGRE_FFMPEG_VIDEOPLAYER_LIBRARY "ogre-ffmpeg-videoplayer") + +# Sources + +set(OGRE_FFMPEG_VIDEOPLAYER_SOURCE_FILES + videoplayer.cpp + videostate.cpp + videodefs.hpp + libavwrapper.cpp + audiodecoder.cpp +) + + +# Find FFMPEG +set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE AVRESAMPLE) +unset(FFMPEG_LIBRARIES CACHE) +find_package(FFmpeg) +if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND ) + message(FATAL_ERROR "FFmpeg component required, but not found!") +endif() +set(VIDEO_FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES}) +if( SWRESAMPLE_FOUND ) + add_definitions(-DHAVE_LIBSWRESAMPLE) + set(VIDEO_FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWRESAMPLE_LIBRARIES}) +else() + if( AVRESAMPLE_FOUND ) + set(VIDEO_FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${AVRESAMPLE_LIBRARIES}) + else() + message(FATAL_ERROR "Install either libswresample (FFmpeg) or libavresample (Libav).") + endif() +endif() + +include_directories(${FFMPEG_INCLUDE_DIRS}) + +add_library(${OGRE_FFMPEG_VIDEOPLAYER_LIBRARY} STATIC ${OGRE_FFMPEG_VIDEOPLAYER_SOURCE_FILES}) +target_link_libraries(${OGRE_FFMPEG_VIDEOPLAYER_LIBRARY} ${VIDEO_FFMPEG_LIBRARIES}) + +link_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/extern/ogre-ffmpeg-videoplayer/License.txt b/extern/ogre-ffmpeg-videoplayer/License.txt new file mode 100644 index 000000000..49f534065 --- /dev/null +++ b/extern/ogre-ffmpeg-videoplayer/License.txt @@ -0,0 +1,9 @@ +Copyright (c) 2014 Jannik Heller , Chris Robinson + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + diff --git a/extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp b/extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp new file mode 100644 index 000000000..ad6439e8b --- /dev/null +++ b/extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp @@ -0,0 +1,307 @@ +#include "audiodecoder.hpp" + + +extern "C" +{ + + #include + + #ifdef HAVE_LIBSWRESAMPLE + #include + #else + // FIXME: remove this section once libswresample is packaged for Debian + #include + #include + #define SwrContext AVAudioResampleContext + int swr_init(AVAudioResampleContext *avr); + void swr_free(AVAudioResampleContext **avr); + int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples); + AVAudioResampleContext * swr_alloc_set_opts( AVAudioResampleContext *avr, int64_t out_ch_layout, AVSampleFormat out_fmt, int out_rate, int64_t in_ch_layout, AVSampleFormat in_fmt, int in_rate, int o, void* l); + #endif + + #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) + #define av_frame_alloc avcodec_alloc_frame + #endif + + +} + +#include "videostate.hpp" + +namespace +{ + void fail(const std::string &str) + { + throw std::runtime_error(str); + } + + const double AUDIO_DIFF_AVG_NB = 20; +} + +namespace Video +{ + +MovieAudioDecoder::MovieAudioDecoder(VideoState* videoState) + : mVideoState(videoState) + , mAVStream(*videoState->audio_st) + , mFrame(av_frame_alloc()) + , 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) + , mSwr(0) + , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) + , mOutputSampleRate(0) + , mOutputChannelLayout(0) + , mDataBuf(NULL) + , mFrameData(NULL) + , mDataBufLen(0) +{ +} + +MovieAudioDecoder::~MovieAudioDecoder() +{ + av_freep(&mFrame); + swr_free(&mSwr); + av_freep(&mDataBuf); +} + +void MovieAudioDecoder::setupFormat() +{ + if (mSwr) + return; // already set up + + AVSampleFormat inputSampleFormat = mAVStream->codec->sample_fmt; + + uint64_t inputChannelLayout = mAVStream->codec->channel_layout; + if (inputChannelLayout == 0) + { + /* Unknown channel layout. Try to guess. */ + if(mAVStream->codec->channels == 1) + inputChannelLayout = AV_CH_LAYOUT_MONO; + else if(mAVStream->codec->channels == 2) + inputChannelLayout = AV_CH_LAYOUT_STEREO; + else + { + std::stringstream sstr("Unsupported raw channel count: "); + sstr << mAVStream->codec->channels; + fail(sstr.str()); + } + } + + int inputSampleRate = mAVStream->codec->sample_rate; + + mOutputSampleRate = inputSampleRate; + mOutputSampleFormat = inputSampleFormat; + mOutputChannelLayout = inputChannelLayout; + adjustAudioSettings(mOutputSampleFormat, mOutputChannelLayout, mOutputSampleRate); + + if (inputSampleFormat != mOutputSampleFormat + || inputChannelLayout != mOutputChannelLayout + || inputSampleRate != mOutputSampleRate) + { + mSwr = swr_alloc_set_opts(mSwr, + mOutputChannelLayout, + mOutputSampleFormat, + mOutputSampleRate, + inputChannelLayout, + inputSampleFormat, + inputSampleRate, + 0, // logging level offset + NULL); // log context + if(!mSwr) + fail(std::string("Couldn't allocate SwrContext")); + if(swr_init(mSwr) < 0) + fail(std::string("Couldn't initialize SwrContext")); + } +} + +int MovieAudioDecoder::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 MovieAudioDecoder::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; + + if(mSwr) + { + if(!mDataBuf || mDataBufLen < frame->nb_samples) + { + av_freep(&mDataBuf); + if(av_samples_alloc(&mDataBuf, NULL, mAVStream->codec->channels, + frame->nb_samples, mOutputSampleFormat, 0) < 0) + break; + else + mDataBufLen = frame->nb_samples; + } + + if(swr_convert(mSwr, (uint8_t**)&mDataBuf, frame->nb_samples, + (const uint8_t**)frame->extended_data, frame->nb_samples) < 0) + { + break; + } + mFrameData = &mDataBuf; + } + else + mFrameData = &frame->data[0]; + + 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; + } +} + +size_t MovieAudioDecoder::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); + if(sample_skip > 0 || mFrameSize > -sample_skip) + sample_skip -= mFramePos; + continue; + } + + size_t len1 = len - total; + if(mFramePos >= 0) + { + len1 = std::min(len1, mFrameSize-mFramePos); + memcpy(stream, mFrameData[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, *mFrameData[0], len1); + else if(n == 2) + { + const int16_t val = *((int16_t*)mFrameData[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*)mFrameData[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*)mFrameData[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, mFrameData[0], n); + } + } + + total += len1; + stream += len1; + mFramePos += len1; + } + + return total; +} + +double MovieAudioDecoder::getAudioClock() +{ + return mAudioClock; +} + +int MovieAudioDecoder::getOutputSampleRate() const +{ + return mOutputSampleRate; +} + +uint64_t MovieAudioDecoder::getOutputChannelLayout() const +{ + return mOutputChannelLayout; +} + +AVSampleFormat MovieAudioDecoder::getOutputSampleFormat() const +{ + return mOutputSampleFormat; +} + +} diff --git a/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp b/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp new file mode 100644 index 000000000..7dcc4ec5d --- /dev/null +++ b/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp @@ -0,0 +1,108 @@ +#ifndef VIDEOPLAYER_AUDIODECODER_H +#define VIDEOPLAYER_AUDIODECODER_H + +#ifndef __STDC_CONSTANT_MACROS +#define __STDC_CONSTANT_MACROS +#endif +#include + +#include + +extern "C" +{ + #include + #include + #include + + #if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ + LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) + #include + #endif + + struct SwrContext; +} + +#ifdef _WIN32 +#include + +typedef SSIZE_T ssize_t; +#endif + +namespace Video +{ + +struct VideoState; + +class MovieAudioDecoder +{ +protected: + VideoState *mVideoState; + AVStream *mAVStream; + enum AVSampleFormat mOutputSampleFormat; + uint64_t mOutputChannelLayout; + int mOutputSampleRate; + ssize_t mFramePos; + ssize_t mFrameSize; + double mAudioClock; + +private: + struct AutoAVPacket : public AVPacket { + AutoAVPacket(int size=0) + { + if(av_new_packet(this, size) < 0) + throw std::bad_alloc(); + } + ~AutoAVPacket() + { av_free_packet(this); } + }; + + + SwrContext *mSwr; + + uint8_t *mDataBuf; + uint8_t **mFrameData; + int mDataBufLen; + + AutoAVPacket mPacket; + AVFrame *mFrame; + + /* 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(); + + int audio_decode_frame(AVFrame *frame); + +public: + MovieAudioDecoder(VideoState *is); + virtual ~MovieAudioDecoder(); + + int getOutputSampleRate() const; + AVSampleFormat getOutputSampleFormat() const; + uint64_t getOutputChannelLayout() const; + + void setupFormat(); + + /// Adjust the given audio settings to the application's needs. The data given by the read() function will + /// be in the desired format written to this function's parameters. + /// @par Depending on the application, we may want either fixed settings, or a "closest supported match" + /// for the input that does not incur precision loss (e.g. planar -> non-planar format). + virtual void adjustAudioSettings(AVSampleFormat& sampleFormat, uint64_t& channelLayout, int& sampleRate) = 0; + + /// Return the current offset in seconds from the beginning of the audio stream. + /// @par An internal clock exists in the mAudioClock member, and is used in the default implementation. However, + /// for an accurate clock, it's best to also take the current offset in the audio buffer into account. + virtual double getAudioClock(); + + /// This is the main interface to be used by the user's audio library. + size_t read(char *stream, size_t len); +}; + +} + +#endif diff --git a/extern/ogre-ffmpeg-videoplayer/audiofactory.hpp b/extern/ogre-ffmpeg-videoplayer/audiofactory.hpp new file mode 100644 index 000000000..29a62fcf7 --- /dev/null +++ b/extern/ogre-ffmpeg-videoplayer/audiofactory.hpp @@ -0,0 +1,20 @@ +#ifndef VIDEO_MOVIEAUDIOFACTORY_H +#define VIDEO_MOVIEAUDIOFACTORY_H + +#include "audiodecoder.hpp" + +#include + +namespace Video +{ + +class MovieAudioFactory +{ +public: + /// @note The ownership of the created decoder is passed to the caller. + virtual boost::shared_ptr createDecoder(VideoState* videoState) = 0; +}; + +} + +#endif diff --git a/extern/ogre-ffmpeg-videoplayer/libavwrapper.cpp b/extern/ogre-ffmpeg-videoplayer/libavwrapper.cpp new file mode 100644 index 000000000..6edc36207 --- /dev/null +++ b/extern/ogre-ffmpeg-videoplayer/libavwrapper.cpp @@ -0,0 +1,113 @@ +// This file is a wrapper around the libavresample library (the API-incompatible swresample replacement in the libav fork of ffmpeg), to make it look like swresample to the user. + +#ifndef HAVE_LIBSWRESAMPLE +extern "C" +{ +#ifndef __STDC_CONSTANT_MACROS +#define __STDC_CONSTANT_MACROS +#endif +#include + +#include +#include +// From libavutil version 52.2.0 and onward the declaration of +// AV_CH_LAYOUT_* is removed from libavcodec/avcodec.h and moved to +// libavutil/channel_layout.h +#if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ + LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) + #include +#endif +#include +#include + +/* FIXME: delete this file once libswresample is packaged for Debian */ + +int swr_init(AVAudioResampleContext *avr) { return 1; } + +void swr_free(AVAudioResampleContext **avr) { avresample_free(avr); } + +int swr_convert( + AVAudioResampleContext *avr, + uint8_t** output, + int out_samples, + const uint8_t** input, + int in_samples) +{ + // FIXME: potential performance hit + int out_plane_size = 0; + int in_plane_size = 0; + return avresample_convert(avr, output, out_plane_size, out_samples, + (uint8_t **)input, in_plane_size, in_samples); +} + +AVAudioResampleContext * swr_alloc_set_opts( + AVAudioResampleContext *avr, + int64_t out_ch_layout, + AVSampleFormat out_fmt, + int out_rate, + int64_t in_ch_layout, + AVSampleFormat in_fmt, + int in_rate, + int o, + void* l) +{ + avr = avresample_alloc_context(); + if(!avr) + return 0; + + int res; + res = av_opt_set_int(avr, "out_channel_layout", out_ch_layout, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_ch_layout = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "out_sample_fmt", out_fmt, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_fmt = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "out_sample_rate", out_rate, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_rate = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "in_channel_layout", in_ch_layout, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_ch_layout = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "in_sample_fmt", in_fmt, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_fmt = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "in_sample_rate", in_rate, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_rate = %d\n", res); + return 0; + } + res = av_opt_set_int(avr, "internal_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); + if(res < 0) + { + av_log(avr, AV_LOG_ERROR, "av_opt_set_int: internal_sample_fmt\n"); + return 0; + } + + + if(avresample_open(avr) < 0) + { + av_log(avr, AV_LOG_ERROR, "Error opening context\n"); + return 0; + } + else + return avr; +} + +} +#endif diff --git a/extern/ogre-ffmpeg-videoplayer/videodefs.hpp b/extern/ogre-ffmpeg-videoplayer/videodefs.hpp new file mode 100644 index 000000000..5647d0b38 --- /dev/null +++ b/extern/ogre-ffmpeg-videoplayer/videodefs.hpp @@ -0,0 +1,12 @@ +#ifndef VIDEOPLAYER_DEFS_H +#define VIDEOPLAYER_DEFS_H + +enum { + AV_SYNC_AUDIO_MASTER, // Play audio with no frame drops, sync video to audio + AV_SYNC_VIDEO_MASTER, // Play video with no frame drops, sync audio to video + AV_SYNC_EXTERNAL_MASTER, // Sync audio and video to an external clock + + AV_SYNC_DEFAULT = AV_SYNC_EXTERNAL_MASTER +}; + +#endif diff --git a/extern/ogre-ffmpeg-videoplayer/videoplayer.cpp b/extern/ogre-ffmpeg-videoplayer/videoplayer.cpp new file mode 100644 index 000000000..434b676ee --- /dev/null +++ b/extern/ogre-ffmpeg-videoplayer/videoplayer.cpp @@ -0,0 +1,95 @@ +#include "videoplayer.hpp" + +#include "videostate.hpp" + +namespace Video +{ + +VideoPlayer::VideoPlayer() + : mState(NULL) +{ + +} + +VideoPlayer::~VideoPlayer() +{ + if(mState) + close(); +} + +void VideoPlayer::setAudioFactory(MovieAudioFactory *factory) +{ + mAudioFactory.reset(factory); +} + +void VideoPlayer::playVideo(const std::string &resourceName) +{ + if(mState) + close(); + + try { + mState = new VideoState; + mState->setAudioFactory(mAudioFactory.get()); + mState->init(resourceName); + } + catch(std::exception& e) { + std::cerr<< "Failed to play video: "<update()) + close(); + } +} + +std::string VideoPlayer::getTextureName() +{ + std::string name; + if (mState) + name = mState->mTexture->getName(); + return name; +} + +int VideoPlayer::getVideoWidth() +{ + int width=0; + if (mState) + width = mState->mTexture->getWidth(); + return width; +} + +int VideoPlayer::getVideoHeight() +{ + int height=0; + if (mState) + height = mState->mTexture->getHeight(); + return height; +} + +void VideoPlayer::close() +{ + if(mState) + { + mState->deinit(); + + delete mState; + mState = NULL; + } +} + +bool VideoPlayer::isPlaying () +{ + return mState != NULL; +} + +bool VideoPlayer::hasAudioStream() +{ + return mState && mState->audio_st != NULL; +} + +} diff --git a/extern/ogre-ffmpeg-videoplayer/videoplayer.hpp b/extern/ogre-ffmpeg-videoplayer/videoplayer.hpp new file mode 100644 index 000000000..3c0ef15fd --- /dev/null +++ b/extern/ogre-ffmpeg-videoplayer/videoplayer.hpp @@ -0,0 +1,58 @@ +#ifndef VIDEOPLAYER_H +#define VIDEOPLAYER_H + +#include +#include + +#include + +namespace Video +{ + + struct VideoState; + class MovieAudioDecoder; + class MovieAudioFactory; + + /** + * @brief Plays a video on an Ogre texture. + */ + class VideoPlayer + { + public: + VideoPlayer(); + ~VideoPlayer(); + + /// @note Takes ownership of the passed pointer. + void setAudioFactory (MovieAudioFactory* factory); + + /// Return true if a video is currently playing and it has an audio stream. + bool hasAudioStream(); + + /// Play the given video. If a video is already playing, the old video is closed first. + void playVideo (const std::string& resourceName); + + /// This should be called every frame by the user to update the video texture. + void update(); + + /// Stop the currently playing video, if a video is playing. + void close(); + + bool isPlaying(); + + /// Return the texture name of the currently playing video, or "" if no video is playing. + std::string getTextureName(); + /// Return the width of the currently playing video, or 0 if no video is playing. + int getVideoWidth(); + /// Return the height of the currently playing video, or 0 if no video is playing. + int getVideoHeight(); + + + private: + VideoState* mState; + + std::auto_ptr mAudioFactory; + }; + +} + +#endif diff --git a/extern/ogre-ffmpeg-videoplayer/videostate.cpp b/extern/ogre-ffmpeg-videoplayer/videostate.cpp new file mode 100644 index 000000000..c77723421 --- /dev/null +++ b/extern/ogre-ffmpeg-videoplayer/videostate.cpp @@ -0,0 +1,671 @@ +#include "videostate.hpp" + +#ifndef __STDC_CONSTANT_MACROS +#define __STDC_CONSTANT_MACROS +#endif +#include + +// Has to be included *before* ffmpeg, due to a macro collision with ffmpeg (#define PixelFormat in avformat.h - grumble) +#include +#include +#include +#include + +extern "C" +{ + #include + #include + #include + + // From libavformat version 55.0.100 and onward the declaration of av_gettime() is + // removed from libavformat/avformat.h and moved to libavutil/time.h + // https://github.com/FFmpeg/FFmpeg/commit/06a83505992d5f49846c18507a6c3eb8a47c650e + #if AV_VERSION_INT(55, 0, 100) <= AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ + LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO) + #include + #endif + + #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) + #define av_frame_alloc avcodec_alloc_frame + #endif + +} + +#include "videoplayer.hpp" +#include "audiodecoder.hpp" +#include "audiofactory.hpp" + +namespace +{ + const int MAX_AUDIOQ_SIZE = (5 * 16 * 1024); + const int MAX_VIDEOQ_SIZE = (5 * 256 * 1024); +} + +namespace Video +{ + +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) + , video_clock(0.0), sws_context(NULL), rgbaFrame(NULL), pictq_size(0) + , pictq_rindex(0), pictq_windex(0) + , quit(false) + , mAudioFactory(NULL) +{ + // Register all formats and codecs + av_register_all(); +} + +VideoState::~VideoState() +{ + deinit(); +} + +void VideoState::setAudioFactory(MovieAudioFactory *factory) +{ + mAudioFactory = factory; +} + + +void PacketQueue::put(AVPacket *pkt) +{ + AVPacketList *pkt1; + pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList)); + if(!pkt1) throw std::bad_alloc(); + pkt1->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 (); +} + +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_display(VideoPicture *vp) +{ + if((*this->video_st)->codec->width != 0 && (*this->video_st)->codec->height != 0) + { + + if(static_cast(mTexture->getWidth()) != (*this->video_st)->codec->width || + static_cast(mTexture->getHeight()) != (*this->video_st)->codec->height) + { + mTexture->unload(); + mTexture->setWidth((*this->video_st)->codec->width); + mTexture->setHeight((*this->video_st)->codec->height); + mTexture->createInternalResources(); + } + Ogre::PixelBox pb((*this->video_st)->codec->width, (*this->video_st)->codec->height, 1, Ogre::PF_BYTE_RGBA, &vp->data[0]); + Ogre::HardwarePixelBufferSharedPtr buffer = mTexture->getBuffer(); + buffer->blitFromMemory(pb); + } +} + +void VideoState::video_refresh() +{ + if(this->pictq_size == 0) + return; + + if (this->av_sync_type == AV_SYNC_VIDEO_MASTER) + { + VideoPicture* vp = &this->pictq[this->pictq_rindex]; + this->video_display(vp); + this->pictq_rindex = (pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE; + this->frame_last_pts = vp->pts; + this->pictq_mutex.lock(); + this->pictq_size--; + this->pictq_cond.notify_one(); + this->pictq_mutex.unlock(); + } + else + { + const float threshold = 0.03; + if (this->pictq[pictq_rindex].pts > this->get_master_clock() + threshold) + return; // not ready yet to show this picture + + // TODO: the conversion to RGBA is done in the decoding thread, so if a picture is skipped here, then it was + // unnecessarily converted. But we may want to replace the conversion by a pixel shader anyway (see comment in queue_picture) + int i=0; + for (; ipictq_size-1; ++i) + { + if (this->pictq[pictq_rindex].pts + threshold <= this->get_master_clock()) + this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE; // not enough time to show this picture + else + break; + } + + VideoPicture* vp = &this->pictq[this->pictq_rindex]; + + this->video_display(vp); + + this->frame_last_pts = vp->pts; + + this->pictq_mutex.lock(); + this->pictq_size -= i; + // update queue for next picture + this->pictq_size--; + this->pictq_rindex++; + 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 + // TODO: we could do this in a pixel shader instead, if the source format + // matches a commonly used format (ie YUV420P) + 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 = static_cast(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; + + pFrame = av_frame_alloc(); + + self->rgbaFrame = av_frame_alloc(); + 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"); + + double 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 && self->audioq.size > MAX_AUDIOQ_SIZE) || + (self->video_st && 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 && self->pictq_size == 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() +{ + if(this->quit) + return false; + + this->video_refresh(); + return true; +} + + +int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) +{ + 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; + + if (!mAudioFactory) + { + std::cerr << "No audio factory registered, can not play audio stream" << std::endl; + avcodec_close((*this->audio_st)->codec); + this->audio_st = NULL; + return -1; + } + + mAudioDecoder = mAudioFactory->createDecoder(this); + if (!mAudioDecoder.get()) + { + std::cerr << "Failed to create audio decoder, can not play audio stream" << std::endl; + avcodec_close((*this->audio_st)->codec); + this->audio_st = NULL; + return -1; + } + mAudioDecoder->setupFormat(); + break; + + case AVMEDIA_TYPE_VIDEO: + this->video_st = pFormatCtx->streams + stream_index; + + codecCtx->get_buffer = our_get_buffer; + codecCtx->release_buffer = our_release_buffer; + this->video_thread = boost::thread(video_thread_loop, 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->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 + /// + /// format_ctx->pb->buffer must be freed by hand, + /// if not, valgrind will show memleak, see: + /// + /// https://trac.ffmpeg.org/ticket/1357 + /// + if(!this->format_ctx || avformat_open_input(&this->format_ctx, resourceName.c_str(), NULL, NULL)) + { + if (this->format_ctx != NULL) + { + if (this->format_ctx->pb != NULL) + { + av_free(this->format_ctx->pb->buffer); + this->format_ctx->pb->buffer = NULL; + + av_free(this->format_ctx->pb); + this->format_ctx->pb = 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); + + int width = (*this->video_st)->codec->width; + int height = (*this->video_st)->codec->height; + static int i = 0; + this->mTexture = Ogre::TextureManager::getSingleton().createManual( + "ffmpeg/VideoTexture" + Ogre::StringConverter::toString(++i), + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + width, height, + 0, + Ogre::PF_BYTE_RGBA, + Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); + + // initialize to (0,0,0,0) + std::vector buffer; + buffer.resize(width * height, 0); + Ogre::PixelBox pb(width, height, 1, Ogre::PF_BYTE_RGBA, &buffer[0]); + this->mTexture->getBuffer()->blitFromMemory(pb); + } + + + this->parse_thread = boost::thread(decode_thread_loop, this); +} + +void VideoState::deinit() +{ + this->quit = true; + + mAudioDecoder.reset(); + + this->audioq.cond.notify_one(); + this->videoq.cond.notify_one(); + + if (this->parse_thread.joinable()) + this->parse_thread.join(); + if (this->video_thread.joinable()) + this->video_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) + { + /// + /// format_ctx->pb->buffer must be freed by hand, + /// if not, valgrind will show memleak, see: + /// + /// https://trac.ffmpeg.org/ticket/1357 + /// + if (this->format_ctx->pb != NULL) + { + av_free(this->format_ctx->pb->buffer); + this->format_ctx->pb->buffer = NULL; + + av_free(this->format_ctx->pb); + this->format_ctx->pb = NULL; + } + avformat_close_input(&this->format_ctx); + } +} + +double VideoState::get_external_clock() +{ + return ((uint64_t)av_gettime()-this->external_clock_base) / 1000000.0; +} + +double VideoState::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(); +} + +double VideoState::get_video_clock() +{ + return this->frame_last_pts; +} + +double VideoState::get_audio_clock() +{ + if (!mAudioDecoder.get()) + return 0.0; + return mAudioDecoder->getAudioClock(); +} + +} + diff --git a/extern/ogre-ffmpeg-videoplayer/videostate.hpp b/extern/ogre-ffmpeg-videoplayer/videostate.hpp new file mode 100644 index 000000000..90ebec0a3 --- /dev/null +++ b/extern/ogre-ffmpeg-videoplayer/videostate.hpp @@ -0,0 +1,125 @@ +#ifndef VIDEOPLAYER_VIDEOSTATE_H +#define VIDEOPLAYER_VIDEOSTATE_H + +#include + +#include + +#include "videodefs.hpp" + +#define VIDEO_PICTURE_QUEUE_SIZE 50 + +extern "C" +{ + struct SwsContext; + struct AVPacketList; + struct AVPacket; + struct AVFormatContext; + struct AVStream; + struct AVFrame; +} + +namespace Video +{ + +struct VideoState; + +class MovieAudioFactory; +class MovieAudioDecoder; + +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(); + ~VideoState(); + + void setAudioFactory(MovieAudioFactory* factory); + + void init(const std::string& resourceName); + void deinit(); + + int stream_open(int stream_index, AVFormatContext *pFormatCtx); + + bool update(); + + static void video_thread_loop(VideoState *is); + static void decode_thread_loop(VideoState *is); + + void video_display(VideoPicture* vp); + void video_refresh(); + + int queue_picture(AVFrame *pFrame, double pts); + double synchronize_video(AVFrame *src_frame, double pts); + + double get_audio_clock(); + double get_video_clock(); + double get_external_clock(); + double get_master_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::TexturePtr mTexture; + + MovieAudioFactory* mAudioFactory; + boost::shared_ptr mAudioDecoder; + + Ogre::DataStreamPtr stream; + AVFormatContext* format_ctx; + + int av_sync_type; + uint64_t external_clock_base; + + AVStream** audio_st; + PacketQueue audioq; + + AVStream** video_st; + double frame_last_pts; + double video_clock; /// Date: Wed, 22 Oct 2014 22:41:05 +0200 Subject: [PATCH 2/4] Add missing header to CMakeLists, remove stale comment --- extern/ogre-ffmpeg-videoplayer/CMakeLists.txt | 1 + extern/ogre-ffmpeg-videoplayer/audiofactory.hpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt b/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt index a864659f7..f34ffa64b 100644 --- a/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt +++ b/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt @@ -8,6 +8,7 @@ set(OGRE_FFMPEG_VIDEOPLAYER_SOURCE_FILES videodefs.hpp libavwrapper.cpp audiodecoder.cpp + audiofactory.hpp ) diff --git a/extern/ogre-ffmpeg-videoplayer/audiofactory.hpp b/extern/ogre-ffmpeg-videoplayer/audiofactory.hpp index 29a62fcf7..cf1ff46a8 100644 --- a/extern/ogre-ffmpeg-videoplayer/audiofactory.hpp +++ b/extern/ogre-ffmpeg-videoplayer/audiofactory.hpp @@ -11,7 +11,6 @@ namespace Video class MovieAudioFactory { public: - /// @note The ownership of the created decoder is passed to the caller. virtual boost::shared_ptr createDecoder(VideoState* videoState) = 0; }; From a9be8628b9be3e6696ebdd9a43a45344de436c80 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 22 Oct 2014 23:22:22 +0200 Subject: [PATCH 3/4] namespace fix, improve comment --- extern/ogre-ffmpeg-videoplayer/videodefs.hpp | 5 +++++ extern/ogre-ffmpeg-videoplayer/videoplayer.hpp | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/extern/ogre-ffmpeg-videoplayer/videodefs.hpp b/extern/ogre-ffmpeg-videoplayer/videodefs.hpp index 5647d0b38..636b6f8d5 100644 --- a/extern/ogre-ffmpeg-videoplayer/videodefs.hpp +++ b/extern/ogre-ffmpeg-videoplayer/videodefs.hpp @@ -1,6 +1,9 @@ #ifndef VIDEOPLAYER_DEFS_H #define VIDEOPLAYER_DEFS_H +namespace Video +{ + enum { AV_SYNC_AUDIO_MASTER, // Play audio with no frame drops, sync video to audio AV_SYNC_VIDEO_MASTER, // Play video with no frame drops, sync audio to video @@ -9,4 +12,6 @@ enum { AV_SYNC_DEFAULT = AV_SYNC_EXTERNAL_MASTER }; +} + #endif diff --git a/extern/ogre-ffmpeg-videoplayer/videoplayer.hpp b/extern/ogre-ffmpeg-videoplayer/videoplayer.hpp index 3c0ef15fd..750ad02e5 100644 --- a/extern/ogre-ffmpeg-videoplayer/videoplayer.hpp +++ b/extern/ogre-ffmpeg-videoplayer/videoplayer.hpp @@ -4,13 +4,10 @@ #include #include -#include - namespace Video { struct VideoState; - class MovieAudioDecoder; class MovieAudioFactory; /** @@ -22,6 +19,9 @@ namespace Video VideoPlayer(); ~VideoPlayer(); + /// @brief Set the MovieAudioFactory to use. + /// @par This class must be implemented by the user and is responsible for reading the decoded audio data. + /// @note If you do not set up a MovieAudioFactory, then audio streams will be ignored and the video will be played with no sound. /// @note Takes ownership of the passed pointer. void setAudioFactory (MovieAudioFactory* factory); From 2a3627b5a8dca9c3d1abc4343cd4b371cafdd997 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Oct 2014 18:27:00 +0200 Subject: [PATCH 4/4] avresample wrapper fix --- .../ogre-ffmpeg-videoplayer/audiodecoder.cpp | 31 ++++++++++++++----- .../ogre-ffmpeg-videoplayer/audiodecoder.hpp | 7 +++-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp b/extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp index ad6439e8b..54fe2b24f 100644 --- a/extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp +++ b/extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp @@ -41,6 +41,22 @@ namespace namespace Video { +// Moved to implementation file, so that HAVE_SWRESAMPLE is used at library compile time only +struct AudioResampler +{ + AudioResampler() + : mSwr(NULL) + { + } + + ~AudioResampler() + { + swr_free(&mSwr); + } + + SwrContext* mSwr; +}; + MovieAudioDecoder::MovieAudioDecoder(VideoState* videoState) : mVideoState(videoState) , mAVStream(*videoState->audio_st) @@ -53,7 +69,6 @@ MovieAudioDecoder::MovieAudioDecoder(VideoState* videoState) /* Correct audio only if larger error than this */ , mAudioDiffThreshold(2.0 * 0.050/* 50 ms */) , mAudioDiffAvgCount(0) - , mSwr(0) , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) , mOutputSampleRate(0) , mOutputChannelLayout(0) @@ -61,18 +76,18 @@ MovieAudioDecoder::MovieAudioDecoder(VideoState* videoState) , mFrameData(NULL) , mDataBufLen(0) { + mAudioResampler.reset(new AudioResampler()); } MovieAudioDecoder::~MovieAudioDecoder() { av_freep(&mFrame); - swr_free(&mSwr); av_freep(&mDataBuf); } void MovieAudioDecoder::setupFormat() { - if (mSwr) + if (mAudioResampler->mSwr) return; // already set up AVSampleFormat inputSampleFormat = mAVStream->codec->sample_fmt; @@ -104,7 +119,7 @@ void MovieAudioDecoder::setupFormat() || inputChannelLayout != mOutputChannelLayout || inputSampleRate != mOutputSampleRate) { - mSwr = swr_alloc_set_opts(mSwr, + mAudioResampler->mSwr = swr_alloc_set_opts(mAudioResampler->mSwr, mOutputChannelLayout, mOutputSampleFormat, mOutputSampleRate, @@ -113,9 +128,9 @@ void MovieAudioDecoder::setupFormat() inputSampleRate, 0, // logging level offset NULL); // log context - if(!mSwr) + if(!mAudioResampler->mSwr) fail(std::string("Couldn't allocate SwrContext")); - if(swr_init(mSwr) < 0) + if(swr_init(mAudioResampler->mSwr) < 0) fail(std::string("Couldn't initialize SwrContext")); } } @@ -171,7 +186,7 @@ int MovieAudioDecoder::audio_decode_frame(AVFrame *frame) if(!got_frame || frame->nb_samples <= 0) continue; - if(mSwr) + if(mAudioResampler->mSwr) { if(!mDataBuf || mDataBufLen < frame->nb_samples) { @@ -183,7 +198,7 @@ int MovieAudioDecoder::audio_decode_frame(AVFrame *frame) mDataBufLen = frame->nb_samples; } - if(swr_convert(mSwr, (uint8_t**)&mDataBuf, frame->nb_samples, + if(swr_convert(mAudioResampler->mSwr, (uint8_t**)&mDataBuf, frame->nb_samples, (const uint8_t**)frame->extended_data, frame->nb_samples) < 0) { break; diff --git a/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp b/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp index 7dcc4ec5d..88406d51d 100644 --- a/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp +++ b/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp @@ -7,6 +7,7 @@ #include #include +#include extern "C" { @@ -18,8 +19,6 @@ extern "C" LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) #include #endif - - struct SwrContext; } #ifdef _WIN32 @@ -31,6 +30,8 @@ typedef SSIZE_T ssize_t; namespace Video { +struct AudioResampler; + struct VideoState; class MovieAudioDecoder @@ -57,7 +58,7 @@ private: }; - SwrContext *mSwr; + std::auto_ptr mAudioResampler; uint8_t *mDataBuf; uint8_t **mFrameData;