/* * 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 #include #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 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& 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 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& 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 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; }