diff options
Diffstat (limited to 'src/video/VideoDecoder.cpp')
-rw-r--r-- | src/video/VideoDecoder.cpp | 485 |
1 files changed, 485 insertions, 0 deletions
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); +} + +} + + |