diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp')
-rw-r--r-- | xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp | 1166 |
1 files changed, 1166 insertions, 0 deletions
diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp new file mode 100644 index 0000000..20f6b3b --- /dev/null +++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp @@ -0,0 +1,1166 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "VideoPlayerVideo.h" + +#include "DVDCodecs/DVDCodecUtils.h" +#include "DVDCodecs/DVDFactoryCodec.h" +#include "DVDCodecs/Video/DVDVideoCodecFFmpeg.h" +#include "ServiceBroker.h" +#include "cores/VideoPlayer/Interface/DemuxPacket.h" +#include "cores/VideoPlayer/Interface/TimingConstants.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/MathUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" +#include "windowing/WinSystem.h" + +#include <iomanip> +#include <iterator> +#include <mutex> +#include <numeric> +#include <sstream> + +using namespace std::chrono_literals; + +class CDVDMsgVideoCodecChange : public CDVDMsg +{ +public: + CDVDMsgVideoCodecChange(const CDVDStreamInfo& hints, std::unique_ptr<CDVDVideoCodec> codec) + : CDVDMsg(GENERAL_STREAMCHANGE), m_codec(std::move(codec)), m_hints(hints) + {} + ~CDVDMsgVideoCodecChange() override = default; + + std::unique_ptr<CDVDVideoCodec> m_codec; + CDVDStreamInfo m_hints; +}; + + +CVideoPlayerVideo::CVideoPlayerVideo(CDVDClock* pClock + ,CDVDOverlayContainer* pOverlayContainer + ,CDVDMessageQueue& parent + ,CRenderManager& renderManager + ,CProcessInfo &processInfo) +: CThread("VideoPlayerVideo") +, IDVDStreamPlayerVideo(processInfo) +, m_messageQueue("video") +, m_messageParent(parent) +, m_renderManager(renderManager) +{ + m_pClock = pClock; + m_pOverlayContainer = pOverlayContainer; + m_speed = DVD_PLAYSPEED_NORMAL; + + m_bRenderSubs = false; + m_paused = false; + m_syncState = IDVDStreamPlayer::SYNC_STARTING; + m_iSubtitleDelay = 0; + m_iLateFrames = 0; + m_iDroppedRequest = 0; + m_fForcedAspectRatio = 0; + m_messageQueue.SetMaxDataSize(40 * 1024 * 1024); + m_messageQueue.SetMaxTimeSize(8.0); + + m_iDroppedFrames = 0; + m_fFrameRate = 25; + m_fStableFrameRate = 0.0; + m_iFrameRateCount = 0; + m_bAllowDrop = false; + m_iFrameRateErr = 0; + m_iFrameRateLength = 0; + m_bFpsInvalid = false; +} + +CVideoPlayerVideo::~CVideoPlayerVideo() +{ + m_bAbortOutput = true; + StopThread(); +} + +double CVideoPlayerVideo::GetOutputDelay() +{ + double time = m_messageQueue.GetPacketCount(CDVDMsg::DEMUXER_PACKET); + if( m_fFrameRate ) + time = (time * DVD_TIME_BASE) / m_fFrameRate; + else + time = 0.0; + + if( m_speed != 0 ) + time = time * DVD_PLAYSPEED_NORMAL / abs(m_speed); + + return time; +} + +bool CVideoPlayerVideo::OpenStream(CDVDStreamInfo hint) +{ + if (hint.flags & AV_DISPOSITION_ATTACHED_PIC) + return false; + if (hint.extrasize == 0) + { + // codecs which require extradata + // clang-format off + if (hint.codec == AV_CODEC_ID_NONE || + hint.codec == AV_CODEC_ID_MPEG1VIDEO || + hint.codec == AV_CODEC_ID_MPEG2VIDEO || + hint.codec == AV_CODEC_ID_H264 || + hint.codec == AV_CODEC_ID_HEVC || + hint.codec == AV_CODEC_ID_MPEG4 || + hint.codec == AV_CODEC_ID_WMV3 || + hint.codec == AV_CODEC_ID_VC1 || + hint.codec == AV_CODEC_ID_AV1) + // clang-format on + return false; + } + + CLog::Log(LOGINFO, "Creating video codec with codec id: {}", hint.codec); + + if (m_messageQueue.IsInited()) + { + if (m_pVideoCodec && !m_processInfo.IsVideoHwDecoder()) + { + hint.codecOptions |= CODEC_ALLOW_FALLBACK; + } + + std::unique_ptr<CDVDVideoCodec> codec = CDVDFactoryCodec::CreateVideoCodec(hint, m_processInfo); + if (!codec) + { + CLog::Log(LOGINFO, "CVideoPlayerVideo::OpenStream - could not open video codec"); + } + + SendMessage(std::make_shared<CDVDMsgVideoCodecChange>(hint, std::move(codec)), 0); + } + else + { + m_processInfo.ResetVideoCodecInfo(); + hint.codecOptions |= CODEC_ALLOW_FALLBACK; + + std::unique_ptr<CDVDVideoCodec> codec = CDVDFactoryCodec::CreateVideoCodec(hint, m_processInfo); + if (!codec) + { + CLog::Log(LOGERROR, "CVideoPlayerVideo::OpenStream - could not open video codec"); + return false; + } + + OpenStream(hint, std::move(codec)); + CLog::Log(LOGINFO, "Creating video thread"); + m_messageQueue.Init(); + m_processInfo.SetLevelVQ(0); + Create(); + } + return true; +} + +void CVideoPlayerVideo::OpenStream(CDVDStreamInfo& hint, std::unique_ptr<CDVDVideoCodec> codec) +{ + CLog::Log(LOGDEBUG, "CVideoPlayerVideo::OpenStream - open stream with codec id: {}", hint.codec); + + m_processInfo.GetVideoBufferManager().ReleasePools(); + + //reported fps is usually not completely correct + if (hint.fpsrate && hint.fpsscale) + { + m_fFrameRate = DVD_TIME_BASE / CDVDCodecUtils::NormalizeFrameduration((double)DVD_TIME_BASE * hint.fpsscale / hint.fpsrate); + m_bFpsInvalid = false; + m_processInfo.SetVideoFps(static_cast<float>(m_fFrameRate)); + } + else + { + m_fFrameRate = 25; + m_bFpsInvalid = true; + m_processInfo.SetVideoFps(0); + } + + m_ptsTracker.ResetVFRDetection(); + ResetFrameRateCalc(); + + m_iDroppedRequest = 0; + m_iLateFrames = 0; + + if( m_fFrameRate > 120 || m_fFrameRate < 5 ) + { + CLog::Log(LOGERROR, + "CVideoPlayerVideo::OpenStream - Invalid framerate {}, using forced 25fps and just " + "trust timestamps", + (int)m_fFrameRate); + m_fFrameRate = 25; + } + + // use aspect in stream if available + if (hint.forced_aspect) + m_fForcedAspectRatio = static_cast<float>(hint.aspect); + else + m_fForcedAspectRatio = 0.0f; + + if (m_pVideoCodec && m_pVideoCodec->Reconfigure(hint)) + { + // reuse old decoder + codec = std::move(m_pVideoCodec); + } + + m_pVideoCodec.reset(); + + if (!codec) + { + CLog::Log(LOGINFO, "Creating video codec with codec id: {}", hint.codec); + hint.codecOptions |= CODEC_ALLOW_FALLBACK; + codec = CDVDFactoryCodec::CreateVideoCodec(hint, m_processInfo); + if (!codec) + { + CLog::Log(LOGERROR, "CVideoPlayerVideo::OpenStream - could not open video codec"); + m_messageParent.Put(std::make_shared<CDVDMsg>(CDVDMsg::PLAYER_ABORT)); + StopThread(); + } + } + + m_pVideoCodec = std::move(codec); + m_hints = hint; + m_stalled = m_messageQueue.GetPacketCount(CDVDMsg::DEMUXER_PACKET) == 0; + m_rewindStalled = false; + m_packets.clear(); + m_syncState = IDVDStreamPlayer::SYNC_STARTING; + m_renderManager.ShowVideo(false); +} + +void CVideoPlayerVideo::CloseStream(bool bWaitForBuffers) +{ + // wait until buffers are empty + if (bWaitForBuffers && m_speed > 0) + { + SendMessage(std::make_shared<CDVDMsg>(CDVDMsg::VIDEO_DRAIN), 0); + m_messageQueue.WaitUntilEmpty(); + } + + m_messageQueue.Abort(); + + // wait for decode_video thread to end + CLog::Log(LOGINFO, "waiting for video thread to exit"); + + m_bAbortOutput = true; + StopThread(); + + m_messageQueue.End(); + + CLog::Log(LOGINFO, "deleting video codec"); + m_pVideoCodec.reset(); + + if (m_picture.videoBuffer) + { + m_picture.videoBuffer->Release(); + m_picture.videoBuffer = nullptr; + } +} + +bool CVideoPlayerVideo::AcceptsData() const +{ + bool full = m_messageQueue.IsFull(); + return !full; +} + +bool CVideoPlayerVideo::HasData() const +{ + return m_messageQueue.GetDataSize() > 0; +} + +bool CVideoPlayerVideo::IsInited() const +{ + return m_messageQueue.IsInited(); +} + +inline void CVideoPlayerVideo::SendMessage(std::shared_ptr<CDVDMsg> pMsg, int priority) +{ + m_messageQueue.Put(pMsg, priority); + m_processInfo.SetLevelVQ(m_messageQueue.GetLevel()); +} + +inline void CVideoPlayerVideo::SendMessageBack(const std::shared_ptr<CDVDMsg>& pMsg, int priority) +{ + m_messageQueue.PutBack(pMsg, priority); + m_processInfo.SetLevelVQ(m_messageQueue.GetLevel()); +} + +inline void CVideoPlayerVideo::FlushMessages() +{ + m_messageQueue.Flush(); + m_processInfo.SetLevelVQ(m_messageQueue.GetLevel()); +} + +inline MsgQueueReturnCode CVideoPlayerVideo::GetMessage(std::shared_ptr<CDVDMsg>& pMsg, + unsigned int iTimeoutInMilliSeconds, + int& priority) +{ + MsgQueueReturnCode ret = m_messageQueue.Get(pMsg, iTimeoutInMilliSeconds, priority); + m_processInfo.SetLevelVQ(m_messageQueue.GetLevel()); + return ret; +} + +void CVideoPlayerVideo::Process() +{ + CLog::Log(LOGINFO, "running thread: video_thread"); + + double pts = 0; + double frametime = (double)DVD_TIME_BASE / m_fFrameRate; + + bool bRequestDrop = false; + int iDropDirective; + bool onlyPrioMsgs = false; + + m_videoStats.Start(); + m_droppingStats.Reset(); + m_iDroppedFrames = 0; + m_rewindStalled = false; + m_outputSate = OUTPUT_NORMAL; + + while (!m_bStop) + { + int iQueueTimeOut = (int)(m_stalled ? frametime : frametime * 10) / 1000; + int iPriority = 0; + + if (m_syncState == IDVDStreamPlayer::SYNC_WAITSYNC) + iPriority = 1; + + if (m_paused) + iPriority = 1; + + if (onlyPrioMsgs) + { + iPriority = 1; + iQueueTimeOut = 1; + } + + std::shared_ptr<CDVDMsg> pMsg; + MsgQueueReturnCode ret = GetMessage(pMsg, iQueueTimeOut, iPriority); + + onlyPrioMsgs = false; + + if (MSGQ_IS_ERROR(ret)) + { + if (!m_messageQueue.ReceivedAbortRequest()) + CLog::Log(LOGERROR, "MSGQ_IS_ERROR returned true ({})", ret); + + break; + } + else if (ret == MSGQ_TIMEOUT) + { + if (m_outputSate == OUTPUT_AGAIN && + m_picture.videoBuffer) + { + m_outputSate = OutputPicture(&m_picture); + if (m_outputSate == OUTPUT_AGAIN) + { + onlyPrioMsgs = true; + continue; + } + } + // don't ask for a new frame if we can't deliver it to renderer + else if ((m_speed != DVD_PLAYSPEED_PAUSE || + m_processInfo.IsFrameAdvance() || + m_syncState != IDVDStreamPlayer::SYNC_INSYNC) && !m_paused) + { + if (ProcessDecoderOutput(frametime, pts)) + { + onlyPrioMsgs = true; + continue; + } + } + + // if we only wanted priority messages, this isn't a stall + if (iPriority) + continue; + + //Okey, start rendering at stream fps now instead, we are likely in a stillframe + if (!m_stalled) + { + // squeeze pictures out + while (!m_bStop && m_pVideoCodec) + { + m_pVideoCodec->SetCodecControl(DVD_CODEC_CTRL_DRAIN); + if (!ProcessDecoderOutput(frametime, pts)) + break; + } + + CLog::Log(LOGDEBUG, "CVideoPlayerVideo - Stillframe detected, switching to forced {:f} fps", + m_fFrameRate); + m_stalled = true; + pts += frametime * 4; + } + + // Waiting timed out, output last picture + if (m_picture.videoBuffer) + { + m_picture.pts = pts; + m_outputSate = OutputPicture(&m_picture); + pts += frametime; + } + + continue; + } + + if (pMsg->IsType(CDVDMsg::GENERAL_SYNCHRONIZE)) + { + if (std::static_pointer_cast<CDVDMsgGeneralSynchronize>(pMsg)->Wait(100ms, SYNCSOURCE_VIDEO)) + { + CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::GENERAL_SYNCHRONIZE"); + } + else + SendMessage(pMsg, 1); /* push back as prio message, to process other prio messages */ + m_droppingStats.Reset(); + } + else if (pMsg->IsType(CDVDMsg::GENERAL_RESYNC)) + { + pts = std::static_pointer_cast<CDVDMsgDouble>(pMsg)->m_value; + + m_syncState = IDVDStreamPlayer::SYNC_INSYNC; + m_droppingStats.Reset(); + m_rewindStalled = false; + m_renderManager.ShowVideo(true); + + CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::GENERAL_RESYNC({:f})", pts); + } + else if (pMsg->IsType(CDVDMsg::VIDEO_SET_ASPECT)) + { + CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::VIDEO_SET_ASPECT"); + m_fForcedAspectRatio = static_cast<float>(*std::static_pointer_cast<CDVDMsgDouble>(pMsg)); + } + else if (pMsg->IsType(CDVDMsg::GENERAL_RESET)) + { + if(m_pVideoCodec) + m_pVideoCodec->Reset(); + + if (m_picture.videoBuffer) + { + m_picture.videoBuffer->Release(); + m_picture.videoBuffer = nullptr; + } + m_packets.clear(); + m_droppingStats.Reset(); + m_syncState = IDVDStreamPlayer::SYNC_STARTING; + m_renderManager.ShowVideo(false); + m_rewindStalled = false; + } + else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH)) // private message sent by (CVideoPlayerVideo::Flush()) + { + bool sync = std::static_pointer_cast<CDVDMsgBool>(pMsg)->m_value; + if(m_pVideoCodec) + m_pVideoCodec->Reset(); + + if (m_picture.videoBuffer) + { + m_picture.videoBuffer->Release(); + m_picture.videoBuffer = nullptr; + } + m_packets.clear(); + pts = 0; + m_rewindStalled = false; + + m_ptsTracker.Flush(); + //we need to recalculate the framerate + //! @todo this needs to be set on a streamchange instead + ResetFrameRateCalc(); + m_droppingStats.Reset(); + + m_stalled = true; + if (sync) + { + m_syncState = IDVDStreamPlayer::SYNC_STARTING; + m_renderManager.ShowVideo(false); + } + + m_renderManager.DiscardBuffer(); + FlushMessages(); + } + else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED)) + { + m_speed = std::static_pointer_cast<CDVDMsgInt>(pMsg)->m_value; + if (m_pVideoCodec) + m_pVideoCodec->SetSpeed(m_speed); + + m_droppingStats.Reset(); + } + else if (pMsg->IsType(CDVDMsg::GENERAL_STREAMCHANGE)) + { + auto msg = std::static_pointer_cast<CDVDMsgVideoCodecChange>(pMsg); + + while (!m_bStop && m_pVideoCodec) + { + m_pVideoCodec->SetCodecControl(DVD_CODEC_CTRL_DRAIN); + bool cont = ProcessDecoderOutput(frametime, pts); + + if (!cont) + break; + } + + OpenStream(msg->m_hints, std::move(msg->m_codec)); + msg->m_codec = NULL; + if (m_picture.videoBuffer) + { + m_picture.videoBuffer->Release(); + m_picture.videoBuffer = nullptr; + } + } + else if (pMsg->IsType(CDVDMsg::VIDEO_DRAIN)) + { + while (!m_bStop && m_pVideoCodec) + { + m_pVideoCodec->SetCodecControl(DVD_CODEC_CTRL_DRAIN); + if (!ProcessDecoderOutput(frametime, pts)) + break; + } + } + else if (pMsg->IsType(CDVDMsg::GENERAL_PAUSE)) + { + m_paused = std::static_pointer_cast<CDVDMsgBool>(pMsg)->m_value; + CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::GENERAL_PAUSE: {}", m_paused); + } + else if (pMsg->IsType(CDVDMsg::PLAYER_REQUEST_STATE)) + { + SStateMsg msg; + msg.player = VideoPlayer_VIDEO; + msg.syncState = m_syncState; + m_messageParent.Put( + std::make_shared<CDVDMsgType<SStateMsg>>(CDVDMsg::PLAYER_REPORT_STATE, msg)); + } + else if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET)) + { + DemuxPacket* pPacket = std::static_pointer_cast<CDVDMsgDemuxerPacket>(pMsg)->GetPacket(); + bool bPacketDrop = std::static_pointer_cast<CDVDMsgDemuxerPacket>(pMsg)->GetPacketDrop(); + + if (m_stalled) + { + CLog::Log(LOGDEBUG, "CVideoPlayerVideo - Stillframe left, switching to normal playback"); + m_stalled = false; + } + + bRequestDrop = false; + iDropDirective = CalcDropRequirement(pts); + if ((iDropDirective & DROP_VERYLATE) && + m_bAllowDrop && + !bPacketDrop) + { + bRequestDrop = true; + } + if (iDropDirective & DROP_DROPPED) + { + m_iDroppedFrames++; + m_ptsTracker.Flush(); + } + if (m_messageQueue.GetDataSize() == 0 || m_speed < 0) + { + bRequestDrop = false; + m_iDroppedRequest = 0; + m_iLateFrames = 0; + } + + int codecControl = 0; + if (iDropDirective & DROP_BUFFER_LEVEL) + codecControl |= DVD_CODEC_CTRL_HURRY; + if (m_speed > DVD_PLAYSPEED_NORMAL) + codecControl |= DVD_CODEC_CTRL_NO_POSTPROC; + if (bPacketDrop) + codecControl |= DVD_CODEC_CTRL_DROP; + if (bRequestDrop) + codecControl |= DVD_CODEC_CTRL_DROP_ANY; + if (!m_renderManager.Supports(RENDERFEATURE_ROTATION)) + codecControl |= DVD_CODEC_CTRL_ROTATE; + m_pVideoCodec->SetCodecControl(codecControl); + + if (m_pVideoCodec->AddData(*pPacket)) + { + // buffer packets so we can recover should decoder flush for some reason + if (m_pVideoCodec->GetConvergeCount() > 0) + { + m_packets.emplace_back(pMsg, 0); + if (m_packets.size() > m_pVideoCodec->GetConvergeCount() || + m_packets.size() * frametime > DVD_SEC_TO_TIME(10)) + m_packets.pop_front(); + } + + m_videoStats.AddSampleBytes(pPacket->iSize); + + if (ProcessDecoderOutput(frametime, pts)) + { + onlyPrioMsgs = true; + } + } + else + { + SendMessageBack(pMsg); + onlyPrioMsgs = true; + } + } + } +} + +bool CVideoPlayerVideo::ProcessDecoderOutput(double &frametime, double &pts) +{ + CDVDVideoCodec::VCReturn decoderState = m_pVideoCodec->GetPicture(&m_picture); + + if (decoderState == CDVDVideoCodec::VC_BUFFER) + { + return false; + } + + // if decoder was flushed, we need to seek back again to resume rendering + if (decoderState == CDVDVideoCodec::VC_FLUSHED) + { + CLog::Log(LOGDEBUG, "CVideoPlayerVideo - video decoder was flushed"); + while (!m_packets.empty()) + { + auto msg = std::static_pointer_cast<CDVDMsgDemuxerPacket>(m_packets.front().message); + m_packets.pop_front(); + + SendMessage(msg, 10); + } + + m_pVideoCodec->Reset(); + m_packets.clear(); + //picture.iFlags &= ~DVP_FLAG_ALLOCATED; + m_renderManager.DiscardBuffer(); + return false; + } + + if (decoderState == CDVDVideoCodec::VC_REOPEN) + { + while (!m_packets.empty()) + { + auto msg = std::static_pointer_cast<CDVDMsgDemuxerPacket>(m_packets.front().message); + m_packets.pop_front(); + SendMessage(msg, 10); + } + + m_pVideoCodec->Reopen(); + m_packets.clear(); + m_renderManager.DiscardBuffer(); + return false; + } + + // if decoder had an error, tell it to reset to avoid more problems + if (decoderState == CDVDVideoCodec::VC_ERROR) + { + CLog::Log(LOGDEBUG, "CVideoPlayerVideo - video decoder returned error"); + return false; + } + + if (decoderState == CDVDVideoCodec::VC_EOF) + { + if (m_syncState == IDVDStreamPlayer::SYNC_STARTING) + { + SStartMsg msg; + msg.player = VideoPlayer_VIDEO; + msg.cachetime = DVD_MSEC_TO_TIME(50); + msg.cachetotal = DVD_MSEC_TO_TIME(100); + msg.timestamp = DVD_NOPTS_VALUE; + m_messageParent.Put(std::make_shared<CDVDMsgType<SStartMsg>>(CDVDMsg::PLAYER_STARTED, msg)); + } + return false; + } + + // check for a new picture + if (decoderState == CDVDVideoCodec::VC_PICTURE) + { + bool hasTimestamp = true; + + m_picture.iDuration = frametime; + + // validate picture timing, + // if both dts/pts invalid, use pts calculated from picture.iDuration + // if pts invalid use dts, else use picture.pts as passed + if (m_picture.dts == DVD_NOPTS_VALUE && m_picture.pts == DVD_NOPTS_VALUE) + { + m_picture.pts = pts; + hasTimestamp = false; + } + else if (m_picture.pts == DVD_NOPTS_VALUE) + m_picture.pts = m_picture.dts; + + // use forced aspect if any + if (m_fForcedAspectRatio != 0.0f) + { + m_picture.iDisplayWidth = (int) (m_picture.iDisplayHeight * m_fForcedAspectRatio); + if (m_picture.iDisplayWidth > m_picture.iWidth) + { + m_picture.iDisplayWidth = m_picture.iWidth; + m_picture.iDisplayHeight = (int) (m_picture.iDisplayWidth / m_fForcedAspectRatio); + } + } + + // set stereo mode if not set by decoder + if (m_picture.stereoMode.empty()) + { + std::string stereoMode; + switch(m_processInfo.GetVideoSettings().m_StereoMode) + { + case RENDER_STEREO_MODE_SPLIT_VERTICAL: + stereoMode = "left_right"; + if (m_processInfo.GetVideoSettings().m_StereoInvert) + stereoMode = "right_left"; + break; + case RENDER_STEREO_MODE_SPLIT_HORIZONTAL: + stereoMode = "top_bottom"; + if (m_processInfo.GetVideoSettings().m_StereoInvert) + stereoMode = "bottom_top"; + break; + default: + stereoMode = m_hints.stereo_mode; + break; + } + if (!stereoMode.empty() && stereoMode != "mono") + { + m_picture.stereoMode = stereoMode; + } + } + + // if frame has a pts (usually originating from demux packet), use that + if (m_picture.pts != DVD_NOPTS_VALUE) + { + pts = m_picture.pts; + } + + double extraDelay = 0.0; + if (m_picture.iRepeatPicture) + { + extraDelay = m_picture.iRepeatPicture * m_picture.iDuration; + m_picture.iDuration += extraDelay; + } + + m_picture.pts = pts + extraDelay; + // guess next frame pts. iDuration is always valid + if (m_speed != 0) + pts += m_picture.iDuration * m_speed / abs(m_speed); + + m_outputSate = OutputPicture(&m_picture); + + if (m_outputSate == OUTPUT_AGAIN) + { + return true; + } + else if (m_outputSate == OUTPUT_ABORT) + { + return false; + } + else if ((m_outputSate == OUTPUT_DROPPED) && !(m_picture.iFlags & DVP_FLAG_DROPPED)) + { + m_iDroppedFrames++; + m_ptsTracker.Flush(); + } + + if (m_syncState == IDVDStreamPlayer::SYNC_STARTING && + m_outputSate != OUTPUT_DROPPED && + !(m_picture.iFlags & DVP_FLAG_DROPPED)) + { + m_syncState = IDVDStreamPlayer::SYNC_WAITSYNC; + SStartMsg msg; + msg.player = VideoPlayer_VIDEO; + msg.cachetime = DVD_MSEC_TO_TIME(50); //! @todo implement + msg.cachetotal = DVD_MSEC_TO_TIME(100); //! @todo implement + msg.timestamp = hasTimestamp ? (pts + m_renderManager.GetDelay() * 1000) : DVD_NOPTS_VALUE; + m_messageParent.Put(std::make_shared<CDVDMsgType<SStartMsg>>(CDVDMsg::PLAYER_STARTED, msg)); + } + + frametime = (double)DVD_TIME_BASE / m_fFrameRate; + } + + return true; +} + +void CVideoPlayerVideo::OnExit() +{ + CLog::Log(LOGINFO, "thread end: video_thread"); +} + +void CVideoPlayerVideo::SetSpeed(int speed) +{ + if(m_messageQueue.IsInited()) + SendMessage(std::make_shared<CDVDMsgInt>(CDVDMsg::PLAYER_SETSPEED, speed), 1); + else + m_speed = speed; +} + +void CVideoPlayerVideo::Flush(bool sync) +{ + /* flush using message as this get's called from VideoPlayer thread */ + /* and any demux packet that has been taken out of queue need to */ + /* be disposed of before we flush */ + SendMessage(std::make_shared<CDVDMsgBool>(CDVDMsg::GENERAL_FLUSH, sync), 1); + m_bAbortOutput = true; +} + +void CVideoPlayerVideo::ProcessOverlays(const VideoPicture* pSource, double pts) +{ + // remove any overlays that are out of time + if (m_syncState == IDVDStreamPlayer::SYNC_INSYNC) + m_pOverlayContainer->CleanUp(pts - m_iSubtitleDelay); + + VecOverlays overlays; + + { + std::unique_lock<CCriticalSection> lock(*m_pOverlayContainer); + + VecOverlays* pVecOverlays = m_pOverlayContainer->GetOverlays(); + VecOverlaysIter it = pVecOverlays->begin(); + + //Check all overlays and render those that should be rendered, based on time and forced + //Both forced and subs should check timing + while (it != pVecOverlays->end()) + { + CDVDOverlay* pOverlay = *it++; + if(!pOverlay->bForced && !m_bRenderSubs) + continue; + + double pts2 = pOverlay->bForced ? pts : pts - m_iSubtitleDelay; + + if((pOverlay->iPTSStartTime <= pts2 && (pOverlay->iPTSStopTime > pts2 || pOverlay->iPTSStopTime == 0LL))) + { + if(pOverlay->IsOverlayType(DVDOVERLAY_TYPE_GROUP)) + overlays.insert(overlays.end(), static_cast<CDVDOverlayGroup*>(pOverlay)->m_overlays.begin() + , static_cast<CDVDOverlayGroup*>(pOverlay)->m_overlays.end()); + else + overlays.push_back(pOverlay); + } + } + + for(it = overlays.begin(); it != overlays.end(); ++it) + { + double pts2 = (*it)->bForced ? pts : pts - m_iSubtitleDelay; + m_renderManager.AddOverlay(*it, pts2); + } + } +} + +CVideoPlayerVideo::EOutputState CVideoPlayerVideo::OutputPicture(const VideoPicture* pPicture) +{ + m_bAbortOutput = false; + + if (m_processInfo.GetVideoStereoMode() != pPicture->stereoMode) + { + m_processInfo.SetVideoStereoMode(pPicture->stereoMode); + // signal about changes in video parameters + m_messageParent.Put(std::make_shared<CDVDMsg>(CDVDMsg::PLAYER_AVCHANGE)); + } + + double config_framerate = m_bFpsInvalid ? 0.0 : m_fFrameRate; + if (m_processInfo.GetVideoInterlaced()) + { + if (MathUtils::FloatEquals(config_framerate, 25.0, 0.02)) + config_framerate = 50.0; + else if (MathUtils::FloatEquals(config_framerate, 29.97, 0.02)) + config_framerate = 59.94; + } + + int sorient = m_processInfo.GetVideoSettings().m_Orientation; + int orientation = sorient != 0 ? (sorient + m_hints.orientation) % 360 + : m_hints.orientation; + + if (!m_renderManager.Configure(*pPicture, + static_cast<float>(config_framerate), + orientation, + m_pVideoCodec->GetAllowedReferences())) + { + CLog::Log(LOGERROR, "{} - failed to configure renderer", __FUNCTION__); + return OUTPUT_ABORT; + } + + //try to calculate the framerate + m_ptsTracker.Add(pPicture->pts); + if (!m_stalled) + CalcFrameRate(); + + // signal to clock what our framerate is, it may want to adjust it's + // speed to better match with our video renderer's output speed + m_pClock->UpdateFramerate(m_fFrameRate); + + // calculate the time we need to delay this picture before displaying + double iPlayingClock, iCurrentClock; + + iPlayingClock = m_pClock->GetClock(iCurrentClock, false); // snapshot current clock + + if (m_speed < 0) + { + double renderPts; + int queued, discard; + int lateframes; + double inputPts = m_droppingStats.m_lastPts; + m_renderManager.GetStats(lateframes, renderPts, queued, discard); + if (pPicture->pts > renderPts || queued > 0) + { + if (inputPts >= renderPts) + { + m_rewindStalled = true; + CThread::Sleep(50ms); + } + return OUTPUT_DROPPED; + } + else if (pPicture->pts < iPlayingClock) + { + return OUTPUT_DROPPED; + } + } + + if ((pPicture->iFlags & DVP_FLAG_DROPPED)) + { + m_droppingStats.AddOutputDropGain(pPicture->pts, 1); + CLog::Log(LOGDEBUG, "{} - dropped in output", __FUNCTION__); + return OUTPUT_DROPPED; + } + + auto timeToDisplay = std::chrono::milliseconds(DVD_TIME_TO_MSEC(pPicture->pts - iPlayingClock)); + + // make sure waiting time is not negative + std::chrono::milliseconds maxWaitTime = std::min(std::max(timeToDisplay + 500ms, 50ms), 500ms); + // don't wait when going ff + if (m_speed > DVD_PLAYSPEED_NORMAL) + maxWaitTime = std::max(timeToDisplay, 0ms); + int buffer = m_renderManager.WaitForBuffer(m_bAbortOutput, maxWaitTime); + if (buffer < 0) + { + if (m_speed != DVD_PLAYSPEED_PAUSE) + CLog::Log(LOGWARNING, "{} - timeout waiting for buffer", __FUNCTION__); + return OUTPUT_AGAIN; + } + + ProcessOverlays(pPicture, pPicture->pts); + + EINTERLACEMETHOD deintMethod = EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE; + deintMethod = m_processInfo.GetVideoSettings().m_InterlaceMethod; + if (!m_processInfo.Supports(deintMethod)) + deintMethod = m_processInfo.GetDeinterlacingMethodDefault(); + + if (!m_renderManager.AddVideoPicture(*pPicture, m_bAbortOutput, deintMethod, (m_syncState == ESyncState::SYNC_STARTING))) + { + m_droppingStats.AddOutputDropGain(pPicture->pts, 1); + return OUTPUT_DROPPED; + } + + return OUTPUT_NORMAL; +} + +std::string CVideoPlayerVideo::GetPlayerInfo() +{ + std::ostringstream s; + s << "vq:" << std::setw(2) << std::min(99, m_processInfo.GetLevelVQ()) << "%"; + s << ", Mb/s:" << std::fixed << std::setprecision(2) << (double)GetVideoBitrate() / (1024.0*1024.0); + s << ", fr:" << std::fixed << std::setprecision(3) << m_fFrameRate; + s << ", drop:" << m_iDroppedFrames; + s << ", skip:" << m_renderManager.GetSkippedFrames(); + + int pc = m_ptsTracker.GetPatternLength(); + if (pc > 0) + s << ", pc:" << pc; + else + s << ", pc:none"; + + return s.str(); +} + +int CVideoPlayerVideo::GetVideoBitrate() +{ + return (int)m_videoStats.GetBitrate(); +} + +void CVideoPlayerVideo::ResetFrameRateCalc() +{ + m_fStableFrameRate = 0.0; + m_iFrameRateCount = 0; + m_iFrameRateLength = 1; + m_iFrameRateErr = 0; + m_bAllowDrop = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoFpsDetect == 0; +} + +double CVideoPlayerVideo::GetCurrentPts() +{ + double renderPts; + int sleepTime; + int queued, discard; + + // get render stats + m_renderManager.GetStats(sleepTime, renderPts, queued, discard); + + if (renderPts == DVD_NOPTS_VALUE) + return DVD_NOPTS_VALUE; + else if (m_stalled) + return DVD_NOPTS_VALUE; + else if (m_speed == DVD_PLAYSPEED_NORMAL) + { + if (renderPts < 0) + renderPts = 0; + } + return renderPts; +} + +#define MAXFRAMERATEDIFF 0.01 +#define MAXFRAMESERR 1000 + +void CVideoPlayerVideo::CalcFrameRate() +{ + if (m_iFrameRateLength >= 128 || CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoFpsDetect == 0) + return; //don't calculate the fps + + if (!m_ptsTracker.HasFullBuffer()) + return; //we can only calculate the frameduration if m_pullupCorrection has a full buffer + + //see if m_pullupCorrection was able to detect a pattern in the timestamps + //and is able to calculate the correct frame duration from it + double frameduration = m_ptsTracker.GetFrameDuration(); + if (m_ptsTracker.VFRDetection()) + frameduration = m_ptsTracker.GetMinFrameDuration(); + + if ((frameduration==DVD_NOPTS_VALUE) || + ((CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoFpsDetect == 1) && ((m_ptsTracker.GetPatternLength() > 1) && !m_ptsTracker.VFRDetection()))) + { + //reset the stored framerates if no good framerate was detected + m_fStableFrameRate = 0.0; + m_iFrameRateCount = 0; + m_iFrameRateErr++; + + if (m_iFrameRateErr == MAXFRAMESERR && m_iFrameRateLength == 1) + { + CLog::Log(LOGDEBUG, + "{} counted {} frames without being able to calculate the framerate, giving up", + __FUNCTION__, m_iFrameRateErr); + m_bAllowDrop = true; + m_iFrameRateLength = 128; + } + return; + } + + double framerate = DVD_TIME_BASE / frameduration; + + //store the current calculated framerate if we don't have any yet + if (m_iFrameRateCount == 0) + { + m_fStableFrameRate = framerate; + m_iFrameRateCount++; + } + //check if the current detected framerate matches with the stored ones + else if (fabs(m_fStableFrameRate / m_iFrameRateCount - framerate) <= MAXFRAMERATEDIFF) + { + m_fStableFrameRate += framerate; //store the calculated framerate + m_iFrameRateCount++; + + //if we've measured m_iFrameRateLength seconds of framerates, + if (m_iFrameRateCount >= MathUtils::round_int(framerate) * m_iFrameRateLength) + { + //store the calculated framerate if it differs too much from m_fFrameRate + if (fabs(m_fFrameRate - (m_fStableFrameRate / m_iFrameRateCount)) > MAXFRAMERATEDIFF || m_bFpsInvalid) + { + CLog::Log(LOGDEBUG, "{} framerate was:{:f} calculated:{:f}", __FUNCTION__, m_fFrameRate, + m_fStableFrameRate / m_iFrameRateCount); + m_fFrameRate = m_fStableFrameRate / m_iFrameRateCount; + m_bFpsInvalid = false; + m_processInfo.SetVideoFps(static_cast<float>(m_fFrameRate)); + } + + //reset the stored framerates + m_fStableFrameRate = 0.0; + m_iFrameRateCount = 0; + m_iFrameRateLength *= 2; //double the length we should measure framerates + + //we're allowed to drop frames because we calculated a good framerate + m_bAllowDrop = true; + } + } + else //the calculated framerate didn't match, reset the stored ones + { + m_fStableFrameRate = 0.0; + m_iFrameRateCount = 0; + } +} + +int CVideoPlayerVideo::CalcDropRequirement(double pts) +{ + int result = 0; + int lateframes; + double iDecoderPts, iRenderPts; + int iSkippedPicture = -1; + int iDroppedFrames = -1; + int iBufferLevel; + int queued, discard; + + m_droppingStats.m_lastPts = pts; + + // get decoder stats + if (!m_pVideoCodec->GetCodecStats(iDecoderPts, iDroppedFrames, iSkippedPicture)) + iDecoderPts = pts; + if (iDecoderPts == DVD_NOPTS_VALUE) + iDecoderPts = pts; + + // get render stats + m_renderManager.GetStats(lateframes, iRenderPts, queued, discard); + iBufferLevel = queued + discard; + + if (iBufferLevel < 0) + result |= DROP_BUFFER_LEVEL; + else if (iBufferLevel < 2) + { + result |= DROP_BUFFER_LEVEL; + CLog::Log(LOGDEBUG, LOGVIDEO, "CVideoPlayerVideo::CalcDropRequirement - hurry: {}", + iBufferLevel); + } + + if (m_bAllowDrop) + { + if (iSkippedPicture > 0) + { + CDroppingStats::CGain gain; + gain.frames = iSkippedPicture; + gain.pts = iDecoderPts; + m_droppingStats.m_gain.push_back(gain); + m_droppingStats.m_totalGain += gain.frames; + result |= DROP_DROPPED; + CLog::Log(LOGDEBUG, LOGVIDEO, + "CVideoPlayerVideo::CalcDropRequirement - dropped pictures, lateframes: {}, " + "Bufferlevel: {}, dropped: {}", + lateframes, iBufferLevel, iSkippedPicture); + } + if (iDroppedFrames > 0) + { + CDroppingStats::CGain gain; + gain.frames = iDroppedFrames; + gain.pts = iDecoderPts; + m_droppingStats.m_gain.push_back(gain); + m_droppingStats.m_totalGain += iDroppedFrames; + result |= DROP_DROPPED; + CLog::Log(LOGDEBUG, LOGVIDEO, + "CVideoPlayerVideo::CalcDropRequirement - dropped in decoder, lateframes: {}, " + "Bufferlevel: {}, dropped: {}", + lateframes, iBufferLevel, iDroppedFrames); + } + } + + // subtract gains + while (!m_droppingStats.m_gain.empty() && + iRenderPts >= m_droppingStats.m_gain.front().pts) + { + m_droppingStats.m_totalGain -= m_droppingStats.m_gain.front().frames; + m_droppingStats.m_gain.pop_front(); + } + + // calculate lateness + int lateness = lateframes - m_droppingStats.m_totalGain; + + if (lateness > 0 && m_speed) + { + result |= DROP_VERYLATE; + } + return result; +} + +void CDroppingStats::Reset() +{ + m_gain.clear(); + m_totalGain = 0; +} + +void CDroppingStats::AddOutputDropGain(double pts, int frames) +{ + CDroppingStats::CGain gain; + gain.frames = frames; + gain.pts = pts; + m_gain.push_back(gain); + m_totalGain += frames; +} |