2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "VideoPlayerVideo.h"
11 #include "DVDCodecs/DVDCodecUtils.h"
12 #include "DVDCodecs/DVDFactoryCodec.h"
13 #include "DVDCodecs/Overlay/DVDOverlay.h"
14 #include "DVDCodecs/Video/DVDVideoCodecFFmpeg.h"
15 #include "ServiceBroker.h"
16 #include "cores/VideoPlayer/Interface/DemuxPacket.h"
17 #include "cores/VideoPlayer/Interface/TimingConstants.h"
18 #include "settings/AdvancedSettings.h"
19 #include "settings/Settings.h"
20 #include "settings/SettingsComponent.h"
21 #include "utils/MathUtils.h"
22 #include "utils/log.h"
23 #include "windowing/GraphicContext.h"
24 #include "windowing/WinSystem.h"
33 using namespace std::chrono_literals
;
35 class CDVDMsgVideoCodecChange
: public CDVDMsg
38 CDVDMsgVideoCodecChange(const CDVDStreamInfo
& hints
, std::unique_ptr
<CDVDVideoCodec
> codec
)
39 : CDVDMsg(GENERAL_STREAMCHANGE
), m_codec(std::move(codec
)), m_hints(hints
)
41 ~CDVDMsgVideoCodecChange() override
= default;
43 std::unique_ptr
<CDVDVideoCodec
> m_codec
;
44 CDVDStreamInfo m_hints
;
47 CVideoPlayerVideo::CVideoPlayerVideo(CDVDClock
* pClock
,
48 CDVDOverlayContainer
* pOverlayContainer
,
49 CDVDMessageQueue
& parent
,
50 CRenderManager
& renderManager
,
51 CProcessInfo
& processInfo
,
52 double messageQueueTimeSize
)
53 : CThread("VideoPlayerVideo"),
54 IDVDStreamPlayerVideo(processInfo
),
55 m_messageQueue("video"),
56 m_messageParent(parent
),
57 m_renderManager(renderManager
)
60 m_pOverlayContainer
= pOverlayContainer
;
61 m_speed
= DVD_PLAYSPEED_NORMAL
;
63 m_bRenderSubs
= false;
65 m_syncState
= IDVDStreamPlayer::SYNC_STARTING
;
68 m_iDroppedRequest
= 0;
69 m_fForcedAspectRatio
= 0;
71 const int sizeMB
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
72 CSettings::SETTING_VIDEOPLAYER_QUEUEDATASIZE
);
74 m_messageQueue
.SetMaxDataSize(sizeMB
* 1024 * 1024);
75 m_messageQueue
.SetMaxTimeSize(messageQueueTimeSize
);
79 m_fStableFrameRate
= 0.0;
80 m_iFrameRateCount
= 0;
83 m_iFrameRateLength
= 0;
84 m_bFpsInvalid
= false;
87 CVideoPlayerVideo::~CVideoPlayerVideo()
89 m_bAbortOutput
= true;
93 double CVideoPlayerVideo::GetOutputDelay()
95 double time
= m_messageQueue
.GetPacketCount(CDVDMsg::DEMUXER_PACKET
);
97 time
= (time
* DVD_TIME_BASE
) / m_fFrameRate
;
102 time
= time
* DVD_PLAYSPEED_NORMAL
/ abs(m_speed
);
107 bool CVideoPlayerVideo::OpenStream(CDVDStreamInfo hint
)
109 if (hint
.flags
& AV_DISPOSITION_ATTACHED_PIC
)
113 // codecs which require extradata
115 if (hint
.codec
== AV_CODEC_ID_NONE
||
116 hint
.codec
== AV_CODEC_ID_MPEG1VIDEO
||
117 hint
.codec
== AV_CODEC_ID_MPEG2VIDEO
||
118 (hint
.codec
== AV_CODEC_ID_H264
&& (hint
.codec_tag
== 0 || hint
.codec_tag
== MKTAG('a','v','c','1') || hint
.codec_tag
== MKTAG('a','v','c','2'))) ||
119 hint
.codec
== AV_CODEC_ID_HEVC
||
120 hint
.codec
== AV_CODEC_ID_MPEG4
||
121 hint
.codec
== AV_CODEC_ID_WMV3
||
122 hint
.codec
== AV_CODEC_ID_VC1
||
123 hint
.codec
== AV_CODEC_ID_AV1
)
125 CLog::LogF(LOGERROR
, "Codec id {} require extradata.", hint
.codec
);
131 CLog::Log(LOGINFO
, "Creating video codec with codec id: {}", hint
.codec
);
133 if (m_messageQueue
.IsInited())
135 if (m_pVideoCodec
&& !m_processInfo
.IsVideoHwDecoder())
137 hint
.codecOptions
|= CODEC_ALLOW_FALLBACK
;
140 std::unique_ptr
<CDVDVideoCodec
> codec
= CDVDFactoryCodec::CreateVideoCodec(hint
, m_processInfo
);
143 CLog::Log(LOGINFO
, "CVideoPlayerVideo::OpenStream - could not open video codec");
146 SendMessage(std::make_shared
<CDVDMsgVideoCodecChange
>(hint
, std::move(codec
)), 0);
150 m_processInfo
.ResetVideoCodecInfo();
151 hint
.codecOptions
|= CODEC_ALLOW_FALLBACK
;
153 std::unique_ptr
<CDVDVideoCodec
> codec
= CDVDFactoryCodec::CreateVideoCodec(hint
, m_processInfo
);
156 CLog::Log(LOGERROR
, "CVideoPlayerVideo::OpenStream - could not open video codec");
160 OpenStream(hint
, std::move(codec
));
161 CLog::Log(LOGINFO
, "Creating video thread");
162 m_messageQueue
.Init();
163 m_processInfo
.SetLevelVQ(0);
169 void CVideoPlayerVideo::OpenStream(CDVDStreamInfo
& hint
, std::unique_ptr
<CDVDVideoCodec
> codec
)
171 CLog::Log(LOGDEBUG
, "CVideoPlayerVideo::OpenStream - open stream with codec id: {}", hint
.codec
);
173 m_processInfo
.GetVideoBufferManager().ReleasePools();
175 //reported fps is usually not completely correct
176 if (hint
.fpsrate
&& hint
.fpsscale
)
178 m_fFrameRate
= DVD_TIME_BASE
/ CDVDCodecUtils::NormalizeFrameduration(
179 (double)DVD_TIME_BASE
* hint
.fpsscale
/ hint
.fpsrate
);
181 m_bFpsInvalid
= false;
182 m_processInfo
.SetVideoFps(static_cast<float>(m_fFrameRate
));
187 m_bFpsInvalid
= true;
188 m_processInfo
.SetVideoFps(0);
191 m_ptsTracker
.ResetVFRDetection();
192 ResetFrameRateCalc();
194 m_iDroppedRequest
= 0;
197 if( m_fFrameRate
> 120 || m_fFrameRate
< 5 )
200 "CVideoPlayerVideo::OpenStream - Invalid framerate {}, using forced 25fps and just "
206 // use aspect in stream if available
207 if (hint
.forced_aspect
)
208 m_fForcedAspectRatio
= static_cast<float>(hint
.aspect
);
210 m_fForcedAspectRatio
= 0.0f
;
212 if (m_pVideoCodec
&& m_pVideoCodec
->Reconfigure(hint
))
215 codec
= std::move(m_pVideoCodec
);
218 m_pVideoCodec
.reset();
222 CLog::Log(LOGINFO
, "Creating video codec with codec id: {}", hint
.codec
);
223 hint
.codecOptions
|= CODEC_ALLOW_FALLBACK
;
224 codec
= CDVDFactoryCodec::CreateVideoCodec(hint
, m_processInfo
);
227 CLog::Log(LOGERROR
, "CVideoPlayerVideo::OpenStream - could not open video codec");
228 m_messageParent
.Put(std::make_shared
<CDVDMsg
>(CDVDMsg::PLAYER_ABORT
));
233 m_pVideoCodec
= std::move(codec
);
235 m_stalled
= m_messageQueue
.GetPacketCount(CDVDMsg::DEMUXER_PACKET
) == 0;
236 m_rewindStalled
= false;
238 m_syncState
= IDVDStreamPlayer::SYNC_STARTING
;
239 m_renderManager
.ShowVideo(false);
242 void CVideoPlayerVideo::CloseStream(bool bWaitForBuffers
)
244 // wait until buffers are empty
245 if (bWaitForBuffers
&& m_speed
> 0)
247 SendMessage(std::make_shared
<CDVDMsg
>(CDVDMsg::VIDEO_DRAIN
), 0);
248 m_messageQueue
.WaitUntilEmpty();
251 m_messageQueue
.Abort();
253 // wait for decode_video thread to end
254 CLog::Log(LOGINFO
, "waiting for video thread to exit");
256 m_bAbortOutput
= true;
259 m_messageQueue
.End();
261 CLog::Log(LOGINFO
, "deleting video codec");
262 m_pVideoCodec
.reset();
264 if (m_picture
.videoBuffer
)
266 m_picture
.videoBuffer
->Release();
267 m_picture
.videoBuffer
= nullptr;
271 bool CVideoPlayerVideo::AcceptsData() const
273 bool full
= m_messageQueue
.IsFull();
277 bool CVideoPlayerVideo::HasData() const
279 return m_messageQueue
.GetDataSize() > 0;
282 bool CVideoPlayerVideo::IsInited() const
284 return m_messageQueue
.IsInited();
287 inline void CVideoPlayerVideo::SendMessage(std::shared_ptr
<CDVDMsg
> pMsg
, int priority
)
289 m_messageQueue
.Put(pMsg
, priority
);
290 m_processInfo
.SetLevelVQ(m_messageQueue
.GetLevel());
293 inline void CVideoPlayerVideo::SendMessageBack(const std::shared_ptr
<CDVDMsg
>& pMsg
, int priority
)
295 m_messageQueue
.PutBack(pMsg
, priority
);
296 m_processInfo
.SetLevelVQ(m_messageQueue
.GetLevel());
299 inline void CVideoPlayerVideo::FlushMessages()
301 m_messageQueue
.Flush();
302 m_processInfo
.SetLevelVQ(m_messageQueue
.GetLevel());
305 inline MsgQueueReturnCode
CVideoPlayerVideo::GetMessage(std::shared_ptr
<CDVDMsg
>& pMsg
,
306 std::chrono::milliseconds timeout
,
309 MsgQueueReturnCode ret
= m_messageQueue
.Get(pMsg
, timeout
, priority
);
310 m_processInfo
.SetLevelVQ(m_messageQueue
.GetLevel());
314 void CVideoPlayerVideo::Process()
316 CLog::Log(LOGINFO
, "running thread: video_thread");
319 double frametime
= (double)DVD_TIME_BASE
/ m_fFrameRate
;
321 bool bRequestDrop
= false;
323 bool onlyPrioMsgs
= false;
325 m_videoStats
.Start();
326 m_droppingStats
.Reset();
327 m_iDroppedFrames
= 0;
328 m_rewindStalled
= false;
329 m_outputSate
= OUTPUT_NORMAL
;
333 auto timeout
= std::chrono::duration_cast
<std::chrono::milliseconds
>(
334 std::chrono::duration
<double, std::micro
>(m_stalled
? frametime
: frametime
* 10));
337 if (m_syncState
== IDVDStreamPlayer::SYNC_WAITSYNC
)
349 std::shared_ptr
<CDVDMsg
> pMsg
;
350 MsgQueueReturnCode ret
= GetMessage(pMsg
, timeout
, iPriority
);
352 onlyPrioMsgs
= false;
354 if (MSGQ_IS_ERROR(ret
))
356 if (!m_messageQueue
.ReceivedAbortRequest())
357 CLog::Log(LOGERROR
, "MSGQ_IS_ERROR returned true ({})", ret
);
361 else if (ret
== MSGQ_TIMEOUT
)
363 if (m_outputSate
== OUTPUT_AGAIN
&&
364 m_picture
.videoBuffer
)
366 m_outputSate
= OutputPicture(&m_picture
);
367 if (m_outputSate
== OUTPUT_AGAIN
)
373 // don't ask for a new frame if we can't deliver it to renderer
374 else if ((m_speed
!= DVD_PLAYSPEED_PAUSE
||
375 m_processInfo
.IsFrameAdvance() ||
376 m_syncState
!= IDVDStreamPlayer::SYNC_INSYNC
) && !m_paused
)
378 if (ProcessDecoderOutput(frametime
, pts
))
385 // if we only wanted priority messages, this isn't a stall
389 //Okey, start rendering at stream fps now instead, we are likely in a stillframe
392 // squeeze pictures out
393 while (!m_bStop
&& m_pVideoCodec
)
395 m_pVideoCodec
->SetCodecControl(DVD_CODEC_CTRL_DRAIN
);
396 if (!ProcessDecoderOutput(frametime
, pts
))
400 CLog::Log(LOGDEBUG
, "CVideoPlayerVideo - Stillframe detected, switching to forced {:f} fps",
403 pts
+= frametime
* 4;
406 // Waiting timed out, output last picture
407 if (m_picture
.videoBuffer
)
410 m_outputSate
= OutputPicture(&m_picture
);
417 if (pMsg
->IsType(CDVDMsg::GENERAL_SYNCHRONIZE
))
419 if (std::static_pointer_cast
<CDVDMsgGeneralSynchronize
>(pMsg
)->Wait(100ms
, SYNCSOURCE_VIDEO
))
421 CLog::Log(LOGDEBUG
, "CVideoPlayerVideo - CDVDMsg::GENERAL_SYNCHRONIZE");
424 SendMessage(pMsg
, 1); /* push back as prio message, to process other prio messages */
425 m_droppingStats
.Reset();
427 else if (pMsg
->IsType(CDVDMsg::GENERAL_RESYNC
))
429 pts
= std::static_pointer_cast
<CDVDMsgDouble
>(pMsg
)->m_value
;
431 m_syncState
= IDVDStreamPlayer::SYNC_INSYNC
;
432 m_droppingStats
.Reset();
433 m_rewindStalled
= false;
434 m_renderManager
.ShowVideo(true);
436 CLog::Log(LOGDEBUG
, "CVideoPlayerVideo - CDVDMsg::GENERAL_RESYNC({:f})", pts
);
438 else if (pMsg
->IsType(CDVDMsg::VIDEO_SET_ASPECT
))
440 CLog::Log(LOGDEBUG
, "CVideoPlayerVideo - CDVDMsg::VIDEO_SET_ASPECT");
441 m_fForcedAspectRatio
= static_cast<float>(*std::static_pointer_cast
<CDVDMsgDouble
>(pMsg
));
443 else if (pMsg
->IsType(CDVDMsg::GENERAL_RESET
))
446 m_pVideoCodec
->Reset();
448 if (m_picture
.videoBuffer
)
450 m_picture
.videoBuffer
->Release();
451 m_picture
.videoBuffer
= nullptr;
454 m_droppingStats
.Reset();
455 m_syncState
= IDVDStreamPlayer::SYNC_STARTING
;
456 m_renderManager
.ShowVideo(false);
457 m_rewindStalled
= false;
459 else if (pMsg
->IsType(CDVDMsg::GENERAL_FLUSH
)) // private message sent by (CVideoPlayerVideo::Flush())
461 bool sync
= std::static_pointer_cast
<CDVDMsgBool
>(pMsg
)->m_value
;
463 m_pVideoCodec
->Reset();
465 if (m_picture
.videoBuffer
)
467 m_picture
.videoBuffer
->Release();
468 m_picture
.videoBuffer
= nullptr;
472 m_rewindStalled
= false;
474 m_ptsTracker
.Flush();
475 //we need to recalculate the framerate
476 //! @todo this needs to be set on a streamchange instead
477 ResetFrameRateCalc();
478 m_droppingStats
.Reset();
483 m_syncState
= IDVDStreamPlayer::SYNC_STARTING
;
484 m_renderManager
.ShowVideo(false);
487 m_renderManager
.DiscardBuffer();
490 else if (pMsg
->IsType(CDVDMsg::PLAYER_SETSPEED
))
492 m_speed
= std::static_pointer_cast
<CDVDMsgInt
>(pMsg
)->m_value
;
494 m_pVideoCodec
->SetSpeed(m_speed
);
496 m_droppingStats
.Reset();
498 else if (pMsg
->IsType(CDVDMsg::GENERAL_STREAMCHANGE
))
500 auto msg
= std::static_pointer_cast
<CDVDMsgVideoCodecChange
>(pMsg
);
502 while (!m_bStop
&& m_pVideoCodec
)
504 m_pVideoCodec
->SetCodecControl(DVD_CODEC_CTRL_DRAIN
);
505 bool cont
= ProcessDecoderOutput(frametime
, pts
);
511 OpenStream(msg
->m_hints
, std::move(msg
->m_codec
));
513 if (m_picture
.videoBuffer
)
515 m_picture
.videoBuffer
->Release();
516 m_picture
.videoBuffer
= nullptr;
519 else if (pMsg
->IsType(CDVDMsg::VIDEO_DRAIN
))
521 while (!m_bStop
&& m_pVideoCodec
)
523 m_pVideoCodec
->SetCodecControl(DVD_CODEC_CTRL_DRAIN
);
524 if (!ProcessDecoderOutput(frametime
, pts
))
528 else if (pMsg
->IsType(CDVDMsg::GENERAL_PAUSE
))
530 m_paused
= std::static_pointer_cast
<CDVDMsgBool
>(pMsg
)->m_value
;
531 CLog::Log(LOGDEBUG
, "CVideoPlayerVideo - CDVDMsg::GENERAL_PAUSE: {}", m_paused
);
533 else if (pMsg
->IsType(CDVDMsg::PLAYER_REQUEST_STATE
))
536 msg
.player
= VideoPlayer_VIDEO
;
537 msg
.syncState
= m_syncState
;
539 std::make_shared
<CDVDMsgType
<SStateMsg
>>(CDVDMsg::PLAYER_REPORT_STATE
, msg
));
541 else if (pMsg
->IsType(CDVDMsg::DEMUXER_PACKET
))
543 DemuxPacket
* pPacket
= std::static_pointer_cast
<CDVDMsgDemuxerPacket
>(pMsg
)->GetPacket();
544 bool bPacketDrop
= std::static_pointer_cast
<CDVDMsgDemuxerPacket
>(pMsg
)->GetPacketDrop();
548 CLog::Log(LOGDEBUG
, "CVideoPlayerVideo - Stillframe left, switching to normal playback");
552 bRequestDrop
= false;
553 iDropDirective
= CalcDropRequirement(pts
);
554 if ((iDropDirective
& DROP_VERYLATE
) &&
560 if (iDropDirective
& DROP_DROPPED
)
563 m_ptsTracker
.Flush();
565 if (m_messageQueue
.GetDataSize() == 0 || m_speed
< 0)
567 bRequestDrop
= false;
568 m_iDroppedRequest
= 0;
572 int codecControl
= 0;
573 if (iDropDirective
& DROP_BUFFER_LEVEL
)
574 codecControl
|= DVD_CODEC_CTRL_HURRY
;
575 if (m_speed
> DVD_PLAYSPEED_NORMAL
)
576 codecControl
|= DVD_CODEC_CTRL_NO_POSTPROC
;
578 codecControl
|= DVD_CODEC_CTRL_DROP
;
580 codecControl
|= DVD_CODEC_CTRL_DROP_ANY
;
581 if (!m_renderManager
.Supports(RENDERFEATURE_ROTATION
))
582 codecControl
|= DVD_CODEC_CTRL_ROTATE
;
583 m_pVideoCodec
->SetCodecControl(codecControl
);
585 if (m_pVideoCodec
->AddData(*pPacket
))
587 // buffer packets so we can recover should decoder flush for some reason
588 if (m_pVideoCodec
->GetConvergeCount() > 0)
590 m_packets
.emplace_back(pMsg
, 0);
591 if (m_packets
.size() > m_pVideoCodec
->GetConvergeCount() ||
592 m_packets
.size() * frametime
> DVD_SEC_TO_TIME(10))
593 m_packets
.pop_front();
596 m_videoStats
.AddSampleBytes(pPacket
->iSize
);
598 if (ProcessDecoderOutput(frametime
, pts
))
605 SendMessageBack(pMsg
);
612 bool CVideoPlayerVideo::ProcessDecoderOutput(double &frametime
, double &pts
)
614 CDVDVideoCodec::VCReturn decoderState
= m_pVideoCodec
->GetPicture(&m_picture
);
616 if (decoderState
== CDVDVideoCodec::VC_BUFFER
)
621 // if decoder was flushed, we need to seek back again to resume rendering
622 if (decoderState
== CDVDVideoCodec::VC_FLUSHED
)
624 CLog::Log(LOGDEBUG
, "CVideoPlayerVideo - video decoder was flushed");
625 while (!m_packets
.empty())
627 auto msg
= std::static_pointer_cast
<CDVDMsgDemuxerPacket
>(m_packets
.front().message
);
628 m_packets
.pop_front();
630 SendMessage(msg
, 10);
633 m_pVideoCodec
->Reset();
635 //picture.iFlags &= ~DVP_FLAG_ALLOCATED;
636 m_renderManager
.DiscardBuffer();
640 if (decoderState
== CDVDVideoCodec::VC_REOPEN
)
642 while (!m_packets
.empty())
644 auto msg
= std::static_pointer_cast
<CDVDMsgDemuxerPacket
>(m_packets
.front().message
);
645 m_packets
.pop_front();
646 SendMessage(msg
, 10);
649 m_pVideoCodec
->Reopen();
651 m_renderManager
.DiscardBuffer();
655 // if decoder had an error, tell it to reset to avoid more problems
656 if (decoderState
== CDVDVideoCodec::VC_ERROR
)
658 CLog::Log(LOGDEBUG
, "CVideoPlayerVideo - video decoder returned error");
662 if (decoderState
== CDVDVideoCodec::VC_EOF
)
664 if (m_syncState
== IDVDStreamPlayer::SYNC_STARTING
)
667 msg
.player
= VideoPlayer_VIDEO
;
668 msg
.cachetime
= DVD_MSEC_TO_TIME(50);
669 msg
.cachetotal
= DVD_MSEC_TO_TIME(100);
670 msg
.timestamp
= DVD_NOPTS_VALUE
;
671 m_messageParent
.Put(std::make_shared
<CDVDMsgType
<SStartMsg
>>(CDVDMsg::PLAYER_STARTED
, msg
));
676 // check for a new picture
677 if (decoderState
== CDVDVideoCodec::VC_PICTURE
)
679 bool hasTimestamp
= true;
681 m_picture
.iDuration
= frametime
;
683 // validate picture timing,
684 // if both dts/pts invalid, use pts calculated from picture.iDuration
685 // if pts invalid use dts, else use picture.pts as passed
686 if (m_picture
.dts
== DVD_NOPTS_VALUE
&& m_picture
.pts
== DVD_NOPTS_VALUE
)
689 hasTimestamp
= false;
691 else if (m_picture
.pts
== DVD_NOPTS_VALUE
)
692 m_picture
.pts
= m_picture
.dts
;
694 // use forced aspect if any
695 if (m_fForcedAspectRatio
!= 0.0f
)
697 m_picture
.iDisplayWidth
= (int) (m_picture
.iDisplayHeight
* m_fForcedAspectRatio
);
698 if (m_picture
.iDisplayWidth
> m_picture
.iWidth
)
700 m_picture
.iDisplayWidth
= m_picture
.iWidth
;
701 m_picture
.iDisplayHeight
= (int) (m_picture
.iDisplayWidth
/ m_fForcedAspectRatio
);
705 // set stereo mode if not set by decoder
706 if (m_picture
.stereoMode
.empty())
708 std::string stereoMode
;
709 switch(m_processInfo
.GetVideoSettings().m_StereoMode
)
711 case RENDER_STEREO_MODE_SPLIT_VERTICAL
:
712 stereoMode
= "left_right";
713 if (m_processInfo
.GetVideoSettings().m_StereoInvert
)
714 stereoMode
= "right_left";
716 case RENDER_STEREO_MODE_SPLIT_HORIZONTAL
:
717 stereoMode
= "top_bottom";
718 if (m_processInfo
.GetVideoSettings().m_StereoInvert
)
719 stereoMode
= "bottom_top";
722 stereoMode
= m_hints
.stereo_mode
;
725 if (!stereoMode
.empty() && stereoMode
!= "mono")
727 m_picture
.stereoMode
= stereoMode
;
731 // if frame has a pts (usually originating from demux packet), use that
732 if (m_picture
.pts
!= DVD_NOPTS_VALUE
)
737 double extraDelay
= 0.0;
738 if (m_picture
.iRepeatPicture
)
740 extraDelay
= m_picture
.iRepeatPicture
* m_picture
.iDuration
;
741 m_picture
.iDuration
+= extraDelay
;
744 m_picture
.pts
= pts
+ extraDelay
;
745 // guess next frame pts. iDuration is always valid
747 pts
+= m_picture
.iDuration
* m_speed
/ abs(m_speed
);
749 m_outputSate
= OutputPicture(&m_picture
);
751 if (m_outputSate
== OUTPUT_AGAIN
)
755 else if (m_outputSate
== OUTPUT_ABORT
)
759 else if ((m_outputSate
== OUTPUT_DROPPED
) && !(m_picture
.iFlags
& DVP_FLAG_DROPPED
))
762 m_ptsTracker
.Flush();
765 if (m_syncState
== IDVDStreamPlayer::SYNC_STARTING
&&
766 m_outputSate
!= OUTPUT_DROPPED
&&
767 !(m_picture
.iFlags
& DVP_FLAG_DROPPED
))
769 m_syncState
= IDVDStreamPlayer::SYNC_WAITSYNC
;
771 msg
.player
= VideoPlayer_VIDEO
;
772 msg
.cachetime
= DVD_MSEC_TO_TIME(50); //! @todo implement
773 msg
.cachetotal
= DVD_MSEC_TO_TIME(100); //! @todo implement
774 msg
.timestamp
= hasTimestamp
? (pts
+ m_renderManager
.GetDelay() * 1000) : DVD_NOPTS_VALUE
;
775 m_messageParent
.Put(std::make_shared
<CDVDMsgType
<SStartMsg
>>(CDVDMsg::PLAYER_STARTED
, msg
));
778 frametime
= (double)DVD_TIME_BASE
/ m_fFrameRate
;
784 void CVideoPlayerVideo::OnExit()
786 CLog::Log(LOGINFO
, "thread end: video_thread");
789 void CVideoPlayerVideo::SetSpeed(int speed
)
791 if(m_messageQueue
.IsInited())
792 SendMessage(std::make_shared
<CDVDMsgInt
>(CDVDMsg::PLAYER_SETSPEED
, speed
), 1);
797 void CVideoPlayerVideo::Flush(bool sync
)
799 /* flush using message as this get's called from VideoPlayer thread */
800 /* and any demux packet that has been taken out of queue need to */
801 /* be disposed of before we flush */
802 SendMessage(std::make_shared
<CDVDMsgBool
>(CDVDMsg::GENERAL_FLUSH
, sync
), 1);
803 m_bAbortOutput
= true;
806 void CVideoPlayerVideo::ProcessOverlays(const VideoPicture
* pSource
, double pts
)
808 // remove any overlays that are out of time
809 if (m_syncState
== IDVDStreamPlayer::SYNC_INSYNC
)
810 m_pOverlayContainer
->CleanUp(pts
- m_iSubtitleDelay
);
812 VecOverlays overlays
;
815 std::unique_lock
<CCriticalSection
> lock(*m_pOverlayContainer
);
817 VecOverlays
* pVecOverlays
= m_pOverlayContainer
->GetOverlays();
818 auto it
= pVecOverlays
->begin();
820 //Check all overlays and render those that should be rendered, based on time and forced
821 //Both forced and subs should check timing
822 while (it
!= pVecOverlays
->end())
824 std::shared_ptr
<CDVDOverlay
>& pOverlay
= *it
++;
825 if(!pOverlay
->bForced
&& !m_bRenderSubs
)
828 double pts2
= pOverlay
->bForced
? pts
: pts
- m_iSubtitleDelay
;
830 if((pOverlay
->iPTSStartTime
<= pts2
&& (pOverlay
->iPTSStopTime
> pts2
|| pOverlay
->iPTSStopTime
== 0LL)))
832 if(pOverlay
->IsOverlayType(DVDOVERLAY_TYPE_GROUP
))
833 overlays
.insert(overlays
.end(),
834 static_cast<CDVDOverlayGroup
&>(*pOverlay
).m_overlays
.begin(),
835 static_cast<CDVDOverlayGroup
&>(*pOverlay
).m_overlays
.end());
837 overlays
.push_back(pOverlay
);
841 for(it
= overlays
.begin(); it
!= overlays
.end(); ++it
)
843 double pts2
= (*it
)->bForced
? pts
: pts
- m_iSubtitleDelay
;
844 m_renderManager
.AddOverlay(*it
, pts2
);
849 CVideoPlayerVideo::EOutputState
CVideoPlayerVideo::OutputPicture(const VideoPicture
* pPicture
)
851 m_bAbortOutput
= false;
853 if (m_processInfo
.GetVideoStereoMode() != pPicture
->stereoMode
)
855 m_processInfo
.SetVideoStereoMode(pPicture
->stereoMode
);
856 // signal about changes in video parameters
857 m_messageParent
.Put(std::make_shared
<CDVDMsg
>(CDVDMsg::PLAYER_AVCHANGE
));
860 double config_framerate
= m_bFpsInvalid
? 0.0 : m_fFrameRate
;
861 if (m_processInfo
.GetVideoInterlaced())
863 if (MathUtils::FloatEquals(config_framerate
, 25.0, 0.02))
864 config_framerate
= 50.0;
865 else if (MathUtils::FloatEquals(config_framerate
, 29.97, 0.02))
866 config_framerate
= 59.94;
869 int sorient
= m_processInfo
.GetVideoSettings().m_Orientation
;
870 int orientation
= sorient
!= 0 ? (sorient
+ m_hints
.orientation
) % 360
871 : m_hints
.orientation
;
873 if (!m_renderManager
.Configure(*pPicture
,
874 static_cast<float>(config_framerate
),
876 m_pVideoCodec
->GetAllowedReferences()))
878 CLog::Log(LOGERROR
, "{} - failed to configure renderer", __FUNCTION__
);
882 //try to calculate the framerate
883 m_ptsTracker
.Add(pPicture
->pts
);
887 // signal to clock what our framerate is, it may want to adjust it's
888 // speed to better match with our video renderer's output speed
889 m_pClock
->UpdateFramerate(m_fFrameRate
);
891 // calculate the time we need to delay this picture before displaying
892 double iPlayingClock
, iCurrentClock
;
894 iPlayingClock
= m_pClock
->GetClock(iCurrentClock
, false); // snapshot current clock
901 double inputPts
= m_droppingStats
.m_lastPts
;
902 m_renderManager
.GetStats(lateframes
, renderPts
, queued
, discard
);
903 if (pPicture
->pts
> renderPts
|| queued
> 0)
905 if (inputPts
>= renderPts
)
907 m_rewindStalled
= true;
908 CThread::Sleep(50ms
);
910 return OUTPUT_DROPPED
;
912 else if (pPicture
->pts
< iPlayingClock
)
914 return OUTPUT_DROPPED
;
918 if ((pPicture
->iFlags
& DVP_FLAG_DROPPED
))
920 m_droppingStats
.AddOutputDropGain(pPicture
->pts
, 1);
921 CLog::Log(LOGDEBUG
, "{} - dropped in output", __FUNCTION__
);
922 return OUTPUT_DROPPED
;
925 auto timeToDisplay
= std::chrono::milliseconds(DVD_TIME_TO_MSEC(pPicture
->pts
- iPlayingClock
));
927 // make sure waiting time is not negative
928 std::chrono::milliseconds maxWaitTime
= std::min(std::max(timeToDisplay
+ 500ms
, 50ms
), 500ms
);
929 // don't wait when going ff
930 if (m_speed
> DVD_PLAYSPEED_NORMAL
)
931 maxWaitTime
= std::max(timeToDisplay
, 0ms
);
932 int buffer
= m_renderManager
.WaitForBuffer(m_bAbortOutput
, maxWaitTime
);
935 if (m_speed
!= DVD_PLAYSPEED_PAUSE
)
936 CLog::Log(LOGWARNING
, "{} - timeout waiting for buffer", __FUNCTION__
);
940 ProcessOverlays(pPicture
, pPicture
->pts
);
942 EINTERLACEMETHOD deintMethod
= EINTERLACEMETHOD::VS_INTERLACEMETHOD_NONE
;
943 deintMethod
= m_processInfo
.GetVideoSettings().m_InterlaceMethod
;
944 if (!m_processInfo
.Supports(deintMethod
))
945 deintMethod
= m_processInfo
.GetDeinterlacingMethodDefault();
947 if (!m_renderManager
.AddVideoPicture(*pPicture
, m_bAbortOutput
, deintMethod
, (m_syncState
== ESyncState::SYNC_STARTING
)))
949 m_droppingStats
.AddOutputDropGain(pPicture
->pts
, 1);
950 return OUTPUT_DROPPED
;
953 return OUTPUT_NORMAL
;
956 std::string
CVideoPlayerVideo::GetPlayerInfo()
958 std::ostringstream s
;
959 s
<< "vq:" << std::setw(2) << std::min(99, m_processInfo
.GetLevelVQ());
960 s
<< "% " << std::fixed
<< std::setprecision(3) << m_messageQueue
.GetTimeSize();
961 s
<< "s, Mb/s:" << std::fixed
<< std::setprecision(2)
962 << static_cast<double>(GetVideoBitrate()) / (1024.0 * 1024.0);
963 s
<< ", fr:" << std::fixed
<< std::setprecision(3) << m_fFrameRate
;
964 s
<< ", drop:" << m_iDroppedFrames
;
965 s
<< ", skip:" << m_renderManager
.GetSkippedFrames();
967 int pc
= m_ptsTracker
.GetPatternLength();
976 int CVideoPlayerVideo::GetVideoBitrate()
978 return (int)m_videoStats
.GetBitrate();
981 void CVideoPlayerVideo::ResetFrameRateCalc()
983 m_fStableFrameRate
= 0.0;
984 m_iFrameRateCount
= 0;
985 m_iFrameRateLength
= 1;
987 m_bAllowDrop
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoFpsDetect
== 0;
990 double CVideoPlayerVideo::GetCurrentPts()
997 m_renderManager
.GetStats(sleepTime
, renderPts
, queued
, discard
);
999 if (renderPts
== DVD_NOPTS_VALUE
)
1000 return DVD_NOPTS_VALUE
;
1002 return DVD_NOPTS_VALUE
;
1003 else if (m_speed
== DVD_PLAYSPEED_NORMAL
)
1011 #define MAXFRAMERATEDIFF 0.01
1012 #define MAXFRAMESERR 1000
1014 void CVideoPlayerVideo::CalcFrameRate()
1016 if (m_iFrameRateLength
>= 128 || CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoFpsDetect
== 0)
1017 return; //don't calculate the fps
1019 if (!m_ptsTracker
.HasFullBuffer())
1020 return; //we can only calculate the frameduration if m_pullupCorrection has a full buffer
1022 //see if m_pullupCorrection was able to detect a pattern in the timestamps
1023 //and is able to calculate the correct frame duration from it
1024 double frameduration
= m_ptsTracker
.GetFrameDuration();
1025 if (m_ptsTracker
.VFRDetection())
1026 frameduration
= m_ptsTracker
.GetMinFrameDuration();
1028 if ((frameduration
==DVD_NOPTS_VALUE
) ||
1029 ((CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoFpsDetect
== 1) && ((m_ptsTracker
.GetPatternLength() > 1) && !m_ptsTracker
.VFRDetection())))
1031 //reset the stored framerates if no good framerate was detected
1032 m_fStableFrameRate
= 0.0;
1033 m_iFrameRateCount
= 0;
1036 if (m_iFrameRateErr
== MAXFRAMESERR
&& m_iFrameRateLength
== 1)
1039 "{} counted {} frames without being able to calculate the framerate, giving up",
1040 __FUNCTION__
, m_iFrameRateErr
);
1041 m_bAllowDrop
= true;
1042 m_iFrameRateLength
= 128;
1047 double framerate
= DVD_TIME_BASE
/ frameduration
;
1049 //store the current calculated framerate if we don't have any yet
1050 if (m_iFrameRateCount
== 0)
1052 m_fStableFrameRate
= framerate
;
1053 m_iFrameRateCount
++;
1055 //check if the current detected framerate matches with the stored ones
1056 else if (fabs(m_fStableFrameRate
/ m_iFrameRateCount
- framerate
) <= MAXFRAMERATEDIFF
)
1058 m_fStableFrameRate
+= framerate
; //store the calculated framerate
1059 m_iFrameRateCount
++;
1061 //if we've measured m_iFrameRateLength seconds of framerates,
1062 if (m_iFrameRateCount
>= MathUtils::round_int(framerate
) * m_iFrameRateLength
)
1064 //store the calculated framerate if it differs too much from m_fFrameRate
1065 if (fabs(m_fFrameRate
- (m_fStableFrameRate
/ m_iFrameRateCount
)) > MAXFRAMERATEDIFF
|| m_bFpsInvalid
)
1067 CLog::Log(LOGDEBUG
, "{} framerate was:{:f} calculated:{:f}", __FUNCTION__
, m_fFrameRate
,
1068 m_fStableFrameRate
/ m_iFrameRateCount
);
1069 m_fFrameRate
= m_fStableFrameRate
/ m_iFrameRateCount
;
1070 m_bFpsInvalid
= false;
1071 m_processInfo
.SetVideoFps(static_cast<float>(m_fFrameRate
));
1074 //reset the stored framerates
1075 m_fStableFrameRate
= 0.0;
1076 m_iFrameRateCount
= 0;
1077 m_iFrameRateLength
*= 2; //double the length we should measure framerates
1079 //we're allowed to drop frames because we calculated a good framerate
1080 m_bAllowDrop
= true;
1083 else //the calculated framerate didn't match, reset the stored ones
1085 m_fStableFrameRate
= 0.0;
1086 m_iFrameRateCount
= 0;
1090 int CVideoPlayerVideo::CalcDropRequirement(double pts
)
1094 double iDecoderPts
, iRenderPts
;
1095 int iSkippedPicture
= -1;
1096 int iDroppedFrames
= -1;
1098 int queued
, discard
;
1100 m_droppingStats
.m_lastPts
= pts
;
1102 // get decoder stats
1103 if (!m_pVideoCodec
->GetCodecStats(iDecoderPts
, iDroppedFrames
, iSkippedPicture
))
1105 if (iDecoderPts
== DVD_NOPTS_VALUE
)
1109 m_renderManager
.GetStats(lateframes
, iRenderPts
, queued
, discard
);
1110 iBufferLevel
= queued
+ discard
;
1112 if (iBufferLevel
< 0)
1113 result
|= DROP_BUFFER_LEVEL
;
1114 else if (iBufferLevel
< 2)
1116 result
|= DROP_BUFFER_LEVEL
;
1117 CLog::Log(LOGDEBUG
, LOGVIDEO
, "CVideoPlayerVideo::CalcDropRequirement - hurry: {}",
1123 if (iSkippedPicture
> 0)
1125 CDroppingStats::CGain gain
;
1126 gain
.frames
= iSkippedPicture
;
1127 gain
.pts
= iDecoderPts
;
1128 m_droppingStats
.m_gain
.push_back(gain
);
1129 m_droppingStats
.m_totalGain
+= gain
.frames
;
1130 result
|= DROP_DROPPED
;
1131 CLog::Log(LOGDEBUG
, LOGVIDEO
,
1132 "CVideoPlayerVideo::CalcDropRequirement - dropped pictures, lateframes: {}, "
1133 "Bufferlevel: {}, dropped: {}",
1134 lateframes
, iBufferLevel
, iSkippedPicture
);
1136 if (iDroppedFrames
> 0)
1138 CDroppingStats::CGain gain
;
1139 gain
.frames
= iDroppedFrames
;
1140 gain
.pts
= iDecoderPts
;
1141 m_droppingStats
.m_gain
.push_back(gain
);
1142 m_droppingStats
.m_totalGain
+= iDroppedFrames
;
1143 result
|= DROP_DROPPED
;
1144 CLog::Log(LOGDEBUG
, LOGVIDEO
,
1145 "CVideoPlayerVideo::CalcDropRequirement - dropped in decoder, lateframes: {}, "
1146 "Bufferlevel: {}, dropped: {}",
1147 lateframes
, iBufferLevel
, iDroppedFrames
);
1152 while (!m_droppingStats
.m_gain
.empty() &&
1153 iRenderPts
>= m_droppingStats
.m_gain
.front().pts
)
1155 m_droppingStats
.m_totalGain
-= m_droppingStats
.m_gain
.front().frames
;
1156 m_droppingStats
.m_gain
.pop_front();
1159 // calculate lateness
1160 int lateness
= lateframes
- m_droppingStats
.m_totalGain
;
1162 if (lateness
> 0 && m_speed
)
1164 result
|= DROP_VERYLATE
;
1169 void CDroppingStats::Reset()
1175 void CDroppingStats::AddOutputDropGain(double pts
, int frames
)
1177 CDroppingStats::CGain gain
;
1178 gain
.frames
= frames
;
1180 m_gain
.push_back(gain
);
1181 m_totalGain
+= frames
;