diff options
Diffstat (limited to 'src/video/AsyncVideoDecoder.cpp')
-rw-r--r-- | src/video/AsyncVideoDecoder.cpp | 505 |
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; +} + +} |