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