1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-21 09:53:52 +00:00

video playback

This commit is contained in:
scrawl 2012-09-25 02:35:50 +02:00
parent 5bdc7bcacf
commit 73c69e8eda
13 changed files with 559 additions and 4 deletions

View file

@ -16,7 +16,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER})
add_openmw_dir (mwrender add_openmw_dir (mwrender
renderingmanager debugging sky player animation npcanimation creatureanimation actors objects renderingmanager debugging sky player animation npcanimation creatureanimation actors objects
renderinginterface localmap occlusionquery terrain terrainmaterial water shadows renderinginterface localmap occlusionquery terrain terrainmaterial water shadows
compositors characterpreview externalrendering globalmap compositors characterpreview externalrendering globalmap videoplayer
) )
add_openmw_dir (mwinput add_openmw_dir (mwinput
@ -108,6 +108,11 @@ target_link_libraries(openmw
components components
) )
if (USE_FFMPEG)
target_link_libraries(openmw
"swscale")
endif()
# Fix for not visible pthreads functions for linker with glibc 2.15 # Fix for not visible pthreads functions for linker with glibc 2.15
if (UNIX AND NOT APPLE) if (UNIX AND NOT APPLE)
target_link_libraries(openmw ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(openmw ${CMAKE_THREAD_LIBS_INIT})

View file

@ -291,6 +291,10 @@ namespace MWBase
/// 1 - only waiting \n /// 1 - only waiting \n
/// 2 - player is underwater \n /// 2 - player is underwater \n
/// 3 - enemies are nearby (not implemented) /// 3 - enemies are nearby (not implemented)
/// \todo this does not belong here
virtual void playVideo(const std::string& name) = 0;
}; };
} }

View file

@ -41,7 +41,9 @@ namespace MWGui
GM_Loading, GM_Loading,
GM_LoadingWallpaper, GM_LoadingWallpaper,
GM_QuickKeysMenu GM_QuickKeysMenu,
GM_Video
}; };
// Windows shown in inventory mode // Windows shown in inventory mode

View file

@ -371,6 +371,9 @@ void WindowManager::updateVisible()
case GM_Loading: case GM_Loading:
MyGUI::PointerManager::getInstance().setVisible(false); MyGUI::PointerManager::getInstance().setVisible(false);
break; break;
case GM_Video:
mHud->setVisible(false);
break;
default: default:
// Unsupported mode, switch back to game // Unsupported mode, switch back to game
break; break;

View file

@ -36,6 +36,7 @@
#include "npcanimation.hpp" #include "npcanimation.hpp"
#include "externalrendering.hpp" #include "externalrendering.hpp"
#include "globalmap.hpp" #include "globalmap.hpp"
#include "videoplayer.hpp"
using namespace MWRender; using namespace MWRender;
using namespace Ogre; using namespace Ogre;
@ -159,6 +160,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const
mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode());
mVideoPlayer = new VideoPlayer(mRendering.getScene ());
mSun = 0; mSun = 0;
mDebugging = new Debugging(mMwRoot, engine); mDebugging = new Debugging(mMwRoot, engine);
@ -180,6 +183,7 @@ RenderingManager::~RenderingManager ()
delete mOcclusionQuery; delete mOcclusionQuery;
delete mCompositors; delete mCompositors;
delete mWater; delete mWater;
delete mVideoPlayer;
} }
MWRender::SkyManager* RenderingManager::getSkyManager() MWRender::SkyManager* RenderingManager::getSkyManager()
@ -339,6 +343,8 @@ void RenderingManager::update (float duration)
mRendering.update(duration); mRendering.update(duration);
mVideoPlayer->update ();
MWWorld::RefData &data = MWWorld::RefData &data =
MWBase::Environment::get() MWBase::Environment::get()
.getWorld() .getWorld()
@ -898,4 +904,9 @@ void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rend
rendering.setup (mRendering.getScene()); rendering.setup (mRendering.getScene());
} }
void RenderingManager::playVideo(const std::string& name)
{
mVideoPlayer->play ("video/" + name);
}
} // namespace } // namespace

View file

@ -45,6 +45,7 @@ namespace MWRender
class Compositors; class Compositors;
class ExternalRendering; class ExternalRendering;
class GlobalMap; class GlobalMap;
class VideoPlayer;
class RenderingManager: private RenderingInterface, public Ogre::WindowEventListener { class RenderingManager: private RenderingInterface, public Ogre::WindowEventListener {
@ -195,6 +196,8 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList
void setupExternalRendering (MWRender::ExternalRendering& rendering); void setupExternalRendering (MWRender::ExternalRendering& rendering);
void playVideo(const std::string& name);
protected: protected:
virtual void windowResized(Ogre::RenderWindow* rw); virtual void windowResized(Ogre::RenderWindow* rw);
virtual void windowClosed(Ogre::RenderWindow* rw); virtual void windowClosed(Ogre::RenderWindow* rw);
@ -248,6 +251,8 @@ class RenderingManager: private RenderingInterface, public Ogre::WindowEventList
MWRender::Shadows* mShadows; MWRender::Shadows* mShadows;
MWRender::Compositors* mCompositors; MWRender::Compositors* mCompositors;
VideoPlayer* mVideoPlayer;
}; };
} }

View file

@ -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

View file

@ -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

View file

@ -205,5 +205,6 @@ op 0x200019e: PlaceAtMe Explicit
op 0x200019f: GetPcSleep op 0x200019f: GetPcSleep
op 0x20001a0: ShowMap op 0x20001a0: ShowMap
op 0x20001a1: FillMap op 0x20001a1: FillMap
opcodes 0x20001a2-0x3ffffff unused op 0x20001a2: PlayBink
opcodes 0x20001a3-0x3ffffff unused

View file

@ -21,6 +21,19 @@ namespace MWScript
{ {
namespace Misc namespace Misc
{ {
class OpPlayBink : public Interpreter::Opcode0
{
public:
virtual void execute (Interpreter::Runtime& runtime)
{
std::string name = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
MWBase::Environment::get().getWorld ()->playVideo (name);
}
};
class OpGetPcSleep : public Interpreter::Opcode0 class OpGetPcSleep : public Interpreter::Opcode0
{ {
public: public:
@ -261,6 +274,7 @@ namespace MWScript
const int opcodeDontSaveObject = 0x2000153; const int opcodeDontSaveObject = 0x2000153;
const int opcodeToggleVanityMode = 0x2000174; const int opcodeToggleVanityMode = 0x2000174;
const int opcodeGetPcSleep = 0x200019f; const int opcodeGetPcSleep = 0x200019f;
const int opcodePlayBink = 0x20001a2;
void registerExtensions (Compiler::Extensions& extensions) void registerExtensions (Compiler::Extensions& extensions)
{ {
@ -286,6 +300,7 @@ namespace MWScript
extensions.registerInstruction ("togglevanitymode", "", opcodeToggleVanityMode); extensions.registerInstruction ("togglevanitymode", "", opcodeToggleVanityMode);
extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode); extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode);
extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep); extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep);
extensions.registerInstruction ("playbink", "S", opcodePlayBink);
} }
void installOpcodes (Interpreter::Interpreter& interpreter) void installOpcodes (Interpreter::Interpreter& interpreter)
@ -307,6 +322,7 @@ namespace MWScript
interpreter.installSegment5 (opcodeDontSaveObject, new OpDontSaveObject); interpreter.installSegment5 (opcodeDontSaveObject, new OpDontSaveObject);
interpreter.installSegment5 (opcodeToggleVanityMode, new OpToggleVanityMode); interpreter.installSegment5 (opcodeToggleVanityMode, new OpToggleVanityMode);
interpreter.installSegment5 (opcodeGetPcSleep, new OpGetPcSleep); interpreter.installSegment5 (opcodeGetPcSleep, new OpGetPcSleep);
interpreter.installSegment5 (opcodePlayBink, new OpPlayBink);
} }
} }
} }

View file

@ -1281,4 +1281,9 @@ namespace MWWorld
return 0; return 0;
} }
void World::playVideo (const std::string &name)
{
mRendering->playVideo(name);
}
} }

View file

@ -322,6 +322,9 @@ namespace MWWorld
/// 1 - only waiting \n /// 1 - only waiting \n
/// 2 - player is underwater \n /// 2 - player is underwater \n
/// 3 - enemies are nearby (not implemented) /// 3 - enemies are nearby (not implemented)
/// \todo this does not belong here
virtual void playVideo(const std::string& name);
}; };
} }

View file

@ -72,7 +72,7 @@ compositor gbufferFinalizer
pass render_scene pass render_scene
{ {
first_render_queue 51 first_render_queue 51
last_render_queue 100 last_render_queue 105
} }
} }
target_output target_output