diff options
Diffstat (limited to 'src/player/VideoWriter.cpp')
-rw-r--r-- | src/player/VideoWriter.cpp | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/src/player/VideoWriter.cpp b/src/player/VideoWriter.cpp new file mode 100644 index 0000000..e91db38 --- /dev/null +++ b/src/player/VideoWriter.cpp @@ -0,0 +1,260 @@ +// +// libavg - Media Playback Engine. +// Copyright (C) 2003-2014 Ulrich von Zadow +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// Current versions can be found at www.libavg.de +// + +#include "VideoWriter.h" +#include "OffscreenCanvas.h" +#include "Player.h" +#include "SDLDisplayEngine.h" + +#include "../graphics/FBO.h" +#include "../graphics/GPURGB2YUVFilter.h" +#include "../graphics/Filterfill.h" +#include "../base/StringHelper.h" + +#include <boost/bind.hpp> + +#include <fcntl.h> +#include <stdio.h> + +using namespace std; +using namespace boost; + +namespace avg { + +VideoWriter::VideoWriter(CanvasPtr pCanvas, const string& sOutFileName, int frameRate, + int qMin, int qMax, bool bSyncToPlayback) + : m_pCanvas(pCanvas), + m_sOutFileName(sOutFileName), + m_FrameRate(frameRate), + m_QMin(qMin), + m_QMax(qMax), + m_bHasValidData(false), + m_bSyncToPlayback(bSyncToPlayback), + m_bPaused(false), + m_PauseTime(0), + m_bStopped(false), + m_CurFrame(0), + m_StartTime(-1), + m_bFramePending(false) +{ + if (!pCanvas) { + throw Exception(AVG_ERR_INVALID_ARGS, "VideoWriter needs a canvas to write to."); + } + if (GLContext::getCurrent()->isGLES()) { + throw Exception(AVG_ERR_UNSUPPORTED, "VideoWriter not supported under GLES."); + } +#ifdef WIN32 + int fd = _open(m_sOutFileName.c_str(), O_RDWR | O_CREAT, _S_IREAD | _S_IWRITE); +#elif defined linux + int fd = open64(m_sOutFileName.c_str(), O_RDWR | O_CREAT, S_IRWXU); +#else + int fd = open(m_sOutFileName.c_str(), O_RDWR | O_CREAT, S_IRWXU); +#endif + if (fd == -1) { + throw Exception(AVG_ERR_VIDEO_INIT_FAILED, + string("Could not open output file '") + m_sOutFileName + "'. Reason: " + + strerror(errno)); + } +#ifdef WIN32 + _close(fd); +#else + close(fd); +#endif + remove(m_sOutFileName.c_str()); + CanvasPtr pMainCanvas = Player::get()->getMainCanvas(); + if (pMainCanvas == m_pCanvas) { + m_FrameSize = Player::get()->getDisplayEngine()->getWindowSize(); + } else { + m_FrameSize = m_pCanvas->getSize(); + m_pFBO = dynamic_pointer_cast<OffscreenCanvas>(m_pCanvas)->getFBO(); + if (GLContext::getMain()->useGPUYUVConversion()) { + m_pFilter = GPURGB2YUVFilterPtr(new GPURGB2YUVFilter(m_FrameSize)); + } + } + VideoWriterThread writer(m_CmdQueue, m_sOutFileName, m_FrameSize, m_FrameRate, + qMin, qMax); + m_pThread = new boost::thread(writer); + m_pCanvas->registerPlaybackEndListener(this); + m_pCanvas->registerFrameEndListener(this); +} + +VideoWriter::~VideoWriter() +{ + stop(); + if (m_pThread) { + m_pThread->join(); + delete m_pThread; + } +} + +void VideoWriter::stop() +{ + if (!m_bStopped) { + getFrameFromPBO(); + if (!m_bHasValidData) { + writeDummyFrame(); + } + + m_bStopped = true; + m_CmdQueue.pushCmd(boost::bind(&VideoWriterThread::stop, _1)); + + m_pCanvas->unregisterFrameEndListener(this); + m_pCanvas->unregisterPlaybackEndListener(this); + } +} + +void VideoWriter::pause() +{ + if (m_bPaused) { + throw Exception(AVG_ERR_UNSUPPORTED, "VideoWriter::pause() called when paused."); + } + if (m_bStopped) { + throw Exception(AVG_ERR_UNSUPPORTED, "VideoWriter::pause() called when stopped."); + } + m_bPaused = true; + m_PauseStartTime = Player::get()->getFrameTime(); +} + +void VideoWriter::play() +{ + if (!m_bPaused) { + throw Exception(AVG_ERR_UNSUPPORTED, + "VideoWriter::play() called when not paused."); + } + m_bPaused = false; + m_PauseTime += (Player::get()->getFrameTime() - m_PauseStartTime); +} + +std::string VideoWriter::getFileName() const +{ + return m_sOutFileName; +} + +int VideoWriter::getFramerate() const +{ + return m_FrameRate; +} + +int VideoWriter::getQMin() const +{ + return m_QMin; +} + +int VideoWriter::getQMax() const +{ + return m_QMax; +} + +void VideoWriter::onFrameEnd() +{ + // The VideoWriter handles OffscreenCanvas and MainCanvas differently: + // For MainCanvas, it simply does a screenshot onFrameEnd and sends that to the + // VideoWriterThread immediately. + // For OffscreenCanvas, an asynchronous PBO readback is started in onFrameEnd. + // In the next frame's onFrameEnd, the data is read into a bitmap and sent to + // the VideoWriterThread. + if (m_pFBO) { + // Read last frame's bitmap. + getFrameFromPBO(); + } + if (m_StartTime == -1) { + m_StartTime = Player::get()->getFrameTime(); + } + if (!m_bPaused) { + if (m_bSyncToPlayback) { + getFrameFromFBO(); + } else { + long long movieTime = Player::get()->getFrameTime() - m_StartTime + - m_PauseTime; + float timePerFrame = 1000.f/m_FrameRate; + int wantedFrame = int(movieTime/timePerFrame+0.1); + if (wantedFrame > m_CurFrame) { + getFrameFromFBO(); + if (wantedFrame > m_CurFrame + 1) { + m_CurFrame = wantedFrame - 1; + } + } + } + } + + if (!m_pFBO) { + getFrameFromPBO(); + } +} + +void VideoWriter::getFrameFromFBO() +{ + if (m_pFBO) { + if (m_pFilter) { + m_pFilter->apply(m_pFBO->getTex()); + FBOPtr pYUVFBO = m_pFilter->getFBO(); + pYUVFBO->moveToPBO(); + } else { + m_pFBO->moveToPBO(); + } + m_bFramePending = true; + } else { + BitmapPtr pBmp = Player::get()->getDisplayEngine()->screenshot(GL_BACK); + sendFrameToEncoder(pBmp); + } +} + +void VideoWriter::getFrameFromPBO() +{ + if (m_bFramePending) { + BitmapPtr pBmp; + if (m_pFilter) { + pBmp = m_pFilter->getFBO()->getImageFromPBO(); + } else { + pBmp = m_pFBO->getImageFromPBO(); + } + sendFrameToEncoder(pBmp); + m_bFramePending = false; + } +} + +void VideoWriter::sendFrameToEncoder(BitmapPtr pBitmap) +{ + m_CurFrame++; + m_bHasValidData = true; + if (m_pFilter) { + m_CmdQueue.pushCmd(boost::bind(&VideoWriterThread::encodeYUVFrame, _1, pBitmap)); + } else { + m_CmdQueue.pushCmd(boost::bind(&VideoWriterThread::encodeFrame, _1, pBitmap)); + } +} + +void VideoWriter::onPlaybackEnd() +{ + stop(); + m_pThread->join(); + delete m_pThread; + m_pThread = 0; +} + +void VideoWriter::writeDummyFrame() +{ + BitmapPtr pBmp = BitmapPtr(new Bitmap(m_FrameSize, B8G8R8X8)); + FilterFill<Pixel32>(Pixel32(0,0,0,255)).applyInPlace(pBmp); + sendFrameToEncoder(pBmp); +} + +} |