summaryrefslogtreecommitdiffstats
path: root/xbmc/cores/VideoPlayer/PTSTracker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/cores/VideoPlayer/PTSTracker.cpp')
-rw-r--r--xbmc/cores/VideoPlayer/PTSTracker.cpp323
1 files changed, 323 insertions, 0 deletions
diff --git a/xbmc/cores/VideoPlayer/PTSTracker.cpp b/xbmc/cores/VideoPlayer/PTSTracker.cpp
new file mode 100644
index 0000000..c5fdbf1
--- /dev/null
+++ b/xbmc/cores/VideoPlayer/PTSTracker.cpp
@@ -0,0 +1,323 @@
+/*
+ * 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 "PTSTracker.h"
+
+#include "DVDCodecs/DVDCodecUtils.h"
+#include "cores/VideoPlayer/Interface/TimingConstants.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <cmath>
+
+#define MAXERR DVD_MSEC_TO_TIME(2.5)
+
+CPtsTracker::CPtsTracker()
+{
+ ResetVFRDetection();
+ Flush();
+}
+
+void CPtsTracker::ResetVFRDetection(void)
+{
+ m_minframeduration = DVD_NOPTS_VALUE;
+ m_maxframeduration = DVD_NOPTS_VALUE;
+ m_VFRCounter = 0;
+ m_patternCounter = 0;
+ m_lastPattern.clear();
+}
+
+void CPtsTracker::Flush()
+{
+ m_pattern.clear();
+ m_ringpos = 0;
+ m_prevpts = DVD_NOPTS_VALUE;
+ m_ringfill = 0;
+ m_haspattern = false;
+ m_patternlength = 0;
+ m_frameduration = DVD_NOPTS_VALUE;
+ memset(m_diffring, 0, sizeof(m_diffring));
+}
+
+void CPtsTracker::Add(double pts)
+{
+ //can't get a diff with just one pts
+ if (m_prevpts == DVD_NOPTS_VALUE)
+ {
+ m_prevpts = pts;
+ return;
+ }
+
+ //increase the ringbuffer position
+ m_ringpos = (m_ringpos + 1) % DIFFRINGSIZE;
+ //add the current diff to the ringbuffer
+ m_diffring[m_ringpos] = pts - m_prevpts;
+ //save the pts
+ m_prevpts = pts;
+
+ if (m_ringfill < DIFFRINGSIZE)
+ m_ringfill++;
+
+ //only search for patterns if we have full ringbuffer
+ if (m_ringfill < DIFFRINGSIZE)
+ return;
+
+ //get the current pattern in the ringbuffer
+ std::vector<double> pattern;
+ GetPattern(pattern);
+
+ //check if the pattern is the same as the saved pattern
+ //and if it is actually a pattern
+ if (!CheckPattern(pattern))
+ {
+ if (m_haspattern)
+ {
+ m_VFRCounter++;
+ m_lastPattern = m_pattern;
+ CLog::Log(LOGDEBUG, "CPtsTracker: pattern lost on diff {:f}, number of losses {}", GetDiff(0),
+ m_VFRCounter);
+ Flush();
+ }
+
+ //no pattern detected or current pattern broke/changed
+ //save detected pattern so we can check it with the next iteration
+ m_pattern = pattern;
+
+ return;
+ }
+ else
+ {
+ if (!m_haspattern)
+ {
+ m_haspattern = true;
+ m_patternlength = m_pattern.size();
+
+ if (!m_lastPattern.empty() && !CheckPattern(m_lastPattern))
+ {
+ m_patternCounter++;
+ }
+
+ double frameduration = CalcFrameDuration();
+ CLog::Log(LOGDEBUG, "CPtsTracker: detected pattern of length {}: {}, frameduration: {:f}",
+ (int)pattern.size(), GetPatternStr(), frameduration);
+ }
+ }
+
+ m_frameduration = CalcFrameDuration();
+}
+
+//gets a diff diffnr into the past
+inline double CPtsTracker::GetDiff(int diffnr)
+{
+ //m_ringpos is the last added diff, so if we want to go in the past we have to move back in the ringbuffer
+ int pos = m_ringpos - diffnr;
+ if (pos < 0)
+ pos += DIFFRINGSIZE;
+
+ return m_diffring[pos];
+}
+
+//calculate the current pattern in the ringbuffer
+void CPtsTracker::GetPattern(std::vector<double>& pattern)
+{
+ int difftypesbuff[DIFFRINGSIZE]; //difftypes of the diffs, difftypesbuff[0] is the last added diff,
+ //difftypesbuff[1] the one added before that etc
+
+ //get the difftypes
+ std::vector<double> difftypes;
+ for (int i = 0; i < m_ringfill; i++)
+ {
+ bool hasmatch = false;
+ for (unsigned int j = 0; j < difftypes.size(); j++)
+ {
+ if (MatchDiff(GetDiff(i), difftypes[j]))
+ {
+ hasmatch = true;
+ break;
+ }
+ }
+
+ //if we don't have a match with a saved difftype, we add it as a new one
+ if (!hasmatch)
+ difftypes.push_back(GetDiff(i));
+ }
+
+ //mark each diff with what difftype it is
+ for (int i = 0; i < m_ringfill; i++)
+ {
+ for (unsigned int j = 0; j < difftypes.size(); j++)
+ {
+ if (MatchDiff(GetDiff(i), difftypes[j]))
+ {
+ difftypesbuff[i] = j;
+ break;
+ }
+ }
+ }
+
+ bool checkexisting = !m_pattern.empty();
+
+ //we check for patterns to the length of DIFFRINGSIZE / 2
+ for (int i = 1; i <= m_ringfill / 2; i++)
+ {
+ //check the existing pattern length first
+ int length = checkexisting ? m_pattern.size() : i;
+
+ bool hasmatch = true;
+ for (int j = 1; j <= m_ringfill / length; j++)
+ {
+ int nrdiffs = length;
+ //we want to check the full buffer to see if the pattern repeats
+ //but we can't go beyond the buffer
+ if (j * length + length > m_ringfill)
+ nrdiffs = m_ringfill - j * length;
+
+ if (nrdiffs < 1) //if the buffersize can be cleanly divided by i we're done here
+ break;
+
+ if (!MatchDifftype(difftypesbuff, difftypesbuff + j * length, nrdiffs))
+ {
+ hasmatch = false;
+ break;
+ }
+ }
+
+ if (checkexisting)
+ {
+ checkexisting = false;
+ i--;
+ }
+
+ if (hasmatch)
+ {
+ for (int i = 0; i < length; i++)
+ {
+ double avgdiff = 0.0;
+ for (int j = 0; j < m_ringfill / length; j++)
+ avgdiff += GetDiff(j * length + i);
+
+ avgdiff /= m_ringfill / length;
+ pattern.push_back(avgdiff);
+ }
+ break;
+ }
+ }
+ std::sort(pattern.begin(), pattern.end());
+}
+
+inline bool CPtsTracker::MatchDiff(double diff1, double diff2)
+{
+ return fabs(diff1 - diff2) < MAXERR;
+}
+
+//check if diffs1 is the same as diffs2
+inline bool CPtsTracker::MatchDifftype(int diffs1[], int diffs2[], int nrdiffs)
+{
+ for (int i = 0; i < nrdiffs; i++)
+ {
+ if (diffs1[i] != diffs2[i])
+ return false;
+ }
+ return true;
+}
+
+//check if our current detected pattern is the same as the one we saved
+bool CPtsTracker::CheckPattern(std::vector<double>& pattern)
+{
+ //if no pattern was detected or if the size of the patterns differ we don't have a match
+ if (pattern.empty() || pattern.size() != m_pattern.size())
+ return false;
+
+ if (pattern.size() == 1)
+ {
+ if (pattern[0] < MAXERR)
+ return false; //all diffs are too close to 0, can't use this
+ }
+
+ //check if the current pattern matches the saved pattern, with an offset of 1
+ for (unsigned int i = 0; i < m_pattern.size(); i++)
+ {
+ double diff = pattern[i];
+
+ if (!MatchDiff(diff, m_pattern[i]))
+ return false;
+ }
+
+ return true;
+}
+
+//calculate how long each frame should last from the saved pattern
+//also retrieve information of max and min frame rate duration, for VFR files case
+double CPtsTracker::CalcFrameDuration()
+{
+ if (!m_pattern.empty())
+ {
+ //take the average of all diffs in the pattern
+ double frameduration;
+ double current, currentmin, currentmax;
+
+ currentmin = m_pattern[0];
+ currentmax = currentmin;
+ frameduration = currentmin;
+ for (unsigned int i = 1; i < m_pattern.size(); i++)
+ {
+ current = m_pattern[i];
+ if (current>currentmax)
+ currentmax = current;
+ if (current<currentmin)
+ currentmin = current;
+ frameduration += current;
+ }
+ frameduration /= m_pattern.size();
+
+ // Update min and max frame duration, only if data is valid
+ bool standard = false;
+ double tempduration = CDVDCodecUtils::NormalizeFrameduration(currentmin, &standard);
+ if (m_minframeduration == DVD_NOPTS_VALUE)
+ {
+ if (standard)
+ m_minframeduration = tempduration;
+ }
+ else
+ {
+ if (standard && (tempduration < m_minframeduration))
+ m_minframeduration = tempduration;
+ }
+
+ tempduration = CDVDCodecUtils::NormalizeFrameduration(currentmax, &standard);
+ if (m_maxframeduration == DVD_NOPTS_VALUE)
+ {
+ if (standard)
+ m_maxframeduration = tempduration;
+ }
+ else
+ {
+ if (standard && (tempduration > m_maxframeduration))
+ m_maxframeduration = tempduration;
+ }
+
+ //frameduration is not completely correct, use a common one if it's close
+ return CDVDCodecUtils::NormalizeFrameduration(frameduration);
+ }
+
+ return DVD_NOPTS_VALUE;
+}
+
+//looks pretty in the log
+std::string CPtsTracker::GetPatternStr()
+{
+ std::string patternstr;
+
+ for (unsigned int i = 0; i < m_pattern.size(); i++)
+ patternstr += StringUtils::Format("{:.2f} ", m_pattern[i]);
+
+ StringUtils::Trim(patternstr);
+
+ return patternstr;
+}