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.loadfix
parent
bcb2d714c0
commit
eb1c24ffe6
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,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})
|
@ -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,307 @@
|
||||
#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
|
||||
{
|
||||
|
||||
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<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,108 @@
|
||||
#ifndef VIDEOPLAYER_AUDIODECODER_H
|
||||
#define VIDEOPLAYER_AUDIODECODER_H
|
||||
|
||||
#ifndef __STDC_CONSTANT_MACROS
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
|
||||
#include <new>
|
||||
|
||||
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
|
||||
|
||||
struct SwrContext;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <BaseTsd.h>
|
||||
|
||||
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
|
@ -0,0 +1,20 @@
|
||||
#ifndef VIDEO_MOVIEAUDIOFACTORY_H
|
||||
#define VIDEO_MOVIEAUDIOFACTORY_H
|
||||
|
||||
#include "audiodecoder.hpp"
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
namespace Video
|
||||
{
|
||||
|
||||
class MovieAudioFactory
|
||||
{
|
||||
public:
|
||||
/// @note The ownership of the created decoder is passed to the caller.
|
||||
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,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
|
@ -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>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
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<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
|
Loading…
Reference in New Issue