forked from teamnwah/openmw-tes3coop
video playback
parent
5bdc7bcacf
commit
73c69e8eda
@ -0,0 +1,395 @@
|
||||
#include "videoplayer.hpp"
|
||||
|
||||
//#ifdef OPENMW_USE_FFMPEG
|
||||
|
||||
#include <OgreMaterialManager.h>
|
||||
#include <OgreSceneManager.h>
|
||||
#include <OgreMaterial.h>
|
||||
#include <OgreHardwarePixelBuffer.h>
|
||||
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
int OgreResource_Read(void *opaque, uint8_t *buf, int buf_size)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = *((Ogre::DataStreamPtr*)opaque);
|
||||
|
||||
int num_read = stream->size() - stream->tell();
|
||||
|
||||
if (num_read > buf_size)
|
||||
num_read = buf_size;
|
||||
|
||||
stream->read(buf, num_read);
|
||||
return num_read;
|
||||
}
|
||||
|
||||
int OgreResource_Write(void *opaque, uint8_t *buf, int buf_size)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = *((Ogre::DataStreamPtr*)opaque);
|
||||
|
||||
int num_write = stream->size() - stream->tell();
|
||||
|
||||
if (num_write > buf_size)
|
||||
num_write = buf_size;
|
||||
|
||||
stream->write (buf, num_write);
|
||||
return num_write;
|
||||
}
|
||||
|
||||
int64_t OgreResource_Seek(void *opaque, int64_t offset, int whence)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = *((Ogre::DataStreamPtr*)opaque);
|
||||
|
||||
switch (whence)
|
||||
{
|
||||
case SEEK_SET:
|
||||
stream->seek(offset);
|
||||
case SEEK_CUR:
|
||||
stream->seek(stream->tell() + offset);
|
||||
case SEEK_END:
|
||||
stream->seek(stream->size() + offset);
|
||||
case AVSEEK_SIZE:
|
||||
return stream->size();
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
return stream->tell();
|
||||
}
|
||||
|
||||
VideoPlayer::VideoPlayer(Ogre::SceneManager *sceneMgr)
|
||||
: mAvContext(NULL)
|
||||
, mVideoStreamId(-1)
|
||||
{
|
||||
Ogre::MaterialPtr videoMaterial = Ogre::MaterialManager::getSingleton ().create("VideoMaterial", "General");
|
||||
videoMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false);
|
||||
videoMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false);
|
||||
videoMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
|
||||
mTextureUnit = videoMaterial->getTechnique(0)->getPass(0)->createTextureUnitState();
|
||||
|
||||
mRectangle = new Ogre::Rectangle2D(true);
|
||||
mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0);
|
||||
mRectangle->setMaterial("VideoMaterial");
|
||||
mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY+1);
|
||||
// Use infinite AAB to always stay visible
|
||||
Ogre::AxisAlignedBox aabInf;
|
||||
aabInf.setInfinite();
|
||||
mRectangle->setBoundingBox(aabInf);
|
||||
// Attach background to the scene
|
||||
Ogre::SceneNode* node = sceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
node->attachObject(mRectangle);
|
||||
mRectangle->setVisible(false);
|
||||
mRectangle->setVisibilityFlags (0x1);
|
||||
}
|
||||
|
||||
VideoPlayer::~VideoPlayer()
|
||||
{
|
||||
if (mAvContext)
|
||||
deleteContext();
|
||||
|
||||
delete mRectangle;
|
||||
}
|
||||
|
||||
void VideoPlayer::play (const std::string& resourceName)
|
||||
{
|
||||
mStream = Ogre::ResourceGroupManager::getSingleton ().openResource (resourceName);
|
||||
|
||||
|
||||
mVideoStreamId = -1;
|
||||
mEOF = false;
|
||||
|
||||
// if something is already playing, close it
|
||||
if (mAvContext)
|
||||
close();
|
||||
|
||||
mRectangle->setVisible(true);
|
||||
|
||||
MWBase::Environment::get().getWindowManager ()->pushGuiMode (MWGui::GM_Video);
|
||||
|
||||
|
||||
// BASIC INITIALIZATION
|
||||
|
||||
// Load all the decoders
|
||||
av_register_all();
|
||||
|
||||
AVIOContext *ioContext = 0;
|
||||
|
||||
int err = 0;
|
||||
|
||||
mAvContext = avformat_alloc_context();
|
||||
if (!mAvContext)
|
||||
throwError(0);
|
||||
|
||||
ioContext = avio_alloc_context(NULL, 0, 0, &mStream, OgreResource_Read, OgreResource_Write, OgreResource_Seek);
|
||||
if (!ioContext)
|
||||
throwError(0);
|
||||
|
||||
mAvContext->pb = ioContext;
|
||||
|
||||
err = avformat_open_input(&mAvContext, resourceName.c_str(), NULL, NULL);
|
||||
if (err != 0)
|
||||
throwError(err);
|
||||
|
||||
err = avformat_find_stream_info(mAvContext, 0);
|
||||
if (err < 0)
|
||||
throwError(err);
|
||||
|
||||
|
||||
|
||||
|
||||
// INIT VIDEO
|
||||
|
||||
// Find the video stream among the different streams
|
||||
for (unsigned int i = 0; i < mAvContext->nb_streams; i++)
|
||||
{
|
||||
if (mAvContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
|
||||
{
|
||||
mVideoStreamId = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (-1 == mVideoStreamId)
|
||||
throw std::runtime_error("No video stream found in the video");
|
||||
|
||||
// Get the video codec
|
||||
mVideoCodecContext = mAvContext->streams[mVideoStreamId]->codec;
|
||||
if (!mVideoCodecContext)
|
||||
throw std::runtime_error("Stream doesn't have a codec");
|
||||
|
||||
// Get the video decoder
|
||||
mVideoCodec = avcodec_find_decoder(mVideoCodecContext->codec_id);
|
||||
if (NULL == mVideoCodec)
|
||||
throw std::runtime_error("No decoder found");
|
||||
|
||||
// Load the video codec
|
||||
err = avcodec_open2(mVideoCodecContext, mVideoCodec, 0);
|
||||
if (err < 0)
|
||||
throwError (err);
|
||||
|
||||
// Create the frame buffers
|
||||
mRawFrame = avcodec_alloc_frame();
|
||||
mRGBAFrame = avcodec_alloc_frame();
|
||||
if (!mRawFrame || !mRGBAFrame)
|
||||
{
|
||||
throw std::runtime_error("Can't allocate video frames");
|
||||
}
|
||||
|
||||
|
||||
avpicture_alloc ((AVPicture *)mRGBAFrame, PIX_FMT_RGBA, mVideoCodecContext->width, mVideoCodecContext->height);
|
||||
|
||||
|
||||
// Setup the image scaler
|
||||
// All this does is convert from YUV to RGB - note it would be faster to do this in a shader,
|
||||
// but i'm not worried about performance just yet
|
||||
mSwsContext = sws_getContext(mVideoCodecContext->width, mVideoCodecContext->height,
|
||||
mVideoCodecContext->pix_fmt,
|
||||
mVideoCodecContext->width, mVideoCodecContext->height,
|
||||
PIX_FMT_RGBA,
|
||||
SWS_BICUBIC, NULL, NULL, NULL);
|
||||
if (!mSwsContext)
|
||||
throw std::runtime_error("Can't create SWS Context");
|
||||
|
||||
// Get the frame time we need for this video
|
||||
AVRational r = mAvContext->streams[mVideoStreamId]->avg_frame_rate;
|
||||
AVRational r2 = mAvContext->streams[mVideoStreamId]->r_frame_rate;
|
||||
if ((!r.num || !r.den) &&
|
||||
(!r2.num || !r2.den))
|
||||
{
|
||||
std::cerr << "Warning - unable to get the video frame rate. Using standard NTSC frame rate : 29.97 fps." << std::endl;
|
||||
mWantedFrameTime = 1.f / 29.97f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r.num && r.den)
|
||||
mWantedFrameTime = 1.f/((float)r.num / r.den);
|
||||
else
|
||||
mWantedFrameTime = 1.f/((float)r2.num / r2.den);
|
||||
}
|
||||
|
||||
|
||||
mTextureUnit->setTextureName ("");
|
||||
|
||||
if (!Ogre::TextureManager::getSingleton ().getByName("VideoTexture").isNull())
|
||||
Ogre::TextureManager::getSingleton().remove("VideoTexture");
|
||||
|
||||
mVideoTexture = Ogre::TextureManager::getSingleton().createManual(
|
||||
"VideoTexture",
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D,
|
||||
mVideoCodecContext->width, mVideoCodecContext->height,
|
||||
0,
|
||||
Ogre::PF_BYTE_RGBA,
|
||||
Ogre::TU_DEFAULT);
|
||||
|
||||
mTextureUnit->setTextureName ("VideoTexture");
|
||||
|
||||
}
|
||||
|
||||
void VideoPlayer::throwError(int error)
|
||||
{
|
||||
char buffer[4096] = {0};
|
||||
|
||||
if (0 == av_strerror(error, buffer, sizeof(buffer)))
|
||||
{
|
||||
std::stringstream msg;
|
||||
msg << "FFMPEG error: ";
|
||||
msg << buffer << std::endl;
|
||||
throw std::runtime_error(msg.str());
|
||||
}
|
||||
else
|
||||
throw std::runtime_error("Unknown FFMPEG error");
|
||||
}
|
||||
|
||||
bool VideoPlayer::readFrameAndQueue ()
|
||||
{
|
||||
bool ret = true;
|
||||
AVPacket *pkt = NULL;
|
||||
|
||||
// check we're not at eof
|
||||
if (mEOF)
|
||||
ret = false;
|
||||
else
|
||||
{
|
||||
// read frame
|
||||
pkt = (AVPacket *)av_malloc(sizeof(*pkt));
|
||||
int res = av_read_frame(mAvContext, pkt);
|
||||
|
||||
// check we didn't reach eof right now
|
||||
if (res < 0)
|
||||
{
|
||||
mEOF = true;
|
||||
ret = false;
|
||||
av_free(pkt);
|
||||
}
|
||||
else
|
||||
{
|
||||
// When a frame has been read, save it
|
||||
if (!saveFrame(pkt))
|
||||
{
|
||||
// we failed to save it, which means it was unknown type of frame - delete it here
|
||||
av_free_packet(pkt);
|
||||
av_free(pkt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool VideoPlayer::saveFrame(AVPacket* frame)
|
||||
{
|
||||
bool saved = false;
|
||||
|
||||
if (frame->stream_index == mVideoStreamId)
|
||||
{
|
||||
// If it was a video frame...
|
||||
mVideoPacketQueue.push(frame);
|
||||
saved = true;
|
||||
}
|
||||
|
||||
return saved;
|
||||
|
||||
}
|
||||
|
||||
void VideoPlayer::update()
|
||||
{
|
||||
if (!mAvContext)
|
||||
return;
|
||||
|
||||
|
||||
if (!mVideoPacketQueue.size() && mEOF)
|
||||
close();
|
||||
|
||||
Ogre::Timer timer;
|
||||
|
||||
if (!mVideoPacketQueue.size())
|
||||
{
|
||||
if (readFrameAndQueue())
|
||||
decodeFrontFrame();
|
||||
}
|
||||
else
|
||||
decodeFrontFrame();
|
||||
|
||||
mDecodingTime = timer.getMilliseconds ()/1000.f;
|
||||
}
|
||||
|
||||
void VideoPlayer::decodeFrontFrame ()
|
||||
{
|
||||
int didDecodeFrame = 0;
|
||||
|
||||
// Make sure there is something to decode
|
||||
if (!mVideoPacketQueue.size())
|
||||
return;
|
||||
|
||||
// Get the front frame and decode it
|
||||
AVPacket *videoPacket = mVideoPacketQueue.front();
|
||||
int res;
|
||||
res = avcodec_decode_video2(mVideoCodecContext, mRawFrame, &didDecodeFrame, videoPacket);
|
||||
|
||||
if (res < 0 || !didDecodeFrame)
|
||||
throw std::runtime_error ("an error occured while decoding the video frame");
|
||||
|
||||
// Convert the frame to RGB
|
||||
sws_scale(mSwsContext,
|
||||
mRawFrame->data, mRawFrame->linesize,
|
||||
0, mVideoCodecContext->height,
|
||||
mRGBAFrame->data, mRGBAFrame->linesize);
|
||||
|
||||
|
||||
Ogre::HardwarePixelBufferSharedPtr pixelBuffer = mVideoTexture->getBuffer();
|
||||
Ogre::PixelBox pb(mVideoCodecContext->width, mVideoCodecContext->height, 1, Ogre::PF_BYTE_RGBA, mRGBAFrame->data[0]);
|
||||
pixelBuffer->blitFromMemory(pb);
|
||||
|
||||
//m_displayedFrameCount++;
|
||||
av_free_packet(mVideoPacketQueue.front());
|
||||
av_free(mVideoPacketQueue.front());
|
||||
mVideoPacketQueue.pop();
|
||||
}
|
||||
|
||||
void VideoPlayer::close ()
|
||||
{
|
||||
mRectangle->setVisible (false);
|
||||
MWBase::Environment::get().getWindowManager ()->removeGuiMode (MWGui::GM_Video);
|
||||
deleteContext();
|
||||
}
|
||||
|
||||
void VideoPlayer::deleteContext()
|
||||
{
|
||||
while (mVideoPacketQueue.size())
|
||||
{
|
||||
av_free_packet(mVideoPacketQueue.front());
|
||||
av_free(mVideoPacketQueue.front());
|
||||
mVideoPacketQueue.pop();
|
||||
}
|
||||
|
||||
avcodec_close(mVideoCodecContext);
|
||||
|
||||
avpicture_free((AVPicture *)mRGBAFrame);
|
||||
|
||||
if (mRawFrame)
|
||||
av_free(mRawFrame);
|
||||
if (mRGBAFrame)
|
||||
av_free(mRGBAFrame);
|
||||
|
||||
sws_freeContext(mSwsContext);
|
||||
|
||||
avformat_close_input(&mAvContext);
|
||||
|
||||
mAvContext = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//#endif
|
@ -0,0 +1,105 @@
|
||||
#ifndef MWRENDER_VIDEOPLAYER_H
|
||||
#define MWRENDER_VIDEOPLAYER_H
|
||||
|
||||
//#ifdef OPENMW_USE_FFMPEG
|
||||
|
||||
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <OgreDataStream.h>
|
||||
#include <OgreTexture.h>
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
class Rectangle2D;
|
||||
class SceneManager;
|
||||
class TextureUnitState;
|
||||
}
|
||||
|
||||
struct AVFormatContext;
|
||||
struct AVCodecContext;
|
||||
struct AVCodec;
|
||||
struct AVFrame;
|
||||
struct SwsContext;
|
||||
struct AVPacket;
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
class VideoPlayer
|
||||
{
|
||||
public:
|
||||
VideoPlayer(Ogre::SceneManager* sceneMgr);
|
||||
~VideoPlayer();
|
||||
|
||||
void play (const std::string& resourceName);
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
Ogre::Rectangle2D* mRectangle;
|
||||
Ogre::TextureUnitState* mTextureUnit;
|
||||
|
||||
Ogre::DataStreamPtr mStream;
|
||||
|
||||
Ogre::TexturePtr mVideoTexture;
|
||||
|
||||
|
||||
private:
|
||||
|
||||
AVFormatContext* mAvContext;
|
||||
|
||||
|
||||
bool mEOF;
|
||||
|
||||
// VIDEO
|
||||
AVCodecContext* mVideoCodecContext;
|
||||
AVCodec* mVideoCodec;
|
||||
int mVideoStreamId;
|
||||
AVFrame* mRawFrame;
|
||||
AVFrame* mRGBAFrame;
|
||||
SwsContext* mSwsContext;
|
||||
float mWantedFrameTime;
|
||||
float mDecodingTime;
|
||||
std::queue <AVPacket *> mVideoPacketQueue;
|
||||
|
||||
|
||||
bool readFrameAndQueue();
|
||||
bool saveFrame(AVPacket* frame);
|
||||
|
||||
void decodeFrontFrame();
|
||||
|
||||
void close();
|
||||
void deleteContext();
|
||||
|
||||
|
||||
void throwError(int error);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//#else
|
||||
/*
|
||||
|
||||
|
||||
// If FFMPEG not available, dummy implentation that does nothing
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
class VideoPlayer
|
||||
{
|
||||
public:
|
||||
VideoPlayer(Ogre::SceneManager* sceneMgr){}
|
||||
|
||||
void play (const std::string& resourceName) {}
|
||||
void update() {}
|
||||
};
|
||||
|
||||
}
|
||||
*/
|
||||
//#endif
|
||||
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue