Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1630 lines (1395 sloc) 51.07 kB
/*
* Copyright (C) 2005-2012 Team XBMC
* http://www.xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBMC; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*
*/
#include "system.h"
#include "cores/VideoRenderers/RenderFlags.h"
#include "windowing/WindowingFactory.h"
#include "settings/AdvancedSettings.h"
#include "settings/GUISettings.h"
#include "settings/Settings.h"
#include "video/VideoReferenceClock.h"
#include "utils/MathUtils.h"
#include "DVDPlayer.h"
#include "DVDPlayerVideo.h"
#include "DVDCodecs/DVDFactoryCodec.h"
#include "DVDCodecs/DVDCodecUtils.h"
#include "DVDCodecs/Video/DVDVideoPPFFmpeg.h"
#include "DVDCodecs/Video/DVDVideoCodecFFmpeg.h"
#include "DVDDemuxers/DVDDemux.h"
#include "DVDDemuxers/DVDDemuxUtils.h"
#include "DVDOverlayRenderer.h"
#include "DVDPerformanceCounter.h"
#include "DVDCodecs/DVDCodecs.h"
#include "DVDCodecs/Overlay/DVDOverlayCodecCC.h"
#include "DVDCodecs/Overlay/DVDOverlaySSA.h"
#include <sstream>
#include <iomanip>
#include <numeric>
#include <iterator>
#include "utils/log.h"
using namespace std;
class CPulldownCorrection
{
public:
CPulldownCorrection()
{
m_duration = 0.0;
m_accum = 0;
m_total = 0;
m_next = m_pattern.end();
}
void init(double fps, int *begin, int *end)
{
std::copy(begin, end, std::back_inserter(m_pattern));
m_duration = DVD_TIME_BASE / fps;
m_accum = 0;
m_total = std::accumulate(m_pattern.begin(), m_pattern.end(), 0);
m_next = m_pattern.begin();
}
double pts()
{
double input = m_duration * std::distance(m_pattern.begin(), m_next);
double output = m_duration * m_accum / m_total;
return output - input;
}
double dur()
{
return m_duration * m_pattern.size() * *m_next / m_total;
}
void next()
{
m_accum += *m_next;
if(++m_next == m_pattern.end())
{
m_next = m_pattern.begin();
m_accum = 0;
}
}
bool enabled()
{
return m_pattern.size() > 0;
}
private:
double m_duration;
int m_total;
int m_accum;
std::vector<int> m_pattern;
std::vector<int>::iterator m_next;
};
class CDVDMsgVideoCodecChange : public CDVDMsg
{
public:
CDVDMsgVideoCodecChange(const CDVDStreamInfo &hints, CDVDVideoCodec* codec)
: CDVDMsg(GENERAL_STREAMCHANGE)
, m_codec(codec)
, m_hints(hints)
{}
~CDVDMsgVideoCodecChange()
{
delete m_codec;
}
CDVDVideoCodec* m_codec;
CDVDStreamInfo m_hints;
};
CDVDPlayerVideo::CDVDPlayerVideo( CDVDClock* pClock
, CDVDOverlayContainer* pOverlayContainer
, CDVDMessageQueue& parent)
: CThread("CDVDPlayerVideo")
, m_messageQueue("video")
, m_messageParent(parent)
{
m_pClock = pClock;
m_pOverlayContainer = pOverlayContainer;
m_pTempOverlayPicture = NULL;
m_pVideoCodec = NULL;
m_pOverlayCodecCC = NULL;
m_speed = DVD_PLAYSPEED_NORMAL;
m_bRenderSubs = false;
m_stalled = false;
m_started = false;
m_iVideoDelay = 0;
m_iSubtitleDelay = 0;
m_FlipTimeStamp = 0.0;
m_iLateFrames = 0;
m_iDroppedRequest = 0;
m_fForcedAspectRatio = 0;
m_iNrOfPicturesNotToSkip = 0;
m_messageQueue.SetMaxDataSize(40 * 1024 * 1024);
m_messageQueue.SetMaxTimeSize(12.0);
g_dvdPerformanceCounter.EnableVideoQueue(&m_messageQueue);
m_iCurrentPts = DVD_NOPTS_VALUE;
m_iDroppedFrames = 0;
m_fFrameRate = 25;
m_bCalcFrameRate = false;
m_fStableFrameRate = 0.0;
m_iFrameRateCount = 0;
m_bAllowDrop = false;
m_iFrameRateErr = 0;
m_iFrameRateLength = 0;
m_bFpsInvalid = false;
m_bAllowFullscreen = false;
m_droptime = 0.0;
m_dropbase = 0.0;
m_autosync = 1;
memset(&m_output, 0, sizeof(m_output));
}
CDVDPlayerVideo::~CDVDPlayerVideo()
{
StopThread();
g_dvdPerformanceCounter.DisableVideoQueue();
g_VideoReferenceClock.StopThread();
}
double CDVDPlayerVideo::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 CDVDPlayerVideo::OpenStream( CDVDStreamInfo &hint )
{
unsigned int surfaces = 0;
std::vector<ERenderFormat> formats;
#ifdef HAS_VIDEO_PLAYBACK
surfaces = g_renderManager.GetProcessorSize();
formats = g_renderManager.SupportedFormats();
#endif
CLog::Log(LOGNOTICE, "Creating video codec with codec id: %i", hint.codec);
CDVDVideoCodec* codec = CDVDFactoryCodec::CreateVideoCodec(hint, surfaces, formats);
if(!codec)
{
CLog::Log(LOGERROR, "Unsupported video codec");
return false;
}
if(g_guiSettings.GetBool("videoplayer.usedisplayasclock") && !g_VideoReferenceClock.IsRunning())
{
g_VideoReferenceClock.Create();
//we have to wait for the clock to start otherwise alsa can cause trouble
if (!g_VideoReferenceClock.WaitStarted(2000))
CLog::Log(LOGDEBUG, "g_VideoReferenceClock didn't start in time");
}
if(m_messageQueue.IsInited())
m_messageQueue.Put(new CDVDMsgVideoCodecChange(hint, codec), 0);
else
{
OpenStream(hint, codec);
CLog::Log(LOGNOTICE, "Creating video thread");
m_messageQueue.Init();
Create();
}
return true;
}
void CDVDPlayerVideo::OpenStream(CDVDStreamInfo &hint, CDVDVideoCodec* codec)
{
//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);
else
m_fFrameRate = 25;
m_bFpsInvalid = (hint.fpsrate == 0 || hint.fpsscale == 0);
m_bCalcFrameRate = g_guiSettings.GetBool("videoplayer.usedisplayasclock") ||
g_guiSettings.GetInt("videoplayer.adjustrefreshrate") != ADJUST_REFRESHRATE_OFF;
ResetFrameRateCalc();
m_iDroppedRequest = 0;
m_iLateFrames = 0;
m_autosync = 1;
if( m_fFrameRate > 100 || m_fFrameRate < 5 )
{
CLog::Log(LOGERROR, "CDVDPlayerVideo::OpenStream - Invalid framerate %d, 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 = hint.aspect;
else
m_fForcedAspectRatio = 0.0;
if (m_pVideoCodec)
delete m_pVideoCodec;
m_pVideoCodec = codec;
m_hints = hint;
m_stalled = m_messageQueue.GetPacketCount(CDVDMsg::DEMUXER_PACKET) == 0;
m_started = false;
m_codecname = m_pVideoCodec->GetName();
}
void CDVDPlayerVideo::CloseStream(bool bWaitForBuffers)
{
// wait until buffers are empty
if (bWaitForBuffers && m_speed > 0) m_messageQueue.WaitUntilEmpty();
m_messageQueue.Abort();
// wait for decode_video thread to end
CLog::Log(LOGNOTICE, "waiting for video thread to exit");
StopThread(); // will set this->m_bStop to true
m_messageQueue.End();
CLog::Log(LOGNOTICE, "deleting video codec");
if (m_pVideoCodec)
{
m_pVideoCodec->Dispose();
delete m_pVideoCodec;
m_pVideoCodec = NULL;
}
if (m_pTempOverlayPicture)
{
CDVDCodecUtils::FreePicture(m_pTempOverlayPicture);
m_pTempOverlayPicture = NULL;
}
//tell the clock we stopped playing video
m_pClock->UpdateFramerate(0.0);
}
void CDVDPlayerVideo::OnStartup()
{
m_iDroppedFrames = 0;
m_crop.x1 = m_crop.x2 = 0.0f;
m_crop.y1 = m_crop.y2 = 0.0f;
m_iCurrentPts = DVD_NOPTS_VALUE;
m_FlipTimeStamp = m_pClock->GetAbsoluteClock();
g_dvdPerformanceCounter.EnableVideoDecodePerformance(this);
}
void CDVDPlayerVideo::Process()
{
CLog::Log(LOGNOTICE, "running thread: video_thread");
DVDVideoPicture picture;
CPulldownCorrection pulldown;
CDVDVideoPPFFmpeg mPostProcess("");
CStdString sPostProcessType;
bool bPostProcessDeint = false;
memset(&picture, 0, sizeof(DVDVideoPicture));
double pts = 0;
double frametime = (double)DVD_TIME_BASE / m_fFrameRate;
int iDropped = 0; //frames dropped in a row
bool bRequestDrop = false;
m_videoStats.Start();
while (!m_bStop)
{
int iQueueTimeOut = (int)(m_stalled ? frametime / 4 : frametime * 10) / 1000;
int iPriority = (m_speed == DVD_PLAYSPEED_PAUSE && m_started) ? 1 : 0;
CDVDMsg* pMsg;
MsgQueueReturnCode ret = m_messageQueue.Get(&pMsg, iQueueTimeOut, iPriority);
if (MSGQ_IS_ERROR(ret) || ret == MSGQ_ABORT)
{
CLog::Log(LOGERROR, "Got MSGQ_ABORT or MSGO_IS_ERROR return true");
break;
}
else if (ret == MSGQ_TIMEOUT)
{
// 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 )
{
if(m_started)
CLog::Log(LOGINFO, "CDVDPlayerVideo - Stillframe detected, switching to forced %f fps", m_fFrameRate);
m_stalled = true;
pts+= frametime*4;
}
//Waiting timed out, output last picture
if( picture.iFlags & DVP_FLAG_ALLOCATED )
{
//Remove interlaced flag before outputting
//no need to output this as if it was interlaced
picture.iFlags &= ~DVP_FLAG_INTERLACED;
picture.iFlags |= DVP_FLAG_NOSKIP;
OutputPicture(&picture, pts);
pts+= frametime;
}
continue;
}
if (pMsg->IsType(CDVDMsg::GENERAL_SYNCHRONIZE))
{
if(((CDVDMsgGeneralSynchronize*)pMsg)->Wait(100, SYNCSOURCE_VIDEO))
{
CLog::Log(LOGDEBUG, "CDVDPlayerVideo - CDVDMsg::GENERAL_SYNCHRONIZE");
/* we may be very much off correct pts here, but next picture may be a still*/
/* make sure it isn't dropped */
m_iNrOfPicturesNotToSkip = 5;
}
else
m_messageQueue.Put(pMsg->Acquire(), 1); /* push back as prio message, to process other prio messages */
pMsg->Release();
continue;
}
else if (pMsg->IsType(CDVDMsg::GENERAL_RESYNC))
{
CDVDMsgGeneralResync* pMsgGeneralResync = (CDVDMsgGeneralResync*)pMsg;
if(pMsgGeneralResync->m_timestamp != DVD_NOPTS_VALUE)
pts = pMsgGeneralResync->m_timestamp;
double delay = m_FlipTimeStamp - m_pClock->GetAbsoluteClock();
if( delay > frametime ) delay = frametime;
else if( delay < 0 ) delay = 0;
if(pMsgGeneralResync->m_clock)
{
CLog::Log(LOGDEBUG, "CDVDPlayerVideo - CDVDMsg::GENERAL_RESYNC(%f, 1)", pts);
m_pClock->Discontinuity(pts - delay);
}
else
CLog::Log(LOGDEBUG, "CDVDPlayerVideo - CDVDMsg::GENERAL_RESYNC(%f, 0)", pts);
pMsgGeneralResync->Release();
continue;
}
else if (pMsg->IsType(CDVDMsg::GENERAL_DELAY))
{
if (m_speed != DVD_PLAYSPEED_PAUSE)
{
double timeout = static_cast<CDVDMsgDouble*>(pMsg)->m_value;
CLog::Log(LOGDEBUG, "CDVDPlayerVideo - CDVDMsg::GENERAL_DELAY(%f)", timeout);
timeout *= (double)DVD_PLAYSPEED_NORMAL / abs(m_speed);
timeout += CDVDClock::GetAbsoluteClock();
while(!m_bStop && CDVDClock::GetAbsoluteClock() < timeout)
Sleep(1);
}
}
else if (pMsg->IsType(CDVDMsg::VIDEO_SET_ASPECT))
{
CLog::Log(LOGDEBUG, "CDVDPlayerVideo - CDVDMsg::VIDEO_SET_ASPECT");
m_fForcedAspectRatio = *((CDVDMsgDouble*)pMsg);
}
else if (pMsg->IsType(CDVDMsg::GENERAL_RESET))
{
if(m_pVideoCodec)
m_pVideoCodec->Reset();
picture.iFlags &= ~DVP_FLAG_ALLOCATED;
m_packets.clear();
m_started = false;
}
else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH)) // private message sent by (CDVDPlayerVideo::Flush())
{
if(m_pVideoCodec)
m_pVideoCodec->Reset();
picture.iFlags &= ~DVP_FLAG_ALLOCATED;
m_packets.clear();
m_pullupCorrection.Flush();
//we need to recalculate the framerate
//TODO: this needs to be set on a streamchange instead
ResetFrameRateCalc();
m_stalled = true;
m_started = false;
}
else if (pMsg->IsType(CDVDMsg::VIDEO_NOSKIP))
{
// libmpeg2 is also returning incomplete frames after a dvd cell change
// so the first few pictures are not the correct ones to display in some cases
// just display those together with the correct one.
// (setting it to 2 will skip some menu stills, 5 is working ok for me).
m_iNrOfPicturesNotToSkip = 5;
}
else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED))
{
m_speed = static_cast<CDVDMsgInt*>(pMsg)->m_value;
if(m_speed == DVD_PLAYSPEED_PAUSE)
m_iNrOfPicturesNotToSkip = 0;
}
else if (pMsg->IsType(CDVDMsg::PLAYER_STARTED))
{
if(m_started)
m_messageParent.Put(new CDVDMsgInt(CDVDMsg::PLAYER_STARTED, DVDPLAYER_VIDEO));
}
else if (pMsg->IsType(CDVDMsg::GENERAL_STREAMCHANGE))
{
CDVDMsgVideoCodecChange* msg(static_cast<CDVDMsgVideoCodecChange*>(pMsg));
OpenStream(msg->m_hints, msg->m_codec);
msg->m_codec = NULL;
picture.iFlags &= ~DVP_FLAG_ALLOCATED;
}
if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET))
{
DemuxPacket* pPacket = ((CDVDMsgDemuxerPacket*)pMsg)->GetPacket();
bool bPacketDrop = ((CDVDMsgDemuxerPacket*)pMsg)->GetPacketDrop();
if (m_stalled)
{
CLog::Log(LOGINFO, "CDVDPlayerVideo - Stillframe left, switching to normal playback");
m_stalled = false;
//don't allow the first frames after a still to be dropped
//sometimes we get multiple stills (long duration frames) after each other
//in normal mpegs
m_iNrOfPicturesNotToSkip = 5;
}
else if( iDropped*frametime > DVD_MSEC_TO_TIME(100) && m_iNrOfPicturesNotToSkip == 0 )
{ // if we dropped too many pictures in a row, insert a forced picture
m_iNrOfPicturesNotToSkip = 1;
}
#ifdef PROFILE
bRequestDrop = false;
#else
if (m_messageQueue.GetDataSize() == 0
|| m_speed < 0)
{
bRequestDrop = false;
m_iDroppedRequest = 0;
m_iLateFrames = 0;
}
#endif
// if player want's us to drop this packet, do so nomatter what
if(bPacketDrop)
bRequestDrop = true;
// tell codec if next frame should be dropped
// problem here, if one packet contains more than one frame
// both frames will be dropped in that case instead of just the first
// decoder still needs to provide an empty image structure, with correct flags
m_pVideoCodec->SetDropState(bRequestDrop);
// ask codec to do deinterlacing if possible
EDEINTERLACEMODE mDeintMode = g_settings.m_currentVideoSettings.m_DeinterlaceMode;
EINTERLACEMETHOD mInt = g_renderManager.AutoInterlaceMethod(g_settings.m_currentVideoSettings.m_InterlaceMethod);
unsigned int mFilters = 0;
if (mDeintMode != VS_DEINTERLACEMODE_OFF)
{
if (mInt == VS_INTERLACEMETHOD_DEINTERLACE)
mFilters = CDVDVideoCodec::FILTER_DEINTERLACE_ANY;
else if(mInt == VS_INTERLACEMETHOD_DEINTERLACE_HALF)
mFilters = CDVDVideoCodec::FILTER_DEINTERLACE_ANY | CDVDVideoCodec::FILTER_DEINTERLACE_HALFED;
if (mDeintMode == VS_DEINTERLACEMODE_AUTO && mFilters)
mFilters |= CDVDVideoCodec::FILTER_DEINTERLACE_FLAGGED;
}
if (!g_renderManager.Supports(RENDERFEATURE_ROTATION))
mFilters |= CDVDVideoCodec::FILTER_ROTATE;
mFilters = m_pVideoCodec->SetFilters(mFilters);
int iDecoderState = m_pVideoCodec->Decode(pPacket->pData, pPacket->iSize, pPacket->dts, pPacket->pts);
// buffer packets so we can recover should decoder flush for some reason
if(m_pVideoCodec->GetConvergeCount() > 0)
{
m_packets.push_back(DVDMessageListItem(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);
// assume decoder dropped a picture if it didn't give us any
// picture from a demux packet, this should be reasonable
// for libavformat as a demuxer as it normally packetizes
// pictures when they come from demuxer
if(bRequestDrop && !bPacketDrop && (iDecoderState & VC_BUFFER) && !(iDecoderState & VC_PICTURE))
{
m_iDroppedFrames++;
iDropped++;
}
// loop while no error
while (!m_bStop)
{
// if decoder was flushed, we need to seek back again to resume rendering
if (iDecoderState & VC_FLUSHED)
{
CLog::Log(LOGDEBUG, "CDVDPlayerVideo - video decoder was flushed");
while(!m_packets.empty())
{
CDVDMsgDemuxerPacket* msg = (CDVDMsgDemuxerPacket*)m_packets.front().message->Acquire();
m_packets.pop_front();
// all packets except the last one should be dropped
// if prio packets and current packet should be dropped, this is likely a new reset
msg->m_drop = !m_packets.empty() || (iPriority > 0 && bPacketDrop);
m_messageQueue.Put(msg, iPriority + 10);
}
m_pVideoCodec->Reset();
m_packets.clear();
break;
}
// if decoder had an error, tell it to reset to avoid more problems
if (iDecoderState & VC_ERROR)
{
CLog::Log(LOGDEBUG, "CDVDPlayerVideo - video decoder returned error");
break;
}
// check for a new picture
if (iDecoderState & VC_PICTURE)
{
// try to retrieve the picture (should never fail!), unless there is a demuxer bug ofcours
m_pVideoCodec->ClearPicture(&picture);
if (m_pVideoCodec->GetPicture(&picture))
{
sPostProcessType.clear();
picture.iGroupId = pPacket->iGroupId;
if(picture.iDuration == 0.0)
picture.iDuration = frametime;
if(bPacketDrop)
picture.iFlags |= DVP_FLAG_DROPPED;
if (m_iNrOfPicturesNotToSkip > 0)
{
picture.iFlags |= DVP_FLAG_NOSKIP;
m_iNrOfPicturesNotToSkip--;
}
// validate picture timing,
// if both dts/pts invalid, use pts calulated from picture.iDuration
// if pts invalid use dts, else use picture.pts as passed
if (picture.dts == DVD_NOPTS_VALUE && picture.pts == DVD_NOPTS_VALUE)
picture.pts = pts;
else if (picture.pts == DVD_NOPTS_VALUE)
picture.pts = picture.dts;
/* use forced aspect if any */
if( m_fForcedAspectRatio != 0.0f )
picture.iDisplayWidth = (int) (picture.iDisplayHeight * m_fForcedAspectRatio);
//Deinterlace if codec said format was interlaced or if we have selected we want to deinterlace
//this video
if ((mDeintMode == VS_DEINTERLACEMODE_AUTO && (picture.iFlags & DVP_FLAG_INTERLACED)) || mDeintMode == VS_DEINTERLACEMODE_FORCE)
{
if(mInt == VS_INTERLACEMETHOD_SW_BLEND)
{
if (!sPostProcessType.empty())
sPostProcessType += ",";
sPostProcessType += g_advancedSettings.m_videoPPFFmpegDeint;
bPostProcessDeint = true;
}
}
if (g_settings.m_currentVideoSettings.m_PostProcess)
{
if (!sPostProcessType.empty())
sPostProcessType += ",";
// This is what mplayer uses for its "high-quality filter combination"
sPostProcessType += g_advancedSettings.m_videoPPFFmpegPostProc;
}
if (!sPostProcessType.empty())
{
mPostProcess.SetType(sPostProcessType, bPostProcessDeint);
if (mPostProcess.Process(&picture))
mPostProcess.GetPicture(&picture);
}
/* if frame has a pts (usually originiating from demux packet), use that */
if(picture.pts != DVD_NOPTS_VALUE)
{
if(pulldown.enabled())
picture.pts += pulldown.pts();
pts = picture.pts;
}
if(pulldown.enabled())
{
picture.iDuration = pulldown.dur();
pulldown.next();
}
if (picture.iRepeatPicture)
picture.iDuration *= picture.iRepeatPicture + 1;
#if 1
int iResult = OutputPicture(&picture, pts);
#elif 0
// testing NV12 rendering functions
DVDVideoPicture* pTempNV12Picture = CDVDCodecUtils::ConvertToNV12Picture(&picture);
int iResult = OutputPicture(pTempNV12Picture, pts);
CDVDCodecUtils::FreePicture(pTempNV12Picture);
#elif 0
// testing YUY2 or UYVY rendering functions
// WARNING: since this scales a full YV12 frame, weaving artifacts will show on interlaced content
// even with the deinterlacer on
DVDVideoPicture* pTempYUVPackedPicture = CDVDCodecUtils::ConvertToYUV422PackedPicture(&picture, RENDER_FMT_UYVY422);
//DVDVideoPicture* pTempYUVPackedPicture = CDVDCodecUtils::ConvertToYUV422PackedPicture(&picture, RENDER_FMT_YUYV422);
int iResult = OutputPicture(pTempYUVPackedPicture, pts);
CDVDCodecUtils::FreePicture(pTempYUVPackedPicture);
#endif
if(m_started == false)
{
m_codecname = m_pVideoCodec->GetName();
m_started = true;
m_messageParent.Put(new CDVDMsgInt(CDVDMsg::PLAYER_STARTED, DVDPLAYER_VIDEO));
}
// guess next frame pts. iDuration is always valid
if (m_speed != 0)
pts += picture.iDuration * m_speed / abs(m_speed);
if( iResult & EOS_ABORT )
{
//if we break here and we directly try to decode again wihout
//flushing the video codec things break for some reason
//i think the decoder (libmpeg2 atleast) still has a pointer
//to the data, and when the packet is freed that will fail.
iDecoderState = m_pVideoCodec->Decode(NULL, 0, DVD_NOPTS_VALUE, DVD_NOPTS_VALUE);
break;
}
if( (iResult & EOS_DROPPED) && !bPacketDrop )
{
m_iDroppedFrames++;
iDropped++;
}
else
iDropped = 0;
bRequestDrop = (iResult & EOS_VERYLATE) == EOS_VERYLATE;
}
else
{
CLog::Log(LOGWARNING, "Decoder Error getting videoPicture.");
m_pVideoCodec->Reset();
}
}
/*
if (iDecoderState & VC_USERDATA)
{
// found some userdata while decoding a frame
// could be closed captioning
DVDVideoUserData videoUserData;
if (m_pVideoCodec->GetUserData(&videoUserData))
{
ProcessVideoUserData(&videoUserData, pts);
}
}
*/
// if the decoder needs more data, we just break this loop
// and try to get more data from the videoQueue
if (iDecoderState & VC_BUFFER)
break;
// the decoder didn't need more data, flush the remaning buffer
iDecoderState = m_pVideoCodec->Decode(NULL, 0, DVD_NOPTS_VALUE, DVD_NOPTS_VALUE);
}
}
// all data is used by the decoder, we can safely free it now
pMsg->Release();
}
// we need to let decoder release any picture retained resources.
m_pVideoCodec->ClearPicture(&picture);
}
void CDVDPlayerVideo::OnExit()
{
g_dvdPerformanceCounter.DisableVideoDecodePerformance();
if (m_pOverlayCodecCC)
{
m_pOverlayCodecCC->Dispose();
m_pOverlayCodecCC = NULL;
}
CLog::Log(LOGNOTICE, "thread end: video_thread");
}
void CDVDPlayerVideo::ProcessVideoUserData(DVDVideoUserData* pVideoUserData, double pts)
{
// check userdata type
BYTE* data = pVideoUserData->data;
int size = pVideoUserData->size;
if (size >= 2)
{
if (data[0] == 'C' && data[1] == 'C')
{
data += 2;
size -= 2;
// closed captioning
if (!m_pOverlayCodecCC)
{
m_pOverlayCodecCC = new CDVDOverlayCodecCC();
CDVDCodecOptions options;
CDVDStreamInfo info;
if (!m_pOverlayCodecCC->Open(info, options))
{
delete m_pOverlayCodecCC;
m_pOverlayCodecCC = NULL;
}
}
if (m_pOverlayCodecCC)
{
m_pOverlayCodecCC->Decode(data, size, DVD_NOPTS_VALUE, DVD_NOPTS_VALUE);
CDVDOverlay* overlay;
while((overlay = m_pOverlayCodecCC->GetOverlay()) != NULL)
{
overlay->iGroupId = 0;
overlay->iPTSStartTime += pts;
if(overlay->iPTSStopTime != 0.0)
overlay->iPTSStopTime += pts;
m_pOverlayContainer->Add(overlay);
overlay->Release();
}
}
}
}
}
bool CDVDPlayerVideo::InitializedOutputDevice()
{
#ifdef HAS_VIDEO_PLAYBACK
return g_renderManager.IsStarted();
#else
return false;
#endif
}
void CDVDPlayerVideo::SetSpeed(int speed)
{
if(m_messageQueue.IsInited())
m_messageQueue.Put( new CDVDMsgInt(CDVDMsg::PLAYER_SETSPEED, speed), 1 );
else
m_speed = speed;
}
void CDVDPlayerVideo::StepFrame()
{
m_iNrOfPicturesNotToSkip++;
}
void CDVDPlayerVideo::Flush()
{
/* flush using message as this get's called from dvdplayer thread */
/* and any demux packet that has been taken out of queue need to */
/* be disposed of before we flush */
m_messageQueue.Flush();
m_messageQueue.Put(new CDVDMsg(CDVDMsg::GENERAL_FLUSH), 1);
}
int CDVDPlayerVideo::GetLevel()
{
int level = m_messageQueue.GetLevel();
// fast exit, if the message queue is full, we do not care about the codec queue.
if (level == 100)
return level;
// Now for the harder choices, the message queue could be time or size based.
// In order to return the proper summed level, we need to know which.
if (m_messageQueue.IsDataBased())
{
int datasize = m_messageQueue.GetDataSize();
if (m_pVideoCodec)
datasize += m_pVideoCodec->GetDataSize();
return min(100, (int)(100 * datasize / (m_messageQueue.GetMaxDataSize() * m_messageQueue.GetMaxTimeSize())));
}
else
{
double timesize = m_messageQueue.GetTimeSize();
if (m_pVideoCodec)
timesize += m_pVideoCodec->GetTimeSize();
return min(100, MathUtils::round_int(100.0 * m_messageQueue.GetMaxTimeSize() * timesize));
}
return level;
}
#ifdef HAS_VIDEO_PLAYBACK
void CDVDPlayerVideo::ProcessOverlays(DVDVideoPicture* pSource, double pts)
{
// remove any overlays that are out of time
if (m_started)
m_pOverlayContainer->CleanUp(pts - m_iSubtitleDelay);
enum EOverlay
{ OVERLAY_AUTO // select mode auto
, OVERLAY_GPU // render osd using gpu
, OVERLAY_BUF // render osd on buffer
} render = OVERLAY_AUTO;
if(pSource->format == RENDER_FMT_YUV420P)
{
if(g_Windowing.GetRenderQuirks() & RENDER_QUIRKS_MAJORMEMLEAK_OVERLAYRENDERER)
{
// for now use cpu for ssa overlays as it currently allocates and
// frees textures for each frame this causes a hugh memory leak
// on some mesa intel drivers
if(m_pOverlayContainer->ContainsOverlayType(DVDOVERLAY_TYPE_SPU)
|| m_pOverlayContainer->ContainsOverlayType(DVDOVERLAY_TYPE_IMAGE)
|| m_pOverlayContainer->ContainsOverlayType(DVDOVERLAY_TYPE_SSA) )
render = OVERLAY_BUF;
}
if(render == OVERLAY_BUF)
{
// rendering spu overlay types directly on video memory costs a lot of processing power.
// thus we allocate a temp picture, copy the original to it (needed because the same picture can be used more than once).
// then do all the rendering on that temp picture and finaly copy it to video memory.
// In almost all cases this is 5 or more times faster!.
if(m_pTempOverlayPicture && ( m_pTempOverlayPicture->iWidth != pSource->iWidth
|| m_pTempOverlayPicture->iHeight != pSource->iHeight))
{
CDVDCodecUtils::FreePicture(m_pTempOverlayPicture);
m_pTempOverlayPicture = NULL;
}
if(!m_pTempOverlayPicture)
m_pTempOverlayPicture = CDVDCodecUtils::AllocatePicture(pSource->iWidth, pSource->iHeight);
if(!m_pTempOverlayPicture)
return;
CDVDCodecUtils::CopyPicture(m_pTempOverlayPicture, pSource);
memcpy(pSource->data , m_pTempOverlayPicture->data , sizeof(pSource->data));
memcpy(pSource->iLineSize, m_pTempOverlayPicture->iLineSize, sizeof(pSource->iLineSize));
}
}
if(render == OVERLAY_AUTO)
render = OVERLAY_GPU;
VecOverlays overlays;
{
CSingleLock 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 timeing, pts == 0 in the stillframe case
while (it != pVecOverlays->end())
{
CDVDOverlay* pOverlay = *it++;
if(!pOverlay->bForced && !m_bRenderSubs)
continue;
if(pOverlay->iGroupId != pSource->iGroupId)
continue;
double pts2 = pOverlay->bForced ? pts : pts - m_iSubtitleDelay;
if((pOverlay->iPTSStartTime <= pts2 && (pOverlay->iPTSStopTime > pts2 || pOverlay->iPTSStopTime == 0LL)) || pts == 0)
{
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;
if (render == OVERLAY_GPU)
g_renderManager.AddOverlay(*it, pts2);
if (render == OVERLAY_BUF)
CDVDOverlayRenderer::Render(pSource, *it, pts2);
}
}
}
#endif
int CDVDPlayerVideo::OutputPicture(const DVDVideoPicture* src, double pts)
{
/* picture buffer is not allowed to be modified in this call */
DVDVideoPicture picture(*src);
DVDVideoPicture* pPicture = &picture;
#ifdef HAS_VIDEO_PLAYBACK
double config_framerate = m_bFpsInvalid ? 0.0 : m_fFrameRate;
/* check so that our format or aspect has changed. if it has, reconfigure renderer */
if (!g_renderManager.IsConfigured()
|| m_output.width != pPicture->iWidth
|| m_output.height != pPicture->iHeight
|| m_output.dwidth != pPicture->iDisplayWidth
|| m_output.dheight != pPicture->iDisplayHeight
|| m_output.framerate != config_framerate
|| m_output.color_format != (unsigned int)pPicture->format
|| m_output.extended_format != pPicture->extended_format
|| ( m_output.color_matrix != pPicture->color_matrix && pPicture->color_matrix != 0 ) // don't reconfigure on unspecified
|| ( m_output.chroma_position != pPicture->chroma_position && pPicture->chroma_position != 0 )
|| ( m_output.color_primaries != pPicture->color_primaries && pPicture->color_primaries != 0 )
|| ( m_output.color_transfer != pPicture->color_transfer && pPicture->color_transfer != 0 )
|| m_output.color_range != pPicture->color_range)
{
CLog::Log(LOGNOTICE, " fps: %f, pwidth: %i, pheight: %i, dwidth: %i, dheight: %i",
config_framerate, pPicture->iWidth, pPicture->iHeight, pPicture->iDisplayWidth, pPicture->iDisplayHeight);
unsigned flags = 0;
if(pPicture->color_range == 1)
flags |= CONF_FLAGS_YUV_FULLRANGE;
switch(pPicture->color_matrix)
{
case 7: // SMPTE 240M (1987)
flags |= CONF_FLAGS_YUVCOEF_240M;
break;
case 6: // SMPTE 170M
case 5: // ITU-R BT.470-2
case 4: // FCC
flags |= CONF_FLAGS_YUVCOEF_BT601;
break;
case 1: // ITU-R Rec.709 (1990) -- BT.709
flags |= CONF_FLAGS_YUVCOEF_BT709;
break;
case 3: // RESERVED
case 2: // UNSPECIFIED
default:
if(pPicture->iWidth > 1024 || pPicture->iHeight >= 600)
flags |= CONF_FLAGS_YUVCOEF_BT709;
else
flags |= CONF_FLAGS_YUVCOEF_BT601;
break;
}
switch(pPicture->chroma_position)
{
case 1:
flags |= CONF_FLAGS_CHROMA_LEFT;
break;
case 2:
flags |= CONF_FLAGS_CHROMA_CENTER;
break;
case 3:
flags |= CONF_FLAGS_CHROMA_TOPLEFT;
break;
}
switch(pPicture->color_primaries)
{
case 1:
flags |= CONF_FLAGS_COLPRI_BT709;
break;
case 4:
flags |= CONF_FLAGS_COLPRI_BT470M;
break;
case 5:
flags |= CONF_FLAGS_COLPRI_BT470BG;
break;
case 6:
flags |= CONF_FLAGS_COLPRI_170M;
break;
case 7:
flags |= CONF_FLAGS_COLPRI_240M;
break;
}
switch(pPicture->color_transfer)
{
case 1:
flags |= CONF_FLAGS_TRC_BT709;
break;
case 4:
flags |= CONF_FLAGS_TRC_GAMMA22;
break;
case 5:
flags |= CONF_FLAGS_TRC_GAMMA28;
break;
}
CStdString formatstr;
switch(pPicture->format)
{
case RENDER_FMT_YUV420P:
formatstr = "YV12";
break;
case RENDER_FMT_YUV420P16:
formatstr = "YV12P16";
break;
case RENDER_FMT_YUV420P10:
formatstr = "YV12P10";
break;
case RENDER_FMT_NV12:
formatstr = "NV12";
break;
case RENDER_FMT_UYVY422:
formatstr = "UYVY";
break;
case RENDER_FMT_YUYV422:
formatstr = "YUY2";
break;
case RENDER_FMT_VDPAU:
formatstr = "VDPAU";
break;
case RENDER_FMT_DXVA:
formatstr = "DXVA";
break;
case RENDER_FMT_VAAPI:
formatstr = "VAAPI";
break;
case RENDER_FMT_OMXEGL:
formatstr = "OMXEGL";
break;
case RENDER_FMT_CVBREF:
formatstr = "BGRA";
break;
case RENDER_FMT_BYPASS:
formatstr = "BYPASS";
break;
case RENDER_FMT_NONE:
formatstr = "NONE";
break;
}
if(m_bAllowFullscreen)
{
flags |= CONF_FLAGS_FULLSCREEN;
m_bAllowFullscreen = false; // only allow on first configure
}
CLog::Log(LOGDEBUG,"%s - change configuration. %dx%d. framerate: %4.2f. format: %s",__FUNCTION__,pPicture->iWidth, pPicture->iHeight, config_framerate, formatstr.c_str());
if(!g_renderManager.Configure(pPicture->iWidth, pPicture->iHeight, pPicture->iDisplayWidth, pPicture->iDisplayHeight, config_framerate, flags, pPicture->format, pPicture->extended_format, m_hints.orientation))
{
CLog::Log(LOGERROR, "%s - failed to configure renderer", __FUNCTION__);
return EOS_ABORT;
}
m_output.width = pPicture->iWidth;
m_output.height = pPicture->iHeight;
m_output.dwidth = pPicture->iDisplayWidth;
m_output.dheight = pPicture->iDisplayHeight;
m_output.framerate = config_framerate;
m_output.color_format = pPicture->format;
m_output.extended_format = pPicture->extended_format;
m_output.color_matrix = pPicture->color_matrix;
m_output.chroma_position = pPicture->chroma_position;
m_output.color_primaries = pPicture->color_primaries;
m_output.color_transfer = pPicture->color_transfer;
m_output.color_range = pPicture->color_range;
}
double maxfps = 60.0;
bool limited = false;
int result = 0;
if (!g_renderManager.IsStarted()) {
CLog::Log(LOGERROR, "%s - renderer not started", __FUNCTION__);
return EOS_ABORT;
}
maxfps = g_renderManager.GetMaximumFPS();
// check if our output will limit speed
if(m_fFrameRate * abs(m_speed) / DVD_PLAYSPEED_NORMAL > maxfps*0.9)
limited = true;
//correct any pattern in the timestamps
m_pullupCorrection.Add(pts);
pts += m_pullupCorrection.GetCorrection();
//try to calculate the framerate
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
double interval;
int refreshrate = m_pClock->UpdateFramerate(m_fFrameRate, &interval);
if (refreshrate > 0) //refreshrate of -1 means the videoreferenceclock is not running
{//when using the videoreferenceclock, a frame is always presented half a vblank interval too late
pts -= DVD_TIME_BASE * interval;
}
// Correct pts by user set delay and rendering delay
pts += m_iVideoDelay - DVD_SEC_TO_TIME(g_renderManager.GetDisplayLatency());
// calculate the time we need to delay this picture before displaying
double iSleepTime, iClockSleep, iFrameSleep, iPlayingClock, iCurrentClock, iFrameDuration;
iPlayingClock = m_pClock->GetClock(iCurrentClock, false); // snapshot current clock
iClockSleep = pts - iPlayingClock; //sleep calculated by pts to clock comparison
iFrameSleep = m_FlipTimeStamp - iCurrentClock; // sleep calculated by duration of frame
iFrameDuration = pPicture->iDuration;
// correct sleep times based on speed
if(m_speed)
{
iClockSleep = iClockSleep * DVD_PLAYSPEED_NORMAL / m_speed;
iFrameSleep = iFrameSleep * DVD_PLAYSPEED_NORMAL / abs(m_speed);
iFrameDuration = iFrameDuration * DVD_PLAYSPEED_NORMAL / abs(m_speed);
}
else
{
iClockSleep = 0;
iFrameSleep = 0;
}
// dropping to a very low framerate is not correct (it should not happen at all)
iClockSleep = min(iClockSleep, DVD_MSEC_TO_TIME(500));
iFrameSleep = min(iFrameSleep, DVD_MSEC_TO_TIME(500));
if( m_stalled )
iSleepTime = iFrameSleep;
else
iSleepTime = iFrameSleep + (iClockSleep - iFrameSleep) / m_autosync;
#ifdef PROFILE /* during profiling, try to play as fast as possible */
iSleepTime = 0;
#endif
// present the current pts of this frame to user, and include the actual
// presentation delay, to allow him to adjust for it
if( m_stalled )
m_iCurrentPts = DVD_NOPTS_VALUE;
else
m_iCurrentPts = pts - max(0.0, iSleepTime);
// timestamp when we think next picture should be displayed based on current duration
m_FlipTimeStamp = iCurrentClock;
m_FlipTimeStamp += max(0.0, iSleepTime);
m_FlipTimeStamp += iFrameDuration;
if (iSleepTime <= 0 && m_speed)
m_iLateFrames++;
else
m_iLateFrames = 0;
// ask decoder to drop frames next round, as we are very late
if(m_iLateFrames > 10)
{
if (!(pPicture->iFlags & DVP_FLAG_NOSKIP))
{
//if we're calculating the framerate,
//don't drop frames until we've calculated a stable framerate
if (m_bAllowDrop || m_speed != DVD_PLAYSPEED_NORMAL)
{
result |= EOS_VERYLATE;
m_pullupCorrection.Flush(); //dropped frames mess up the pattern, so just flush it
}
//if we requested 5 drops in a row and we're still late, drop on output
//this keeps a/v sync if the decoder can't drop, or we're still calculating the framerate
if (m_iDroppedRequest > 5)
{
m_iDroppedRequest--; //decrease so we only drop half the frames
return result | EOS_DROPPED;
}
m_iDroppedRequest++;
}
}
else
{
m_iDroppedRequest = 0;
}
if( m_speed < 0 )
{
if( iClockSleep < -DVD_MSEC_TO_TIME(200)
&& !(pPicture->iFlags & DVP_FLAG_NOSKIP) )
return result | EOS_DROPPED;
}
if( (pPicture->iFlags & DVP_FLAG_DROPPED) )
return result | EOS_DROPPED;
if( m_speed != DVD_PLAYSPEED_NORMAL && limited )
{
// calculate frame dropping pattern to render at this speed
// we do that by deciding if this or next frame is closest
// to the flip timestamp
double current = fabs(m_dropbase - m_droptime);
double next = fabs(m_dropbase - (m_droptime + iFrameDuration));
double frametime = (double)DVD_TIME_BASE / maxfps;
m_droptime += iFrameDuration;
#ifndef PROFILE
if( next < current && !(pPicture->iFlags & DVP_FLAG_NOSKIP) )
return result | EOS_DROPPED;
#endif
while(!m_bStop && m_dropbase < m_droptime) m_dropbase += frametime;
while(!m_bStop && m_dropbase - frametime > m_droptime) m_dropbase -= frametime;
m_pullupCorrection.Flush();
}
else
{
m_droptime = 0.0;
m_dropbase = 0.0;
}
// set fieldsync if picture is interlaced
EFIELDSYNC mDisplayField = FS_NONE;
if( pPicture->iFlags & DVP_FLAG_INTERLACED )
{
if( pPicture->iFlags & DVP_FLAG_TOP_FIELD_FIRST )
mDisplayField = FS_TOP;
else
mDisplayField = FS_BOT;
}
ProcessOverlays(pPicture, pts);
AutoCrop(pPicture);
int index = g_renderManager.AddVideoPicture(*pPicture);
// video device might not be done yet
while (index < 0 && !CThread::m_bStop &&
CDVDClock::GetAbsoluteClock(false) < iCurrentClock + iSleepTime + DVD_MSEC_TO_TIME(500) )
{
Sleep(1);
index = g_renderManager.AddVideoPicture(*pPicture);
}
if (index < 0)
return EOS_DROPPED;
g_renderManager.FlipPage(CThread::m_bStop, (iCurrentClock + iSleepTime) / DVD_TIME_BASE, -1, mDisplayField);
return result;
#else
// no video renderer, let's mark it as dropped
return EOS_DROPPED;
#endif
}
void CDVDPlayerVideo::AutoCrop(DVDVideoPicture *pPicture)
{
if ((pPicture->format == RENDER_FMT_YUV420P) ||
(pPicture->format == RENDER_FMT_NV12) ||
(pPicture->format == RENDER_FMT_YUYV422) ||
(pPicture->format == RENDER_FMT_UYVY422))
{
RECT crop;
if (g_settings.m_currentVideoSettings.m_Crop)
AutoCrop(pPicture, crop);
else
{ // reset to defaults
crop.left = 0;
crop.right = 0;
crop.top = 0;
crop.bottom = 0;
}
m_crop.x1 += ((float)crop.left - m_crop.x1) * 0.1;
m_crop.x2 += ((float)crop.right - m_crop.x2) * 0.1;
m_crop.y1 += ((float)crop.top - m_crop.y1) * 0.1;
m_crop.y2 += ((float)crop.bottom - m_crop.y2) * 0.1;
crop.left = MathUtils::round_int(m_crop.x1);
crop.right = MathUtils::round_int(m_crop.x2);
crop.top = MathUtils::round_int(m_crop.y1);
crop.bottom = MathUtils::round_int(m_crop.y2);
//compare with hysteresis
# define HYST(n, o) ((n) > (o) || (n) + 1 < (o))
if(HYST(g_settings.m_currentVideoSettings.m_CropLeft , crop.left)
|| HYST(g_settings.m_currentVideoSettings.m_CropRight , crop.right)
|| HYST(g_settings.m_currentVideoSettings.m_CropTop , crop.top)
|| HYST(g_settings.m_currentVideoSettings.m_CropBottom, crop.bottom))
{
g_settings.m_currentVideoSettings.m_CropLeft = crop.left;
g_settings.m_currentVideoSettings.m_CropRight = crop.right;
g_settings.m_currentVideoSettings.m_CropTop = crop.top;
g_settings.m_currentVideoSettings.m_CropBottom = crop.bottom;
g_renderManager.SetViewMode(g_settings.m_currentVideoSettings.m_ViewMode);
}
# undef HYST
}
}
void CDVDPlayerVideo::AutoCrop(DVDVideoPicture *pPicture, RECT &crop)
{
crop.left = g_settings.m_currentVideoSettings.m_CropLeft;
crop.right = g_settings.m_currentVideoSettings.m_CropRight;
crop.top = g_settings.m_currentVideoSettings.m_CropTop;
crop.bottom = g_settings.m_currentVideoSettings.m_CropBottom;
int black = 16; // what is black in the image
int level = 8; // how high above this should we detect
int multi = 4; // what multiple of last line should failing line be to accept
BYTE *s;
int last, detect, black2;
// top and bottom levels
black2 = black * pPicture->iWidth;
detect = level * pPicture->iWidth + black2;
//YV12 and NV12 have planar Y plane
//YUY2 and UYVY have Y packed with U and V
int xspacing = 1;
int xstart = 0;
if (pPicture->format == RENDER_FMT_YUYV422)
xspacing = 2;
else if (pPicture->format == RENDER_FMT_UYVY422)
{
xspacing = 2;
xstart = 1;
}
// Crop top
s = pPicture->data[0];
last = black2;
for (unsigned int y = 0; y < pPicture->iHeight/2; y++)
{
int total = 0;
for (unsigned int x = xstart; x < pPicture->iWidth * xspacing; x += xspacing)
total += s[x];
s += pPicture->iLineSize[0];
if (total > detect)
{
if (total - black2 > (last - black2) * multi)
crop.top = y;
break;
}
last = total;
}
// Crop bottom
s = pPicture->data[0] + (pPicture->iHeight-1) * pPicture->iLineSize[0];
last = black2;
for (unsigned int y = (int)pPicture->iHeight; y > pPicture->iHeight/2; y--)
{
int total = 0;
for (unsigned int x = xstart; x < pPicture->iWidth * xspacing; x += xspacing)
total += s[x];
s -= pPicture->iLineSize[0];
if (total > detect)
{
if (total - black2 > (last - black2) * multi)
crop.bottom = pPicture->iHeight - y;
break;
}
last = total;
}
// left and right levels
black2 = black * pPicture->iHeight;
detect = level * pPicture->iHeight + black2;
// Crop left
s = pPicture->data[0];
last = black2;
for (unsigned int x = xstart; x < pPicture->iWidth/2*xspacing; x += xspacing)
{
int total = 0;
for (unsigned int y = 0; y < pPicture->iHeight; y++)
total += s[y * pPicture->iLineSize[0]];
s++;
if (total > detect)
{
if (total - black2 > (last - black2) * multi)
crop.left = x / xspacing;
break;
}
last = total;
}
// Crop right
s = pPicture->data[0] + (pPicture->iWidth-1);
last = black2;
for (unsigned int x = (int)pPicture->iWidth*xspacing-1; x > pPicture->iWidth/2*xspacing; x -= xspacing)
{
int total = 0;
for (unsigned int y = 0; y < pPicture->iHeight; y++)
total += s[y * pPicture->iLineSize[0]];
s--;
if (total > detect)
{
if (total - black2 > (last - black2) * multi)
crop.right = pPicture->iWidth - (x / xspacing);
break;
}
last = total;
}
// We always crop equally on each side to get zoom
// effect intead of moving the image. Aslong as the
// max crop isn't much larger than the min crop
// use that.
int min, max;
min = std::min(crop.left, crop.right);
max = std::max(crop.left, crop.right);
if(10 * (max - min) / pPicture->iWidth < 1)
crop.left = crop.right = max;
else
crop.left = crop.right = min;
min = std::min(crop.top, crop.bottom);
max = std::max(crop.top, crop.bottom);
if(10 * (max - min) / pPicture->iHeight < 1)
crop.top = crop.bottom = max;
else
crop.top = crop.bottom = min;
}
std::string CDVDPlayerVideo::GetPlayerInfo()
{
std::ostringstream s;
s << "fr:" << fixed << setprecision(3) << m_fFrameRate;
s << ", vq:" << setw(2) << min(99,GetLevel()) << "%";
s << ", dc:" << m_codecname;
s << ", Mb/s:" << fixed << setprecision(2) << (double)GetVideoBitrate() / (1024.0*1024.0);
s << ", drop:" << m_iDroppedFrames;
int pc = m_pullupCorrection.GetPatternLength();
if (pc > 0)
s << ", pc:" << pc;
else
s << ", pc:none";
return s.str();
}
int CDVDPlayerVideo::GetVideoBitrate()
{
return (int)m_videoStats.GetBitrate();
}
void CDVDPlayerVideo::ResetFrameRateCalc()
{
m_fStableFrameRate = 0.0;
m_iFrameRateCount = 0;
m_iFrameRateLength = 1;
m_iFrameRateErr = 0;
m_bAllowDrop = (!m_bCalcFrameRate && g_settings.m_currentVideoSettings.m_ScalingMethod != VS_SCALINGMETHOD_AUTO) ||
g_advancedSettings.m_videoFpsDetect == 0;
}
#define MAXFRAMERATEDIFF 0.01
#define MAXFRAMESERR 1000
void CDVDPlayerVideo::CalcFrameRate()
{
if (m_iFrameRateLength >= 128 || g_advancedSettings.m_videoFpsDetect == 0)
return; //don't calculate the fps
//only calculate the framerate if sync playback to display is on, adjust refreshrate is on,
//or scaling method is set to auto
if (!m_bCalcFrameRate && g_settings.m_currentVideoSettings.m_ScalingMethod != VS_SCALINGMETHOD_AUTO)
{
ResetFrameRateCalc();
return;
}
if (!m_pullupCorrection.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_pullupCorrection.GetFrameDuration();
if (frameduration == DVD_NOPTS_VALUE ||
(g_advancedSettings.m_videoFpsDetect == 1 && m_pullupCorrection.GetPatternLength() > 1))
{
//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,"%s counted %i 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,"%s framerate was:%f calculated:%f", __FUNCTION__, m_fFrameRate, m_fStableFrameRate / m_iFrameRateCount);
m_fFrameRate = m_fStableFrameRate / m_iFrameRateCount;
m_bFpsInvalid = false;
}
//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;
}
}
Jump to Line
Something went wrong with that request. Please try again.