diff options
author | Dimitri John Ledkov <xnox@ubuntu.com> | 2014-06-24 20:05:13 +0100 |
---|---|---|
committer | Dimitri John Ledkov <xnox@ubuntu.com> | 2014-06-24 20:05:13 +0100 |
commit | dd22bd15f6ed3e5eb5c77ab427029be50fe20148 (patch) | |
tree | d9491ee40d80688b7f5b1f20504f022686827a57 /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')
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 Binary files differnew file mode 100644 index 0000000..0a8e5ca --- /dev/null +++ b/src/video/baseline/mjpeg-48x48.avi_1.png diff --git a/src/video/baseline/mjpeg-48x48.avi_100.png b/src/video/baseline/mjpeg-48x48.avi_100.png Binary files differnew file mode 100644 index 0000000..343e45b --- /dev/null +++ b/src/video/baseline/mjpeg-48x48.avi_100.png diff --git a/src/video/baseline/mjpeg-48x48.avi_2.png b/src/video/baseline/mjpeg-48x48.avi_2.png Binary files differnew file mode 100644 index 0000000..93a551b --- /dev/null +++ b/src/video/baseline/mjpeg-48x48.avi_2.png diff --git a/src/video/baseline/mjpeg-48x48.avi_201.png b/src/video/baseline/mjpeg-48x48.avi_201.png Binary files differnew file mode 100644 index 0000000..eb9d4a0 --- /dev/null +++ b/src/video/baseline/mjpeg-48x48.avi_201.png diff --git a/src/video/baseline/mjpeg-48x48.avi_53.png b/src/video/baseline/mjpeg-48x48.avi_53.png Binary files differnew file mode 100644 index 0000000..22f00c2 --- /dev/null +++ b/src/video/baseline/mjpeg-48x48.avi_53.png diff --git a/src/video/baseline/mjpeg-48x48.avi_end.png b/src/video/baseline/mjpeg-48x48.avi_end.png Binary files differnew file mode 100644 index 0000000..ed5df07 --- /dev/null +++ b/src/video/baseline/mjpeg-48x48.avi_end.png diff --git a/src/video/baseline/mjpeg-48x48.avi_loop.png b/src/video/baseline/mjpeg-48x48.avi_loop.png Binary files differnew file mode 100644 index 0000000..f711a88 --- /dev/null +++ b/src/video/baseline/mjpeg-48x48.avi_loop.png diff --git a/src/video/baseline/mpeg1-48x48-sound.avi_end.png b/src/video/baseline/mpeg1-48x48-sound.avi_end.png Binary files differnew file mode 100644 index 0000000..ad712f0 --- /dev/null +++ b/src/video/baseline/mpeg1-48x48-sound.avi_end.png diff --git a/src/video/baseline/mpeg1-48x48-sound.avi_loop.png b/src/video/baseline/mpeg1-48x48-sound.avi_loop.png Binary files differnew file mode 100644 index 0000000..db42610 --- /dev/null +++ b/src/video/baseline/mpeg1-48x48-sound.avi_loop.png diff --git a/src/video/baseline/mpeg1-48x48.mov_1.png b/src/video/baseline/mpeg1-48x48.mov_1.png Binary files differnew file mode 100644 index 0000000..f14e25b --- /dev/null +++ b/src/video/baseline/mpeg1-48x48.mov_1.png diff --git a/src/video/baseline/mpeg1-48x48.mov_2.png b/src/video/baseline/mpeg1-48x48.mov_2.png Binary files differnew file mode 100644 index 0000000..0c0aa5a --- /dev/null +++ b/src/video/baseline/mpeg1-48x48.mov_2.png diff --git a/src/video/baseline/mpeg1-48x48.mov_end.png b/src/video/baseline/mpeg1-48x48.mov_end.png Binary files differnew file mode 100644 index 0000000..07efff2 --- /dev/null +++ b/src/video/baseline/mpeg1-48x48.mov_end.png diff --git a/src/video/baseline/mpeg1-48x48.mov_loop.png b/src/video/baseline/mpeg1-48x48.mov_loop.png Binary files differnew file mode 100644 index 0000000..f14e25b --- /dev/null +++ b/src/video/baseline/mpeg1-48x48.mov_loop.png 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; + } +} + |