24 isBlank(
unsigned char median,
float stddev,
unsigned char maxmedian,
27 return ((median < maxmedian) ||
28 ((median == maxmedian) && (stddev <= maxstddev)));
32 sort_ascending_uchar(
const void *aa,
const void *bb)
34 return *(
unsigned char*)aa - *(
unsigned char*)bb;
38 sort_ascending_float(
const void *aa,
const void *bb)
40 float faa = *(
float*)aa;
41 float fbb = *(
float*)bb;
42 return faa < fbb ? -1 : faa == fbb ? 0 : 1;
46 pickmedian(
const unsigned char medianval,
47 unsigned char minval,
unsigned char maxval)
49 return medianval >= minval && medianval <= maxval;
54 const unsigned char *median,
const float *stddev,
55 const unsigned char *monochromatic)
61 const unsigned char MINBLANKMEDIAN = 1;
62 const unsigned char MAXBLANKMEDIAN = 96;
63 const float MEDIANPCTILE = 0.95;
64 const float STDDEVPCTILE = 0.85;
66 long long frameno = 1;
72 long long nblanks = 0;
73 for (frameno = 0; frameno < nframes; frameno++)
75 if (monochromatic[frameno] && pickmedian(median[frameno],
76 MINBLANKMEDIAN, MAXBLANKMEDIAN))
83 LOG(VB_COMMFLAG, LOG_INFO,
84 "BlankFrameDetector::computeBlankMap: No blank frames.");
90 auto *blankmedian =
new unsigned char[nblanks];
91 auto *blankstddev =
new float[nblanks];
92 long long blankno = 0;
93 for (frameno = 0; frameno < nframes; frameno++)
95 if (monochromatic[frameno] && pickmedian(median[frameno],
96 MINBLANKMEDIAN, MAXBLANKMEDIAN))
98 blankmedian[blankno] = median[frameno];
99 blankstddev[blankno] = stddev[frameno];
104 qsort(blankmedian, nblanks,
sizeof(*blankmedian), sort_ascending_uchar);
105 blankno = std::min(nblanks - 1, (
long long)roundf(nblanks * MEDIANPCTILE));
106 uchar maxmedian = blankmedian[blankno];
108 qsort(blankstddev, nblanks,
sizeof(*blankstddev), sort_ascending_float);
109 long long stddevno = std::min(nblanks - 1, (
long long)roundf(nblanks * STDDEVPCTILE));
110 float maxstddev = blankstddev[stddevno];
114 long long blankno1 = blankno;
115 long long blankno2 = blankno;
116 while (blankno1 > 0 && blankmedian[blankno1] == maxmedian)
118 if (blankmedian[blankno1] != maxmedian)
120 while (blankno2 < nblanks && blankmedian[blankno2] == maxmedian)
122 if (blankno2 == nblanks)
125 long long stddevno1 = stddevno;
126 long long stddevno2 = stddevno;
127 while (stddevno1 > 0 && blankstddev[stddevno1] == maxstddev)
129 if (blankstddev[stddevno1] != maxstddev)
131 while (stddevno2 < nblanks && blankstddev[stddevno2] == maxstddev)
133 if (stddevno2 == nblanks)
136 LOG(VB_COMMFLAG, LOG_INFO,
137 QString(
"Blanks selecting median<=%1 (%2-%3%), stddev<=%4 (%5-%6%)")
139 .arg(blankno1 * 100 / nblanks).arg(blankno2 * 100 / nblanks)
141 .arg(stddevno1 * 100 / nblanks).arg(stddevno2 * 100 / nblanks));
143 delete []blankmedian;
144 delete []blankstddev;
147 if (monochromatic[0] && isBlank(median[0], stddev[0], maxmedian, maxstddev))
155 blankMap->insert(0, 0);
159 for (frameno = 1; frameno < nframes; frameno++)
161 if (monochromatic[frameno] && isBlank(median[frameno], stddev[frameno],
162 maxmedian, maxstddev))
165 if (sege < frameno - 1)
177 else if (sege == frameno - 1)
180 long long seglen = frameno - segb;
181 blankMap->insert(segb, seglen);
184 if (sege == frameno - 1)
187 long long seglen = frameno - segb;
188 blankMap->insert(segb, seglen);
191 FrameAnalyzer::FrameMap::Iterator iiblank = blankMap->end();
193 if (iiblank.key() + *iiblank < nframes)
199 blankMap->insert(nframes - 1, 0);
214 std::chrono::seconds m_len;
215 std::chrono::seconds m_delta;
217 static constexpr std::array<const breakType,4> kBreakType {{
231 static const int kMinContentLen = (int)roundf(10 * fps);
234 for (FrameAnalyzer::FrameMap::const_iterator iiblank = blankMap->begin();
235 iiblank != blankMap->end();
238 long long brkb = iiblank.key();
239 long long iilen = *iiblank;
240 long long start = brkb + iilen / 2;
242 for (
const auto&
type : kBreakType)
245 FrameAnalyzer::FrameMap::const_iterator jjblank = iiblank;
246 for (++jjblank; jjblank != blankMap->end(); ++jjblank)
248 long long brke = jjblank.key();
249 long long jjlen = *jjblank;
250 long long end = brke + jjlen / 2;
252 auto testlen = std::chrono::seconds(lroundf((end - start) / fps));
253 if (testlen >
type.m_len +
type.m_delta)
256 std::chrono::seconds delta = testlen -
type.m_len;
257 delta = std::chrono::abs(delta);
259 if (delta >
type.m_delta)
263 bool inserted =
false;
264 for (
unsigned int jj = 0;; jj++)
266 long long newbrkb = brkb + jj;
269 LOG(VB_COMMFLAG, LOG_INFO,
270 QString(
"BF [%1,%2] ran out of slots")
271 .arg(brkb).arg(brke - 1));
274 if (breakMap->find(newbrkb) == breakMap->end())
276 breakMap->insert(newbrkb, brke - newbrkb);
290 LOG(VB_COMMFLAG, LOG_INFO,
291 "BF coalescing overlapping/nearby breaks ...");
300 bool coalesced =
false;
301 FrameAnalyzer::FrameMap::iterator iibreak = breakMap->begin();
302 while (iibreak != breakMap->end())
304 long long iib = iibreak.key();
305 long long iie = iib + *iibreak;
307 FrameAnalyzer::FrameMap::iterator jjbreak = iibreak;
309 if (jjbreak == breakMap->end())
312 long long jjb = jjbreak.key();
313 long long jje = jjb + *jjbreak;
322 if (iie + kMinContentLen < jjb)
332 breakMap->remove(iib);
333 breakMap->insert(iib, jje - iib);
335 breakMap->erase(jjbreak);
337 iibreak = breakMap->find(iib);
344 FrameAnalyzer::FrameMap::iterator iibreak = breakMap->begin();
345 while (iibreak != breakMap->end())
347 long long iib = iibreak.key();
348 long long iie = iib + *iibreak;
349 iibreak = breakMap->erase(iibreak);
352 auto iter = blankMap->find(iib);
353 if (iter == blankMap->end())
355 long long addb = *iter;
361 iter = blankMap->find(iib);
362 if (iter == blankMap->end())
364 long long adde = *iter;
366 long long sube = adde / 2;
370 breakMap->insert(iib, iie - iib);
377 const QString &debugdir)
378 : m_histogramAnalyzer(
std::move(ha))
389 QString(
"BlankFrameDetector debugLevel %1").arg(
m_debugLevel));
402 LOG(VB_COMMFLAG, LOG_INFO,
403 QString(
"BlankFrameDetector::MythPlayerInited %1x%2")
404 .arg(video_disp_dim.width())
405 .arg(video_disp_dim.height()));
412 long long *pNextFrame)
420 LOG(VB_COMMFLAG, LOG_INFO,
421 QString(
"BlankFrameDetector::analyzeFrame error at frame %1")
432 LOG(VB_COMMFLAG, LOG_INFO, QString(
"BlankFrameDetector::finished(%1)")
457 const int MAXBLANKADJUSTMENT = (int)roundf(5 *
m_fps);
459 LOG(VB_COMMFLAG, LOG_INFO,
"BlankFrameDetector adjusting for logo surplus");
465 for (FrameAnalyzer::FrameMap::const_iterator ii =
466 logoBreakMap->constBegin();
467 ii != logoBreakMap->constEnd();
471 long long iikey = ii.key();
472 long long iibb = iikey - MAXBLANKADJUSTMENT;
473 long long iiee = iikey + MAXBLANKADJUSTMENT;
474 FrameAnalyzer::FrameMap::Iterator jjfound =
m_blankMap.end();
479 long long jjbb = jj.key();
480 long long jjee = jjbb + *jj;
502 long long jjee = jjfound.key() + *jjfound;
518 long long kkkey = ii.key() + *ii;
519 long long kkbb = kkkey - MAXBLANKADJUSTMENT;
520 long long kkee = kkkey + MAXBLANKADJUSTMENT;
521 FrameAnalyzer::FrameMap::Iterator mmfound =
m_blankMap.end();
526 long long mmbb = mm.key();
527 long long mmee = mmbb + *mm;
536 if (mmee < kkkey || mmfound ==
m_blankMap.end())
545 long long mmbb = mmfound.key();
571 for (FrameAnalyzer::FrameMap::const_iterator ii =
572 logoBreakMap->constBegin();
573 ii != logoBreakMap->constEnd();
576 long long iibb = ii.key();
577 long long iiee = iibb + *ii;
578 bool overlap =
false;
582 long long jjbb = jj.key();
583 long long jjee = jjbb + *jj;
595 if (iibb < jjbb && jjbb < iiee)
600 m_breakMap.insert(iibb, std::max(iiee, jjee) - iibb);
603 if (jjbb < iibb && iibb < jjee)
626 LOG(VB_COMMFLAG, LOG_INFO,
"BlankFrameDetector adjusting for "
627 "too little logo coverage (unimplemented)");
643 breaks->insert(bb.key(), *bb);