summaryrefslogtreecommitdiff
path: root/src/video
diff options
context:
space:
mode:
authorDimitri John Ledkov <xnox@ubuntu.com>2014-06-24 20:05:13 +0100
committerDimitri John Ledkov <xnox@ubuntu.com>2014-06-24 20:05:13 +0100
commitdd22bd15f6ed3e5eb5c77ab427029be50fe20148 (patch)
treed9491ee40d80688b7f5b1f20504f022686827a57 /src/video
libavg (1.8.1-1) unstable; urgency=medium
* New upstream release (Closes: #739664) * Mark libdc1394-22-dev as linux-any build-dependency. * Add libvdpau-dev build-dependency. * Add libavresample-dev build-dependency. # imported from the archive
Diffstat (limited to 'src/video')
-rw-r--r--src/video/AsyncVideoDecoder.cpp505
-rw-r--r--src/video/AsyncVideoDecoder.h117
-rw-r--r--src/video/AudioDecoderThread.cpp366
-rw-r--r--src/video/AudioDecoderThread.h88
-rw-r--r--src/video/FFMpegDemuxer.cpp153
-rw-r--r--src/video/FFMpegDemuxer.h58
-rw-r--r--src/video/FFMpegFrameDecoder.cpp258
-rw-r--r--src/video/FFMpegFrameDecoder.h72
-rw-r--r--src/video/Makefile.am50
-rw-r--r--src/video/SyncVideoDecoder.cpp261
-rw-r--r--src/video/SyncVideoDecoder.h77
-rw-r--r--src/video/VDPAUDecoder.cpp251
-rw-r--r--src/video/VDPAUDecoder.h67
-rw-r--r--src/video/VDPAUHelper.cpp182
-rw-r--r--src/video/VDPAUHelper.h76
-rw-r--r--src/video/VideoDecoder.cpp485
-rw-r--r--src/video/VideoDecoder.h138
-rw-r--r--src/video/VideoDecoderThread.cpp222
-rw-r--r--src/video/VideoDecoderThread.h83
-rw-r--r--src/video/VideoDemuxerThread.cpp157
-rw-r--r--src/video/VideoDemuxerThread.h63
-rw-r--r--src/video/VideoInfo.cpp94
-rw-r--r--src/video/VideoInfo.h71
-rw-r--r--src/video/VideoMsg.cpp94
-rw-r--r--src/video/VideoMsg.h72
-rw-r--r--src/video/WrapFFMpeg.h93
-rw-r--r--src/video/baseline/mjpeg-48x48.avi_1.pngbin0 -> 7047 bytes
-rw-r--r--src/video/baseline/mjpeg-48x48.avi_100.pngbin0 -> 6784 bytes
-rw-r--r--src/video/baseline/mjpeg-48x48.avi_2.pngbin0 -> 7029 bytes
-rw-r--r--src/video/baseline/mjpeg-48x48.avi_201.pngbin0 -> 6978 bytes
-rw-r--r--src/video/baseline/mjpeg-48x48.avi_53.pngbin0 -> 6817 bytes
-rw-r--r--src/video/baseline/mjpeg-48x48.avi_end.pngbin0 -> 6967 bytes
-rw-r--r--src/video/baseline/mjpeg-48x48.avi_loop.pngbin0 -> 7041 bytes
-rw-r--r--src/video/baseline/mpeg1-48x48-sound.avi_end.pngbin0 -> 3282 bytes
-rw-r--r--src/video/baseline/mpeg1-48x48-sound.avi_loop.pngbin0 -> 3428 bytes
-rw-r--r--src/video/baseline/mpeg1-48x48.mov_1.pngbin0 -> 3451 bytes
-rw-r--r--src/video/baseline/mpeg1-48x48.mov_2.pngbin0 -> 3301 bytes
-rw-r--r--src/video/baseline/mpeg1-48x48.mov_end.pngbin0 -> 3284 bytes
-rw-r--r--src/video/baseline/mpeg1-48x48.mov_loop.pngbin0 -> 3451 bytes
-rw-r--r--src/video/testvideo.cpp543
40 files changed, 4696 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;
+}
+
+}
diff --git a/src/video/AsyncVideoDecoder.h b/src/video/AsyncVideoDecoder.h
new file mode 100644
index 0000000..7773831
--- /dev/null
+++ b/src/video/AsyncVideoDecoder.h
@@ -0,0 +1,117 @@
+//
+// 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
+//
+
+#ifndef _AsyncVideoDecoder_H_
+#define _AsyncVideoDecoder_H_
+
+#include "../api.h"
+#include "VideoDecoder.h"
+#include "VideoDemuxerThread.h"
+#include "VideoDecoderThread.h"
+#include "AudioDecoderThread.h"
+#include "VideoMsg.h"
+
+#include "../graphics/Bitmap.h"
+#include "../audio/AudioParams.h"
+
+#include <boost/thread/mutex.hpp>
+
+#include <string>
+
+namespace avg {
+
+class AVG_API AsyncVideoDecoder: public VideoDecoder
+{
+public:
+ AsyncVideoDecoder(int queueLength);
+ virtual ~AsyncVideoDecoder();
+ virtual void open(const std::string& sFilename, bool bUseHardwareAcceleration,
+ bool bEnableSound);
+ virtual void startDecoding(bool bDeliverYCbCr, const AudioParams* pAP);
+ virtual void close();
+ virtual void seek(float destTime);
+ virtual void loop();
+ virtual int getCurFrame() const;
+ virtual int getNumFramesQueued() const;
+ virtual float getCurTime() const;
+ virtual float getFPS() const;
+ virtual void setFPS(float fps);
+
+ virtual FrameAvailableCode renderToBmps(std::vector<BitmapPtr>& pBmps,
+ float timeWanted);
+ void updateAudioStatus();
+ virtual bool isEOF() const;
+ virtual void throwAwayFrame(float timeWanted);
+
+ AudioMsgQueuePtr getAudioMsgQ();
+ AudioMsgQueuePtr getAudioStatusQ() const;
+
+private:
+ void setupDemuxer(std::vector<int> streamIndexes);
+ void deleteDemuxer();
+ VideoMsgPtr getBmpsForTime(float timeWanted, FrameAvailableCode& frameAvailable);
+ VideoMsgPtr getNextBmps(bool bWait);
+ void waitForSeekDone();
+ void checkForSeekDone();
+ void handleVSeekMsg(VideoMsgPtr pMsg);
+ void handleVSeekDone(AudioMsgPtr pMsg);
+ void handleAudioMsg(AudioMsgPtr pMsg);
+ void returnFrame(VideoMsgPtr pFrameMsg);
+ bool isSeeking() const;
+ bool isVSeeking() const;
+
+ int m_QueueLength;
+
+ boost::thread* m_pDemuxThread;
+ std::map<int, VideoMsgQueuePtr> m_PacketQs;
+ VideoDemuxerThread::CQueuePtr m_pDemuxCmdQ;
+
+ boost::thread* m_pVDecoderThread;
+ VideoDecoderThread::CQueuePtr m_pVCmdQ;
+ VideoMsgQueuePtr m_pVMsgQ;
+
+ boost::thread* m_pADecoderThread;
+ AudioDecoderThread::CQueuePtr m_pACmdQ;
+ AudioMsgQueuePtr m_pAMsgQ;
+ AudioMsgQueuePtr m_pAStatusQ;
+
+ bool m_bUseStreamFPS;
+ float m_FPS;
+
+ int m_NumSeeksSent;
+ int m_NumVSeeksDone;
+ int m_NumASeeksDone;
+ bool m_bWasVSeeking;
+ bool m_bWasSeeking;
+
+ bool m_bAudioEOF;
+ bool m_bVideoEOF;
+
+ float m_LastVideoFrameTime;
+ float m_CurVideoFrameTime;
+ float m_LastAudioFrameTime;
+};
+
+typedef boost::shared_ptr<AsyncVideoDecoder> AsyncVideoDecoderPtr;
+
+}
+#endif
+
diff --git a/src/video/AudioDecoderThread.cpp b/src/video/AudioDecoderThread.cpp
new file mode 100644
index 0000000..8f4aee3
--- /dev/null
+++ b/src/video/AudioDecoderThread.cpp
@@ -0,0 +1,366 @@
+//
+// 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
+//
+// Original author of this file is Nick Hebner (hebnern@gmail.com).
+//
+
+#include "AudioDecoderThread.h"
+
+#include "../base/Logger.h"
+#include "../base/TimeSource.h"
+#include "../base/ScopeTimer.h"
+
+#if AVUTIL_VERSION_INT > AV_VERSION_INT(52, 0, 0)
+#include <libavutil/samplefmt.h>
+#endif
+
+#ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE
+ #define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000
+#endif
+
+using namespace std;
+
+namespace avg {
+
+AudioDecoderThread::AudioDecoderThread(CQueue& cmdQ, AudioMsgQueue& msgQ,
+ VideoMsgQueue& packetQ, AVStream* pStream, const AudioParams& ap)
+ : WorkerThread<AudioDecoderThread>(string("AudioDecoderThread"), cmdQ),
+ m_MsgQ(msgQ),
+ m_PacketQ(packetQ),
+ m_AP(ap),
+ m_pStream(pStream),
+ m_pResampleContext(0),
+ m_State(DECODING)
+{
+ m_LastFrameTime = 0;
+ m_AudioStartTimestamp = 0;
+
+ if (m_pStream->start_time != (long long)AV_NOPTS_VALUE) {
+ m_AudioStartTimestamp = float(av_q2d(m_pStream->time_base)*m_pStream->start_time);
+ }
+ m_InputSampleRate = (int)(m_pStream->codec->sample_rate);
+ m_InputSampleFormat = m_pStream->codec->sample_fmt;
+}
+
+AudioDecoderThread::~AudioDecoderThread()
+{
+ if (m_pResampleContext) {
+#ifdef LIBAVRESAMPLE_VERSION
+ avresample_close(m_pResampleContext);
+ avresample_free(&m_pResampleContext);
+#else
+ audio_resample_close(m_pResampleContext);
+#endif
+ m_pResampleContext = 0;
+ }
+}
+
+static ProfilingZoneID DecoderProfilingZone("Audio Decoder Thread", true);
+static ProfilingZoneID PacketWaitProfilingZone("Audio Wait for packet", true);
+
+bool AudioDecoderThread::work()
+{
+ ScopeTimer timer(DecoderProfilingZone);
+ VideoMsgPtr pMsg;
+ {
+ ScopeTimer timer(PacketWaitProfilingZone);
+ pMsg = m_PacketQ.pop(true);
+ }
+ switch (pMsg->getType()) {
+ case VideoMsg::PACKET: {
+ AVPacket* pPacket = pMsg->getPacket();
+ switch(m_State) {
+ case DECODING:
+ decodePacket(pPacket);
+ break;
+ case SEEK_DONE:
+ handleSeekDone(pPacket);
+ break;
+ case DISCARDING:
+ discardPacket(pPacket);
+ break;
+ default:
+ AVG_ASSERT(false);
+ }
+ av_free_packet(pPacket);
+ delete pPacket;
+ break;
+ }
+ case VideoMsg::SEEK_DONE:
+ m_State = SEEK_DONE;
+ m_SeekSeqNum = pMsg->getSeekSeqNum();
+ m_SeekTime = pMsg->getSeekTime();
+ break;
+ case VideoMsg::END_OF_FILE:
+ pushEOF();
+ break;
+ case VideoMsg::CLOSED:
+ m_MsgQ.clear();
+ stop();
+ break;
+ default:
+ pMsg->dump();
+ AVG_ASSERT(false);
+ }
+ ThreadProfiler::get()->reset();
+ return true;
+}
+
+void AudioDecoderThread::decodePacket(AVPacket* pPacket)
+{
+ char* pDecodedData = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE +
+ FF_INPUT_BUFFER_PADDING_SIZE);
+ AVPacket* pTempPacket = new AVPacket;
+ av_init_packet(pTempPacket);
+ pTempPacket->data = pPacket->data;
+ pTempPacket->size = pPacket->size;
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 25, 0)
+ int gotFrame = 0;
+ AVFrame* pDecodedFrame;
+ pDecodedFrame = avcodec_alloc_frame();
+#endif
+ while (pTempPacket->size > 0) {
+ int bytesDecoded = AVCODEC_MAX_AUDIO_FRAME_SIZE;
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 25, 0)
+ int bytesConsumed = avcodec_decode_audio4(m_pStream->codec, pDecodedFrame,
+ &gotFrame, pTempPacket);
+ int planeSize;
+ bytesDecoded = av_samples_get_buffer_size(&planeSize, m_pStream->codec->channels,
+ pDecodedFrame->nb_samples, m_pStream->codec->sample_fmt, 1);
+#else
+ int bytesConsumed = avcodec_decode_audio3(m_pStream->codec, (short*)pDecodedData,
+ &bytesDecoded, pTempPacket);
+#endif
+// This is triggered for some strange/broken videos.
+// AVG_ASSERT(bytesConsumed != 0);
+ if (bytesConsumed < 0) {
+ // Error decoding -> throw away current packet.
+ bytesDecoded = 0;
+ pTempPacket->size = 0;
+ } else {
+ pTempPacket->data += bytesConsumed;
+ pTempPacket->size -= bytesConsumed;
+ }
+ if (bytesDecoded > 0) {
+ int framesDecoded = bytesDecoded/(m_pStream->codec->channels*
+ getBytesPerSample(m_InputSampleFormat));
+ AudioBufferPtr pBuffer;
+ bool bNeedsResample = (m_InputSampleRate != m_AP.m_SampleRate ||
+ m_InputSampleFormat != SAMPLE_FMT_S16 ||
+ m_pStream->codec->channels != m_AP.m_Channels);
+ bool bIsPlanar = false;
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51, 27, 0)
+ bIsPlanar = av_sample_fmt_is_planar((SampleFormat)m_InputSampleFormat);
+ if (bIsPlanar) {
+ char* pPackedData = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE +
+ FF_INPUT_BUFFER_PADDING_SIZE);
+ planarToInterleaved(pPackedData, pDecodedData, m_pStream->codec->channels,
+ m_pStream->codec->frame_size);
+ pBuffer = resampleAudio(pPackedData, framesDecoded,
+ av_get_packed_sample_fmt((SampleFormat)m_InputSampleFormat));
+ av_free(pPackedData);
+ bNeedsResample = false;
+ }
+#endif
+ if (bNeedsResample) {
+ pBuffer = resampleAudio(pDecodedData, framesDecoded,
+ m_InputSampleFormat);
+ } else if (!bIsPlanar) {
+ pBuffer = AudioBufferPtr(new AudioBuffer(framesDecoded, m_AP));
+ memcpy(pBuffer->getData(), pDecodedData, bytesDecoded);
+ }
+ m_LastFrameTime += float(pBuffer->getNumFrames())/m_AP.m_SampleRate;
+ pushAudioMsg(pBuffer, m_LastFrameTime);
+ }
+ }
+ av_free(pDecodedData);
+#if LIBAVCODEC_VERSION_MAJOR > 53
+ avcodec_free_frame(&pDecodedFrame);
+#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 25, 0)
+ delete pDecodedFrame;
+#endif
+ delete pTempPacket;
+}
+
+void AudioDecoderThread::handleSeekDone(AVPacket* pPacket)
+{
+ m_MsgQ.clear();
+ m_LastFrameTime = float(pPacket->dts*av_q2d(m_pStream->time_base))
+ - m_AudioStartTimestamp;
+
+ if (fabs(m_LastFrameTime - m_SeekTime) < 0.01) {
+ pushSeekDone(m_LastFrameTime, m_SeekSeqNum);
+ decodePacket(pPacket);
+ m_State = DECODING;
+ } else {
+ if (m_LastFrameTime-0.01f < m_SeekTime) {
+ // Received frame that's earlier than the destination, so throw away frames
+ // until the time is correct.
+ m_State = DISCARDING;
+ } else {
+ // Received frame that's too late, so insert a buffer of silence to
+ // compensate.
+ insertSilence(m_LastFrameTime - m_SeekTime);
+ m_LastFrameTime = m_SeekTime;
+ pushSeekDone(m_LastFrameTime, m_SeekSeqNum);
+ decodePacket(pPacket);
+ m_State = DECODING;
+ }
+ }
+}
+
+void AudioDecoderThread::discardPacket(AVPacket* pPacket)
+{
+ m_LastFrameTime = float(pPacket->dts*av_q2d(m_pStream->time_base))
+ - m_AudioStartTimestamp;
+ if (m_LastFrameTime-0.01f > m_SeekTime) {
+ pushSeekDone(m_LastFrameTime, m_SeekSeqNum);
+ m_State = DECODING;
+ }
+}
+
+AudioBufferPtr AudioDecoderThread::resampleAudio(char* pDecodedData, int framesDecoded,
+ int currentSampleFormat)
+{
+ if (!m_pResampleContext) {
+#ifdef LIBAVRESAMPLE_VERSION
+ m_pResampleContext = avresample_alloc_context();
+ av_opt_set_int(m_pResampleContext, "in_channel_layout",
+ av_get_default_channel_layout(m_pStream->codec->channels), 0);
+ av_opt_set_int(m_pResampleContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
+ av_opt_set_int(m_pResampleContext, "in_sample_rate", m_InputSampleRate, 0);
+ av_opt_set_int(m_pResampleContext, "out_sample_rate", m_AP.m_SampleRate, 0);
+ av_opt_set_int(m_pResampleContext, "in_sample_fmt",
+ (SampleFormat)currentSampleFormat, 0);
+ av_opt_set_int(m_pResampleContext, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
+ int err = avresample_open(m_pResampleContext);
+ AVG_ASSERT(err >= 0);
+#else
+ m_pResampleContext = av_audio_resample_init(m_AP.m_Channels,
+ m_pStream->codec->channels, m_AP.m_SampleRate, m_InputSampleRate,
+ SAMPLE_FMT_S16, (SampleFormat)currentSampleFormat, 16, 10, 0, 0.8);
+#endif
+ AVG_ASSERT(m_pResampleContext);
+ }
+#ifdef LIBAVRESAMPLE_VERSION
+ uint8_t *pResampledData;
+ int leftoverSamples = avresample_available(m_pResampleContext);
+ int framesAvailable = leftoverSamples +
+ av_rescale_rnd(avresample_get_delay(m_pResampleContext) +
+ framesDecoded, m_AP.m_SampleRate, m_InputSampleRate, AV_ROUND_UP);
+ av_samples_alloc(&pResampledData, 0, 2, framesAvailable,
+ AV_SAMPLE_FMT_S16, 0);
+ int framesResampled = avresample_convert(m_pResampleContext, &pResampledData, 0,
+ framesAvailable, (uint8_t**)&pDecodedData, 0, framesDecoded);
+ AudioBufferPtr pBuffer(new AudioBuffer(framesResampled, m_AP));
+ memcpy(pBuffer->getData(), pResampledData,
+ framesResampled*m_AP.m_Channels*sizeof(short));
+ av_freep(&pResampledData);
+#else
+ short pResampledData[AVCODEC_MAX_AUDIO_FRAME_SIZE/2];
+ int framesResampled = audio_resample(m_pResampleContext, pResampledData,
+ (short*)pDecodedData, framesDecoded);
+ AudioBufferPtr pBuffer(new AudioBuffer(framesResampled, m_AP));
+ memcpy(pBuffer->getData(), pResampledData,
+ framesResampled*m_AP.m_Channels*sizeof(short));
+#endif
+ return pBuffer;
+}
+
+void AudioDecoderThread::planarToInterleaved(char* pOutput, char* pInput, int numChannels,
+ int numSamples)
+{
+ AVG_ASSERT(numChannels <= 8);
+ if (numSamples == 0) {
+ // Fishy, some ogg files have no proper frame_size set. But outputBufferSamples
+ // worked for sample ogg file.
+ numSamples = m_AP.m_OutputBufferSamples;
+ }
+ int i, j;
+ int bytesPerSample = getBytesPerSample(m_InputSampleFormat);
+ char * pPlanes[8] = {};
+ for (i=0; i<numChannels; i++) {
+ pPlanes[i] = pInput + i*(numSamples*bytesPerSample);
+ }
+ for (i=0; i<numSamples; i++) {
+ for (j=0; j<numChannels; j++) {
+ memcpy(pOutput, pPlanes[j], bytesPerSample);
+ pOutput += bytesPerSample;
+ pPlanes[j] += bytesPerSample;
+ }
+ }
+}
+
+void AudioDecoderThread::insertSilence(float duration)
+{
+ int numDelaySamples = int(duration*m_AP.m_SampleRate);
+ AudioBufferPtr pBuffer(new AudioBuffer(numDelaySamples, m_AP));
+ pBuffer->clear();
+ pushAudioMsg(pBuffer, m_LastFrameTime);
+}
+
+void AudioDecoderThread::pushAudioMsg(AudioBufferPtr pBuffer, float time)
+{
+ VideoMsgPtr pMsg(new VideoMsg());
+ pMsg->setAudio(pBuffer, time);
+ m_MsgQ.push(pMsg);
+}
+
+void AudioDecoderThread::pushSeekDone(float time, int seqNum)
+{
+ VideoMsgPtr pMsg(new VideoMsg());
+ pMsg->setSeekDone(seqNum, time);
+ m_MsgQ.push(pMsg);
+}
+
+void AudioDecoderThread::pushEOF()
+{
+ VideoMsgPtr pMsg(new VideoMsg());
+ pMsg->setEOF();
+ m_MsgQ.push(pMsg);
+}
+
+int AudioDecoderThread::getBytesPerSample(int sampleFormat)
+{
+ switch (sampleFormat) {
+ case SAMPLE_FMT_U8:
+ return 1;
+ case SAMPLE_FMT_S16:
+ return 2;
+ case SAMPLE_FMT_S32:
+ return 4;
+ case SAMPLE_FMT_FLT:
+ return 4;
+ case SAMPLE_FMT_DBL:
+ return 8;
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 3, 0)
+ case SAMPLE_FMT_S16P:
+ return 2;
+ case SAMPLE_FMT_FLTP:
+ return 4;
+#endif
+ default:
+ AVG_LOG_ERROR("Unknown SampleFormat: " << sampleFormat << "\n");
+ AVG_ASSERT(false);
+ return 0;
+ }
+}
+
+}
diff --git a/src/video/AudioDecoderThread.h b/src/video/AudioDecoderThread.h
new file mode 100644
index 0000000..c8493b2
--- /dev/null
+++ b/src/video/AudioDecoderThread.h
@@ -0,0 +1,88 @@
+//
+// 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
+//
+// Original author of this file is Nick Hebner (hebnern@gmail.com).
+//
+
+#ifndef _AudioDecoderThread_H_
+#define _AudioDecoderThread_H_
+
+#include "../avgconfigwrapper.h"
+#include "VideoMsg.h"
+
+#include "../base/WorkerThread.h"
+#include "../base/Command.h"
+#include "../audio/AudioParams.h"
+
+#include "WrapFFMpeg.h"
+
+#include <boost/thread.hpp>
+
+#include <string>
+
+namespace avg {
+
+class AVG_API AudioDecoderThread : public WorkerThread<AudioDecoderThread> {
+ public:
+ AudioDecoderThread(CQueue& cmdQ, AudioMsgQueue& msgQ, VideoMsgQueue& packetQ,
+ AVStream* pStream, const AudioParams& ap);
+ virtual ~AudioDecoderThread();
+
+ bool work();
+
+ private:
+ void decodePacket(AVPacket* pPacket);
+ void handleSeekDone(AVPacket* pPacket);
+ void discardPacket(AVPacket* pPacket);
+ AudioBufferPtr resampleAudio(char* pDecodedData, int framesDecoded,
+ int currentSampleFormat);
+ void insertSilence(float duration);
+ void planarToInterleaved(char* pOutput, char* pInput, int numChannels,
+ int numSamples);
+ void pushAudioMsg(AudioBufferPtr pBuffer, float time);
+ void pushSeekDone(float time, int seqNum);
+ void pushEOF();
+ int getBytesPerSample(int sampleFormat);
+
+ AudioMsgQueue& m_MsgQ;
+ VideoMsgQueue& m_PacketQ;
+ AudioParams m_AP;
+
+ AVStream * m_pStream;
+
+ int m_InputSampleRate;
+ int m_InputSampleFormat;
+#ifdef LIBAVRESAMPLE_VERSION
+ AVAudioResampleContext * m_pResampleContext;
+#else
+ ReSampleContext * m_pResampleContext;
+#endif
+ float m_AudioStartTimestamp;
+ float m_LastFrameTime;
+
+ enum State {DECODING, SEEK_DONE, DISCARDING};
+ State m_State;
+ int m_SeekSeqNum;
+ float m_SeekTime;
+};
+
+}
+#endif
+
diff --git a/src/video/FFMpegDemuxer.cpp b/src/video/FFMpegDemuxer.cpp
new file mode 100644
index 0000000..b092c7d
--- /dev/null
+++ b/src/video/FFMpegDemuxer.cpp
@@ -0,0 +1,153 @@
+//
+// 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 "FFMpegDemuxer.h"
+
+#include "../base/ScopeTimer.h"
+#include "../base/ObjectCounter.h"
+#include "../base/Exception.h"
+#include "../base/Logger.h"
+
+#include <cstring>
+#include <iostream>
+
+using namespace std;
+
+namespace avg {
+
+FFMpegDemuxer::FFMpegDemuxer(AVFormatContext * pFormatContext, vector<int> streamIndexes)
+ : m_pFormatContext(pFormatContext)
+{
+ ObjectCounter::get()->incRef(&typeid(*this));
+ for (unsigned i = 0; i < streamIndexes.size(); ++i) {
+ m_PacketLists[streamIndexes[i]] = PacketList();
+ }
+}
+
+FFMpegDemuxer::~FFMpegDemuxer()
+{
+ clearPacketCache();
+ ObjectCounter::get()->decRef(&typeid(*this));
+}
+
+AVPacket * FFMpegDemuxer::getPacket(int streamIndex)
+{
+ // Make sure enableStream was called on streamIndex.
+ AVG_ASSERT(m_PacketLists.size() > 0);
+ AVG_ASSERT(streamIndex > -1 && streamIndex < 10);
+
+ if (m_PacketLists.find(streamIndex) == m_PacketLists.end()) {
+ cerr << this << ": getPacket: Stream " << streamIndex << " not found." << endl;
+ dump();
+ AVG_ASSERT(false);
+ }
+
+ PacketList& curPacketList = m_PacketLists.find(streamIndex)->second;
+ AVPacket* pPacket;
+ if (!curPacketList.empty()) {
+ // The stream has packets queued already.
+ pPacket = curPacketList.front();
+ curPacketList.pop_front();
+ } else {
+ // No packets queued for this stream -> read and queue packets until we get one
+ // that is meant for this stream.
+ do {
+ pPacket = new AVPacket;
+ memset(pPacket, 0, sizeof(AVPacket));
+ int err = av_read_frame(m_pFormatContext, pPacket);
+ if (err < 0) {
+ // EOF or error
+#if LIBAVUTIL_VERSION_MAJOR > 50
+ if (err != int(AVERROR_EOF)) {
+ char sz[256];
+ av_strerror(err, sz, 256);
+ AVG_TRACE(Logger::category::PLAYER, Logger::severity::ERROR,
+ "Error decoding video: " << sz);
+ }
+#endif
+ av_free_packet(pPacket);
+ delete pPacket;
+ pPacket = 0;
+ return 0;
+ }
+ if (pPacket->stream_index != streamIndex) {
+ if (m_PacketLists.find(pPacket->stream_index) != m_PacketLists.end()) {
+ // Relevant stream, but not ours
+ av_dup_packet(pPacket);
+ PacketList& otherPacketList =
+ m_PacketLists.find(pPacket->stream_index)->second;
+ otherPacketList.push_back(pPacket);
+ } else {
+ // Disabled stream
+ av_free_packet(pPacket);
+ delete pPacket;
+ pPacket = 0;
+ }
+ } else {
+ // Our stream
+ av_dup_packet(pPacket);
+ }
+ } while (!pPacket || pPacket->stream_index != streamIndex);
+ }
+
+ return pPacket;
+}
+
+void FFMpegDemuxer::seek(float destTime)
+{
+#if LIBAVFORMAT_BUILD <= 4616
+ av_seek_frame(m_pFormatContext, -1, destTime*1000000);
+#else
+#if LIBAVFORMAT_BUILD < ((49<<16)+(0<<8)+0)
+ av_seek_frame(m_pFormatContext, -1, destTime*1000000, 0);
+#else
+ av_seek_frame(m_pFormatContext, -1, (long long)(destTime*AV_TIME_BASE),
+ AVSEEK_FLAG_BACKWARD);
+#endif
+#endif
+ clearPacketCache();
+}
+
+void FFMpegDemuxer::clearPacketCache()
+{
+ map<int, PacketList>::iterator it;
+ for (it = m_PacketLists.begin(); it != m_PacketLists.end(); ++it) {
+ PacketList::iterator it2;
+ PacketList* pPacketList = &(it->second);
+ for (it2 = pPacketList->begin(); it2 != pPacketList->end(); ++it2) {
+ av_free_packet(*it2);
+ delete *it2;
+ }
+ pPacketList->clear();
+ }
+}
+
+void FFMpegDemuxer::dump()
+{
+ map<int, PacketList>::iterator it;
+ cerr << "FFMpegDemuxer " << this << endl;
+ cerr << "packetlists.size(): " << int(m_PacketLists.size()) << endl;
+ for (it = m_PacketLists.begin(); it != m_PacketLists.end(); ++it) {
+ cerr << " " << it->first << ": " << int(it->second.size()) << endl;
+ }
+}
+
+}
diff --git a/src/video/FFMpegDemuxer.h b/src/video/FFMpegDemuxer.h
new file mode 100644
index 0000000..b8219de
--- /dev/null
+++ b/src/video/FFMpegDemuxer.h
@@ -0,0 +1,58 @@
+//
+// 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
+//
+
+#ifndef _FFMpegDemuxer_H_
+#define _FFMpegDemuxer_H_
+
+#include "../avgconfigwrapper.h"
+
+#include "WrapFFMpeg.h"
+
+#include <list>
+#include <vector>
+#include <map>
+
+#include <boost/shared_ptr.hpp>
+
+namespace avg {
+
+class AVG_API FFMpegDemuxer {
+ public:
+ FFMpegDemuxer(AVFormatContext * pFormatContext, std::vector<int> streamIndexes);
+ virtual ~FFMpegDemuxer();
+
+ AVPacket * getPacket(int streamIndex);
+ void seek(float destTime);
+ void dump();
+
+ private:
+ void clearPacketCache();
+
+ // Packets that haven't been delivered yet.
+ typedef std::list<AVPacket *> PacketList;
+ std::map<int, PacketList> m_PacketLists;
+
+ AVFormatContext * m_pFormatContext;
+};
+typedef boost::shared_ptr<FFMpegDemuxer> FFMpegDemuxerPtr;
+}
+
+#endif
diff --git a/src/video/FFMpegFrameDecoder.cpp b/src/video/FFMpegFrameDecoder.cpp
new file mode 100644
index 0000000..685b341
--- /dev/null
+++ b/src/video/FFMpegFrameDecoder.cpp
@@ -0,0 +1,258 @@
+//
+// 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 "FFMpegFrameDecoder.h"
+#include "FFMpegDemuxer.h"
+#include "VideoInfo.h"
+#ifdef AVG_ENABLE_VDPAU
+#include "VDPAUDecoder.h"
+#endif
+
+#include "../base/Exception.h"
+#include "../base/Logger.h"
+#include "../base/ScopeTimer.h"
+#include "../base/ObjectCounter.h"
+#include "../base/ProfilingZoneID.h"
+#include "../base/StringHelper.h"
+
+#include <iostream>
+#include <sstream>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+using namespace std;
+
+namespace avg {
+
+FFMpegFrameDecoder::FFMpegFrameDecoder(AVStream* pStream)
+ : m_pSwsContext(0),
+ m_pStream(pStream),
+ m_bEOF(false),
+ m_StartTimestamp(-1),
+ m_LastFrameTime(-1),
+ m_bUseStreamFPS(true)
+{
+ m_TimeUnitsPerSecond = float(1.0/av_q2d(pStream->time_base));
+ m_FPS = getStreamFPS(pStream);
+
+ ObjectCounter::get()->incRef(&typeid(*this));
+}
+
+FFMpegFrameDecoder::~FFMpegFrameDecoder()
+{
+ if (m_pSwsContext) {
+ sws_freeContext(m_pSwsContext);
+ m_pSwsContext = 0;
+ }
+ ObjectCounter::get()->decRef(&typeid(*this));
+}
+
+static ProfilingZoneID DecodePacketProfilingZone("Decode packet", true);
+
+bool FFMpegFrameDecoder::decodePacket(AVPacket* pPacket, AVFrame* pFrame,
+ bool bFrameAfterSeek)
+{
+ ScopeTimer timer(DecodePacketProfilingZone);
+ int bGotPicture = 0;
+ AVCodecContext* pContext = m_pStream->codec;
+ AVG_ASSERT(pPacket);
+ avcodec_decode_video2(pContext, pFrame, &bGotPicture, pPacket);
+ if (bGotPicture) {
+ m_LastFrameTime = getFrameTime(pPacket->dts, bFrameAfterSeek);
+ }
+ av_free_packet(pPacket);
+ delete pPacket;
+ return (bGotPicture != 0);
+}
+
+bool FFMpegFrameDecoder::decodeLastFrame(AVFrame* pFrame)
+{
+ // EOF. Decode the last data we got.
+ int bGotPicture = 0;
+ AVCodecContext* pContext = m_pStream->codec;
+ AVPacket packet;
+ av_init_packet(&packet);
+ packet.data = 0;
+ packet.size = 0;
+ avcodec_decode_video2(pContext, pFrame, &bGotPicture, &packet);
+ m_bEOF = true;
+
+ // We don't have a timestamp for the last frame, so we'll
+ // calculate it based on the frame before.
+ m_LastFrameTime += 1.0f/m_FPS;
+ return (bGotPicture != 0);
+}
+
+
+static ProfilingZoneID ConvertImageLibavgProfilingZone(
+ "FFMpeg: colorspace conv (libavg)", true);
+static ProfilingZoneID ConvertImageSWSProfilingZone(
+ "FFMpeg: colorspace conv (SWS)", true);
+static ProfilingZoneID SetAlphaProfilingZone("FFMpeg: set alpha channel", true);
+
+void FFMpegFrameDecoder::convertFrameToBmp(AVFrame* pFrame, BitmapPtr pBmp)
+{
+ AVPicture destPict;
+ unsigned char * pDestBits = pBmp->getPixels();
+ destPict.data[0] = pDestBits;
+ destPict.linesize[0] = pBmp->getStride();
+ AVPixelFormat destFmt;
+ switch (pBmp->getPixelFormat()) {
+ case R8G8B8X8:
+ case R8G8B8A8:
+ destFmt = PIX_FMT_RGBA;
+ break;
+ case B8G8R8X8:
+ case B8G8R8A8:
+ destFmt = PIX_FMT_BGRA;
+ break;
+ case R8G8B8:
+ destFmt = PIX_FMT_RGB24;
+ break;
+ case B8G8R8:
+ destFmt = PIX_FMT_BGR24;
+ break;
+ case YCbCr422:
+ destFmt = PIX_FMT_YUYV422;
+ break;
+ default:
+ AVG_ASSERT_MSG(false, (string("FFMpegFrameDecoder: Dest format ") +
+ toString(pBmp->getPixelFormat()) + " not supported.").c_str());
+ destFmt = PIX_FMT_BGRA;
+ }
+ AVCodecContext const* pContext = m_pStream->codec;
+ if (destFmt == PIX_FMT_BGRA && (pContext->pix_fmt == PIX_FMT_YUV420P ||
+ pContext->pix_fmt == PIX_FMT_YUVJ420P))
+ {
+ ScopeTimer timer(ConvertImageLibavgProfilingZone);
+ BitmapPtr pBmpY(new Bitmap(pBmp->getSize(), I8, pFrame->data[0],
+ pFrame->linesize[0], false));
+ BitmapPtr pBmpU(new Bitmap(pBmp->getSize(), I8, pFrame->data[1],
+ pFrame->linesize[1], false));
+ BitmapPtr pBmpV(new Bitmap(pBmp->getSize(), I8, pFrame->data[2],
+ pFrame->linesize[2], false));
+ pBmp->copyYUVPixels(*pBmpY, *pBmpU, *pBmpV,
+ pContext->pix_fmt == PIX_FMT_YUVJ420P);
+ } else {
+ if (!m_pSwsContext) {
+ m_pSwsContext = sws_getContext(pContext->width, pContext->height,
+ pContext->pix_fmt, pContext->width, pContext->height, destFmt,
+ SWS_BICUBIC, 0, 0, 0);
+ AVG_ASSERT(m_pSwsContext);
+ }
+ {
+ ScopeTimer timer(ConvertImageSWSProfilingZone);
+ sws_scale(m_pSwsContext, pFrame->data, pFrame->linesize, 0,
+ pContext->height, destPict.data, destPict.linesize);
+ }
+ if (pBmp->getPixelFormat() == B8G8R8X8 || pBmp->getPixelFormat() == R8G8B8X8) {
+ ScopeTimer timer(SetAlphaProfilingZone);
+ // Make sure the alpha channel is white.
+ // TODO: This is slow. Make OpenGL do it.
+ unsigned char * pLine = pBmp->getPixels();
+ IntPoint size = pBmp->getSize();
+ for (int y = 0; y < size.y; ++y) {
+ unsigned char * pPixel = pLine;
+ for (int x = 0; x < size.x; ++x) {
+ pPixel[3] = 0xFF;
+ pPixel += 4;
+ }
+ pLine = pLine + pBmp->getStride();
+ }
+ }
+ }
+}
+
+void FFMpegFrameDecoder::copyPlaneToBmp(BitmapPtr pBmp, unsigned char * pData, int stride)
+{
+ unsigned char * pSrc=pData;
+ unsigned char * pDest= pBmp->getPixels();
+ int destStride = pBmp->getStride();
+ int height = pBmp->getSize().y;
+ int width = pBmp->getSize().x;
+ for (int y = 0; y < height; y++) {
+ memcpy(pDest, pSrc, width);
+ pSrc += stride;
+ pDest += destStride;
+ }
+}
+
+void FFMpegFrameDecoder::handleSeek()
+{
+ m_LastFrameTime = -1.0f;
+ avcodec_flush_buffers(m_pStream->codec);
+ m_bEOF = false;
+ if (m_StartTimestamp == -1) {
+ m_StartTimestamp = 0;
+ }
+}
+
+float FFMpegFrameDecoder::getCurTime() const
+{
+ return m_LastFrameTime;
+}
+
+float FFMpegFrameDecoder::getFPS() const
+{
+ return m_FPS;
+}
+
+void FFMpegFrameDecoder::setFPS(float fps)
+{
+ m_bUseStreamFPS = (fps == 0);
+ if (fps == 0) {
+ m_FPS = getStreamFPS(m_pStream);
+ } else {
+ m_FPS = fps;
+ }
+}
+
+bool FFMpegFrameDecoder::isEOF() const
+{
+ return m_bEOF;
+}
+
+float FFMpegFrameDecoder::getFrameTime(long long dts, bool bFrameAfterSeek)
+{
+ bool bUseStreamFPS = m_bUseStreamFPS;
+ if (dts == (long long)AV_NOPTS_VALUE) {
+ bUseStreamFPS = false;
+ dts = 0;
+ }
+ if (m_StartTimestamp == -1) {
+ m_StartTimestamp = dts;
+ }
+ float frameTime;
+ if (bUseStreamFPS || bFrameAfterSeek) {
+ frameTime = float(dts-m_StartTimestamp)/m_TimeUnitsPerSecond;
+ } else {
+ if (m_LastFrameTime == -1) {
+ frameTime = 0;
+ } else {
+ frameTime = m_LastFrameTime + 1.0f/m_FPS;
+ }
+ }
+ return frameTime;
+}
+
+}
+
diff --git a/src/video/FFMpegFrameDecoder.h b/src/video/FFMpegFrameDecoder.h
new file mode 100644
index 0000000..51b875f
--- /dev/null
+++ b/src/video/FFMpegFrameDecoder.h
@@ -0,0 +1,72 @@
+//
+// 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
+//
+
+#ifndef _FFMpegFrameDecoder_H_
+#define _FFMpegFrameDecoder_H_
+
+#include "../avgconfigwrapper.h"
+
+#include "../graphics/Bitmap.h"
+
+#include "WrapFFMpeg.h"
+
+namespace avg {
+
+class AVG_API FFMpegFrameDecoder
+{
+ public:
+ FFMpegFrameDecoder(AVStream* pStream);
+ virtual ~FFMpegFrameDecoder();
+
+ bool decodePacket(AVPacket* pPacket, AVFrame* pFrame, bool bFrameAfterSeek);
+ bool decodeLastFrame(AVFrame* pFrame);
+ void convertFrameToBmp(AVFrame* pFrame, BitmapPtr pBmp);
+ void copyPlaneToBmp(BitmapPtr pBmp, unsigned char * pData, int stride);
+
+ void handleSeek();
+
+ virtual float getCurTime() const;
+ virtual float getFPS() const;
+ virtual void setFPS(float fps);
+
+ virtual bool isEOF() const;
+
+ private:
+ float getFrameTime(long long dts, bool bFrameAfterSeek);
+
+ SwsContext * m_pSwsContext;
+ AVStream* m_pStream;
+
+ bool m_bEOF;
+
+ float m_TimeUnitsPerSecond;
+ long long m_StartTimestamp;
+ float m_LastFrameTime;
+
+ bool m_bUseStreamFPS;
+ float m_FPS;
+};
+
+typedef boost::shared_ptr<FFMpegFrameDecoder> FFMpegFrameDecoderPtr;
+
+}
+#endif
+
diff --git a/src/video/Makefile.am b/src/video/Makefile.am
new file mode 100644
index 0000000..a48d1f6
--- /dev/null
+++ b/src/video/Makefile.am
@@ -0,0 +1,50 @@
+AM_CPPFLAGS = -I.. @XML2_CFLAGS@ @PTHREAD_CFLAGS@ @GDK_PIXBUF_CFLAGS@
+
+if APPLE
+ X_LIBS =
+else
+if ENABLE_RPI
+ X_LIBS = -lX11 -lGLESv2 -lEGL
+else
+if ENABLE_EGL
+ X_LIBS = -lXxf86vm -lX11 -lGLESv2 -lEGL
+else
+ X_LIBS = -lXxf86vm -lX11
+endif
+endif
+endif
+
+ALL_H = FFMpegDemuxer.h VideoDemuxerThread.h VideoDecoder.h \
+ VideoDecoderThread.h AudioDecoderThread.h VideoMsg.h FFMpegFrameDecoder.h \
+ AsyncVideoDecoder.h VideoDecoderThread.h SyncVideoDecoder.h \
+ VideoInfo.h WrapFFMpeg.h
+
+if USE_VDPAU_SRC
+ ALL_H += VDPAUDecoder.h VDPAUHelper.h
+endif
+
+TESTS = testvideo
+
+EXTRA_DIST = $(wildcard baseline/*.png)
+
+noinst_LTLIBRARIES = libvideo.la
+noinst_PROGRAMS = testvideo
+
+libvideo_la_SOURCES = FFMpegDemuxer.cpp VideoDemuxerThread.cpp VideoDecoder.cpp \
+ VideoDecoderThread.cpp AudioDecoderThread.cpp VideoMsg.cpp \
+ AsyncVideoDecoder.cpp VideoInfo.cpp SyncVideoDecoder.cpp \
+ FFMpegFrameDecoder.cpp \
+ $(ALL_H)
+
+if USE_VDPAU_SRC
+ libvideo_la_SOURCES += VDPAUDecoder.cpp VDPAUHelper.cpp
+endif
+
+libvideo_la_LIBADD = @LIBVIDEO_LDADD@
+
+testvideo_SOURCES = testvideo.cpp $(ALL_H)
+testvideo_LDADD = ./libvideo.la ../audio/libaudio.la ../graphics/libgraphics.la \
+ ../base/libbase.la ../base/triangulate/libtriangulate.la -ldl \
+ @GL_LIBS@ @GLU_LIBS@ @SDL_LIBS@ @XML2_LIBS@ \
+ @BOOST_THREAD_LIBS@ @PTHREAD_LIBS@ @LIBFFMPEG@ @LIBAVRESAMPLE@ @GDK_PIXBUF_LIBS@ \
+ $(X_LIBS)
diff --git a/src/video/SyncVideoDecoder.cpp b/src/video/SyncVideoDecoder.cpp
new file mode 100644
index 0000000..a0c62ce
--- /dev/null
+++ b/src/video/SyncVideoDecoder.cpp
@@ -0,0 +1,261 @@
+//
+// 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 "SyncVideoDecoder.h"
+#include "FFMpegDemuxer.h"
+
+#include "../base/Exception.h"
+#include "../base/Logger.h"
+#include "../base/ScopeTimer.h"
+#include "../base/ObjectCounter.h"
+#include "../base/ProfilingZoneID.h"
+#include "../base/StringHelper.h"
+
+#include "../graphics/BitmapLoader.h"
+
+#include <iostream>
+#include <sstream>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+using namespace std;
+
+namespace avg {
+
+SyncVideoDecoder::SyncVideoDecoder()
+ : m_pDemuxer(0),
+ m_bFirstPacket(false),
+ m_bUseStreamFPS(true),
+ m_FPS(0)
+{
+ ObjectCounter::get()->incRef(&typeid(*this));
+}
+
+SyncVideoDecoder::~SyncVideoDecoder()
+{
+ ObjectCounter::get()->decRef(&typeid(*this));
+}
+
+void SyncVideoDecoder::open(const string& sFilename, bool bUseHardwareAcceleration,
+ bool bEnableSound)
+{
+ m_bProcessingLastFrames = false;
+ VideoDecoder::open(sFilename, false, false);
+
+ if (getVStreamIndex() >= 0) {
+ if (m_bUseStreamFPS) {
+ m_FPS = getStreamFPS();
+ }
+ m_bFirstPacket = true;
+ m_bVideoSeekDone = false;
+ }
+}
+
+void SyncVideoDecoder::startDecoding(bool bDeliverYCbCr, const AudioParams* pAP)
+{
+ VideoDecoder::startDecoding(bDeliverYCbCr, 0);
+
+ AVG_ASSERT(!m_pDemuxer);
+ vector<int> streamIndexes;
+ streamIndexes.push_back(getVStreamIndex());
+ m_pDemuxer = new FFMpegDemuxer(getFormatContext(), streamIndexes);
+
+ m_pFrameDecoder = FFMpegFrameDecoderPtr(new FFMpegFrameDecoder(getVideoStream()));
+ m_pFrameDecoder->setFPS(m_FPS);
+#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(54, 28, 0)
+ m_pFrame = avcodec_alloc_frame();
+#else
+ m_pFrame = new AVFrame;
+#endif
+}
+
+void SyncVideoDecoder::close()
+{
+ delete m_pDemuxer;
+ m_pDemuxer = 0;
+
+ m_pFrameDecoder = FFMpegFrameDecoderPtr();
+ VideoDecoder::close();
+#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(54, 28, 0)
+ avcodec_free_frame(&m_pFrame);
+#else
+ delete m_pFrame;
+#endif
+}
+
+void SyncVideoDecoder::seek(float destTime)
+{
+ AVG_ASSERT(getState() == DECODING);
+
+ if (m_bFirstPacket) {
+ readFrame(m_pFrame);
+ }
+ m_pDemuxer->seek(destTime);
+ m_bVideoSeekDone = true;
+ m_pFrameDecoder->handleSeek();
+}
+
+void SyncVideoDecoder::loop()
+{
+ seek(0);
+}
+
+int SyncVideoDecoder::getCurFrame() const
+{
+ return int(getCurTime()*getStreamFPS()+0.49);
+}
+
+int SyncVideoDecoder::getNumFramesQueued() const
+{
+ return 0;
+}
+
+float SyncVideoDecoder::getCurTime() const
+{
+ AVG_ASSERT(getState() != CLOSED);
+ if (m_pFrameDecoder) {
+ return m_pFrameDecoder->getCurTime();
+ } else {
+ return 0;
+ }
+}
+
+float SyncVideoDecoder::getFPS() const
+{
+ AVG_ASSERT(getState() != CLOSED);
+ return m_FPS;
+}
+
+void SyncVideoDecoder::setFPS(float fps)
+{
+ m_bUseStreamFPS = (fps == 0);
+ if (fps == 0) {
+ m_FPS = getStreamFPS();
+ } else {
+ m_FPS = fps;
+ }
+ if (m_pFrameDecoder) {
+ m_pFrameDecoder->setFPS(m_FPS);
+ }
+}
+
+static ProfilingZoneID RenderToBmpProfilingZone("FFMpeg: renderToBmp", true);
+static ProfilingZoneID CopyImageProfilingZone("FFMpeg: copy image", true);
+
+FrameAvailableCode SyncVideoDecoder::renderToBmps(vector<BitmapPtr>& pBmps,
+ float timeWanted)
+{
+ AVG_ASSERT(getState() == DECODING);
+ ScopeTimer timer(RenderToBmpProfilingZone);
+ FrameAvailableCode frameAvailable;
+ if (timeWanted == -1) {
+ readFrame(m_pFrame);
+ frameAvailable = FA_NEW_FRAME;
+ } else {
+ frameAvailable = readFrameForTime(m_pFrame, timeWanted);
+ }
+ if (frameAvailable == FA_USE_LAST_FRAME || isEOF()) {
+ return FA_USE_LAST_FRAME;
+ } else {
+ if (pixelFormatIsPlanar(getPixelFormat())) {
+ ScopeTimer timer(CopyImageProfilingZone);
+ for (unsigned i = 0; i < pBmps.size(); ++i) {
+ m_pFrameDecoder->copyPlaneToBmp(pBmps[i], m_pFrame->data[i],
+ m_pFrame->linesize[i]);
+ }
+ } else {
+ m_pFrameDecoder->convertFrameToBmp(m_pFrame, pBmps[0]);
+ }
+ return FA_NEW_FRAME;
+ }
+}
+
+void SyncVideoDecoder::throwAwayFrame(float timeWanted)
+{
+ AVG_ASSERT(getState() == DECODING);
+ readFrameForTime(m_pFrame, timeWanted);
+}
+
+bool SyncVideoDecoder::isEOF() const
+{
+ AVG_ASSERT(getState() == DECODING);
+ return m_pFrameDecoder->isEOF() && !m_bProcessingLastFrames;
+}
+
+FrameAvailableCode SyncVideoDecoder::readFrameForTime(AVFrame* pFrame, float timeWanted)
+{
+ AVG_ASSERT(getState() == DECODING);
+ float timePerFrame = 1.0f/m_FPS;
+ if (!m_bVideoSeekDone && timeWanted-m_pFrameDecoder->getCurTime() < 0.5f*timePerFrame)
+ {
+ // The last frame is still current. Display it again.
+ return FA_USE_LAST_FRAME;
+ } else {
+ bool bInvalidFrame = true;
+ while (bInvalidFrame && !isEOF()) {
+ readFrame(pFrame);
+ bInvalidFrame = m_pFrameDecoder->getCurTime()-timeWanted < -0.5f*timePerFrame;
+ }
+ }
+ if (m_bVideoSeekDone) {
+ m_bVideoSeekDone = false;
+ }
+ return FA_NEW_FRAME;
+}
+
+static ProfilingZoneID DecodeProfilingZone("FFMpeg: decode", true);
+
+void SyncVideoDecoder::readFrame(AVFrame* pFrame)
+{
+ AVG_ASSERT(getState() == DECODING);
+ ScopeTimer timer(DecodeProfilingZone);
+
+ if (m_bProcessingLastFrames) {
+ // EOF received, but last frames still need to be decoded.
+ bool bGotPicture = m_pFrameDecoder->decodeLastFrame(pFrame);
+ if (!bGotPicture) {
+ m_bProcessingLastFrames = false;
+ }
+ } else {
+ bool bDone = false;
+ while (!bDone) {
+ AVPacket* pPacket = m_pDemuxer->getPacket(getVStreamIndex());
+ m_bFirstPacket = false;
+ bool bGotPicture;
+ if (pPacket) {
+ bGotPicture = m_pFrameDecoder->decodePacket(pPacket, pFrame,
+ m_bVideoSeekDone);
+ } else {
+ bGotPicture = m_pFrameDecoder->decodeLastFrame(pFrame);
+ }
+ if (bGotPicture && m_pFrameDecoder->isEOF()) {
+ m_bProcessingLastFrames = true;
+ }
+ if (bGotPicture || m_pFrameDecoder->isEOF()) {
+ bDone = true;
+ }
+ }
+ }
+}
+
+}
+
diff --git a/src/video/SyncVideoDecoder.h b/src/video/SyncVideoDecoder.h
new file mode 100644
index 0000000..345e8f6
--- /dev/null
+++ b/src/video/SyncVideoDecoder.h
@@ -0,0 +1,77 @@
+//
+// 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
+//
+
+#ifndef _SyncVideoDecoder_H_
+#define _SyncVideoDecoder_H_
+
+#include "../avgconfigwrapper.h"
+#include "VideoDecoder.h"
+#include "FFMpegDemuxer.h"
+#include "FFMpegFrameDecoder.h"
+
+namespace avg {
+
+class AVG_API SyncVideoDecoder: public VideoDecoder
+{
+ public:
+ SyncVideoDecoder();
+ virtual ~SyncVideoDecoder();
+ virtual void open(const std::string& sFilename, bool bUseHardwareAcceleration,
+ bool bEnableSound);
+ virtual void startDecoding(bool bDeliverYCbCr, const AudioParams* pAP);
+ virtual void close();
+
+
+ virtual int getCurFrame() const;
+ virtual int getNumFramesQueued() const;
+ virtual float getCurTime() const;
+ virtual float getFPS() const;
+ virtual void setFPS(float fps);
+ virtual FrameAvailableCode renderToBmps(std::vector<BitmapPtr>& pBmps,
+ float timeWanted);
+ virtual void throwAwayFrame(float timeWanted);
+
+ virtual void seek(float destTime);
+ virtual void loop();
+ virtual bool isEOF() const;
+
+ private:
+ FrameAvailableCode readFrameForTime(AVFrame* pFrame, float timeWanted);
+ void readFrame(AVFrame* pFrame);
+
+ FFMpegFrameDecoderPtr m_pFrameDecoder;
+ bool m_bVideoSeekDone;
+
+ FFMpegDemuxer * m_pDemuxer;
+
+ bool m_bProcessingLastFrames;
+ bool m_bFirstPacket;
+
+ bool m_bUseStreamFPS;
+ float m_FPS;
+ AVFrame* m_pFrame;
+};
+
+typedef boost::shared_ptr<SyncVideoDecoder> SyncVideoDecoderPtr;
+
+}
+#endif
+
diff --git a/src/video/VDPAUDecoder.cpp b/src/video/VDPAUDecoder.cpp
new file mode 100644
index 0000000..2a344d6
--- /dev/null
+++ b/src/video/VDPAUDecoder.cpp
@@ -0,0 +1,251 @@
+//
+// 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 "VDPAUDecoder.h"
+#include "VDPAUHelper.h"
+
+#include "../base/Exception.h"
+
+#include <iostream>
+
+/*
+Notes on adding Render-to-texture support:
+- For every Surface, call
+ VDPAURegisterSurfaceNV()
+ VDPAUSurfaceAccessNV(..., READ)
+- VideoNode: Create OGLSurface and GLTexture objs each frame from texids,
+ map VDPAU surfaces while textures exist.
+- New OGLSurface pixel format VDPAU_INTERLACED
+- Support new OGLSurface pixel format in shader
+*/
+
+using namespace std;
+
+namespace avg {
+
+VDPAUDecoder::VDPAUDecoder()
+ : m_VDPDecoder(VDP_INVALID_HANDLE),
+ m_VDPMixer(VDP_INVALID_HANDLE),
+ m_PixFmt(PIX_FMT_NONE),
+ m_Size(-1,-1)
+{
+}
+
+VDPAUDecoder::~VDPAUDecoder()
+{
+ if (m_VDPMixer != VDP_INVALID_HANDLE) {
+ vdp_video_mixer_destroy(m_VDPMixer);
+ }
+ if (m_VDPDecoder != VDP_INVALID_HANDLE) {
+ vdp_decoder_destroy(m_VDPDecoder);
+ }
+ for (unsigned i = 0; i < m_RenderStates.size(); i++) {
+ vdp_video_surface_destroy(m_RenderStates[i]->surface);
+ delete m_RenderStates[i];
+ }
+}
+
+AVCodec* VDPAUDecoder::openCodec(AVCodecContext* pContext)
+{
+ if (!isAvailable()) {
+ return 0;
+ }
+
+ AVCodec* pCodec = 0;
+ switch (pContext->codec_id) {
+ case AV_CODEC_ID_MPEG1VIDEO:
+ pCodec = avcodec_find_decoder_by_name("mpeg1video_vdpau");
+ if(pCodec) {
+ pCodec->id = AV_CODEC_ID_MPEG1VIDEO;
+ }
+ break;
+ case AV_CODEC_ID_MPEG2VIDEO:
+ pCodec = avcodec_find_decoder_by_name("mpegvideo_vdpau");
+ break;
+ case AV_CODEC_ID_H264:
+ pCodec = avcodec_find_decoder_by_name("h264_vdpau");
+ break;
+ case AV_CODEC_ID_WMV3:
+ pCodec = avcodec_find_decoder_by_name("wmv3_vdpau");
+ break;
+ case AV_CODEC_ID_VC1:
+ pCodec = avcodec_find_decoder_by_name("vc1_vdpau");
+ break;
+ default:
+ pCodec = 0;
+ }
+ if (pCodec) {
+ pContext->get_buffer = VDPAUDecoder::getBuffer;
+ pContext->release_buffer = VDPAUDecoder::releaseBuffer;
+ pContext->draw_horiz_band = VDPAUDecoder::drawHorizBand;
+ pContext->get_format = VDPAUDecoder::getFormat;
+ pContext->slice_flags = SLICE_FLAG_CODED_ORDER | SLICE_FLAG_ALLOW_FIELD;
+ m_Size = IntPoint(pContext->width, pContext->height);
+ }
+ return pCodec;
+}
+
+bool VDPAUDecoder::isAvailable()
+{
+#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(53, 34, 0)
+ return getVDPAUDevice() != 0;
+#else
+ return false;
+#endif
+}
+
+int VDPAUDecoder::getBuffer(AVCodecContext* pContext, AVFrame* pFrame)
+{
+ VDPAUDecoder* pVDPAUDecoder = (VDPAUDecoder*)pContext->opaque;
+ return pVDPAUDecoder->getBufferInternal(pContext, pFrame);
+}
+
+// does not release the render structure, that will be unlocked after getting data
+void VDPAUDecoder::releaseBuffer(struct AVCodecContext* pContext, AVFrame* pFrame)
+{
+ pFrame->data[0] = 0;
+}
+
+
+// main rendering routine
+void VDPAUDecoder::drawHorizBand(struct AVCodecContext* pContext, const AVFrame* src,
+ int offset[4], int y, int type, int height)
+{
+ VDPAUDecoder* pVDPAUDecoder = (VDPAUDecoder*)pContext->opaque;
+ pVDPAUDecoder->render(pContext, src);
+}
+
+AVPixelFormat VDPAUDecoder::getFormat(AVCodecContext* pContext, const AVPixelFormat* pFmt)
+{
+ switch (pContext->codec_id) {
+ case AV_CODEC_ID_H264:
+ return PIX_FMT_VDPAU_H264;
+ case AV_CODEC_ID_MPEG1VIDEO:
+ return PIX_FMT_VDPAU_MPEG1;
+ case AV_CODEC_ID_MPEG2VIDEO:
+ return PIX_FMT_VDPAU_MPEG2;
+ case AV_CODEC_ID_WMV3:
+ return PIX_FMT_VDPAU_WMV3;
+ case AV_CODEC_ID_VC1:
+ return PIX_FMT_VDPAU_VC1;
+ default:
+ return pFmt[0];
+ }
+}
+
+vdpau_render_state* VDPAUDecoder::getFreeRenderState()
+{
+ for (unsigned i = 0; i < m_RenderStates.size(); i++) {
+ vdpau_render_state* pRenderState = m_RenderStates[i];
+ if (!(pRenderState->state & FF_VDPAU_STATE_USED_FOR_REFERENCE)) {
+ return m_RenderStates[i];
+ }
+ }
+
+ // No free surfaces available -> create new surface
+ vdpau_render_state* pRenderState = new vdpau_render_state;
+ m_RenderStates.push_back(pRenderState);
+ memset(pRenderState, 0, sizeof(vdpau_render_state));
+ pRenderState->surface = VDP_INVALID_HANDLE;
+ VdpStatus status = vdp_video_surface_create(getVDPAUDevice(), VDP_CHROMA_TYPE_420,
+ m_Size.x, m_Size.y, &pRenderState->surface);
+ AVG_ASSERT(status == VDP_STATUS_OK);
+
+ return pRenderState;
+}
+
+int VDPAUDecoder::getBufferInternal(AVCodecContext* pContext, AVFrame* pFrame)
+{
+ vdpau_render_state* pRenderState = getFreeRenderState();
+ pFrame->data[0] = (uint8_t*)pRenderState;
+ pFrame->type = FF_BUFFER_TYPE_USER;
+
+ pRenderState->state |= FF_VDPAU_STATE_USED_FOR_REFERENCE;
+ return 0;
+}
+
+void VDPAUDecoder::render(AVCodecContext* pContext, const AVFrame* pFrame)
+{
+ vdpau_render_state* pRenderState = (vdpau_render_state*)pFrame->data[0];
+
+ if (m_VDPDecoder == VDP_INVALID_HANDLE) {
+ setupDecoder(pContext);
+ }
+
+ VdpStatus status = vdp_decoder_render(m_VDPDecoder, pRenderState->surface,
+ (VdpPictureInfo const*)&(pRenderState->info),
+ pRenderState->bitstream_buffers_used, pRenderState->bitstream_buffers);
+ AVG_ASSERT(status == VDP_STATUS_OK);
+}
+
+void VDPAUDecoder::setupDecoder(AVCodecContext* pContext)
+{
+ VdpStatus status;
+
+ // Create new decoder and mixer.
+ VdpDecoderProfile profile = 0;
+ switch (pContext->pix_fmt) {
+ case PIX_FMT_VDPAU_MPEG1:
+ profile = VDP_DECODER_PROFILE_MPEG1;
+ break;
+ case PIX_FMT_VDPAU_MPEG2:
+ profile = VDP_DECODER_PROFILE_MPEG2_MAIN;
+ break;
+ case PIX_FMT_VDPAU_H264:
+ profile = VDP_DECODER_PROFILE_H264_HIGH;
+ break;
+ case PIX_FMT_VDPAU_WMV3:
+ profile = VDP_DECODER_PROFILE_VC1_SIMPLE;
+ break;
+ case PIX_FMT_VDPAU_VC1:
+ profile = VDP_DECODER_PROFILE_VC1_SIMPLE;
+ break;
+ default:
+ AVG_ASSERT(false);
+ }
+ status = vdp_decoder_create(getVDPAUDevice(), profile, m_Size.x, m_Size.y, 16,
+ &m_VDPDecoder);
+ AVG_ASSERT(status == VDP_STATUS_OK);
+
+ m_PixFmt = pContext->pix_fmt;
+
+ VdpVideoMixerFeature features[] = {
+ VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL,
+ VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL,
+ };
+ VdpVideoMixerParameter params[] = {
+ VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH,
+ VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT,
+ VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE,
+ VDP_VIDEO_MIXER_PARAMETER_LAYERS
+ };
+ VdpChromaType chroma = VDP_CHROMA_TYPE_420;
+ int numLayers = 0;
+ void const* paramValues [] = { &m_Size.x, &m_Size.y, &chroma, &numLayers };
+
+ status = vdp_video_mixer_create(getVDPAUDevice(), 2, features, 4, params,
+ paramValues, &m_VDPMixer);
+ AVG_ASSERT(status == VDP_STATUS_OK);
+
+}
+
+}
+
+
diff --git a/src/video/VDPAUDecoder.h b/src/video/VDPAUDecoder.h
new file mode 100644
index 0000000..15dc5b4
--- /dev/null
+++ b/src/video/VDPAUDecoder.h
@@ -0,0 +1,67 @@
+//
+// 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
+//
+#ifndef _VDPAUDecoder_H_
+#define _VDPAUDecoder_H_
+
+
+#include "../avgconfigwrapper.h"
+#include "../base/GLMHelper.h"
+
+#include "WrapFFMpeg.h"
+
+#include <vdpau/vdpau.h>
+#include <libavcodec/vdpau.h>
+
+namespace avg {
+
+class VDPAUDecoder
+{
+public:
+ VDPAUDecoder();
+ ~VDPAUDecoder();
+ AVCodec* openCodec(AVCodecContext* pCodec);
+
+ static bool isAvailable();
+
+private:
+ // Callbacks
+ static int getBuffer(AVCodecContext* pContext, AVFrame* pFrame);
+ static void releaseBuffer(struct AVCodecContext* pContext, AVFrame* pFrame);
+ static void drawHorizBand(AVCodecContext* pContext, const AVFrame* pFrame,
+ int offset[4], int y, int type, int height);
+ static AVPixelFormat getFormat(AVCodecContext* pContext, const AVPixelFormat* pFmt);
+
+ vdpau_render_state* getFreeRenderState();
+ int getBufferInternal(AVCodecContext* pContext, AVFrame* pFrame);
+ void render(AVCodecContext* pContext, const AVFrame* pFrame);
+ void setupDecoder(AVCodecContext* pContext);
+
+ VdpDecoder m_VDPDecoder;
+ VdpVideoMixer m_VDPMixer;
+ AVPixelFormat m_PixFmt;
+ IntPoint m_Size;
+ std::vector<vdpau_render_state*> m_RenderStates;
+
+};
+
+}
+#endif
+
diff --git a/src/video/VDPAUHelper.cpp b/src/video/VDPAUHelper.cpp
new file mode 100644
index 0000000..36d8a9f
--- /dev/null
+++ b/src/video/VDPAUHelper.cpp
@@ -0,0 +1,182 @@
+//
+// 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 "VDPAUHelper.h"
+
+#include "../base/Exception.h"
+#include "../base/ConfigMgr.h"
+
+#include "../graphics/Bitmap.h"
+
+#include <dlfcn.h>
+
+using namespace std;
+
+namespace avg {
+
+VdpGetProcAddress* vdp_get_proc_address;
+
+VdpVideoSurfaceGetParameters* vdp_video_surface_get_parameters;
+VdpVideoSurfaceGetBitsYCbCr* vdp_video_surface_get_bits_y_cb_cr;
+VdpVideoSurfaceCreate* vdp_video_surface_create;
+VdpVideoSurfaceDestroy* vdp_video_surface_destroy;
+
+VdpDeviceDestroy* vdp_device_destroy;
+
+VdpDecoderCreate* vdp_decoder_create;
+VdpDecoderDestroy* vdp_decoder_destroy;
+VdpDecoderRender* vdp_decoder_render;
+
+VdpOutputSurfaceCreate* vdp_output_surface_create;
+VdpOutputSurfaceDestroy* vdp_output_surface_destroy;
+VdpOutputSurfaceGetBitsNative* vdp_output_surface_get_bits_native;
+VdpOutputSurfaceGetParameters* vdp_output_surface_get_parameters;
+
+VdpVideoMixerCreate* vdp_video_mixer_create;
+VdpVideoMixerDestroy* vdp_video_mixer_destroy;
+VdpVideoMixerRender* vdp_video_mixer_render;
+
+VdpPresentationQueueCreate* vdp_presentation_queue_create;
+VdpPresentationQueueDestroy* vdp_presentation_queue_destroy;
+VdpPresentationQueueGetTime* vdp_presentation_queue_get_time;
+VdpPresentationQueueTargetCreateX11* vdp_presentation_queue_target_create_x11;
+VdpPresentationQueueQuerySurfaceStatus* vdp_presentation_queue_query_surface_status;
+VdpPresentationQueueDisplay* vdp_presentation_queue_display;
+VdpPresentationQueueBlockUntilSurfaceIdle*
+ vdp_presentation_queue_block_until_surface_idle;
+
+
+void safeGetProcAddress(VdpFuncId functionId, void** functionPointer)
+{
+ VdpStatus status;
+ status = vdp_get_proc_address(getVDPAUDevice(), functionId, functionPointer);
+ AVG_ASSERT(status == VDP_STATUS_OK);
+}
+
+VdpDevice getVDPAUDevice()
+{
+ static VdpDevice vdpDevice = 0;
+ static bool bInitFailed = false;
+
+ if (vdpDevice) {
+ return vdpDevice;
+ }
+
+ if (bInitFailed) {
+ return 0;
+ }
+
+ Display* pXDisplay = XOpenDisplay(0);
+ AVG_ASSERT(pXDisplay);
+
+ if (!(ConfigMgr::get()->getBoolOption("scr", "videoaccel", true))) {
+ bInitFailed = true;
+ return 0;
+ }
+ VdpStatus status;
+ status = vdp_device_create_x11(pXDisplay, DefaultScreen(pXDisplay), &vdpDevice,
+ &vdp_get_proc_address);
+ if (status != VDP_STATUS_OK)
+ {
+ bInitFailed = true;
+ return 0;
+ }
+
+ safeGetProcAddress(VDP_FUNC_ID_DEVICE_DESTROY, (void**)&vdp_device_destroy);
+ safeGetProcAddress(VDP_FUNC_ID_OUTPUT_SURFACE_CREATE,
+ (void**)&vdp_output_surface_create);
+ safeGetProcAddress(VDP_FUNC_ID_OUTPUT_SURFACE_DESTROY,
+ (void**)&vdp_output_surface_destroy);
+ safeGetProcAddress(VDP_FUNC_ID_OUTPUT_SURFACE_GET_BITS_NATIVE,
+ (void**)&vdp_output_surface_get_bits_native);
+ safeGetProcAddress(VDP_FUNC_ID_VIDEO_SURFACE_CREATE,
+ (void**)&vdp_video_surface_create);
+ safeGetProcAddress(VDP_FUNC_ID_VIDEO_SURFACE_DESTROY,
+ (void**)&vdp_video_surface_destroy);
+ safeGetProcAddress(VDP_FUNC_ID_DECODER_CREATE, (void**)&vdp_decoder_create);
+ safeGetProcAddress(VDP_FUNC_ID_DECODER_DESTROY, (void**)&vdp_decoder_destroy);
+ safeGetProcAddress(VDP_FUNC_ID_DECODER_RENDER, (void**)&vdp_decoder_render);
+ safeGetProcAddress(VDP_FUNC_ID_VIDEO_SURFACE_GET_BITS_Y_CB_CR,
+ (void**)&vdp_video_surface_get_bits_y_cb_cr);
+ safeGetProcAddress(VDP_FUNC_ID_VIDEO_MIXER_CREATE,
+ (void**)&vdp_video_mixer_create);
+ safeGetProcAddress(VDP_FUNC_ID_VIDEO_MIXER_DESTROY,
+ (void**)&vdp_video_mixer_destroy);
+ safeGetProcAddress(VDP_FUNC_ID_VIDEO_MIXER_RENDER,
+ (void**)&vdp_video_mixer_render);
+ safeGetProcAddress(VDP_FUNC_ID_PRESENTATION_QUEUE_CREATE,
+ (void**)&vdp_presentation_queue_create);
+ safeGetProcAddress(VDP_FUNC_ID_PRESENTATION_QUEUE_DESTROY,
+ (void**)&vdp_presentation_queue_destroy);
+ safeGetProcAddress(VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_CREATE_X11,
+ (void**)&vdp_presentation_queue_target_create_x11);
+ safeGetProcAddress(VDP_FUNC_ID_PRESENTATION_QUEUE_QUERY_SURFACE_STATUS,
+ (void**)&vdp_presentation_queue_query_surface_status);
+ safeGetProcAddress(VDP_FUNC_ID_PRESENTATION_QUEUE_DISPLAY,
+ (void**)&vdp_presentation_queue_display);
+ safeGetProcAddress(VDP_FUNC_ID_PRESENTATION_QUEUE_GET_TIME,
+ (void**)&vdp_presentation_queue_get_time);
+ safeGetProcAddress(VDP_FUNC_ID_PRESENTATION_QUEUE_BLOCK_UNTIL_SURFACE_IDLE,
+ (void**)&vdp_presentation_queue_block_until_surface_idle);
+ safeGetProcAddress(VDP_FUNC_ID_VIDEO_SURFACE_GET_PARAMETERS,
+ (void**)&vdp_video_surface_get_parameters);
+ safeGetProcAddress(VDP_FUNC_ID_OUTPUT_SURFACE_GET_PARAMETERS,
+ (void**)&vdp_output_surface_get_parameters);
+ return vdpDevice;
+}
+
+void getPlanesFromVDPAU(vdpau_render_state* pRenderState, BitmapPtr pBmpY,
+ BitmapPtr pBmpU, BitmapPtr pBmpV)
+{
+ VdpStatus status;
+ void *dest[3] = {
+ pBmpY->getPixels(),
+ pBmpV->getPixels(),
+ pBmpU->getPixels()
+ };
+ uint32_t pitches[3] = {
+ uint32_t(pBmpY->getStride()),
+ uint32_t(pBmpV->getStride()),
+ uint32_t(pBmpU->getStride())
+ };
+ status = vdp_video_surface_get_bits_y_cb_cr(pRenderState->surface,
+ VDP_YCBCR_FORMAT_YV12, dest, pitches);
+ AVG_ASSERT(status == VDP_STATUS_OK);
+ unlockVDPAUSurface(pRenderState);
+}
+
+void getBitmapFromVDPAU(vdpau_render_state* pRenderState, BitmapPtr pBmpDest)
+{
+ IntPoint YSize = pBmpDest->getSize();
+ IntPoint UVSize(YSize.x/2, YSize.y/2);
+ BitmapPtr pBmpY(new Bitmap(YSize, I8));
+ BitmapPtr pBmpU(new Bitmap(UVSize, I8));
+ BitmapPtr pBmpV(new Bitmap(UVSize, I8));
+ getPlanesFromVDPAU(pRenderState, pBmpY, pBmpU, pBmpV);
+ pBmpDest->copyYUVPixels(*pBmpY, *pBmpU, *pBmpV, false);
+}
+
+void unlockVDPAUSurface(vdpau_render_state* pRenderState)
+{
+ pRenderState->state &= ~FF_VDPAU_STATE_USED_FOR_REFERENCE;
+}
+
+}
diff --git a/src/video/VDPAUHelper.h b/src/video/VDPAUHelper.h
new file mode 100644
index 0000000..5c84788
--- /dev/null
+++ b/src/video/VDPAUHelper.h
@@ -0,0 +1,76 @@
+//
+// 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
+//
+#ifndef _VDPAUHelper_H_
+#define _VDPAUHelper_H_
+
+
+#include "../avgconfigwrapper.h"
+#include "../graphics/Bitmap.h"
+
+#include <vdpau/vdpau.h>
+#include <libavcodec/vdpau.h>
+#include <boost/shared_ptr.hpp>
+
+namespace avg {
+
+extern VdpGetProcAddress* vdp_get_proc_address;
+
+extern VdpVideoSurfaceGetParameters* vdp_video_surface_get_parameters;
+extern VdpVideoSurfaceGetBitsYCbCr* vdp_video_surface_get_bits_y_cb_cr;
+extern VdpVideoSurfaceCreate* vdp_video_surface_create;
+extern VdpVideoSurfaceDestroy* vdp_video_surface_destroy;
+
+extern VdpDeviceDestroy* vdp_device_destroy;
+
+extern VdpDecoderCreate* vdp_decoder_create;
+extern VdpDecoderDestroy* vdp_decoder_destroy;
+extern VdpDecoderRender* vdp_decoder_render;
+
+extern VdpOutputSurfaceCreate* vdp_output_surface_create;
+extern VdpOutputSurfaceDestroy* vdp_output_surface_destroy;
+extern VdpOutputSurfaceGetBitsNative* vdp_output_surface_get_bits_native;
+extern VdpOutputSurfaceGetParameters* vdp_output_surface_get_parameters;
+
+extern VdpVideoMixerCreate* vdp_video_mixer_create;
+extern VdpVideoMixerDestroy* vdp_video_mixer_destroy;
+extern VdpVideoMixerRender* vdp_video_mixer_render;
+
+extern VdpPresentationQueueCreate* vdp_presentation_queue_create;
+extern VdpPresentationQueueDestroy* vdp_presentation_queue_destroy;
+extern VdpPresentationQueueGetTime* vdp_presentation_queue_get_time;
+extern VdpPresentationQueueTargetCreateX11* vdp_presentation_queue_target_create_x11;
+extern VdpPresentationQueueQuerySurfaceStatus*
+ vdp_presentation_queue_query_surface_status;
+extern VdpPresentationQueueDisplay* vdp_presentation_queue_display;
+extern VdpPresentationQueueBlockUntilSurfaceIdle*
+ vdp_presentation_queue_block_until_surface_idle;
+
+
+VdpDevice getVDPAUDevice();
+
+void getPlanesFromVDPAU(vdpau_render_state* pRenderState, BitmapPtr pBmpY,
+ BitmapPtr pBmpU, BitmapPtr pBmpV);
+void getBitmapFromVDPAU(vdpau_render_state* pRenderState, BitmapPtr pBmpDest);
+void unlockVDPAUSurface(vdpau_render_state* pRenderState);
+
+}
+#endif
+
diff --git a/src/video/VideoDecoder.cpp b/src/video/VideoDecoder.cpp
new file mode 100644
index 0000000..eddf002
--- /dev/null
+++ b/src/video/VideoDecoder.cpp
@@ -0,0 +1,485 @@
+//
+// 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 "VideoDecoder.h"
+#ifdef AVG_ENABLE_VDPAU
+#include "VDPAUDecoder.h"
+#endif
+
+#include "../base/Exception.h"
+#include "../base/Logger.h"
+#include "../base/ObjectCounter.h"
+#include "../base/StringHelper.h"
+
+#include "../graphics/Bitmap.h"
+#include "../graphics/BitmapLoader.h"
+#include "../graphics/GLTexture.h"
+
+#include <string>
+
+#include "WrapFFMpeg.h"
+
+using namespace std;
+using namespace boost;
+
+namespace avg {
+
+bool VideoDecoder::s_bInitialized = false;
+boost::mutex VideoDecoder::s_OpenMutex;
+
+
+VideoDecoder::VideoDecoder()
+ : m_State(CLOSED),
+ m_pFormatContext(0),
+ m_VStreamIndex(-1),
+ m_pVStream(0),
+ m_PF(NO_PIXELFORMAT),
+ m_Size(0,0),
+#ifdef AVG_ENABLE_VDPAU
+ m_pVDPAUDecoder(0),
+#endif
+ m_AStreamIndex(-1),
+ m_pAStream(0)
+{
+ ObjectCounter::get()->incRef(&typeid(*this));
+ initVideoSupport();
+}
+
+VideoDecoder::~VideoDecoder()
+{
+ if (m_pFormatContext) {
+ close();
+ }
+#ifdef AVG_ENABLE_VDPAU
+ if (m_pVDPAUDecoder) {
+ delete m_pVDPAUDecoder;
+ }
+#endif
+ ObjectCounter::get()->decRef(&typeid(*this));
+}
+
+void VideoDecoder::open(const string& sFilename, bool bUseHardwareAcceleration,
+ bool bEnableSound)
+{
+ lock_guard lock(s_OpenMutex);
+ int err;
+ m_sFilename = sFilename;
+
+ AVG_TRACE(Logger::category::MEMORY, Logger::severity::INFO, "Opening " << sFilename);
+ err = avformat_open_input(&m_pFormatContext, sFilename.c_str(), 0, 0);
+ if (err < 0) {
+ m_sFilename = "";
+ m_pFormatContext = 0;
+ avcodecError(sFilename, err);
+ }
+
+ err = avformat_find_stream_info(m_pFormatContext, 0);
+
+ if (err < 0) {
+ m_sFilename = "";
+ m_pFormatContext = 0;
+ throw Exception(AVG_ERR_VIDEO_INIT_FAILED,
+ sFilename + ": Could not find codec parameters.");
+ }
+ if (strcmp(m_pFormatContext->iformat->name, "image2") == 0) {
+ m_sFilename = "";
+ m_pFormatContext = 0;
+ throw Exception(AVG_ERR_VIDEO_INIT_FAILED,
+ sFilename + ": Image files not supported as videos.");
+ }
+ av_read_play(m_pFormatContext);
+
+ // Find audio and video streams in the file
+ m_VStreamIndex = -1;
+ m_AStreamIndex = -1;
+ for (unsigned i = 0; i < m_pFormatContext->nb_streams; i++) {
+ AVCodecContext* pContext = m_pFormatContext->streams[i]->codec;
+ switch (pContext->codec_type) {
+ case AVMEDIA_TYPE_VIDEO:
+ if (m_VStreamIndex < 0) {
+ m_VStreamIndex = i;
+ }
+ break;
+ case AVMEDIA_TYPE_AUDIO:
+ if (m_AStreamIndex < 0 && bEnableSound) {
+ m_AStreamIndex = i;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Enable video stream demuxing
+ if (m_VStreamIndex >= 0) {
+ m_pVStream = m_pFormatContext->streams[m_VStreamIndex];
+
+ m_Size = IntPoint(m_pVStream->codec->width, m_pVStream->codec->height);
+
+ char szCodec[256];
+ avcodec_string(szCodec, sizeof(szCodec), m_pVStream->codec, 0);
+ int rc = openCodec(m_VStreamIndex, bUseHardwareAcceleration);
+ if (rc == -1) {
+ m_VStreamIndex = -1;
+ m_pVStream = 0;
+ throw Exception(AVG_ERR_VIDEO_INIT_FAILED,
+ sFilename + ": unsupported video codec ("+szCodec+").");
+ }
+ m_PF = calcPixelFormat(true);
+ }
+ // Enable audio stream demuxing.
+ if (m_AStreamIndex >= 0) {
+ m_pAStream = m_pFormatContext->streams[m_AStreamIndex];
+ char szCodec[256];
+ avcodec_string(szCodec, sizeof(szCodec), m_pAStream->codec, 0);
+ int rc = openCodec(m_AStreamIndex, false);
+ if (rc == -1) {
+ m_AStreamIndex = -1;
+ m_pAStream = 0;
+ throw Exception(AVG_ERR_VIDEO_INIT_FAILED,
+ sFilename + ": unsupported audio codec ("+szCodec+").");
+ }
+ }
+ if (!m_pVStream && !m_pAStream) {
+ throw Exception(AVG_ERR_VIDEO_INIT_FAILED,
+ sFilename + ": no usable streams found.");
+ }
+
+ m_State = OPENED;
+}
+
+void VideoDecoder::startDecoding(bool bDeliverYCbCr, const AudioParams* pAP)
+{
+ AVG_ASSERT(m_State == OPENED);
+ if (m_VStreamIndex >= 0) {
+ m_PF = calcPixelFormat(bDeliverYCbCr);
+ }
+ bool bAudioEnabled = (pAP!=0);
+ if (!bAudioEnabled) {
+ m_AStreamIndex = -1;
+ if (m_pAStream) {
+ avcodec_close(m_pAStream->codec);
+ }
+ m_pAStream = 0;
+ }
+
+ if (m_AStreamIndex >= 0) {
+ if (m_pAStream->codec->channels > pAP->m_Channels) {
+ throw Exception(AVG_ERR_VIDEO_INIT_FAILED,
+ m_sFilename + ": unsupported number of audio channels (" +
+ toString(m_pAStream->codec->channels) + ").");
+ m_AStreamIndex = -1;
+ m_pAStream = 0;
+ }
+ }
+
+ if (!m_pVStream && !m_pAStream) {
+ throw Exception(AVG_ERR_VIDEO_INIT_FAILED,
+ m_sFilename + ": no usable streams found.");
+ }
+
+ m_State = DECODING;
+}
+
+void VideoDecoder::close()
+{
+ lock_guard lock(s_OpenMutex);
+ AVG_TRACE(Logger::category::MEMORY, Logger::severity::INFO, "Closing " << m_sFilename);
+
+ // Close audio and video codecs
+ if (m_pVStream) {
+ avcodec_close(m_pVStream->codec);
+ m_pVStream = 0;
+ m_VStreamIndex = -1;
+ }
+
+ if (m_pAStream) {
+ avcodec_close(m_pAStream->codec);
+ m_pAStream = 0;
+ m_AStreamIndex = -1;
+ }
+ if (m_pFormatContext) {
+#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(53, 21, 0)
+ avformat_close_input(&m_pFormatContext);
+#else
+ av_close_input_file(m_pFormatContext);
+ m_pFormatContext = 0;
+#endif
+ }
+
+ m_State = CLOSED;
+}
+
+VideoDecoder::DecoderState VideoDecoder::getState() const
+{
+ return m_State;
+}
+
+VideoInfo VideoDecoder::getVideoInfo() const
+{
+ AVG_ASSERT(m_State != CLOSED);
+ AVG_ASSERT(m_pVStream || m_pAStream);
+ float duration = getDuration(SS_DEFAULT);
+
+ VideoInfo info(m_pFormatContext->iformat->name, duration, m_pFormatContext->bit_rate,
+ m_pVStream != 0, m_pAStream != 0);
+ if (m_pVStream) {
+ info.setVideoData(m_Size, getStreamPF(), getNumFrames(), getStreamFPS(),
+ m_pVStream->codec->codec->name, usesVDPAU(), getDuration(SS_VIDEO));
+ }
+ if (m_pAStream) {
+ AVCodecContext * pACodec = m_pAStream->codec;
+ info.setAudioData(pACodec->codec->name, pACodec->sample_rate,
+ pACodec->channels, getDuration(SS_AUDIO));
+ }
+ return info;
+}
+
+PixelFormat VideoDecoder::getPixelFormat() const
+{
+ AVG_ASSERT(m_State != CLOSED);
+ return m_PF;
+}
+
+IntPoint VideoDecoder::getSize() const
+{
+ AVG_ASSERT(m_State != CLOSED);
+ return m_Size;
+}
+
+float VideoDecoder::getStreamFPS() const
+{
+ AVG_ASSERT(m_State != CLOSED);
+ return avg::getStreamFPS(m_pVStream);
+}
+
+FrameAvailableCode VideoDecoder::renderToBmp(BitmapPtr pBmp, float timeWanted)
+{
+ std::vector<BitmapPtr> pBmps;
+ pBmps.push_back(pBmp);
+ return renderToBmps(pBmps, timeWanted);
+}
+
+FrameAvailableCode VideoDecoder::renderToTexture(GLTexturePtr pTextures[4],
+ float timeWanted)
+{
+ std::vector<BitmapPtr> pBmps;
+ for (unsigned i=0; i<getNumPixelFormatPlanes(m_PF); ++i) {
+ pBmps.push_back(pTextures[i]->lockStreamingBmp());
+ }
+ FrameAvailableCode frameAvailable;
+ if (pixelFormatIsPlanar(m_PF)) {
+ frameAvailable = renderToBmps(pBmps, timeWanted);
+ } else {
+ frameAvailable = renderToBmp(pBmps[0], timeWanted);
+ }
+ for (unsigned i=0; i<getNumPixelFormatPlanes(m_PF); ++i) {
+ pTextures[i]->unlockStreamingBmp(frameAvailable == FA_NEW_FRAME);
+ }
+ return frameAvailable;
+}
+
+void VideoDecoder::logConfig()
+{
+ bool bVDPAUAvailable = false;
+#ifdef AVG_ENABLE_VDPAU
+ bVDPAUAvailable = VDPAUDecoder::isAvailable();
+#endif
+ if (bVDPAUAvailable) {
+ AVG_TRACE(Logger::category::CONFIG, Logger::severity::INFO,
+ "Hardware video acceleration: VDPAU");
+ } else {
+ AVG_TRACE(Logger::category::CONFIG, Logger::severity::INFO,
+ "Hardware video acceleration: Off");
+ }
+}
+
+int VideoDecoder::getNumFrames() const
+{
+ AVG_ASSERT(m_State != CLOSED);
+ int numFrames = int(m_pVStream->nb_frames);
+ if (numFrames > 0) {
+ return numFrames;
+ } else {
+ return int(getDuration(SS_VIDEO) * getStreamFPS());
+ }
+}
+
+AVFormatContext* VideoDecoder::getFormatContext()
+{
+ AVG_ASSERT(m_pFormatContext);
+ return m_pFormatContext;
+}
+
+bool VideoDecoder::usesVDPAU() const
+{
+#ifdef AVG_ENABLE_VDPAU
+ AVCodecContext const* pContext = getCodecContext();
+ return pContext->codec && (pContext->codec->capabilities & CODEC_CAP_HWACCEL_VDPAU);
+#else
+ return false;
+#endif
+}
+
+AVCodecContext const* VideoDecoder::getCodecContext() const
+{
+ return m_pVStream->codec;
+}
+
+AVCodecContext* VideoDecoder::getCodecContext()
+{
+ return m_pVStream->codec;
+}
+
+int VideoDecoder::getVStreamIndex() const
+{
+ return m_VStreamIndex;
+}
+
+AVStream* VideoDecoder::getVideoStream() const
+{
+ return m_pVStream;
+}
+
+int VideoDecoder::getAStreamIndex() const
+{
+ return m_AStreamIndex;
+}
+
+AVStream* VideoDecoder::getAudioStream() const
+{
+ return m_pAStream;
+}
+
+void VideoDecoder::initVideoSupport()
+{
+ if (!s_bInitialized) {
+ av_register_all();
+ s_bInitialized = true;
+ // Tune libavcodec console spam.
+// av_log_set_level(AV_LOG_DEBUG);
+ av_log_set_level(AV_LOG_QUIET);
+ }
+}
+
+int VideoDecoder::openCodec(int streamIndex, bool bUseHardwareAcceleration)
+{
+ AVCodecContext* pContext;
+ pContext = m_pFormatContext->streams[streamIndex]->codec;
+// pContext->debug = 0x0001; // see avcodec.h
+
+ AVCodec * pCodec = 0;
+#ifdef AVG_ENABLE_VDPAU
+ if (bUseHardwareAcceleration) {
+ m_pVDPAUDecoder = new VDPAUDecoder();
+ pContext->opaque = m_pVDPAUDecoder;
+ pCodec = m_pVDPAUDecoder->openCodec(pContext);
+ }
+#endif
+ if (!pCodec) {
+ pCodec = avcodec_find_decoder(pContext->codec_id);
+ }
+ if (!pCodec) {
+ return -1;
+ }
+ int rc = avcodec_open2(pContext, pCodec, 0);
+
+ if (rc < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+float VideoDecoder::getDuration(StreamSelect streamSelect) const
+{
+ AVG_ASSERT(m_State != CLOSED);
+ long long duration;
+ AVRational time_base;
+ if (streamSelect == SS_DEFAULT) {
+ if (m_pVStream) {
+ streamSelect = SS_VIDEO;
+ } else {
+ streamSelect = SS_AUDIO;
+ }
+ }
+ if (streamSelect == SS_VIDEO) {
+ duration = m_pVStream->duration;
+ time_base = m_pVStream->time_base;
+ } else {
+ duration = m_pAStream->duration;
+ time_base = m_pAStream->time_base;
+ }
+ if (duration == (long long)AV_NOPTS_VALUE) {
+ return 0;
+ } else {
+ return float(duration)*float(av_q2d(time_base));
+ }
+}
+
+PixelFormat VideoDecoder::calcPixelFormat(bool bUseYCbCr)
+{
+ AVCodecContext const* pContext = getCodecContext();
+ if (bUseYCbCr) {
+ switch(pContext->pix_fmt) {
+ case PIX_FMT_YUV420P:
+#ifdef AVG_ENABLE_VDPAU
+ case PIX_FMT_VDPAU_H264:
+ case PIX_FMT_VDPAU_MPEG1:
+ case PIX_FMT_VDPAU_MPEG2:
+ case PIX_FMT_VDPAU_WMV3:
+ case PIX_FMT_VDPAU_VC1:
+#endif
+ return YCbCr420p;
+ case PIX_FMT_YUVJ420P:
+ return YCbCrJ420p;
+ case PIX_FMT_YUVA420P:
+ return YCbCrA420p;
+ default:
+ break;
+ }
+ }
+ bool bAlpha = (pContext->pix_fmt == PIX_FMT_BGRA ||
+ pContext->pix_fmt == PIX_FMT_YUVA420P);
+ return BitmapLoader::get()->getDefaultPixelFormat(bAlpha);
+}
+
+string VideoDecoder::getStreamPF() const
+{
+ AVCodecContext const* pCodec = getCodecContext();
+ AVPixelFormat pf = pCodec->pix_fmt;
+ const char* psz = av_get_pix_fmt_name(pf);
+ string s;
+ if (psz) {
+ s = psz;
+ }
+ return s;
+}
+
+void avcodecError(const string& sFilename, int err)
+{
+ char buf[256];
+ av_strerror(err, buf, 256);
+ throw Exception(AVG_ERR_VIDEO_INIT_FAILED, sFilename + ": " + buf);
+}
+
+}
+
+
diff --git a/src/video/VideoDecoder.h b/src/video/VideoDecoder.h
new file mode 100644
index 0000000..6d99da8
--- /dev/null
+++ b/src/video/VideoDecoder.h
@@ -0,0 +1,138 @@
+//
+// 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
+//
+
+#ifndef _VideoDecoder_H_
+#define _VideoDecoder_H_
+
+#include "../api.h"
+#include "../avgconfigwrapper.h"
+
+#include "VideoInfo.h"
+
+#include "../audio/AudioParams.h"
+#include "../graphics/PixelFormat.h"
+
+#include "WrapFFMpeg.h"
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <boost/thread/mutex.hpp>
+
+struct vdpau_render_state;
+
+namespace avg {
+
+class Bitmap;
+typedef boost::shared_ptr<Bitmap> BitmapPtr;
+class GLTexture;
+typedef boost::shared_ptr<GLTexture> GLTexturePtr;
+class VDPAUDecoder;
+
+enum FrameAvailableCode {
+ FA_NEW_FRAME, FA_USE_LAST_FRAME, FA_STILL_DECODING
+};
+
+enum StreamSelect {
+ SS_AUDIO, SS_VIDEO, SS_DEFAULT
+};
+
+class AVG_API VideoDecoder
+{
+ public:
+ enum DecoderState {CLOSED, OPENED, DECODING};
+ VideoDecoder();
+ virtual ~VideoDecoder();
+ virtual void open(const std::string& sFilename, bool bUseHardwareAcceleration,
+ bool bEnableSound);
+ virtual void startDecoding(bool bDeliverYCbCr, const AudioParams* pAP);
+ virtual void close();
+ virtual DecoderState getState() const;
+ VideoInfo getVideoInfo() const;
+ PixelFormat getPixelFormat() const;
+ IntPoint getSize() const;
+ float getStreamFPS() const;
+
+ virtual void seek(float destTime) = 0;
+ virtual void loop() = 0;
+ virtual int getCurFrame() const = 0;
+ virtual int getNumFramesQueued() const = 0;
+ virtual float getCurTime() const = 0;
+ virtual float getFPS() const = 0;
+ virtual void setFPS(float fps) = 0;
+
+ virtual FrameAvailableCode renderToBmp(BitmapPtr pBmp, float timeWanted);
+ virtual FrameAvailableCode renderToBmps(std::vector<BitmapPtr>& pBmps,
+ float timeWanted) = 0;
+ virtual FrameAvailableCode renderToTexture(GLTexturePtr pTextures[4],
+ float timeWanted);
+ virtual bool isEOF() const = 0;
+ virtual void throwAwayFrame(float timeWanted) = 0;
+
+ static void logConfig();
+
+ protected:
+ int getNumFrames() const;
+ AVFormatContext* getFormatContext();
+ bool usesVDPAU() const;
+ AVCodecContext const * getCodecContext() const;
+ AVCodecContext * getCodecContext();
+
+ int getVStreamIndex() const;
+ AVStream* getVideoStream() const;
+ int getAStreamIndex() const;
+ AVStream* getAudioStream() const;
+
+ private:
+ void initVideoSupport();
+ int openCodec(int streamIndex, bool bUseHardwareAcceleration);
+ float getDuration(StreamSelect streamSelect) const;
+ PixelFormat calcPixelFormat(bool bUseYCbCr);
+ std::string getStreamPF() const;
+
+ DecoderState m_State;
+ AVFormatContext * m_pFormatContext;
+ std::string m_sFilename;
+
+ // Video
+ int m_VStreamIndex;
+ AVStream * m_pVStream;
+ PixelFormat m_PF;
+ IntPoint m_Size;
+#ifdef AVG_ENABLE_VDPAU
+ VDPAUDecoder* m_pVDPAUDecoder;
+#endif
+
+ // Audio
+ int m_AStreamIndex;
+ AVStream * m_pAStream;
+
+ static bool s_bInitialized;
+ // Prevents different decoder instances from executing open/close simultaneously
+ static boost::mutex s_OpenMutex;
+};
+
+typedef boost::shared_ptr<VideoDecoder> VideoDecoderPtr;
+
+void avcodecError(const std::string& sFilename, int err);
+
+}
+#endif
+
diff --git a/src/video/VideoDecoderThread.cpp b/src/video/VideoDecoderThread.cpp
new file mode 100644
index 0000000..d1c701c
--- /dev/null
+++ b/src/video/VideoDecoderThread.cpp
@@ -0,0 +1,222 @@
+//
+// 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 "VideoDecoderThread.h"
+#include "FFMpegFrameDecoder.h"
+
+#include "../base/Logger.h"
+#include "../base/Exception.h"
+#include "../base/ScopeTimer.h"
+#include "../base/TimeSource.h"
+#include "../avgconfigwrapper.h"
+
+struct vdpau_render_state;
+
+using namespace std;
+
+namespace avg {
+
+VideoDecoderThread::VideoDecoderThread(CQueue& cmdQ, VideoMsgQueue& msgQ,
+ VideoMsgQueue& packetQ, AVStream* pStream, const IntPoint& size, PixelFormat pf,
+ bool bUseVDPAU)
+ : WorkerThread<VideoDecoderThread>(string("Video Decoder"), cmdQ,
+ Logger::category::PROFILE_VIDEO),
+ m_MsgQ(msgQ),
+ m_PacketQ(packetQ),
+ m_pBmpQ(new BitmapQueue()),
+ m_pHalfBmpQ(new BitmapQueue()),
+ m_Size(size),
+ m_PF(pf),
+ m_bUseVDPAU(bUseVDPAU),
+ m_bSeekDone(false),
+ m_bProcessingLastFrames(false)
+{
+ m_pFrameDecoder = FFMpegFrameDecoderPtr(new FFMpegFrameDecoder(pStream));
+}
+
+VideoDecoderThread::~VideoDecoderThread()
+{
+}
+
+bool VideoDecoderThread::init()
+{
+#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(54, 28, 0)
+ m_pFrame = avcodec_alloc_frame();
+#else
+ m_pFrame = new AVFrame;
+#endif
+ return true;
+}
+
+void VideoDecoderThread::deinit()
+{
+#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(54, 28, 0)
+ avcodec_free_frame(&m_pFrame);
+#else
+ delete m_pFrame;
+#endif
+}
+
+static ProfilingZoneID DecoderProfilingZone("Video Decoder Thread", true);
+static ProfilingZoneID PacketWaitProfilingZone("Video wait for packet", true);
+
+bool VideoDecoderThread::work()
+{
+ ScopeTimer timer(DecoderProfilingZone);
+ if (m_bProcessingLastFrames) {
+ // EOF received, but last frames still need to be decoded.
+ handleEOF();
+ } else {
+ // Standard decoding.
+ VideoMsgPtr pMsg;
+ {
+ ScopeTimer timer(PacketWaitProfilingZone);
+ pMsg = m_PacketQ.pop(true);
+ }
+ switch (pMsg->getType()) {
+ case VideoMsg::PACKET:
+ decodePacket(pMsg->getPacket());
+ break;
+ case VideoMsg::END_OF_FILE:
+ handleEOF();
+ m_bProcessingLastFrames = true;
+ break;
+ case VideoMsg::SEEK_DONE:
+ handleSeekDone(pMsg);
+ break;
+ case VideoMsg::CLOSED:
+ close();
+ break;
+ default:
+ pMsg->dump();
+ AVG_ASSERT(false);
+ }
+ }
+ ThreadProfiler::get()->reset();
+ return true;
+}
+
+void VideoDecoderThread::setFPS(float fps)
+{
+ m_pFrameDecoder->setFPS(fps);
+}
+
+void VideoDecoderThread::returnFrame(VideoMsgPtr pMsg)
+{
+ m_pBmpQ->push(pMsg->getFrameBitmap(0));
+ if (pixelFormatIsPlanar(m_PF)) {
+ m_pHalfBmpQ->push(pMsg->getFrameBitmap(1));
+ m_pHalfBmpQ->push(pMsg->getFrameBitmap(2));
+ if (m_PF == YCbCrA420p) {
+ m_pBmpQ->push(pMsg->getFrameBitmap(3));
+ }
+ }
+}
+
+void VideoDecoderThread::decodePacket(AVPacket* pPacket)
+{
+ bool bGotPicture = m_pFrameDecoder->decodePacket(pPacket, m_pFrame, m_bSeekDone);
+ if (bGotPicture) {
+ m_bSeekDone = false;
+ sendFrame(m_pFrame);
+ }
+}
+
+void VideoDecoderThread::handleEOF()
+{
+ bool bGotPicture = m_pFrameDecoder->decodeLastFrame(m_pFrame);
+ if (bGotPicture) {
+ sendFrame(m_pFrame);
+ } else {
+ m_bProcessingLastFrames = false;
+ VideoMsgPtr pMsg(new VideoMsg());
+ pMsg->setEOF();
+ pushMsg(pMsg);
+ }
+}
+
+void VideoDecoderThread::handleSeekDone(VideoMsgPtr pMsg)
+{
+ m_pFrameDecoder->handleSeek();
+ m_bSeekDone = true;
+ m_MsgQ.clear();
+ pushMsg(pMsg);
+}
+
+static ProfilingZoneID CopyImageProfilingZone("Copy image", true);
+
+void VideoDecoderThread::sendFrame(AVFrame* pFrame)
+{
+ VideoMsgPtr pMsg(new VideoMsg());
+ if (m_bUseVDPAU) {
+ vdpau_render_state *pRenderState = (vdpau_render_state *)pFrame->data[0];
+ pMsg->setVDPAUFrame(pRenderState, m_pFrameDecoder->getCurTime());
+ } else {
+ vector<BitmapPtr> pBmps;
+ if (pixelFormatIsPlanar(m_PF)) {
+ ScopeTimer timer(CopyImageProfilingZone);
+ IntPoint halfSize(m_Size.x/2, m_Size.y/2);
+ pBmps.push_back(getBmp(m_pBmpQ, m_Size, I8));
+ pBmps.push_back(getBmp(m_pHalfBmpQ, halfSize, I8));
+ pBmps.push_back(getBmp(m_pHalfBmpQ, halfSize, I8));
+ if (m_PF == YCbCrA420p) {
+ pBmps.push_back(getBmp(m_pBmpQ, m_Size, I8));
+ }
+ for (unsigned i = 0; i < pBmps.size(); ++i) {
+ m_pFrameDecoder->copyPlaneToBmp(pBmps[i], pFrame->data[i],
+ pFrame->linesize[i]);
+ }
+ } else {
+ pBmps.push_back(getBmp(m_pBmpQ, m_Size, m_PF));
+ m_pFrameDecoder->convertFrameToBmp(pFrame, pBmps[0]);
+ }
+ pMsg->setFrame(pBmps, m_pFrameDecoder->getCurTime());
+ }
+ pushMsg(pMsg);
+}
+
+void VideoDecoderThread::close()
+{
+ m_MsgQ.clear();
+ stop();
+}
+
+BitmapPtr VideoDecoderThread::getBmp(BitmapQueuePtr pBmpQ, const IntPoint& size,
+ PixelFormat pf)
+{
+ BitmapPtr pBmp = pBmpQ->pop(false);
+ if (pBmp) {
+ AVG_ASSERT (pBmp->getSize() == size && pBmp->getPixelFormat() == pf);
+ return pBmp;
+ } else {
+ return BitmapPtr(new Bitmap(size, pf));
+ }
+}
+
+static ProfilingZoneID PushMsgProfilingZone("Push message", true);
+
+void VideoDecoderThread::pushMsg(VideoMsgPtr pMsg)
+{
+ ScopeTimer timer(PushMsgProfilingZone);
+ m_MsgQ.push(pMsg);
+}
+
+}
diff --git a/src/video/VideoDecoderThread.h b/src/video/VideoDecoderThread.h
new file mode 100644
index 0000000..c03e7cb
--- /dev/null
+++ b/src/video/VideoDecoderThread.h
@@ -0,0 +1,83 @@
+//
+// 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
+//
+
+#ifndef _VideoDecoderThread_H_
+#define _VideoDecoderThread_H_
+
+#include "../api.h"
+#include "VideoMsg.h"
+
+#include "../base/WorkerThread.h"
+#include "../base/Command.h"
+#include "../base/Queue.h"
+
+#include "WrapFFMpeg.h"
+
+#include <boost/thread.hpp>
+
+namespace avg {
+
+typedef Queue<Bitmap> BitmapQueue;
+typedef boost::shared_ptr<BitmapQueue> BitmapQueuePtr;
+
+class FFMpegFrameDecoder;
+typedef boost::shared_ptr<FFMpegFrameDecoder> FFMpegFrameDecoderPtr;
+
+class AVG_API VideoDecoderThread: public WorkerThread<VideoDecoderThread> {
+ public:
+ VideoDecoderThread(CQueue& cmdQ, VideoMsgQueue& msgQ, VideoMsgQueue& packetQ,
+ AVStream* pStream, const IntPoint& size, PixelFormat pf, bool bUseVDPAU);
+ virtual ~VideoDecoderThread();
+ virtual bool init();
+ virtual void deinit();
+
+ bool work();
+ void setFPS(float fps);
+ void returnFrame(VideoMsgPtr pMsg);
+
+ private:
+ void decodePacket(AVPacket* pPacket);
+ void handleEOF();
+ void handleSeekDone(VideoMsgPtr pMsg);
+ void sendFrame(AVFrame* pFrame);
+ void close();
+ BitmapPtr getBmp(BitmapQueuePtr pBmpQ, const IntPoint& size, PixelFormat pf);
+ void pushMsg(VideoMsgPtr pMsg);
+
+ VideoMsgQueue& m_MsgQ;
+ FFMpegFrameDecoderPtr m_pFrameDecoder;
+ VideoMsgQueue& m_PacketQ;
+
+ BitmapQueuePtr m_pBmpQ;
+ BitmapQueuePtr m_pHalfBmpQ;
+
+ IntPoint m_Size;
+ PixelFormat m_PF;
+ bool m_bUseVDPAU;
+
+ bool m_bSeekDone;
+ bool m_bProcessingLastFrames;
+ AVFrame* m_pFrame;
+};
+
+}
+#endif
+
diff --git a/src/video/VideoDemuxerThread.cpp b/src/video/VideoDemuxerThread.cpp
new file mode 100644
index 0000000..370e237
--- /dev/null
+++ b/src/video/VideoDemuxerThread.cpp
@@ -0,0 +1,157 @@
+//
+// 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 "VideoDemuxerThread.h"
+
+#include "../base/TimeSource.h"
+
+using namespace std;
+
+namespace avg {
+
+VideoDemuxerThread::VideoDemuxerThread(CQueue& cmdQ, AVFormatContext* pFormatContext,
+ const map<int, VideoMsgQueuePtr>& packetQs)
+ : WorkerThread<VideoDemuxerThread>("VideoDemuxer", cmdQ),
+ m_PacketQs(packetQs),
+ m_bEOF(false),
+ m_pFormatContext(pFormatContext),
+ m_pDemuxer()
+{
+ map<int, VideoMsgQueuePtr>::iterator it;
+ for (it = m_PacketQs.begin(); it != m_PacketQs.end(); it++) {
+ int streamIndex = it->first;
+ m_PacketQEOFMap[streamIndex] = false;
+ }
+}
+
+VideoDemuxerThread::~VideoDemuxerThread()
+{
+}
+
+bool VideoDemuxerThread::init()
+{
+ map<int, VideoMsgQueuePtr>::iterator it;
+ vector<int> streamIndexes;
+ for (it = m_PacketQs.begin(); it != m_PacketQs.end(); it++) {
+ streamIndexes.push_back(it->first);
+ }
+ m_pDemuxer = FFMpegDemuxerPtr(new FFMpegDemuxer(m_pFormatContext, streamIndexes));
+ return true;
+}
+
+bool VideoDemuxerThread::work()
+{
+ if (m_bEOF) {
+ waitForCommand();
+ } else {
+ map<int, VideoMsgQueuePtr>::iterator it;
+ int shortestQ = -1;
+ int shortestLength = INT_MAX;
+ for (it = m_PacketQs.begin(); it != m_PacketQs.end(); it++) {
+ if (it->second->size() < shortestLength &&
+ it->second->size() < it->second->getMaxSize() &&
+ !m_PacketQEOFMap[it->first])
+ {
+ shortestLength = it->second->size();
+ shortestQ = it->first;
+ }
+ }
+
+ if (shortestQ < 0) {
+ // All queues are at their max capacity. Take a nap and try again later.
+ // Note that we can't wait on the queue. If decoding is paused, the queues can
+ // remain full indefinitely and commands from the application (seek() and
+ // close() must still be processed.
+ msleep(10);
+ return true;
+ }
+
+ AVPacket * pPacket = m_pDemuxer->getPacket(shortestQ);
+ VideoMsgPtr pMsg(new VideoMsg);
+ if (pPacket == 0) {
+ onStreamEOF(shortestQ);
+ pMsg->setEOF();
+ } else {
+ pMsg->setPacket(pPacket);
+ }
+ m_PacketQs[shortestQ]->push(pMsg);
+ msleep(0);
+ }
+ return true;
+}
+
+void VideoDemuxerThread::seek(int seqNum, float destTime)
+{
+ map<int, VideoMsgQueuePtr>::iterator it;
+ m_pDemuxer->seek(destTime);
+ for (it = m_PacketQs.begin(); it != m_PacketQs.end(); it++) {
+ VideoMsgQueuePtr pPacketQ = it->second;
+ clearQueue(pPacketQ);
+
+ // send SEEK_DONE
+ VideoMsgPtr pMsg(new VideoMsg);
+ pMsg->setSeekDone(seqNum, destTime);
+ pPacketQ->push(pMsg);
+ m_PacketQEOFMap[it->first] = false;
+ }
+ m_bEOF = false;
+}
+
+void VideoDemuxerThread::close()
+{
+ map<int, VideoMsgQueuePtr>::iterator it;
+ for (it = m_PacketQs.begin(); it != m_PacketQs.end(); it++) {
+ VideoMsgQueuePtr pPacketQ = it->second;
+ clearQueue(pPacketQ);
+
+ VideoMsgPtr pMsg(new VideoMsg);
+ pMsg->setClosed();
+ pPacketQ->push(pMsg);
+ m_PacketQEOFMap[it->first] = false;
+ }
+ stop();
+}
+
+void VideoDemuxerThread::onStreamEOF(int streamIndex)
+{
+ m_PacketQEOFMap[streamIndex] = true;
+ m_bEOF = true;
+ map<int, bool>::iterator it;
+ for (it = m_PacketQEOFMap.begin(); it != m_PacketQEOFMap.end(); it++) {
+ if (!it->second) {
+ m_bEOF = false;
+ break;
+ }
+ }
+}
+
+void VideoDemuxerThread::clearQueue(VideoMsgQueuePtr pPacketQ)
+{
+ VideoMsgPtr pMsg;
+ do {
+ pMsg = pPacketQ->pop(false);
+ if (pMsg) {
+ pMsg->freePacket();
+ }
+ } while (pMsg);
+}
+
+}
diff --git a/src/video/VideoDemuxerThread.h b/src/video/VideoDemuxerThread.h
new file mode 100644
index 0000000..b89e58c
--- /dev/null
+++ b/src/video/VideoDemuxerThread.h
@@ -0,0 +1,63 @@
+//
+// 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
+//
+
+#ifndef _VideoDemuxerThread_H_
+#define _VideoDemuxerThread_H_
+
+#include "../api.h"
+#include "FFMpegDemuxer.h"
+#include "VideoMsg.h"
+
+#include "../base/WorkerThread.h"
+#include "../base/Command.h"
+
+#include <boost/thread.hpp>
+
+#include <string>
+#include <vector>
+
+namespace avg {
+
+class AVG_API VideoDemuxerThread: public WorkerThread<VideoDemuxerThread> {
+ public:
+ VideoDemuxerThread(CQueue& cmdQ, AVFormatContext* pFormatContext,
+ const std::map<int, VideoMsgQueuePtr>& packetQs);
+ virtual ~VideoDemuxerThread();
+ bool init();
+ bool work();
+
+ void seek(int seqNum, float DestTime);
+ void close();
+
+ private:
+ void onStreamEOF(int streamIndex);
+ void clearQueue(VideoMsgQueuePtr pPacketQ);
+
+ std::map<int, VideoMsgQueuePtr> m_PacketQs;
+ std::map<int, bool> m_PacketQEOFMap;
+ bool m_bEOF;
+ AVFormatContext* m_pFormatContext;
+ FFMpegDemuxerPtr m_pDemuxer;
+};
+
+}
+#endif
+
diff --git a/src/video/VideoInfo.cpp b/src/video/VideoInfo.cpp
new file mode 100644
index 0000000..50cfec4
--- /dev/null
+++ b/src/video/VideoInfo.cpp
@@ -0,0 +1,94 @@
+//
+// 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 "VideoInfo.h"
+
+#include "../base/Exception.h"
+
+namespace avg {
+
+using namespace std;
+
+VideoInfo::VideoInfo()
+{
+}
+
+VideoInfo::VideoInfo(string sContainerFormat, float duration, int bitrate, bool bHasVideo,
+ bool bHasAudio)
+ : m_sContainerFormat(sContainerFormat),
+ m_Duration(duration),
+ m_Bitrate(bitrate),
+ m_bHasVideo(bHasVideo),
+ m_bHasAudio(bHasAudio)
+{
+}
+
+void VideoInfo::setVideoData(const IntPoint& size, const string& sPixelFormat,
+ int numFrames, float streamFPS, const string& sVCodec,
+ bool bUsesVDPAU, float duration)
+{
+ AVG_ASSERT(m_bHasVideo);
+ m_Size = size;
+ m_sPixelFormat = sPixelFormat;
+ m_NumFrames = numFrames;
+ m_StreamFPS = streamFPS;
+ m_sVCodec = sVCodec;
+ m_bUsesVDPAU = bUsesVDPAU;
+ m_VideoDuration = duration;
+}
+
+void VideoInfo::setAudioData(const string& sACodec, int sampleRate, int numAudioChannels,
+ float duration)
+{
+ AVG_ASSERT(m_bHasAudio);
+ m_sACodec = sACodec;
+ m_SampleRate = sampleRate;
+ m_NumAudioChannels = numAudioChannels;
+ m_AudioDuration = duration;
+}
+
+float getStreamFPS(AVStream* pStream)
+{
+ float fps = 0;
+ if (pStream->avg_frame_rate.den != 0) {
+ fps = float(av_q2d(pStream->avg_frame_rate));
+ }
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 25, 0)
+ if (fps == 0 && pStream->r_frame_rate.den != 0) {
+ fps = float(av_q2d(pStream->r_frame_rate));
+ }
+#endif
+ if (fps == 0) {
+ float duration = float(pStream->duration)*float(av_q2d(pStream->time_base));
+ fps = pStream->nb_frames/duration;
+ }
+ AVG_ASSERT(fps < 10000);
+/*
+ cerr << "getStreamFPS: fps= " << fps << endl;
+ cerr << " r_frame_rate num: " << m_pVStream->r_frame_rate.num << ", den: " << m_pVStream->r_frame_rate.den << endl;
+ cerr << " avg_frame_rate: num: " << m_pVStream->avg_frame_rate.num << ", den: " << m_pVStream->avg_frame_rate.den << endl;
+ cerr << " numFrames= " << getNumFrames() << ", duration= "
+ << getDuration(SS_VIDEO) << endl;
+*/
+ return fps;
+}
+
+}
diff --git a/src/video/VideoInfo.h b/src/video/VideoInfo.h
new file mode 100644
index 0000000..f2c1311
--- /dev/null
+++ b/src/video/VideoInfo.h
@@ -0,0 +1,71 @@
+//
+// 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
+//
+
+#ifndef _VideoInfo_H_
+#define _VideoInfo_H_
+
+#include "../api.h"
+
+#include "WrapFFMpeg.h"
+
+#include "../base/GLMHelper.h"
+
+#include <string>
+
+namespace avg {
+
+struct AVG_API VideoInfo
+{
+ VideoInfo();
+ VideoInfo(std::string sContainerFormat, float duration, int bitrate, bool bHasVideo,
+ bool bHasAudio);
+ void setVideoData(const IntPoint& size, const std::string& sPixelFormat,
+ int numFrames, float streamFPS, const std::string& sVCodec,
+ bool bUsesVDPAU, float duration);
+
+ void setAudioData(const std::string& sACodec, int sampleRate, int numAudioChannels,
+ float duration);
+
+ std::string m_sContainerFormat;
+ float m_Duration;
+ int m_Bitrate;
+
+ bool m_bHasVideo;
+ IntPoint m_Size;
+ std::string m_sPixelFormat;
+ int m_NumFrames;
+ float m_StreamFPS;
+ std::string m_sVCodec;
+ bool m_bUsesVDPAU;
+ float m_VideoDuration;
+
+ bool m_bHasAudio;
+ std::string m_sACodec;
+ int m_SampleRate;
+ int m_NumAudioChannels;
+ float m_AudioDuration;
+};
+
+float getStreamFPS(AVStream* pStream);
+
+}
+#endif
+
diff --git a/src/video/VideoMsg.cpp b/src/video/VideoMsg.cpp
new file mode 100644
index 0000000..6e3be26
--- /dev/null
+++ b/src/video/VideoMsg.cpp
@@ -0,0 +1,94 @@
+//
+// 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 "VideoMsg.h"
+#include "WrapFFMpeg.h"
+
+#include "../base/ObjectCounter.h"
+#include "../base/Exception.h"
+
+namespace avg {
+
+VideoMsg::VideoMsg()
+{
+}
+
+VideoMsg::~VideoMsg()
+{
+}
+
+void VideoMsg::setFrame(const std::vector<BitmapPtr>& pBmps, float frameTime)
+{
+ AVG_ASSERT(pBmps.size() == 1 || pBmps.size() == 3 || pBmps.size() == 4);
+ setType(FRAME);
+ m_pBmps = pBmps;
+ m_FrameTime = frameTime;
+}
+
+void VideoMsg::setVDPAUFrame(vdpau_render_state* pRenderState, float frameTime)
+{
+ setType(VDPAU_FRAME);
+ m_pRenderState = pRenderState;
+ m_FrameTime = frameTime;
+}
+
+void VideoMsg::setPacket(AVPacket* pPacket)
+{
+ setType(PACKET);
+ AVG_ASSERT(pPacket);
+ m_pPacket = pPacket;
+}
+
+AVPacket * VideoMsg::getPacket()
+{
+ AVG_ASSERT(getType() == PACKET);
+ return m_pPacket;
+}
+
+void VideoMsg::freePacket()
+{
+ if (getType() == PACKET) {
+ av_free_packet(m_pPacket);
+ delete m_pPacket;
+ m_pPacket = 0;
+ }
+}
+
+BitmapPtr VideoMsg::getFrameBitmap(int i)
+{
+ AVG_ASSERT(getType() == FRAME);
+ return m_pBmps[i];
+}
+
+float VideoMsg::getFrameTime()
+{
+ AVG_ASSERT(getType() == FRAME || getType() == VDPAU_FRAME);
+ return m_FrameTime;
+}
+
+vdpau_render_state* VideoMsg::getRenderState()
+{
+ AVG_ASSERT(getType() == VDPAU_FRAME);
+ return m_pRenderState;
+}
+
+}
+
diff --git a/src/video/VideoMsg.h b/src/video/VideoMsg.h
new file mode 100644
index 0000000..15a6007
--- /dev/null
+++ b/src/video/VideoMsg.h
@@ -0,0 +1,72 @@
+//
+// 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
+//
+
+#ifndef _VideoMsg_H_
+#define _VideoMsg_H_
+
+#include "../api.h"
+#include "../base/Queue.h"
+#include "../graphics/Bitmap.h"
+
+#include "../audio/AudioMsg.h"
+
+#include <boost/shared_ptr.hpp>
+
+struct vdpau_render_state;
+struct AVPacket;
+
+namespace avg {
+
+class AVG_API VideoMsg: public AudioMsg {
+public:
+ VideoMsg();
+ void setFrame(const std::vector<BitmapPtr>& pBmps, float frameTime);
+ void setVDPAUFrame(vdpau_render_state* pRenderState, float frameTime);
+ void setPacket(AVPacket* pPacket);
+
+ virtual ~VideoMsg();
+
+ BitmapPtr getFrameBitmap(int i);
+ float getFrameTime();
+ AVPacket* getPacket();
+ void freePacket();
+
+ vdpau_render_state* getRenderState();
+
+private:
+ // FRAME
+ std::vector<BitmapPtr> m_pBmps;
+ float m_FrameTime;
+
+ // VDPAU_FRAME
+ vdpau_render_state* m_pRenderState;
+
+ // PACKET
+ AVPacket * m_pPacket;
+};
+
+typedef boost::shared_ptr<VideoMsg> VideoMsgPtr;
+typedef Queue<VideoMsg> VideoMsgQueue;
+typedef boost::shared_ptr<VideoMsgQueue> VideoMsgQueuePtr;
+
+}
+#endif
+
diff --git a/src/video/WrapFFMpeg.h b/src/video/WrapFFMpeg.h
new file mode 100644
index 0000000..d61d8a0
--- /dev/null
+++ b/src/video/WrapFFMpeg.h
@@ -0,0 +1,93 @@
+//
+// 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
+//
+
+#ifndef _WrapFFMpeg_H_
+#define _WrapFFMpeg_H_
+
+#include "../avgconfigwrapper.h"
+
+#ifdef _WIN32
+#define EMULATE_INTTYPES
+#else
+// This is probably GCC-specific.
+#if !defined INT64_C
+#if defined __WORDSIZE && __WORDSIZE == 64
+#define INT64_C(c) c ## L
+#define UINT64_C(c) c ## UL
+#else
+#define INT64_C(c) c ## LL
+#define UINT64_C(c) c ## ULL
+#endif
+#endif
+#endif
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#include <libavutil/avutil.h>
+#include <libavutil/pixdesc.h>
+#include <libavutil/mathematics.h>
+#include <libavutil/opt.h>
+#if LIBAVFORMAT_VERSION_MAJOR < 53
+#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
+#define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO
+#endif
+
+#if LIBAVFORMAT_VERSION_MAJOR > 52
+ #define SAMPLE_FMT_S16 AV_SAMPLE_FMT_S16
+ #define SAMPLE_FMT_FLT AV_SAMPLE_FMT_FLT
+ #define SAMPLE_FMT_DBL AV_SAMPLE_FMT_DBL
+ #define SAMPLE_FMT_S32 AV_SAMPLE_FMT_S32
+ #define SAMPLE_FMT_U8 AV_SAMPLE_FMT_U8
+ #define SAMPLE_FMT_S16P AV_SAMPLE_FMT_S16P
+ #define SAMPLE_FMT_FLTP AV_SAMPLE_FMT_FLTP
+ #define SampleFormat AVSampleFormat
+#endif
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 25, 00)
+ #define AV_CODEC_ID_MPEG1VIDEO CODEC_ID_MPEG1VIDEO
+ #define AV_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO
+ #define AV_CODEC_ID_H264 CODEC_ID_H264
+ #define AV_CODEC_ID_WMV3 CODEC_ID_WMV3
+ #define AV_CODEC_ID_VC1 CODEC_ID_VC1
+ #define AV_CODEC_ID_MJPEG CODEC_ID_MJPEG
+ #define AV_CODEC_ID_NONE CODEC_ID_NONE
+#endif
+
+#ifndef URL_WRONLY
+ #define url_fopen avio_open
+ #define url_fclose avio_close
+ #define URL_WRONLY AVIO_FLAG_WRITE
+#endif
+#ifdef HAVE_LIBAVRESAMPLE_AVRESAMPLE_H
+ #include <libavresample/avresample.h>
+ #include <libavresample/version.h>
+#endif
+}
+
+#ifdef PixelFormat
+#undef PixelFormat
+#else
+#define AVPixelFormat ::PixelFormat
+#endif
+
+#endif
diff --git a/src/video/baseline/mjpeg-48x48.avi_1.png b/src/video/baseline/mjpeg-48x48.avi_1.png
new file mode 100644
index 0000000..0a8e5ca
--- /dev/null
+++ b/src/video/baseline/mjpeg-48x48.avi_1.png
Binary files differ
diff --git a/src/video/baseline/mjpeg-48x48.avi_100.png b/src/video/baseline/mjpeg-48x48.avi_100.png
new file mode 100644
index 0000000..343e45b
--- /dev/null
+++ b/src/video/baseline/mjpeg-48x48.avi_100.png
Binary files differ
diff --git a/src/video/baseline/mjpeg-48x48.avi_2.png b/src/video/baseline/mjpeg-48x48.avi_2.png
new file mode 100644
index 0000000..93a551b
--- /dev/null
+++ b/src/video/baseline/mjpeg-48x48.avi_2.png
Binary files differ
diff --git a/src/video/baseline/mjpeg-48x48.avi_201.png b/src/video/baseline/mjpeg-48x48.avi_201.png
new file mode 100644
index 0000000..eb9d4a0
--- /dev/null
+++ b/src/video/baseline/mjpeg-48x48.avi_201.png
Binary files differ
diff --git a/src/video/baseline/mjpeg-48x48.avi_53.png b/src/video/baseline/mjpeg-48x48.avi_53.png
new file mode 100644
index 0000000..22f00c2
--- /dev/null
+++ b/src/video/baseline/mjpeg-48x48.avi_53.png
Binary files differ
diff --git a/src/video/baseline/mjpeg-48x48.avi_end.png b/src/video/baseline/mjpeg-48x48.avi_end.png
new file mode 100644
index 0000000..ed5df07
--- /dev/null
+++ b/src/video/baseline/mjpeg-48x48.avi_end.png
Binary files differ
diff --git a/src/video/baseline/mjpeg-48x48.avi_loop.png b/src/video/baseline/mjpeg-48x48.avi_loop.png
new file mode 100644
index 0000000..f711a88
--- /dev/null
+++ b/src/video/baseline/mjpeg-48x48.avi_loop.png
Binary files differ
diff --git a/src/video/baseline/mpeg1-48x48-sound.avi_end.png b/src/video/baseline/mpeg1-48x48-sound.avi_end.png
new file mode 100644
index 0000000..ad712f0
--- /dev/null
+++ b/src/video/baseline/mpeg1-48x48-sound.avi_end.png
Binary files differ
diff --git a/src/video/baseline/mpeg1-48x48-sound.avi_loop.png b/src/video/baseline/mpeg1-48x48-sound.avi_loop.png
new file mode 100644
index 0000000..db42610
--- /dev/null
+++ b/src/video/baseline/mpeg1-48x48-sound.avi_loop.png
Binary files differ
diff --git a/src/video/baseline/mpeg1-48x48.mov_1.png b/src/video/baseline/mpeg1-48x48.mov_1.png
new file mode 100644
index 0000000..f14e25b
--- /dev/null
+++ b/src/video/baseline/mpeg1-48x48.mov_1.png
Binary files differ
diff --git a/src/video/baseline/mpeg1-48x48.mov_2.png b/src/video/baseline/mpeg1-48x48.mov_2.png
new file mode 100644
index 0000000..0c0aa5a
--- /dev/null
+++ b/src/video/baseline/mpeg1-48x48.mov_2.png
Binary files differ
diff --git a/src/video/baseline/mpeg1-48x48.mov_end.png b/src/video/baseline/mpeg1-48x48.mov_end.png
new file mode 100644
index 0000000..07efff2
--- /dev/null
+++ b/src/video/baseline/mpeg1-48x48.mov_end.png
Binary files differ
diff --git a/src/video/baseline/mpeg1-48x48.mov_loop.png b/src/video/baseline/mpeg1-48x48.mov_loop.png
new file mode 100644
index 0000000..f14e25b
--- /dev/null
+++ b/src/video/baseline/mpeg1-48x48.mov_loop.png
Binary files differ
diff --git a/src/video/testvideo.cpp b/src/video/testvideo.cpp
new file mode 100644
index 0000000..0bca650
--- /dev/null
+++ b/src/video/testvideo.cpp
@@ -0,0 +1,543 @@
+//
+// 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"
+#include "SyncVideoDecoder.h"
+#ifdef AVG_ENABLE_VDPAU
+#include "VDPAUDecoder.h"
+#endif
+
+#include "../graphics/Filterfliprgba.h"
+#include "../graphics/Filterfliprgb.h"
+#include "../graphics/GraphicsTest.h"
+#include "../graphics/BitmapLoader.h"
+
+#include "../base/StringHelper.h"
+#include "../base/TimeSource.h"
+#include "../base/TestSuite.h"
+#include "../base/Exception.h"
+#include "../base/ThreadProfiler.h"
+#include "../base/Directory.h"
+#include "../base/DirEntry.h"
+
+#include <string>
+#include <sstream>
+#include <cmath>
+
+#include <glib-object.h>
+
+using namespace avg;
+using namespace std;
+using namespace boost;
+
+class DecoderTest: public GraphicsTest {
+ public:
+ DecoderTest(const string& sClassName, bool bThreaded,
+ bool bUseHardwareAcceleration)
+ : GraphicsTest(sClassName+getDecoderName(bThreaded, bUseHardwareAcceleration),
+ 2),
+ m_bThreaded(bThreaded),
+ m_bUseHardwareAcceleration(bUseHardwareAcceleration)
+ {}
+
+ protected:
+ bool isThreaded()
+ {
+ return m_bThreaded;
+ }
+
+ bool useHardwareAcceleration()
+ {
+ return m_bUseHardwareAcceleration;
+ }
+
+ VideoDecoderPtr createDecoder()
+ {
+ VideoDecoderPtr pDecoder;
+ if (m_bThreaded) {
+ pDecoder = VideoDecoderPtr(new AsyncVideoDecoder(8));
+ } else {
+ pDecoder = VideoDecoderPtr(new SyncVideoDecoder());
+ }
+
+ return pDecoder;
+ }
+
+ const AudioParams* getAudioParams()
+ {
+ static AudioParams AP(44100, 2, 256);
+ return &AP;
+ }
+
+ int processAudioMsg(AudioMsgQueuePtr pMsgQ, AudioMsgQueuePtr pStatusQ)
+ {
+ AudioMsgPtr pMsg = pMsgQ->pop(false);
+ if (pMsg) {
+ switch (pMsg->getType()) {
+ case AudioMsg::AUDIO: {
+ AudioBufferPtr pBuffer = pMsg->getAudioBuffer();
+ AudioMsgPtr pStatusMsg(new AudioMsg);
+ pStatusMsg->setAudioTime(pMsg->getAudioTime());
+ pStatusQ->push(AudioMsgPtr(pStatusMsg));
+ return pBuffer->getNumFrames();
+ }
+ case AudioMsg::SEEK_DONE: {
+ AudioMsgPtr pStatusMsg(new AudioMsg);
+ pStatusMsg->setSeekDone(pMsg->getSeekSeqNum(),
+ pMsg->getSeekTime());
+ pStatusQ->push(AudioMsgPtr(pStatusMsg));
+ return -1;
+ }
+ default:
+ pStatusQ->push(pMsg);
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+
+ void processAudioSeek(AudioMsgQueuePtr pMsgQ, AudioMsgQueuePtr pStatusQ)
+ {
+ int framesDecoded = 0;
+ while (framesDecoded != -1) {
+ // The real AudioSource blocks on pMsgQ->pop()
+ msleep(10);
+ framesDecoded = processAudioMsg(pMsgQ, pStatusQ);
+ }
+ }
+
+ virtual void testEqual(Bitmap& resultBmp, const std::string& sFName,
+ avg::PixelFormat pf = NO_PIXELFORMAT, float maxAverage=1.0,
+ float maxStdDev=1.0)
+ {
+ GraphicsTest::testEqual(resultBmp, sFName, pf, maxAverage, maxStdDev);
+ }
+
+ void testEqual(Bitmap& resultBmp, Bitmap& BaselineBmp,
+ const std::string& sFName, float maxAverage=1.0f, float maxStdDev=1.0f)
+ {
+ GraphicsTest::testEqual(resultBmp, BaselineBmp, sFName,
+ maxAverage, maxStdDev);
+ }
+
+ string getMediaLoc(const string& sFilename)
+ {
+ return getSrcDirName()+"../test/media/"+sFilename;
+ }
+
+ private:
+ string getDecoderName(bool bThreaded, bool bUseHardwareAcceleration)
+ {
+ string sName = "(";
+ if (bThreaded) {
+ sName += "Threaded";
+ } else {
+ sName += "Sync";
+ }
+ if (bUseHardwareAcceleration) {
+ sName += ", VDPAU)";
+ } else {
+ sName += ")";
+ }
+ return sName;
+ }
+
+ bool m_bThreaded;
+ bool m_bUseHardwareAcceleration;
+};
+
+class VideoDecoderTest: public DecoderTest {
+ public:
+ VideoDecoderTest(bool bThreaded, bool bUseHardwareAcceleration)
+ : DecoderTest("VideoDecoderTest", bThreaded, bUseHardwareAcceleration)
+ {}
+
+ void runTests()
+ {
+ basicFileTest("mpeg1-48x48.mov", 30);
+#ifndef AVG_ENABLE_RPI
+ basicFileTest("mjpeg-48x48.avi", 202);
+ testSeeks("mjpeg-48x48.avi");
+#else
+ cerr << "Skipping mjpeg tests: SW decoding too slow on RaspberryPi." << endl;
+#endif
+ }
+
+ private:
+ void basicFileTest(const string& sFilename, int expectedNumFrames)
+ {
+ try {
+ cerr << " Testing " << sFilename << endl;
+
+ VideoDecoderPtr pDecoder = createDecoder();
+ pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true);
+ IntPoint frameSize = pDecoder->getSize();
+ TEST(frameSize == IntPoint(48, 48));
+ TEST(pDecoder->getVideoInfo().m_bHasVideo);
+ TEST(pDecoder->getVideoInfo().m_Duration != 0);
+ TEST(pDecoder->getStreamFPS() != 0);
+ TEST(pDecoder->getFPS() != 0);
+ pDecoder->startDecoding(false, getAudioParams());
+ TEST(pDecoder->getPixelFormat() == B8G8R8X8);
+ BitmapPtr pBmp(new Bitmap(frameSize, B8G8R8X8));
+
+ // Test first two frames.
+ pDecoder->renderToBmp(pBmp, -1);
+ testEqual(*pBmp, sFilename+"_1", B8G8R8X8);
+ TEST(pDecoder->getCurFrame() == 0);
+ TEST(pDecoder->getCurTime() == 0);
+
+ pDecoder->renderToBmp(pBmp, -1);
+ testEqual(*pBmp, sFilename+"_2", B8G8R8X8);
+ pDecoder->close();
+
+ readWholeFile(sFilename, 1, expectedNumFrames);
+ readWholeFile(sFilename, 0.5, expectedNumFrames);
+ readWholeFile(sFilename, 2, expectedNumFrames/2);
+ } catch (Exception & ex) {
+ cerr << string(m_IndentLevel+6, ' ') << ex.getStr() << endl;
+ throw;
+ }
+ }
+
+ void testSeeks(const string& sFilename)
+ {
+ cerr << " Testing " << sFilename << " (seek)" << endl;
+
+ VideoDecoderPtr pDecoder = createDecoder();
+ pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true);
+ pDecoder->startDecoding(false, getAudioParams());
+
+ // Seek forward
+ testSeek(100, sFilename, pDecoder);
+ // Seek backward
+ testSeek(53, sFilename, pDecoder);
+ // Seek to last frame
+ testSeek(201, sFilename, pDecoder);
+
+ pDecoder->close();
+ }
+
+ void testSeek(int frameNum, const string& sFilename, VideoDecoderPtr pDecoder)
+ {
+ IntPoint frameSize = pDecoder->getSize();
+
+ BitmapPtr pBmp(new Bitmap(frameSize, B8G8R8X8));
+ pDecoder->seek(float(frameNum)/pDecoder->getStreamFPS());
+ pDecoder->renderToBmp(pBmp, -1);
+ testEqual(*pBmp, sFilename+"_"+toString(frameNum), B8G8R8X8);
+
+ }
+
+ void readWholeFile(const string& sFilename, float speedFactor,
+ int expectedNumFrames)
+ {
+ // Read whole file, test last image.
+ VideoDecoderPtr pDecoder = createDecoder();
+ pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true);
+ IntPoint frameSize = pDecoder->getSize();
+ float timePerFrame = (1.0f/pDecoder->getFPS())*speedFactor;
+ pDecoder->startDecoding(false, getAudioParams());
+ BitmapPtr pBmp(new Bitmap(frameSize, B8G8R8X8));
+ int numFrames = 0;
+ float curTime = 0;
+
+ while (!pDecoder->isEOF()) {
+ FrameAvailableCode frameAvailable = pDecoder->renderToBmp(pBmp, curTime);
+ if (frameAvailable == FA_NEW_FRAME) {
+/*
+ stringstream ss;
+ ss << "resultimages/" << sFilename << numFrames << ".png";
+ pBmp->save(ss.str());
+*/
+ numFrames++;
+ } else {
+ msleep(0);
+ }
+ if (frameAvailable == FA_NEW_FRAME || frameAvailable == FA_USE_LAST_FRAME)
+ {
+ curTime += timePerFrame;
+ }
+ }
+// cerr << "numFrames: " << numFrames <<
+// ", expectedNumFrames: " << expectedNumFrames << endl;
+ TEST(numFrames == expectedNumFrames);
+ if (speedFactor == 1 && !useHardwareAcceleration()) {
+ // The last frame is broken with VDPAU sometimes. Not sure why this is,
+ // possibly a libav bug.
+ testEqual(*pBmp, sFilename+"_end", B8G8R8X8);
+ }
+
+ // Test loop.
+ pDecoder->loop();
+ pDecoder->renderToBmp(pBmp, -1);
+ testEqual(*pBmp, sFilename+"_loop", B8G8R8X8);
+
+ pDecoder->close();
+ }
+
+};
+
+class AudioDecoderTest: public DecoderTest {
+ public:
+ AudioDecoderTest()
+ : DecoderTest("AudioDecoderTest", true, false)
+ {}
+
+ void runTests()
+ {
+
+ testOneFile("22.050Hz_16bit_mono.wav");
+
+ testOneFile("44.1kHz_16bit_mono.wav");
+ testOneFile("44.1kHz_16bit_stereo.wav");
+ testOneFile("44.1kHz_24bit_mono.wav");
+ testOneFile("44.1kHz_24bit_stereo.wav");
+
+ testOneFile("48kHz_16bit_mono.wav");
+ testOneFile("48kHz_16bit_stereo.wav");
+ testOneFile("48kHz_24bit_mono.wav");
+ testOneFile("48kHz_24bit_stereo.wav");
+
+ testOneFile("44.1kHz_16bit_stereo.aif");
+ testOneFile("44.1kHz_stereo.mp3");
+ }
+
+ private:
+ void testOneFile(const string& sFilename)
+ {
+ try {
+ cerr << " Testing " << sFilename << endl;
+
+ {
+ cerr << " Reading complete file." << endl;
+ AsyncVideoDecoderPtr pDecoder =
+ dynamic_pointer_cast<AsyncVideoDecoder>(createDecoder());
+ pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true);
+ TEST(pDecoder->getVideoInfo().m_bHasAudio);
+ pDecoder->startDecoding(false, getAudioParams());
+ AudioMsgQueuePtr pMsgQ = pDecoder->getAudioMsgQ();
+ AudioMsgQueuePtr pStatusQ = pDecoder->getAudioStatusQ();
+ int totalFramesDecoded = 0;
+ readAudioToEOF(pDecoder, pMsgQ, pStatusQ, totalFramesDecoded, true);
+
+ // Check if we've decoded the whole file.
+ int framesInDuration = int(pDecoder->getVideoInfo().m_Duration*44100);
+// cerr << "framesInDuration: " << framesInDuration << endl;
+// cerr << "framesDecoded: " << totalFramesDecoded << endl;
+ TEST(abs(totalFramesDecoded-framesInDuration) < 65);
+ }
+ {
+ cerr << " Seek test." << endl;
+ AsyncVideoDecoderPtr pDecoder =
+ dynamic_pointer_cast<AsyncVideoDecoder>(createDecoder());
+ pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true);
+ float duration = pDecoder->getVideoInfo().m_Duration;
+ pDecoder->startDecoding(false, getAudioParams());
+ AudioMsgQueuePtr pMsgQ = pDecoder->getAudioMsgQ();
+ AudioMsgQueuePtr pStatusQ = pDecoder->getAudioStatusQ();
+ pDecoder->seek(duration/2);
+ processAudioSeek(pMsgQ, pStatusQ);
+ int totalFramesDecoded = 0;
+
+ readAudioToEOF(pDecoder, pMsgQ, pStatusQ, totalFramesDecoded, false);
+ if (sFilename.find(".mp3") == string::npos) {
+ // Check if we've decoded half the file.
+ // TODO: Find out why there are problems with this
+ // for mp3 files.
+ int framesInDuration =
+ int(pDecoder->getVideoInfo().m_Duration*44100);
+// cerr << "framesDecoded: " << totalFramesDecoded << endl;
+// cerr << "framesInDuration: " << framesInDuration << endl;
+ TEST(abs(totalFramesDecoded-framesInDuration/2) < 65);
+ }
+ }
+
+ } catch (Exception & ex) {
+ cerr << string(m_IndentLevel+6, ' ') << ex.getStr() << endl;
+ throw;
+ }
+ }
+
+ void readAudioToEOF(AsyncVideoDecoderPtr pDecoder, AudioMsgQueuePtr pMsgQ,
+ AudioMsgQueuePtr pStatusQ, int& totalFramesDecoded,
+ bool bCheckTimestamps)
+ {
+ int numWrongTimestamps = 0;
+ while (!pDecoder->isEOF()) {
+ int framesDecoded = 0;
+ while (framesDecoded == 0 && !pDecoder->isEOF()) {
+ framesDecoded = processAudioMsg(pMsgQ, pStatusQ);
+ AVG_ASSERT(framesDecoded != -1);
+ pDecoder->updateAudioStatus();
+ msleep(0);
+ }
+ totalFramesDecoded += framesDecoded;
+ float curTime = float(totalFramesDecoded)/44100;
+ if (abs(curTime-pDecoder->getCurTime()) > 0.02f) {
+ numWrongTimestamps++;
+ }
+ }
+ if (bCheckTimestamps) {
+ if (numWrongTimestamps>0) {
+ TEST_FAILED(numWrongTimestamps << " wrong timestamps.");
+ }
+ }
+ }
+};
+
+
+class AVDecoderTest: public DecoderTest {
+ public:
+ AVDecoderTest(bool bUseHardwareAcceleration)
+ : DecoderTest("AVDecoderTest", true, bUseHardwareAcceleration)
+ {}
+
+ void runTests()
+ {
+ basicFileTest("mpeg1-48x48-sound.avi", 30);
+ }
+
+ private:
+ void basicFileTest(const string& sFilename, int expectedNumFrames)
+ {
+ VideoDecoderPtr pDecoder = createDecoder();
+ pDecoder->open(getMediaLoc(sFilename), useHardwareAcceleration(), true);
+ TEST(pDecoder->getVideoInfo().m_bHasVideo);
+ TEST(pDecoder->getStreamFPS() != 0);
+ pDecoder->startDecoding(false, getAudioParams());
+ AudioMsgQueuePtr pMsgQ;
+ AudioMsgQueuePtr pStatusQ;
+
+ pMsgQ = dynamic_pointer_cast<AsyncVideoDecoder>(pDecoder) ->getAudioMsgQ();
+ pStatusQ = dynamic_pointer_cast<AsyncVideoDecoder>(pDecoder)
+ ->getAudioStatusQ();
+ TEST(pDecoder->getVideoInfo().m_bHasAudio);
+
+ IntPoint frameSize = pDecoder->getSize();
+ BitmapPtr pBmp(new Bitmap(frameSize, B8G8R8X8));
+ int numFrames = 0;
+ int totalFramesDecoded = 0;
+ float curTime = 0;
+
+ while (!pDecoder->isEOF()) {
+ FrameAvailableCode frameAvailable;
+ do {
+ frameAvailable = pDecoder->renderToBmp(pBmp, curTime);
+ msleep(0);
+ } while (frameAvailable == FA_STILL_DECODING);
+ if (frameAvailable == FA_NEW_FRAME) {
+// stringstream ss;
+// ss << sFilename << numFrames << ".png";
+// pBmp->save(ss.str());
+ numFrames++;
+ }
+ int framesDecoded = 0;
+ while (framesDecoded == 0 && !pDecoder->isEOF()) {
+ framesDecoded = processAudioMsg(pMsgQ, pStatusQ);
+ dynamic_pointer_cast<AsyncVideoDecoder>(pDecoder)
+ ->updateAudioStatus();
+ msleep(0);
+ }
+ totalFramesDecoded += framesDecoded;
+
+ curTime += 1.0f/pDecoder->getFPS();
+ }
+ TEST(pDecoder->isEOF());
+ TEST(numFrames == expectedNumFrames);
+ testEqual(*pBmp, sFilename+"_end", B8G8R8X8);
+
+ // Test loop.
+ pDecoder->seek(0);
+ processAudioSeek(pMsgQ, pStatusQ);
+ pDecoder->renderToBmp(pBmp, -1);
+ testEqual(*pBmp, sFilename+"_loop", B8G8R8X8);
+
+ pDecoder->close();
+ }
+};
+
+
+class VideoTestSuite: public TestSuite {
+public:
+ VideoTestSuite()
+ : TestSuite("VideoTestSuite")
+ {
+ addAudioTests();
+ addVideoTests(false);
+#ifdef AVG_ENABLE_VDPAU
+ if (VDPAUDecoder::isAvailable()) {
+ addVideoTests(true);
+ } else {
+ cerr << "Skipping VDPAU tests: VDPAU configured but not available." << endl;
+ }
+#else
+ cerr << "Skipping VDPAU tests: VDPAU not configured." << endl;
+#endif
+ }
+private:
+
+ void addAudioTests()
+ {
+ addTest(TestPtr(new AudioDecoderTest()));
+ }
+
+ void addVideoTests(bool bUseHardwareAcceleration)
+ {
+ addTest(TestPtr(new VideoDecoderTest(false, bUseHardwareAcceleration)));
+ addTest(TestPtr(new VideoDecoderTest(true, bUseHardwareAcceleration)));
+
+ addTest(TestPtr(new AVDecoderTest(bUseHardwareAcceleration)));
+ }
+};
+
+
+int main(int nargs, char** args)
+{
+ ThreadProfiler* pProfiler = ThreadProfiler::get();
+ pProfiler->setName("main");
+
+ GraphicsTest::createResultImgDir();
+
+ BitmapLoader::init(true);
+ VideoTestSuite suite;
+ bool bOk;
+
+ suite.runTests();
+ bOk = suite.isOk();
+/*
+ for (int i=0; i<300; ++i) {
+// while(true) {
+ suite.runTests();
+ bOk = suite.isOk();
+ if (!bOk) {
+ return 1;
+ }
+ }
+*/
+ if (bOk) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+