MythTV  master
TemplateMatcher.cpp
Go to the documentation of this file.
1 // POSIX headers
2 #include <sys/time.h> /* gettimeofday */
3 
4 // ANSI C headers
5 #include <cstdlib>
6 #include <cmath>
7 
8 // C++ headers
9 #include <algorithm>
10 using namespace std;
11 
12 // Qt headers
13 #include <QFile>
14 #include <QFileInfo>
15 
16 // MythTV headers
17 #include "mythplayer.h"
18 #include "mythcorecontext.h"
19 #include "mythlogging.h"
20 
21 // Commercial Flagging headers
22 #include "CommDetector2.h"
23 #include "FrameAnalyzer.h"
24 #include "pgm.h"
25 #include "PGMConverter.h"
26 #include "EdgeDetector.h"
27 #include "BlankFrameDetector.h"
28 #include "TemplateFinder.h"
29 #include "TemplateMatcher.h"
30 
31 extern "C" {
32 #include "libavutil/imgutils.h"
33 }
34 
35 using namespace commDetector2;
36 using namespace frameAnalyzer;
37 
38 namespace {
39 
40 int pgm_set(const AVFrame *pict, int height)
41 {
42  const int width = pict->linesize[0];
43  const int size = height * width;
44 
45  int score = 0;
46  for (int ii = 0; ii < size; ii++)
47  if (pict->data[0][ii])
48  score++;
49  return score;
50 }
51 
52 int pgm_match(const AVFrame *tmpl, const AVFrame *test, int height,
53  int radius, unsigned short *pscore)
54 {
55  /* Return the number of matching "edge" and non-edge pixels. */
56  const int width = tmpl->linesize[0];
57 
58  if (width != test->linesize[0])
59  {
60  LOG(VB_COMMFLAG, LOG_ERR,
61  QString("pgm_match widths don't match: %1 != %2")
62  .arg(width).arg(test->linesize[0]));
63  return -1;
64  }
65 
66  int score = 0;
67  for (int rr = 0; rr < height; rr++)
68  {
69  for (int cc = 0; cc < width; cc++)
70  {
71  if (!tmpl->data[0][rr * width + cc])
72  continue;
73 
74  int r2min = max(0, rr - radius);
75  int r2max = min(height, rr + radius);
76 
77  int c2min = max(0, cc - radius);
78  int c2max = min(width, cc + radius);
79 
80  for (int r2 = r2min; r2 <= r2max; r2++)
81  {
82  for (int c2 = c2min; c2 <= c2max; c2++)
83  {
84  if (test->data[0][r2 * width + c2])
85  {
86  score++;
87  goto next_pixel;
88  }
89  }
90  }
91 next_pixel:
92  ;
93  }
94  }
95 
96  *pscore = score;
97  return 0;
98 }
99 
100 bool readMatches(const QString& filename, unsigned short *matches, long long nframes)
101 {
102  QByteArray fname = filename.toLocal8Bit();
103  FILE *fp = fopen(fname.constData(), "r");
104  if (fp == nullptr)
105  return false;
106 
107  for (long long frameno = 0; frameno < nframes; frameno++)
108  {
109  int nitems = fscanf(fp, "%20hu", &matches[frameno]);
110  if (nitems != 1)
111  {
112  LOG(VB_COMMFLAG, LOG_ERR,
113  QString("Not enough data in %1: frame %2")
114  .arg(filename).arg(frameno));
115  goto error;
116  }
117  }
118 
119  if (fclose(fp))
120  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
121  .arg(filename).arg(strerror(errno)));
122  return true;
123 
124 error:
125  if (fclose(fp))
126  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
127  .arg(filename).arg(strerror(errno)));
128  return false;
129 }
130 
131 bool writeMatches(const QString& filename, unsigned short *matches, long long nframes)
132 {
133  QByteArray fname = filename.toLocal8Bit();
134  FILE *fp = fopen(fname.constData(), "w");
135  if (fp == nullptr)
136  return false;
137 
138  for (long long frameno = 0; frameno < nframes; frameno++)
139  (void)fprintf(fp, "%hu\n", matches[frameno]);
140 
141  if (fclose(fp))
142  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
143  .arg(filename).arg(strerror(errno)));
144  return true;
145 }
146 
147 int finishedDebug(long long nframes, const unsigned short *matches,
148  const unsigned char *match)
149 {
150  ushort score = matches[0];
151  ushort low = score;
152  ushort high = score;
153  long long startframe = 0;
154 
155  for (long long frameno = 1; frameno < nframes; frameno++)
156  {
157  score = matches[frameno];
158  if (match[frameno - 1] == match[frameno])
159  {
160  if (score < low)
161  low = score;
162  if (score > high)
163  high = score;
164  continue;
165  }
166 
167  LOG(VB_COMMFLAG, LOG_INFO, QString("Frame %1-%2: %3 L-H: %4-%5 (%6)")
168  .arg(startframe, 6).arg(frameno - 1, 6)
169  .arg(match[frameno - 1] ? "logo " : " no-logo")
170  .arg(low, 4).arg(high, 4).arg(frameno - startframe, 5));
171 
172  low = score;
173  high = score;
174  startframe = frameno;
175  }
176 
177  return 0;
178 }
179 
180 int sort_ascending(const void *aa, const void *bb)
181 {
182  return *(unsigned short*)aa - *(unsigned short*)bb;
183 }
184 
185 long long matchspn(long long nframes, const unsigned char *match, long long frameno,
186  unsigned char acceptval)
187 {
188  /*
189  * strspn(3)-style interface: return the first frame number that does not
190  * match "acceptval".
191  */
192  while (frameno < nframes && match[frameno] == acceptval)
193  frameno++;
194  return frameno;
195 }
196 
197 unsigned int range_area(const unsigned short *freq, unsigned short start,
198  unsigned short end)
199 {
200  /* Return the integrated area under the curve of the plotted histogram. */
201  const unsigned short width = end - start;
202 
203  uint sum = 0;
204  uint nsamples = 0;
205  for (ushort matchcnt = start; matchcnt < end; matchcnt++)
206  {
207  if (freq[matchcnt])
208  {
209  sum += freq[matchcnt];
210  nsamples++;
211  }
212  }
213  if (!nsamples)
214  return 0;
215  return width * sum / nsamples;
216 }
217 
218 unsigned short pick_mintmpledges(const unsigned short *matches,
219  long long nframes)
220 {
221  /*
222  * Most frames either match the template very well, or don't match
223  * very well at all. This allows us to assume a bimodal
224  * distribution of the values in a frequency histogram of the
225  * "matches" array.
226  *
227  * Return a local minima between the two modes representing the
228  * threshold value that decides whether or not a frame matches the
229  * template.
230  *
231  * See "mythcommflag-analyze" and its output.
232  */
233 
234  /*
235  * TUNABLE:
236  *
237  * Given a point to test, require the integrated area to the left
238  * of the point to be greater than some (larger) area to the right
239  * of the point.
240  */
241  static constexpr float kLeftWidth = 0.04;
242  static constexpr float kMiddleWidth = 0.04;
243  static constexpr float kRightWidth = 0.04;
244 
245  static constexpr float kMatchStart = 0.20;
246  static constexpr float kMatchEnd = 0.80;
247 
248  auto *sorted = new unsigned short[nframes];
249  memcpy(sorted, matches, nframes * sizeof(*matches));
250  qsort(sorted, nframes, sizeof(*sorted), sort_ascending);
251  ushort minmatch = sorted[0];
252  ushort maxmatch = sorted[nframes - 1];
253  ushort matchrange = maxmatch - minmatch;
254  /* degenerate minmatch==maxmatch case is gracefully handled */
255 
256  auto leftwidth = (unsigned short)(kLeftWidth * matchrange);
257  auto middlewidth = (unsigned short)(kMiddleWidth * matchrange);
258  auto rightwidth = (unsigned short)(kRightWidth * matchrange);
259 
260  int nfreq = maxmatch + 1;
261  auto *freq = new unsigned short[nfreq];
262  memset(freq, 0, nfreq * sizeof(*freq));
263  for (long long frameno = 0; frameno < nframes; frameno++)
264  freq[matches[frameno]]++; /* freq[<matchcnt>] = <framecnt> */
265 
266  ushort matchstart = minmatch + (unsigned short)(kMatchStart * matchrange);
267  ushort matchend = minmatch + (unsigned short)(kMatchEnd * matchrange);
268 
269  int local_minimum = matchstart;
270  uint maxdelta = 0;
271  for (int matchcnt = matchstart + leftwidth + middlewidth / 2;
272  matchcnt < matchend - rightwidth - middlewidth / 2;
273  matchcnt++)
274  {
275  ushort p0 = matchcnt - leftwidth - middlewidth / 2;
276  ushort p1 = p0 + leftwidth;
277  ushort p2 = p1 + middlewidth;
278  ushort p3 = p2 + rightwidth;
279 
280  uint leftscore = range_area(freq, p0, p1);
281  uint middlescore = range_area(freq, p1, p2);
282  uint rightscore = range_area(freq, p2, p3);
283  if (middlescore < leftscore && middlescore < rightscore)
284  {
285  unsigned int delta = (leftscore - middlescore) +
286  (rightscore - middlescore);
287  if (delta > maxdelta)
288  {
289  local_minimum = matchcnt;
290  maxdelta = delta;
291  }
292  }
293  }
294 
295  LOG(VB_COMMFLAG, LOG_INFO,
296  QString("pick_mintmpledges minmatch=%1 maxmatch=%2"
297  " matchstart=%3 matchend=%4 widths=%5,%6,%7 local_minimum=%8")
298  .arg(minmatch).arg(maxmatch).arg(matchstart).arg(matchend)
299  .arg(leftwidth).arg(middlewidth).arg(rightwidth)
300  .arg(local_minimum));
301 
302  delete []freq;
303  delete []sorted;
304  return local_minimum;
305 }
306 
307 }; /* namespace */
308 
310  TemplateFinder *tf, const QString& debugdir) :
311  m_pgmConverter(pgmc),
312  m_edgeDetector(ed), m_templateFinder(tf),
313  m_debugdir(debugdir),
315  m_debugdata(debugdir + "/TemplateMatcher-pgm.txt")
316 #else /* !PGM_CONVERT_GREYSCALE */
317  m_debugdata(debugdir + "/TemplateMatcher-yuv.txt")
318 #endif /* !PGM_CONVERT_GREYSCALE */
319 {
320  /*
321  * debugLevel:
322  * 0: no debugging
323  * 1: cache frame edge counts into debugdir [1 file]
324  * 2: extra verbosity [O(nframes)]
325  */
326  m_debugLevel = gCoreContext->GetNumSetting("TemplateMatcherDebugLevel", 0);
327 
328  if (m_debugLevel >= 1)
329  {
331  QString("TemplateMatcher debugLevel %1").arg(m_debugLevel));
332  m_debug_matches = true;
333  if (m_debugLevel >= 2)
334  m_debug_removerunts = true;
335  }
336 }
337 
339 {
340  delete []m_matches;
341  delete []m_match;
342  av_freep(&m_cropped.data[0]);
343 }
344 
347  long long nframes)
348 {
349  m_player = _player;
351 
354  {
355  LOG(VB_COMMFLAG, LOG_ERR,
356  QString("TemplateMatcher::MythPlayerInited: no template"));
357  return ANALYZE_FATAL;
358  }
359 
360  if (av_image_alloc(m_cropped.data, m_cropped.linesize,
361  m_tmplwidth, m_tmplheight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN))
362  {
363  LOG(VB_COMMFLAG, LOG_ERR,
364  QString("TemplateMatcher::MythPlayerInited "
365  "av_image_alloc cropped (%1x%2) failed")
366  .arg(m_tmplwidth).arg(m_tmplheight));
367  return ANALYZE_FATAL;
368  }
369 
371  goto free_cropped;
372 
373  m_matches = new unsigned short[nframes];
374  memset(m_matches, 0, nframes * sizeof(*m_matches));
375 
376  m_match = new unsigned char[nframes];
377 
378  if (m_debug_matches)
379  {
380  if (readMatches(m_debugdata, m_matches, nframes))
381  {
382  LOG(VB_COMMFLAG, LOG_INFO,
383  QString("TemplateMatcher::MythPlayerInited read %1")
384  .arg(m_debugdata));
385  m_matches_done = true;
386  }
387  }
388 
389  if (m_matches_done)
390  return ANALYZE_FINISHED;
391 
392  return ANALYZE_OK;
393 
394 free_cropped:
395  av_freep(&m_cropped.data[0]);
396  return ANALYZE_FATAL;
397 }
398 
400 TemplateMatcher::analyzeFrame(const VideoFrame *frame, long long frameno,
401  long long *pNextFrame)
402 {
403  /*
404  * TUNABLE:
405  *
406  * The matching area should be a lot smaller than the area used by
407  * TemplateFinder, so use a smaller percentile than the TemplateFinder
408  * (intensity requirements to be considered an "edge" are lower, because
409  * there should be less pollution from non-template edges).
410  *
411  * Higher values mean fewer edge pixels in the candidate template area;
412  * occluded or faint templates might be missed.
413  *
414  * Lower values mean more edge pixels in the candidate template area;
415  * non-template edges can be picked up and cause false identification of
416  * matches.
417  */
418  const int FRAMESGMPCTILE = 70;
419 
420  /*
421  * TUNABLE:
422  *
423  * The per-pixel search radius for matching the template. Meant to deal
424  * with template edges that might minimally slide around (such as for
425  * animated lighting effects).
426  *
427  * Higher values will pick up more pixels as matching the template
428  * (possibly false matches).
429  *
430  * Lower values will require more exact template matches, possibly missing
431  * true matches.
432  *
433  * The TemplateMatcher accumulates all its state in the "matches" array to
434  * be processed later by TemplateMatcher::finished.
435  */
436  const int JITTER_RADIUS = 0;
437 
438  const AVFrame *edges = nullptr;
439  int pgmwidth = 0, pgmheight = 0;
440  struct timeval start {}, end {}, elapsed {};
441 
442  *pNextFrame = NEXTFRAME;
443 
444  const AVFrame *pgm = m_pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight);
445  if (pgm == nullptr)
446  goto error;
447 
448  (void)gettimeofday(&start, nullptr);
449 
450  if (pgm_crop(&m_cropped, pgm, pgmheight, m_tmplrow, m_tmplcol,
452  goto error;
453 
455  FRAMESGMPCTILE)))
456  goto error;
457 
458  if (pgm_match(m_tmpl, edges, m_tmplheight, JITTER_RADIUS, &m_matches[frameno]))
459  goto error;
460 
461  (void)gettimeofday(&end, nullptr);
462  timersub(&end, &start, &elapsed);
463  timeradd(&m_analyze_time, &elapsed, &m_analyze_time);
464 
465  return ANALYZE_OK;
466 
467 error:
468  LOG(VB_COMMFLAG, LOG_ERR,
469  QString("TemplateMatcher::analyzeFrame error at frame %1 of %2")
470  .arg(frameno));
471  return ANALYZE_ERROR;
472 }
473 
474 int
475 TemplateMatcher::finished(long long nframes, bool final)
476 {
477  /*
478  * TUNABLE:
479  *
480  * Eliminate false negatives and false positives by eliminating breaks and
481  * segments shorter than these periods, subject to maximum bounds.
482  *
483  * Higher values could eliminate real breaks or segments entirely.
484  * Lower values can yield more false "short" breaks or segments.
485  */
486  const int MINBREAKLEN = (int)roundf(45 * m_fps); /* frames */
487  const int MINSEGLEN = (int)roundf(105 * m_fps); /* frames */
488 
489  int minbreaklen = 1, minseglen = 1;
490  long long brkb = 0;
491 
493  {
494  if (final && writeMatches(m_debugdata, m_matches, nframes))
495  {
496  LOG(VB_COMMFLAG, LOG_INFO,
497  QString("TemplateMatcher::finished wrote %1") .arg(m_debugdata));
498  m_matches_done = true;
499  }
500  }
501 
502  int tmpledges = pgm_set(m_tmpl, m_tmplheight);
503  int mintmpledges = pick_mintmpledges(m_matches, nframes);
504 
505  LOG(VB_COMMFLAG, LOG_INFO,
506  QString("TemplateMatcher::finished %1x%2@(%3,%4),"
507  " %5 edge pixels, want %6")
508  .arg(m_tmplwidth).arg(m_tmplheight).arg(m_tmplcol).arg(m_tmplrow)
509  .arg(tmpledges).arg(mintmpledges));
510 
511  for (long long ii = 0; ii < nframes; ii++)
512  m_match[ii] = m_matches[ii] >= mintmpledges ? 1 : 0;
513 
514  if (m_debugLevel >= 2)
515  {
516  if (final && finishedDebug(nframes, m_matches, m_match))
517  goto error;
518  }
519 
520  /*
521  * Construct breaks; find the first logo break.
522  */
523  m_breakMap.clear();
524  brkb = m_match[0] ? matchspn(nframes, m_match, 0, m_match[0]) : 0;
525  while (brkb < nframes)
526  {
527  /* Skip over logo-less frames; find the next logo frame (brke). */
528  long long brke = matchspn(nframes, m_match, brkb, m_match[brkb]);
529  long long brklen = brke - brkb;
530  m_breakMap.insert(brkb, brklen);
531 
532  /* Find the next logo break. */
533  brkb = matchspn(nframes, m_match, brke, m_match[brke]);
534  }
535 
536  /* Clean up the "noise". */
537  minbreaklen = 1;
538  minseglen = 1;
539  for (;;)
540  {
541  bool f1 = false;
542  bool f2 = false;
543  if (minbreaklen <= MINBREAKLEN)
544  {
545  f1 = removeShortBreaks(&m_breakMap, m_fps, minbreaklen,
547  minbreaklen++;
548  }
549  if (minseglen <= MINSEGLEN)
550  {
551  f2 = removeShortSegments(&m_breakMap, nframes, m_fps, minseglen,
553  minseglen++;
554  }
555  if (minbreaklen > MINBREAKLEN && minseglen > MINSEGLEN)
556  break;
557  if (m_debug_removerunts && (f1 || f2))
558  frameAnalyzerReportMap(&m_breakMap, m_fps, "** TM Break");
559  }
560 
561  /*
562  * Report breaks.
563  */
564  frameAnalyzerReportMap(&m_breakMap, m_fps, "TM Break");
565 
566  return 0;
567 
568 error:
569  return -1;
570 }
571 
572 int
574 {
575  if (m_pgmConverter->reportTime())
576  return -1;
577 
578  LOG(VB_COMMFLAG, LOG_INFO, QString("TM Time: analyze=%1s")
579  .arg(strftimeval(&m_analyze_time)));
580  return 0;
581 }
582 
583 int
584 TemplateMatcher::templateCoverage(long long nframes, bool final) const
585 {
586  /*
587  * Return <0 for too little logo coverage (some content has no logo), 0 for
588  * "correct" coverage, and >0 for too much coverage (some commercials have
589  * logos).
590  */
591 
592  /*
593  * TUNABLE:
594  *
595  * Expect 20%-45% of total length to be commercials.
596  */
597  const int MINBREAKS = nframes * 20 / 100;
598  const int MAXBREAKS = nframes * 45 / 100;
599 
600  const long long brklen = frameAnalyzerMapSum(&m_breakMap);
601  const bool good = brklen >= MINBREAKS && brklen <= MAXBREAKS;
602 
603  if (m_debugLevel >= 1)
604  {
605  if (!m_tmpl)
606  {
607  LOG(VB_COMMFLAG, LOG_ERR,
608  QString("TemplateMatcher: no template (wanted %2-%3%)")
609  .arg(100 * MINBREAKS / nframes)
610  .arg(100 * MAXBREAKS / nframes));
611  }
612  else if (!final)
613  {
614  LOG(VB_COMMFLAG, LOG_ERR,
615  QString("TemplateMatcher has %1% breaks (real-time flagging)")
616  .arg(100 * brklen / nframes));
617  }
618  else if (good)
619  {
620  LOG(VB_COMMFLAG, LOG_INFO, QString("TemplateMatcher has %1% breaks")
621  .arg(100 * brklen / nframes));
622  }
623  else
624  {
625  LOG(VB_COMMFLAG, LOG_INFO,
626  QString("TemplateMatcher has %1% breaks (wanted %2-%3%)")
627  .arg(100 * brklen / nframes)
628  .arg(100 * MINBREAKS / nframes)
629  .arg(100 * MAXBREAKS / nframes));
630  }
631  }
632 
633  if (!final)
634  return 0; /* real-time flagging */
635 
636  return brklen < MINBREAKS ? 1 : brklen <= MAXBREAKS ? 0 : -1;
637 }
638 
639 int
641  long long nframes)
642 {
643  const FrameAnalyzer::FrameMap *blankMap = blankFrameDetector->getBlanks();
644 
645  /*
646  * TUNABLE:
647  *
648  * Tune the search for blank frames near appearances and disappearances of
649  * the template.
650  *
651  * TEMPLATE_DISAPPEARS_EARLY: Handle the scenario where the template can
652  * disappear "early" before switching to commercial. Delay the beginning of
653  * the commercial break by searching forwards to find a blank frame.
654  *
655  * Setting this value too low can yield false positives. If template
656  * does indeed disappear "early" (common in US broadcast), the end of
657  * the broadcast segment can be misidentified as part of the beginning
658  * of the commercial break.
659  *
660  * Setting this value too high can yield or worsen false negatives. If
661  * the template presence extends at all into the commercial break,
662  * immediate cuts to commercial without any intervening blank frames
663  * can cause the broadcast to "continue" even further into the
664  * commercial break.
665  *
666  * TEMPLATE_DISAPPEARS_LATE: Handle the scenario where the template
667  * disappears "late" after having already switched to commercial (template
668  * presence extends into commercial break). Accelerate the beginning of the
669  * commercial break by searching backwards to find a blank frame.
670  *
671  * Setting this value too low can yield false negatives. If the
672  * template does extend deep into the commercial break, the first part
673  * of the commercial break can be misidentifed as part of the
674  * broadcast.
675  *
676  * Setting this value too high can yield or worsen false positives. If
677  * the template disappears extremely early (too early for
678  * TEMPLATE_DISAPPEARS_EARLY), blank frames before the template
679  * disappears can cause even more of the end of the broadcast segment
680  * to be misidentified as the first part of the commercial break.
681  *
682  * TEMPLATE_REAPPEARS_LATE: Handle the scenario where the template
683  * reappears "late" after having already returned to the broadcast.
684  * Accelerate the beginning of the broadcast by searching backwards to find
685  * a blank frame.
686  *
687  * Setting this value too low can yield false positives. If the
688  * template does indeed reappear "late" (common in US broadcast), the
689  * beginning of the broadcast segment can be misidentified as the end
690  * of the commercial break.
691  *
692  * Setting this value too high can yield or worsen false negatives. If
693  * the template actually reappears early, blank frames before the
694  * template reappears can cause even more of the end of the commercial
695  * break to be misidentified as the first part of the broadcast break.
696  *
697  * TEMPLATE_REAPPEARS_EARLY: Handle the scenario where the template
698  * reappears "early" before resuming the broadcast. Delay the beginning of
699  * the broadcast by searching forwards to find a blank frame.
700  *
701  * Setting this value too low can yield false negatives. If the
702  * template does reappear "early", the the last part of the commercial
703  * break can be misidentified as part of the beginning of the
704  * following broadcast segment.
705  *
706  * Setting this value too high can yield or worsen false positives. If
707  * the template reappears extremely late (too late for
708  * TEMPLATE_REAPPEARS_LATE), blank frames after the template reappears
709  * can cause even more of the broadcast segment can be misidentified
710  * as part of the end of the commercial break.
711  */
712  const int BLANK_NEARBY = (int)roundf(0.5F * m_fps);
713  const int TEMPLATE_DISAPPEARS_EARLY = (int)roundf(25 * m_fps);
714  const int TEMPLATE_DISAPPEARS_LATE = (int)roundf(0 * m_fps);
715  const int TEMPLATE_REAPPEARS_LATE = (int)roundf(35 * m_fps);
716  const int TEMPLATE_REAPPEARS_EARLY = (int)roundf(1.5F * m_fps);
717 
718  LOG(VB_COMMFLAG, LOG_INFO, QString("TemplateMatcher adjusting for blanks"));
719 
720  FrameAnalyzer::FrameMap::Iterator ii = m_breakMap.begin();
721  long long prevbrke = 0;
722  while (ii != m_breakMap.end())
723  {
724  FrameAnalyzer::FrameMap::Iterator iinext = ii;
725  ++iinext;
726 
727  /*
728  * Where the template disappears, look for nearby blank frames. Only
729  * adjust beginning-of-breaks that are not at the very beginning. Do
730  * not search into adjacent breaks.
731  */
732  const long long brkb = ii.key();
733  const long long brke = brkb + *ii;
734  FrameAnalyzer::FrameMap::const_iterator jj = blankMap->constEnd();
735  if (brkb > 0)
736  {
737  jj = frameMapSearchForwards(blankMap,
738  max(prevbrke,
739  brkb - max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_LATE)),
740  min(brke,
741  brkb + max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_EARLY)));
742  }
743  long long newbrkb = brkb;
744  if (jj != blankMap->constEnd())
745  {
746  newbrkb = jj.key();
747  long long adj = *jj / 2;
748  if (adj > MAX_BLANK_FRAMES)
749  adj = MAX_BLANK_FRAMES;
750  newbrkb += adj;
751  }
752 
753  /*
754  * Where the template reappears, look for nearby blank frames. Do not
755  * search into adjacent breaks.
756  */
757  FrameAnalyzer::FrameMap::const_iterator kk = frameMapSearchBackwards(
758  blankMap,
759  max(newbrkb,
760  brke - max(BLANK_NEARBY, TEMPLATE_REAPPEARS_LATE)),
761  min(iinext == m_breakMap.end() ? nframes : iinext.key(),
762  brke + max(BLANK_NEARBY, TEMPLATE_REAPPEARS_EARLY)));
763  long long newbrke = brke;
764  if (kk != blankMap->constEnd())
765  {
766  newbrke = kk.key();
767  long long adj = *kk;
768  newbrke += adj;
769  adj /= 2;
770  if (adj > MAX_BLANK_FRAMES)
771  adj = MAX_BLANK_FRAMES;
772  newbrke -= adj;
773  }
774 
775  /*
776  * Adjust breakMap for blank frames.
777  */
778  long long newbrklen = newbrke - newbrkb;
779  if (newbrkb != brkb)
780  {
781  m_breakMap.erase(ii);
782  if (newbrkb < nframes && newbrklen)
783  m_breakMap.insert(newbrkb, newbrklen);
784  }
785  else if (newbrke != brke)
786  {
787  if (newbrklen)
788  {
789  m_breakMap.remove(newbrkb);
790  m_breakMap.insert(newbrkb, newbrklen);
791  }
792  else
793  m_breakMap.erase(ii);
794  }
795 
796  prevbrke = newbrke;
797  ii = iinext;
798  }
799 
800  /*
801  * Report breaks.
802  */
803  frameAnalyzerReportMap(&m_breakMap, m_fps, "TM Break");
804  return 0;
805 }
806 
807 int
809 {
810  breaks->clear();
811  for (auto bb = m_breakMap.begin();
812  bb != m_breakMap.end();
813  ++bb)
814  {
815  breaks->insert(bb.key(), *bb);
816  }
817  return 0;
818 }
819 
820 /* vim: set expandtab tabstop=4 shiftwidth=4: */
bool removeShortBreaks(FrameAnalyzer::FrameMap *breakMap, float fps, int minbreaklen, bool verbose)
#define MAX_BLANK_FRAMES
FrameAnalyzer::FrameMap m_breakMap
int reportTime(void) const override
#define PGM_CONVERT_GREYSCALE
Definition: PGMConverter.h:27
Definition: cc.h:13
static void error(const char *str,...)
Definition: vbi.c:42
#define timeradd(a, b, result)
Definition: compat.h:300
static const long long NEXTFRAME
Definition: FrameAnalyzer.h:57
struct AVFrame AVFrame
unsigned short * m_matches
static int sort_ascending(const void *aa, const void *bb)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
bool removeShortSegments(FrameAnalyzer::FrameMap *breakMap, long long nframes, float fps, int minseglen, bool verbose)
uint32_t freq[4]
Definition: element.c:44
enum analyzeFrameResult analyzeFrame(const VideoFrame *frame, long long frameno, long long *pNextFrame) override
FrameAnalyzer::FrameMap::const_iterator frameMapSearchBackwards(const FrameAnalyzer::FrameMap *frameMap, long long markbegin, long long mark)
int adjustForBlanks(const BlankFrameDetector *blankFrameDetector, long long nframes)
enum analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes) override
PGMConverter * m_pgmConverter
const struct AVFrame * m_tmpl
TemplateFinder * m_templateFinder
int computeBreaks(FrameMap *breaks)
int MythPlayerInited(const MythPlayer *player)
unsigned char * m_match
const struct AVFrame * getTemplate(int *prow, int *pcol, int *pwidth, int *pheight) const
float GetFrameRate(void) const
Definition: mythplayer.h:190
EdgeDetector * m_edgeDetector
unsigned int uint
Definition: compat.h:140
const FrameAnalyzer::FrameMap * getBlanks(void) const
int FILE
Definition: mythburn.py:110
int templateCoverage(long long nframes, bool final) const
void frameAnalyzerReportMap(const FrameAnalyzer::FrameMap *frameMap, float fps, const char *comment)
TemplateMatcher(PGMConverter *pgmc, EdgeDetector *ed, TemplateFinder *tf, const QString &debugdir)
int GetNumSetting(const QString &key, int defaultval=0)
QMap< long long, long long > FrameMap
Definition: FrameAnalyzer.h:43
void createDebugDirectory(const QString &dirname, const QString &comment)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
long long frameAnalyzerMapSum(const FrameAnalyzer::FrameMap *frameMap)
int pgm_crop(AVFrame *dst, const AVFrame *src, int srcheight, int srcrow, int srccol, int cropwidth, int cropheight)
Definition: pgm.cpp:157
FrameAnalyzer::FrameMap::const_iterator frameMapSearchForwards(const FrameAnalyzer::FrameMap *frameMap, long long mark, long long markend)
MythPlayer * m_player
QString strftimeval(const struct timeval *tv)
static guint32 * p2
Definition: goom_core.c:35
int finished(long long nframes, bool final) override
#define timersub(a, b, result)
Definition: compat.h:310
virtual const AVFrame * detectEdges(const AVFrame *pgm, int pgmheight, int percentile)=0
const AVFrame * getImage(const VideoFrame *frame, long long frameno, int *pwidth, int *pheight)
static guint32 * p1
Definition: goom_core.c:35
int reportTime(void)