Merge remote-tracking branch 'upstream/master' into mouse-picking
commit
dd9208afeb
@ -0,0 +1,89 @@
|
||||
#include <components/esm/loaddial.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/store.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "keywordsearch.hpp"
|
||||
|
||||
#include "hypertextparser.hpp"
|
||||
|
||||
namespace MWDialogue
|
||||
{
|
||||
namespace HyperTextParser
|
||||
{
|
||||
std::vector<Token> parseHyperText(const std::string & text)
|
||||
{
|
||||
std::vector<Token> result;
|
||||
size_t pos_end, iteration_pos = 0;
|
||||
for(;;)
|
||||
{
|
||||
size_t pos_begin = text.find('@', iteration_pos);
|
||||
if (pos_begin != std::string::npos)
|
||||
pos_end = text.find('#', pos_begin);
|
||||
|
||||
if (pos_begin != std::string::npos && pos_end != std::string::npos)
|
||||
{
|
||||
if (pos_begin != iteration_pos)
|
||||
tokenizeKeywords(text.substr(iteration_pos, pos_begin - iteration_pos), result);
|
||||
|
||||
std::string link = text.substr(pos_begin + 1, pos_end - pos_begin - 1);
|
||||
result.push_back(Token(link, Token::ExplicitLink));
|
||||
|
||||
iteration_pos = pos_end + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (iteration_pos != text.size())
|
||||
tokenizeKeywords(text.substr(iteration_pos), result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void tokenizeKeywords(const std::string & text, std::vector<Token> & tokens)
|
||||
{
|
||||
const MWWorld::Store<ESM::Dialogue> & dialogs =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
|
||||
|
||||
std::list<std::string> keywordList;
|
||||
for (MWWorld::Store<ESM::Dialogue>::iterator it = dialogs.begin(); it != dialogs.end(); ++it)
|
||||
keywordList.push_back(Misc::StringUtils::lowerCase(it->mId));
|
||||
keywordList.sort(Misc::StringUtils::ciLess);
|
||||
|
||||
KeywordSearch<std::string, int /*unused*/> keywordSearch;
|
||||
KeywordSearch<std::string, int /*unused*/>::Match match;
|
||||
|
||||
for (std::list<std::string>::const_iterator it = keywordList.begin(); it != keywordList.end(); ++it)
|
||||
keywordSearch.seed(*it, 0 /*unused*/);
|
||||
|
||||
for (std::string::const_iterator it = text.begin(); it != text.end() && keywordSearch.search(it, text.end(), match, text.begin()); it = match.mEnd)
|
||||
tokens.push_back(Token(std::string(match.mBeg, match.mEnd), Token::ImplicitKeyword));
|
||||
}
|
||||
|
||||
size_t removePseudoAsterisks(std::string & phrase)
|
||||
{
|
||||
size_t pseudoAsterisksCount = 0;
|
||||
|
||||
if( !phrase.empty() )
|
||||
{
|
||||
std::string::reverse_iterator rit = phrase.rbegin();
|
||||
|
||||
const char specialPseudoAsteriskCharacter = 127;
|
||||
while( rit != phrase.rend() && *rit == specialPseudoAsteriskCharacter )
|
||||
{
|
||||
pseudoAsterisksCount++;
|
||||
++rit;
|
||||
}
|
||||
}
|
||||
|
||||
phrase = phrase.substr(0, phrase.length() - pseudoAsterisksCount);
|
||||
|
||||
return pseudoAsterisksCount;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
#ifndef GAME_MWDIALOGUE_HYPERTEXTPARSER_H
|
||||
#define GAME_MWDIALOGUE_HYPERTEXTPARSER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace MWDialogue
|
||||
{
|
||||
namespace HyperTextParser
|
||||
{
|
||||
struct Token
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
ExplicitLink, // enclosed in @#
|
||||
ImplicitKeyword
|
||||
};
|
||||
|
||||
Token(const std::string & text, Type type) : mText(text), mType(type) {}
|
||||
|
||||
bool isExplicitLink() { return mType == ExplicitLink; }
|
||||
bool isImplicitKeyword() { return mType == ImplicitKeyword; }
|
||||
|
||||
std::string mText;
|
||||
Type mType;
|
||||
};
|
||||
|
||||
// In translations (at least Russian) the links are marked with @#, so
|
||||
// it should be a function to parse it
|
||||
std::vector<Token> parseHyperText(const std::string & text);
|
||||
void tokenizeKeywords(const std::string & text, std::vector<Token> & tokens);
|
||||
size_t removePseudoAsterisks(std::string & phrase);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
@ -1,37 +0,0 @@
|
||||
#ifndef VIDEOPLAYER_H
|
||||
#define VIDEOPLAYER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
@ -0,0 +1,173 @@
|
||||
#include "movieaudiofactory.hpp"
|
||||
|
||||
#include <extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp>
|
||||
#include <extern/ogre-ffmpeg-videoplayer/videostate.hpp>
|
||||
|
||||
#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<MWSoundDecoderBridge> 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<Video::MovieAudioDecoder> MovieAudioFactory::createDecoder(Video::VideoState* videoState)
|
||||
{
|
||||
boost::shared_ptr<MWSound::MovieAudioDecoder> 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
#ifndef OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H
|
||||
#define OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H
|
||||
|
||||
#include <extern/ogre-ffmpeg-videoplayer/audiofactory.hpp>
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
|
||||
class MovieAudioFactory : public Video::MovieAudioFactory
|
||||
{
|
||||
virtual boost::shared_ptr<Video::MovieAudioDecoder> createDecoder(Video::VideoState* videoState);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,91 @@
|
||||
///This file holds the main classes of NIF Records used by everything else.
|
||||
#ifndef OPENMW_COMPONENTS_NIF_BASE_HPP
|
||||
#define OPENMW_COMPONENTS_NIF_BASE_HPP
|
||||
|
||||
#include "record.hpp"
|
||||
#include "niffile.hpp"
|
||||
#include "recordptr.hpp"
|
||||
#include "nifstream.hpp"
|
||||
#include "nifkey.hpp"
|
||||
|
||||
namespace Nif
|
||||
{
|
||||
/** A record that can have extra data. The extra data objects
|
||||
themselves descend from the Extra class, and all the extra data
|
||||
connected to an object form a linked list
|
||||
*/
|
||||
class Extra : public Record
|
||||
{
|
||||
public:
|
||||
ExtraPtr extra;
|
||||
|
||||
void read(NIFStream *nif) { extra.read(nif); }
|
||||
void post(NIFFile *nif) { extra.post(nif); }
|
||||
};
|
||||
|
||||
class Controller : public Record
|
||||
{
|
||||
public:
|
||||
ControllerPtr next;
|
||||
int flags;
|
||||
float frequency, phase;
|
||||
float timeStart, timeStop;
|
||||
ControlledPtr target;
|
||||
|
||||
void read(NIFStream *nif)
|
||||
{
|
||||
next.read(nif);
|
||||
|
||||
flags = nif->getUShort();
|
||||
|
||||
frequency = nif->getFloat();
|
||||
phase = nif->getFloat();
|
||||
timeStart = nif->getFloat();
|
||||
timeStop = nif->getFloat();
|
||||
|
||||
target.read(nif);
|
||||
}
|
||||
|
||||
void post(NIFFile *nif)
|
||||
{
|
||||
Record::post(nif);
|
||||
next.post(nif);
|
||||
target.post(nif);
|
||||
}
|
||||
};
|
||||
|
||||
/// Anything that has a controller
|
||||
class Controlled : public Extra
|
||||
{
|
||||
public:
|
||||
ControllerPtr controller;
|
||||
|
||||
void read(NIFStream *nif)
|
||||
{
|
||||
Extra::read(nif);
|
||||
controller.read(nif);
|
||||
}
|
||||
|
||||
void post(NIFFile *nif)
|
||||
{
|
||||
Extra::post(nif);
|
||||
controller.post(nif);
|
||||
}
|
||||
};
|
||||
|
||||
/// Has name, extra-data and controller
|
||||
class Named : public Controlled
|
||||
{
|
||||
public:
|
||||
std::string name;
|
||||
|
||||
void read(NIFStream *nif)
|
||||
{
|
||||
name = nif->getString();
|
||||
Controlled::read(nif);
|
||||
}
|
||||
};
|
||||
typedef Named NiSequenceStreamHelper;
|
||||
|
||||
} // Namespace
|
||||
#endif
|
@ -0,0 +1,39 @@
|
||||
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
|
||||
audiofactory.hpp
|
||||
)
|
||||
|
||||
|
||||
# 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})
|
@ -0,0 +1,9 @@
|
||||
Copyright (c) 2014 Jannik Heller <scrawl@baseoftrash.de>, 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.
|
||||
|
||||
|
@ -0,0 +1,322 @@
|
||||
#include "audiodecoder.hpp"
|
||||
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#ifdef HAVE_LIBSWRESAMPLE
|
||||
#include <libswresample/swresample.h>
|
||||
#else
|
||||
// FIXME: remove this section once libswresample is packaged for Debian
|
||||
#include <libavresample/avresample.h>
|
||||
#include <libavutil/opt.h>
|
||||
#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
|
||||
{
|
||||
|
||||
// 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)
|
||||
, 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)
|
||||
, mOutputSampleFormat(AV_SAMPLE_FMT_NONE)
|
||||
, mOutputSampleRate(0)
|
||||
, mOutputChannelLayout(0)
|
||||
, mDataBuf(NULL)
|
||||
, mFrameData(NULL)
|
||||
, mDataBufLen(0)
|
||||
{
|
||||
mAudioResampler.reset(new AudioResampler());
|
||||
}
|
||||
|
||||
MovieAudioDecoder::~MovieAudioDecoder()
|
||||
{
|
||||
av_freep(&mFrame);
|
||||
av_freep(&mDataBuf);
|
||||
}
|
||||
|
||||
void MovieAudioDecoder::setupFormat()
|
||||
{
|
||||
if (mAudioResampler->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)
|
||||
{
|
||||
mAudioResampler->mSwr = swr_alloc_set_opts(mAudioResampler->mSwr,
|
||||
mOutputChannelLayout,
|
||||
mOutputSampleFormat,
|
||||
mOutputSampleRate,
|
||||
inputChannelLayout,
|
||||
inputSampleFormat,
|
||||
inputSampleRate,
|
||||
0, // logging level offset
|
||||
NULL); // log context
|
||||
if(!mAudioResampler->mSwr)
|
||||
fail(std::string("Couldn't allocate SwrContext"));
|
||||
if(swr_init(mAudioResampler->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(mAudioResampler->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(mAudioResampler->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<ssize_t>(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<size_t>(len1, mFrameSize-mFramePos);
|
||||
memcpy(stream, mFrameData[0]+mFramePos, len1);
|
||||
}
|
||||
else
|
||||
{
|
||||
len1 = std::min<size_t>(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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
#ifndef VIDEOPLAYER_AUDIODECODER_H
|
||||
#define VIDEOPLAYER_AUDIODECODER_H
|
||||
|
||||
#ifndef __STDC_CONSTANT_MACROS
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
|
||||
#include <new>
|
||||
#include <memory>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
|
||||
LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO)
|
||||
#include <libavutil/channel_layout.h>
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <BaseTsd.h>
|
||||
|
||||
typedef SSIZE_T ssize_t;
|
||||
#endif
|
||||
|
||||
namespace Video
|
||||
{
|
||||
|
||||
struct AudioResampler;
|
||||
|
||||
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); }
|
||||
};
|
||||
|
||||
|
||||
std::auto_ptr<AudioResampler> mAudioResampler;
|
||||
|
||||
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
|
@ -0,0 +1,19 @@
|
||||
#ifndef VIDEO_MOVIEAUDIOFACTORY_H
|
||||
#define VIDEO_MOVIEAUDIOFACTORY_H
|
||||
|
||||
#include "audiodecoder.hpp"
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
namespace Video
|
||||
{
|
||||
|
||||
class MovieAudioFactory
|
||||
{
|
||||
public:
|
||||
virtual boost::shared_ptr<MovieAudioDecoder> createDecoder(VideoState* videoState) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -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 <stdint.h>
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
// 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 <libavutil/channel_layout.h>
|
||||
#endif
|
||||
#include <libavresample/avresample.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
/* 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
|
@ -0,0 +1,17 @@
|
||||
#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
|
||||
AV_SYNC_EXTERNAL_MASTER, // Sync audio and video to an external clock
|
||||
|
||||
AV_SYNC_DEFAULT = AV_SYNC_EXTERNAL_MASTER
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -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: "<<e.what() <<std::endl;
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPlayer::update ()
|
||||
{
|
||||
if(mState)
|
||||
{
|
||||
if(!mState->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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
#ifndef VIDEOPLAYER_H
|
||||
#define VIDEOPLAYER_H
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace Video
|
||||
{
|
||||
|
||||
struct VideoState;
|
||||
class MovieAudioFactory;
|
||||
|
||||
/**
|
||||
* @brief Plays a video on an Ogre texture.
|
||||
*/
|
||||
class VideoPlayer
|
||||
{
|
||||
public:
|
||||
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);
|
||||
|
||||
/// 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<MovieAudioFactory> mAudioFactory;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,671 @@
|
||||
#include "videostate.hpp"
|
||||
|
||||
#ifndef __STDC_CONSTANT_MACROS
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
|
||||
// Has to be included *before* ffmpeg, due to a macro collision with ffmpeg (#define PixelFormat in avformat.h - grumble)
|
||||
#include <OgreTextureManager.h>
|
||||
#include <OgreHardwarePixelBuffer.h>
|
||||
#include <OgreResourceGroupManager.h>
|
||||
#include <OgreStringConverter.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
// 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 <libavutil/time.h>
|
||||
#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<boost::mutex> 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<VideoState*>(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<VideoState*>(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<VideoState*>(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<int>(mTexture->getWidth()) != (*this->video_st)->codec->width ||
|
||||
static_cast<int>(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 (; i<this->pictq_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<boost::mutex> 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<uint64_t>(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<int>(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<Ogre::uint32> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,125 @@
|
||||
#ifndef VIDEOPLAYER_VIDEOSTATE_H
|
||||
#define VIDEOPLAYER_VIDEOSTATE_H
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include <OgreTexture.h>
|
||||
|
||||
#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<uint8_t> 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<MovieAudioDecoder> 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; ///<pts of last decoded frame / predicted pts of next decoded frame
|
||||
PacketQueue videoq;
|
||||
SwsContext* sws_context;
|
||||
VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
|
||||
AVFrame* rgbaFrame; // used as buffer for the frame converted from its native format to RGBA
|
||||
int pictq_size, pictq_rindex, pictq_windex;
|
||||
boost::mutex pictq_mutex;
|
||||
boost::condition_variable pictq_cond;
|
||||
|
||||
|
||||
boost::thread parse_thread;
|
||||
boost::thread video_thread;
|
||||
|
||||
volatile bool quit;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,7 +0,0 @@
|
||||
[Display%20Format]
|
||||
Record%20Status%20Display=Icon Only
|
||||
Referenceable%20ID%20Type%20Display=Text Only
|
||||
|
||||
[Window%20Size]
|
||||
Height=900
|
||||
Width=1440
|
@ -0,0 +1,26 @@
|
||||
\section{Records Modification}
|
||||
|
||||
\subsection{Introduction}
|
||||
So far you learned how to browse trough records stored inside the content files, but not how to modify them using the \OCS{} editor. Although browsing is certainly a usefull ability on it's own, You probabbly counted on doing actual editing with this editor. There are few ways user can alter records stored in the content files, each suited for certain class of a problem. In this section We will describe how to do change records using tables interface and edit panel.
|
||||
|
||||
\subsubsection{Glossary}
|
||||
\begin{description}
|
||||
\item[Edit Panel] Interface element used inside the \OCS{} to present records data for editing. Unlike table it showes only one record at the time. However it also presents fields that are not visible inside the table. It is also safe to say that Edit Panel presents data in way that is easier to read thanks to it's horizontal layout.
|
||||
\end{description}
|
||||
|
||||
\subsection{Edit Panel Interface}
|
||||
Edit Panel is designed to aid you with record modification tasks. As It has been said, it uses vertical layout and presents some additional fields when compared with the table -- and some fields, even if they are actually displayed in the table, clearly ill-suited for modification isnide of them (this applies to fields that holds long text strings -- like descriptions). It also displays visual difference beetween non-editable field and editable.\\
|
||||
To open edit panel, please open context menu on any record and choose edit action. This will open edit panel in the same window as your table and will present you the record fields. First data fields are actually not user editable and presented in the form of the text labels at the top of the edit panel. Lower data fields are presented in the form of actually user-editable widgets. Those includes spinboxes, text edits and text fields\footnote{Those are actually a valid terms used to describe classes of the user interface elements. If you don't understand those, don't worry -- those are very standard {GUI} elements present in almost every application since the rise of the desktop metaphor.}. Once you will finish editing one of those fields, data will be updated. There is no apply button of any sort -- simply use one of those widgets and be merry.\\
|
||||
In addition to that you probabbly noticed some icons in the bar located at the very bottom of the edit panel. Those can be used to perform the following actions:
|
||||
|
||||
\begin{description}
|
||||
\item[Preview] This will launch simple preview panel -- which will be described later.
|
||||
\item[Next] This will switch edit panel to the next record. It is worth noticing that edit panel will skip deleted records.
|
||||
\item[Prev] Do We really need to say what this button does? I guess we should! Well, this will switch edit panel to former record. Deleted records are skipped.
|
||||
\end{description}
|
||||
|
||||
\subsection{Verification tool}
|
||||
As you could notice there is nothing that can stop you from breaking the game by violating record fields logic, and yet -- it is something that you are always trying to avoid. To adress this problem \OCS{} utilizes so called verification tool (or verifer as many prefer to call it) that basicly goes trough all records and checks if it contains any illogical fields. This includes for instance torch duration equal 0\footnote{Interestingly negative values are perfectly fine: they indicate that light source has no duration limit at all. There are records like this in the original game.} or characters without name, race or any other record with a mandatory field missing.\\
|
||||
This tool is even more usefull than it seems. If you somehow delete race that is used by some of the characters, all those characters will be suddenly broken. As a rule of thumb it is a good idea to use verifer before saving your content file.\\
|
||||
To launch this usefull tool %don't remember, todo...
|
||||
Resoults are presented as a yet another table with short (and hopefully descriptive enough) description of the identified problem. It is worth noticing that some records located in the \MW{} esm files are listed by the verification tool -- it is not fault of our tool: those records are just broken. For instance, you actually may find the 0 duration torch. However, those records are usually not placed in game world itself -- and that's good since \MW{} game engine will crash if player equip light source like this!\footnote{We would like to thanks \BS{} for such a usefull testing material. It makes us feel special.}
|
@ -0,0 +1,18 @@
|
||||
\section{Record Types}
|
||||
|
||||
\subsection{Introduction}
|
||||
A gameworld contains many objects, such as chests, weapons and monsters. All these objects are merely instances of templates that we call Referenceables. The OpenCS Referenceables table contains information about each of these template objects, eg. its value and weight in the case of items and an aggression level in the case of NPCs.
|
||||
|
||||
Let's go through all Record Types and discuss what you can tell OpenCS about them.
|
||||
|
||||
\begin{description}
|
||||
\item[Activator:] When the player enters the same cell as this object, a script is started. Often it also has a \textbf{Script} attached to it, though it not mandatory. These scripts are small bits of code written in a special scripting language that OpenCS can read and interpret.
|
||||
\item[Potion:] This is a potion that is not self-made. It has an \textbf{Icon} for your inventory, Aside from the self-explanatory \textbf{Weight} and \textbf{Coin Value}, it has an attribute called \textbf{Auto Calc} set to ``False''. This means that the effects of this potion are preconfigured. This does not happen when the player makes their own potion.
|
||||
\item[Apparatus:] This is a tool to make potions. Again there's an icon for your inventory as well as a weight and a coin value. It also has a \textbf{Quality} value attached to it: higher the number, the better the effect on your potions will be. The \textbf{Apparatus Type} describes if the item is a Calcinator, Retort, Alembir or Mortar & Pestal. Each has a different effect on the potion the player makes. For more information on this subject, please refer to the \href{http://www.uesp.net/wiki/Morrowind:Alchemy#Tools}{UESP page on Alchemy Tools}.
|
||||
\item[Armor:] This type of item adds \textbf{Enchantment Points} to the mix. Every piece of clothing or armor has a ''pool'' of potential magicka that gets unlocked when you enchant it. Strong enchantments consume more magicka from this pool: the stronger the enchantment, the more Enchantment Points each cast will take up. For more information on this subject, please refer to the \href{Enchant page on UESP}{http://www.uesp.net/wiki/Morrowind:Enchant}. \textbf{Health} means the amount of hit points this piece of armor has. If it sustains enough damage, the armor will be destroyed. Finally, \textbf{Armor Value} tells the game how much points to add to the player character's Armor Rating.
|
||||
\item[Book:] This includes scrolls and notes. For the game to make the distinction between books and scrolls, an extra property, \textbf{Scroll}, has been added. Under the \textbf{Skill} column a scroll or book can have an in-game skill listed. Reading this item will raise the player's level in that specific skill. For more information on this, please refer to the \href{Skill Books page on UESP}{http://www.uesp.net/wiki/Morrowind:Skill_Books}.
|
||||
\item[Clothing:] These items work just like Armors, but confer no protective properties. Rather than ``Armor Type'', these items have a ``Clothing Type''.
|
||||
\item[Container:] This is all the stuff that stores items, from chests to sacks to plants. Its \textbf{Capacity} shows how much stuff you can put in the container. You can compare it to the maximum allowed load a player character can carry (who will get over-encumbered and unable to move if he crosses this threshold). A container, however, will just refuse to take the item in question when it gets ''over-encumbered''. \textbf{Organic Container}s are containers such as plants. Containers that \textbf{Respawn} are not safe to store stuff in. After a certain amount of time they will reset to their default contents, meaning that everything in it is gone forever.
|
||||
\item[Creature:] These can be monsters, animals and the like.
|
||||
|
||||
\end{description}
|
Loading…
Reference in New Issue