summaryrefslogtreecommitdiff
path: root/src/video/AsyncVideoDecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/video/AsyncVideoDecoder.cpp')
-rw-r--r--src/video/AsyncVideoDecoder.cpp505
1 files changed, 505 insertions, 0 deletions
diff --git a/src/video/AsyncVideoDecoder.cpp b/src/video/AsyncVideoDecoder.cpp
new file mode 100644
index 0000000..4ba7033
--- /dev/null
+++ b/src/video/AsyncVideoDecoder.cpp
@@ -0,0 +1,505 @@
+//
+// 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 "AsyncVideoDecoder.h"
+
+#ifdef AVG_ENABLE_VDPAU
+#include "VDPAUDecoder.h"
+#include "VDPAUHelper.h"
+#endif
+
+#include "../base/ObjectCounter.h"
+#include "../base/Exception.h"
+#include "../base/ScopeTimer.h"
+
+#include <boost/thread/thread.hpp>
+#include <boost/bind.hpp>
+
+#include <math.h>
+#include <iostream>
+
+using namespace std;
+using boost::dynamic_pointer_cast;
+
+#define AUDIO_MSG_QUEUE_LENGTH 50
+#define AUDIO_STATUS_QUEUE_LENGTH -1
+#define PACKET_QUEUE_LENGTH 50
+
+namespace avg {
+
+AsyncVideoDecoder::AsyncVideoDecoder(int queueLength)
+ : m_QueueLength(queueLength),
+ m_pDemuxThread(0),
+ m_pVDecoderThread(0),
+ m_pADecoderThread(0),
+ m_bUseStreamFPS(true),
+ m_FPS(0)
+{
+ ObjectCounter::get()->incRef(&typeid(*this));
+}
+
+AsyncVideoDecoder::~AsyncVideoDecoder()
+{
+ if (m_pVDecoderThread || m_pADecoderThread) {
+ close();
+ }
+ ObjectCounter::get()->decRef(&typeid(*this));
+}
+
+void AsyncVideoDecoder::open(const std::string& sFilename, bool bUseHardwareAcceleration,
+ bool bEnableSound)
+{
+ m_NumSeeksSent = 0;
+ m_NumVSeeksDone = 0;
+ m_NumASeeksDone = 0;
+ m_bAudioEOF = false;
+ m_bVideoEOF = false;
+ m_bWasVSeeking = false;
+ m_bWasSeeking = false;
+ m_CurVideoFrameTime = -1;
+
+ VideoDecoder::open(sFilename, bUseHardwareAcceleration, bEnableSound);
+
+ if (getVideoInfo().m_bHasVideo && m_bUseStreamFPS) {
+ m_FPS = getStreamFPS();
+ }
+}
+
+void AsyncVideoDecoder::startDecoding(bool bDeliverYCbCr, const AudioParams* pAP)
+{
+ VideoDecoder::startDecoding(bDeliverYCbCr, pAP);
+
+ AVG_ASSERT(!m_pDemuxThread);
+ vector<int> streamIndexes;
+ if (getVStreamIndex() >= 0) {
+ streamIndexes.push_back(getVStreamIndex());
+ }
+ if (getAStreamIndex() >= 0) {
+ streamIndexes.push_back(getAStreamIndex());
+ }
+ setupDemuxer(streamIndexes);
+
+ if (getVideoInfo().m_bHasVideo) {
+ m_LastVideoFrameTime = -1;
+ m_CurVideoFrameTime = -1;
+ if (m_bUseStreamFPS) {
+ m_FPS = getStreamFPS();
+ }
+ m_pVCmdQ = VideoDecoderThread::CQueuePtr(new VideoDecoderThread::CQueue);
+ m_pVMsgQ = VideoMsgQueuePtr(new VideoMsgQueue(m_QueueLength));
+ VideoMsgQueue& packetQ = *m_PacketQs[getVStreamIndex()];
+
+ m_pVDecoderThread = new boost::thread(VideoDecoderThread(
+ *m_pVCmdQ, *m_pVMsgQ, packetQ, getVideoStream(),
+ getSize(), getPixelFormat(), usesVDPAU()));
+ }
+
+ if (getVideoInfo().m_bHasAudio) {
+ m_pACmdQ = AudioDecoderThread::CQueuePtr(new AudioDecoderThread::CQueue);
+ m_pAMsgQ = AudioMsgQueuePtr(new AudioMsgQueue(AUDIO_MSG_QUEUE_LENGTH));
+ m_pAStatusQ = AudioMsgQueuePtr(new AudioMsgQueue(AUDIO_STATUS_QUEUE_LENGTH));
+ VideoMsgQueue& packetQ = *m_PacketQs[getAStreamIndex()];
+ m_pADecoderThread = new boost::thread(
+ AudioDecoderThread(*m_pACmdQ, *m_pAMsgQ, packetQ, getAudioStream(), *pAP));
+ m_LastAudioFrameTime = 0;
+ }
+}
+
+void AsyncVideoDecoder::close()
+{
+ AVG_ASSERT(getState() != CLOSED);
+
+ if (m_pDemuxThread) {
+ m_pDemuxCmdQ->pushCmd(boost::bind(&VideoDemuxerThread::close, _1));
+ m_pDemuxThread->join();
+ }
+
+ if (m_pVDecoderThread) {
+ m_pVMsgQ->clear();
+ m_pVDecoderThread->join();
+ delete m_pVDecoderThread;
+ m_pVDecoderThread = 0;
+ m_pVMsgQ = VideoMsgQueuePtr();
+ }
+ if (m_pADecoderThread) {
+ m_pAMsgQ->clear();
+ m_pAStatusQ->clear();
+ m_pADecoderThread->join();
+ delete m_pADecoderThread;
+ m_pADecoderThread = 0;
+ m_pAStatusQ = AudioMsgQueuePtr();
+ m_pAMsgQ = AudioMsgQueuePtr();
+ }
+ VideoDecoder::close();
+ if (m_pDemuxThread) {
+ deleteDemuxer();
+ }
+}
+
+void AsyncVideoDecoder::seek(float destTime)
+{
+ AVG_ASSERT(getState() == DECODING);
+ m_bAudioEOF = false;
+ m_bVideoEOF = false;
+ m_NumSeeksSent++;
+ m_pDemuxCmdQ->pushCmd(boost::bind(&VideoDemuxerThread::seek, _1, m_NumSeeksSent,
+ destTime));
+}
+
+void AsyncVideoDecoder::loop()
+{
+ m_LastVideoFrameTime = -1;
+ m_bAudioEOF = false;
+ m_bVideoEOF = false;
+ seek(0);
+}
+
+int AsyncVideoDecoder::getCurFrame() const
+{
+ AVG_ASSERT(getState() != CLOSED);
+ return int(getCurTime()*getVideoInfo().m_StreamFPS+0.5);
+}
+
+int AsyncVideoDecoder::getNumFramesQueued() const
+{
+ AVG_ASSERT(getState() == DECODING);
+ return m_pVMsgQ->size();
+}
+
+float AsyncVideoDecoder::getCurTime() const
+{
+ AVG_ASSERT(getState() != CLOSED);
+ if (getVideoInfo().m_bHasVideo) {
+ return m_CurVideoFrameTime;
+ } else {
+ return m_LastAudioFrameTime;
+ }
+}
+
+float AsyncVideoDecoder::getFPS() const
+{
+ AVG_ASSERT(getState() != CLOSED);
+ return m_FPS;
+}
+
+void AsyncVideoDecoder::setFPS(float fps)
+{
+ AVG_ASSERT(!m_pADecoderThread);
+ m_pVCmdQ->pushCmd(boost::bind(&VideoDecoderThread::setFPS, _1, fps));
+ m_bUseStreamFPS = (fps == 0);
+ if (m_bUseStreamFPS) {
+ m_FPS = getVideoInfo().m_StreamFPS;
+ } else {
+ m_FPS = fps;
+ }
+}
+
+static ProfilingZoneID VDPAUDecodeProfilingZone("AsyncVideoDecoder: VDPAU", true);
+
+FrameAvailableCode AsyncVideoDecoder::renderToBmps(vector<BitmapPtr>& pBmps,
+ float timeWanted)
+{
+ AVG_ASSERT(getState() == DECODING);
+ FrameAvailableCode frameAvailable;
+ VideoMsgPtr pFrameMsg;
+ if (timeWanted == -1) {
+ waitForSeekDone();
+ pFrameMsg = getNextBmps(true);
+ frameAvailable = FA_NEW_FRAME;
+ } else {
+ pFrameMsg = getBmpsForTime(timeWanted, frameAvailable);
+ }
+ if (frameAvailable == FA_NEW_FRAME) {
+ AVG_ASSERT(pFrameMsg);
+ m_LastVideoFrameTime = pFrameMsg->getFrameTime();
+ m_CurVideoFrameTime = m_LastVideoFrameTime;
+ if (pFrameMsg->getType() == VideoMsg::VDPAU_FRAME) {
+#ifdef AVG_ENABLE_VDPAU
+ ScopeTimer timer(VDPAUDecodeProfilingZone);
+ vdpau_render_state* pRenderState = pFrameMsg->getRenderState();
+ if (pixelFormatIsPlanar(getPixelFormat())) {
+ getPlanesFromVDPAU(pRenderState, pBmps[0], pBmps[1], pBmps[2]);
+ } else {
+ getBitmapFromVDPAU(pRenderState, pBmps[0]);
+ }
+#endif
+ } else {
+ for (unsigned i = 0; i < pBmps.size(); ++i) {
+ pBmps[i]->copyPixels(*(pFrameMsg->getFrameBitmap(i)));
+ }
+ returnFrame(pFrameMsg);
+ }
+ }
+ return frameAvailable;
+}
+
+void AsyncVideoDecoder::updateAudioStatus()
+{
+ if (m_pAStatusQ) {
+ AudioMsgPtr pMsg = m_pAStatusQ->pop(false);
+ while (pMsg) {
+ handleAudioMsg(pMsg);
+ pMsg = m_pAStatusQ->pop(false);
+ }
+ }
+}
+
+bool AsyncVideoDecoder::isEOF() const
+{
+ AVG_ASSERT(getState() == DECODING);
+ bool bEOF = true;
+ if (getVideoInfo().m_bHasAudio && !m_bAudioEOF) {
+ bEOF = false;
+ }
+ if (getVideoInfo().m_bHasVideo && !m_bVideoEOF) {
+ bEOF = false;
+ }
+ return bEOF;
+}
+
+void AsyncVideoDecoder::throwAwayFrame(float timeWanted)
+{
+ AVG_ASSERT(getState() == DECODING);
+ FrameAvailableCode frameAvailable;
+ VideoMsgPtr pFrameMsg = getBmpsForTime(timeWanted, frameAvailable);
+}
+
+AudioMsgQueuePtr AsyncVideoDecoder::getAudioMsgQ()
+{
+ return m_pAMsgQ;
+}
+
+AudioMsgQueuePtr AsyncVideoDecoder::getAudioStatusQ() const
+{
+ return m_pAStatusQ;
+}
+
+void AsyncVideoDecoder::setupDemuxer(vector<int> streamIndexes)
+{
+ m_pDemuxCmdQ = VideoDemuxerThread::CQueuePtr(new VideoDemuxerThread::CQueue());
+ for (unsigned i = 0; i < streamIndexes.size(); ++i) {
+ VideoMsgQueuePtr pPacketQ(new VideoMsgQueue(PACKET_QUEUE_LENGTH));
+ m_PacketQs[streamIndexes[i]] = pPacketQ;
+ }
+ m_pDemuxThread = new boost::thread(VideoDemuxerThread(*m_pDemuxCmdQ,
+ getFormatContext(), m_PacketQs));
+}
+
+void AsyncVideoDecoder::deleteDemuxer()
+{
+ delete m_pDemuxThread;
+ m_pDemuxThread = 0;
+ map<int, VideoMsgQueuePtr>::iterator it;
+ for (it = m_PacketQs.begin(); it != m_PacketQs.end(); it++) {
+ VideoMsgQueuePtr pPacketQ = it->second;
+ VideoMsgPtr pPacketMsg;
+ pPacketMsg = pPacketQ->pop(false);
+ while (pPacketMsg) {
+ pPacketMsg->freePacket();
+ pPacketMsg = pPacketQ->pop(false);
+ }
+ }
+}
+
+VideoMsgPtr AsyncVideoDecoder::getBmpsForTime(float timeWanted,
+ FrameAvailableCode& frameAvailable)
+{
+ if (timeWanted < 0) {
+ cerr << "Illegal timeWanted: " << timeWanted << endl;
+ AVG_ASSERT(false);
+ }
+ VideoMsgPtr pFrameMsg;
+ float timePerFrame = 1.0f/getFPS();
+
+ checkForSeekDone();
+ bool bVSeekDone = (!isVSeeking() && m_bWasVSeeking);
+ m_bWasVSeeking = isVSeeking();
+
+ if (!isSeeking() && m_bWasSeeking) {
+// cerr << "timeWanted: " << timeWanted << ", audio: " << m_LastAudioFrameTime
+// << ", diff: " << timeWanted-m_LastAudioFrameTime << endl;
+ }
+ m_bWasSeeking = isSeeking();
+ if ((!bVSeekDone &&
+ (isVSeeking() ||
+ fabs(float(timeWanted-m_LastVideoFrameTime)) < 0.5*timePerFrame ||
+ m_LastVideoFrameTime > timeWanted+timePerFrame)) ||
+ m_bVideoEOF)
+ {
+ // The last frame is still current. Display it again.
+ frameAvailable = FA_USE_LAST_FRAME;
+ return VideoMsgPtr();
+ } else {
+ float frameTime = -1;
+ while (frameTime-timeWanted < -0.5*timePerFrame && !m_bVideoEOF) {
+ if (pFrameMsg) {
+ if (pFrameMsg->getType() == VideoMsg::FRAME) {
+ returnFrame(pFrameMsg);
+ } else {
+#if AVG_ENABLE_VDPAU
+ vdpau_render_state* pRenderState = pFrameMsg->getRenderState();
+ unlockVDPAUSurface(pRenderState);
+#endif
+ }
+ }
+ pFrameMsg = getNextBmps(false);
+ if (pFrameMsg) {
+ frameTime = pFrameMsg->getFrameTime();
+ } else {
+ frameAvailable = FA_STILL_DECODING;
+ return VideoMsgPtr();
+ }
+ }
+ if (!pFrameMsg) {
+ cerr << "frameTime=" << frameTime << ", timeWanted=" << timeWanted
+ << ", timePerFrame=" << timePerFrame << ", m_bVideoEOF="
+ << m_bVideoEOF << endl;
+ AVG_ASSERT(false);
+ }
+ frameAvailable = FA_NEW_FRAME;
+ }
+ return pFrameMsg;
+}
+
+VideoMsgPtr AsyncVideoDecoder::getNextBmps(bool bWait)
+{
+ VideoMsgPtr pMsg = m_pVMsgQ->pop(bWait);
+ if (pMsg) {
+ switch (pMsg->getType()) {
+ case VideoMsg::FRAME:
+ case VideoMsg::VDPAU_FRAME:
+ return pMsg;
+ case VideoMsg::END_OF_FILE:
+ m_NumVSeeksDone = m_NumSeeksSent;
+ m_bVideoEOF = true;
+ return VideoMsgPtr();
+ case VideoMsg::ERROR:
+ m_bVideoEOF = true;
+ return VideoMsgPtr();
+ case AudioMsg::SEEK_DONE:
+ handleVSeekDone(pMsg);
+ return getNextBmps(bWait);
+ default:
+ // Unhandled message type.
+ AVG_ASSERT(false);
+ return VideoMsgPtr();
+ }
+ } else {
+ return pMsg;
+ }
+}
+void AsyncVideoDecoder::waitForSeekDone()
+{
+ while (isVSeeking()) {
+ VideoMsgPtr pMsg = m_pVMsgQ->pop(true);
+ handleVSeekMsg(pMsg);
+ }
+}
+
+void AsyncVideoDecoder::checkForSeekDone()
+{
+ if (isVSeeking()) {
+ VideoMsgPtr pMsg;
+ do {
+ pMsg = m_pVMsgQ->pop(false);
+ if (pMsg) {
+ handleVSeekMsg(pMsg);
+ }
+ } while (pMsg && isVSeeking());
+ }
+}
+
+void AsyncVideoDecoder::handleVSeekMsg(VideoMsgPtr pMsg)
+{
+ switch (pMsg->getType()) {
+ case AudioMsg::SEEK_DONE:
+ handleVSeekDone(pMsg);
+ break;
+ case VideoMsg::FRAME:
+ returnFrame(dynamic_pointer_cast<VideoMsg>(pMsg));
+ break;
+ case VideoMsg::VDPAU_FRAME:
+#ifdef AVG_ENABLE_VDPAU
+ unlockVDPAUSurface(pMsg->getRenderState());
+#endif
+ break;
+ case VideoMsg::END_OF_FILE:
+ m_NumVSeeksDone = m_NumSeeksSent;
+ m_bVideoEOF = true;
+ break;
+ default:
+ // TODO: Handle ERROR messages here.
+ AVG_ASSERT(false);
+ }
+}
+
+void AsyncVideoDecoder::handleVSeekDone(AudioMsgPtr pMsg)
+{
+ m_LastVideoFrameTime = pMsg->getSeekTime() - 1/m_FPS;
+ if (m_NumVSeeksDone < pMsg->getSeekSeqNum()) {
+ m_NumVSeeksDone = pMsg->getSeekSeqNum();
+ }
+}
+
+void AsyncVideoDecoder::handleAudioMsg(AudioMsgPtr pMsg)
+{
+ switch (pMsg->getType()) {
+ case AudioMsg::END_OF_FILE:
+ case AudioMsg::ERROR:
+ m_bAudioEOF = true;
+ break;
+ case AudioMsg::SEEK_DONE:
+// pMsg->dump();
+ m_bAudioEOF = false;
+ m_LastAudioFrameTime = pMsg->getSeekTime();
+ if (m_NumASeeksDone < pMsg->getSeekSeqNum()) {
+ m_NumASeeksDone = pMsg->getSeekSeqNum();
+ }
+ break;
+ case AudioMsg::AUDIO_TIME:
+ m_LastAudioFrameTime = pMsg->getAudioTime();
+ break;
+ default:
+ // Unhandled message type.
+ pMsg->dump();
+ AVG_ASSERT(false);
+ }
+}
+
+void AsyncVideoDecoder::returnFrame(VideoMsgPtr pFrameMsg)
+{
+ if (pFrameMsg) {
+ AVG_ASSERT(pFrameMsg->getType() == VideoMsg::FRAME);
+ m_pVCmdQ->pushCmd(boost::bind(&VideoDecoderThread::returnFrame, _1, pFrameMsg));
+ }
+}
+
+bool AsyncVideoDecoder::isSeeking() const
+{
+ return (m_NumSeeksSent > m_NumVSeeksDone || m_NumSeeksSent > m_NumASeeksDone);
+}
+
+bool AsyncVideoDecoder::isVSeeking() const
+{
+ return m_NumSeeksSent > m_NumVSeeksDone;
+}
+
+}