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(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(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, 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, QString debugdir) :
333  FrameAnalyzer(), pgmConverter(pgmc),
334  edgeDetector(ed), templateFinder(tf),
335  tmpl(nullptr),
336  tmplrow(-1), tmplcol(-1),
337  tmplwidth(-1), tmplheight(-1),
338  matches(nullptr), match(nullptr),
339  fps(0.0f),
340  debugLevel(0), debugdir(debugdir),
342  debugdata(debugdir + "/TemplateMatcher-pgm.txt"),
343 #else /* !PGM_CONVERT_GREYSCALE */
344  debugdata(debugdir + "/TemplateMatcher-yuv.txt"),
345 #endif /* !PGM_CONVERT_GREYSCALE */
346  player(nullptr),
347  debug_matches(false), debug_removerunts(false),
348  matches_done(false)
349 {
350  memset(&cropped, 0, sizeof(cropped));
351  memset(&analyze_time, 0, sizeof(analyze_time));
352 
353  /*
354  * debugLevel:
355  * 0: no debugging
356  * 1: cache frame edge counts into debugdir [1 file]
357  * 2: extra verbosity [O(nframes)]
358  */
359  debugLevel = gCoreContext->GetNumSetting("TemplateMatcherDebugLevel", 0);
360 
361  if (debugLevel >= 1)
362  {
364  QString("TemplateMatcher debugLevel %1").arg(debugLevel));
365  debug_matches = true;
366  if (debugLevel >= 2)
367  debug_removerunts = true;
368  }
369 }
370 
372 {
373  if (matches)
374  delete []matches;
375  if (match)
376  delete []match;
377  av_freep(&cropped.data[0]);
378 }
379 
382  long long nframes)
383 {
384  player = _player;
385  fps = player->GetFrameRate();
386 
388  &tmplwidth, &tmplheight)))
389  {
390  LOG(VB_COMMFLAG, LOG_ERR,
391  QString("TemplateMatcher::MythPlayerInited: no template"));
392  return ANALYZE_FATAL;
393  }
394 
395  if (av_image_alloc(cropped.data, cropped.linesize,
396  tmplwidth, tmplheight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN))
397  {
398  LOG(VB_COMMFLAG, LOG_ERR,
399  QString("TemplateMatcher::MythPlayerInited "
400  "av_image_alloc cropped (%1x%2) failed")
401  .arg(tmplwidth).arg(tmplheight));
402  return ANALYZE_FATAL;
403  }
404 
406  goto free_cropped;
407 
408  matches = new unsigned short[nframes];
409  memset(matches, 0, nframes * sizeof(*matches));
410 
411  match = new unsigned char[nframes];
412 
413  if (debug_matches)
414  {
415  if (readMatches(debugdata, matches, nframes))
416  {
417  LOG(VB_COMMFLAG, LOG_INFO,
418  QString("TemplateMatcher::MythPlayerInited read %1")
419  .arg(debugdata));
420  matches_done = true;
421  }
422  }
423 
424  if (matches_done)
425  return ANALYZE_FINISHED;
426 
427  return ANALYZE_OK;
428 
429 free_cropped:
430  av_freep(&cropped.data[0]);
431  return ANALYZE_FATAL;
432 }
433 
435 TemplateMatcher::analyzeFrame(const VideoFrame *frame, long long frameno,
436  long long *pNextFrame)
437 {
438  /*
439  * TUNABLE:
440  *
441  * The matching area should be a lot smaller than the area used by
442  * TemplateFinder, so use a smaller percentile than the TemplateFinder
443  * (intensity requirements to be considered an "edge" are lower, because
444  * there should be less pollution from non-template edges).
445  *
446  * Higher values mean fewer edge pixels in the candidate template area;
447  * occluded or faint templates might be missed.
448  *
449  * Lower values mean more edge pixels in the candidate template area;
450  * non-template edges can be picked up and cause false identification of
451  * matches.
452  */
453  const int FRAMESGMPCTILE = 70;
454 
455  /*
456  * TUNABLE:
457  *
458  * The per-pixel search radius for matching the template. Meant to deal
459  * with template edges that might minimally slide around (such as for
460  * animated lighting effects).
461  *
462  * Higher values will pick up more pixels as matching the template
463  * (possibly false matches).
464  *
465  * Lower values will require more exact template matches, possibly missing
466  * true matches.
467  *
468  * The TemplateMatcher accumulates all its state in the "matches" array to
469  * be processed later by TemplateMatcher::finished.
470  */
471  const int JITTER_RADIUS = 0;
472 
473  const AVFrame *pgm;
474  const AVFrame *edges;
475  int pgmwidth, pgmheight;
476  struct timeval start, end, elapsed;
477 
478  *pNextFrame = NEXTFRAME;
479 
480  if (!(pgm = pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight)))
481  goto error;
482 
483  (void)gettimeofday(&start, nullptr);
484 
485  if (pgm_crop(&cropped, pgm, pgmheight, tmplrow, tmplcol,
487  goto error;
488 
489  if (!(edges = edgeDetector->detectEdges(&cropped, tmplheight,
490  FRAMESGMPCTILE)))
491  goto error;
492 
493  if (pgm_match(tmpl, edges, tmplheight, JITTER_RADIUS, &matches[frameno]))
494  goto error;
495 
496  (void)gettimeofday(&end, nullptr);
497  timersub(&end, &start, &elapsed);
498  timeradd(&analyze_time, &elapsed, &analyze_time);
499 
500  return ANALYZE_OK;
501 
502 error:
503  LOG(VB_COMMFLAG, LOG_ERR,
504  QString("TemplateMatcher::analyzeFrame error at frame %1 of %2")
505  .arg(frameno));
506  return ANALYZE_ERROR;
507 }
508 
509 int
510 TemplateMatcher::finished(long long nframes, bool final)
511 {
512  /*
513  * TUNABLE:
514  *
515  * Eliminate false negatives and false positives by eliminating breaks and
516  * segments shorter than these periods, subject to maximum bounds.
517  *
518  * Higher values could eliminate real breaks or segments entirely.
519  * Lower values can yield more false "short" breaks or segments.
520  */
521  const int MINBREAKLEN = (int)roundf(45 * fps); /* frames */
522  const int MINSEGLEN = (int)roundf(105 * fps); /* frames */
523 
524  int tmpledges, mintmpledges;
525  int minbreaklen, minseglen;
526  long long brkb;
527  FrameAnalyzer::FrameMap::Iterator bb;
528 
529  if (!matches_done && debug_matches)
530  {
531  if (final && writeMatches(debugdata, matches, nframes))
532  {
533  LOG(VB_COMMFLAG, LOG_INFO,
534  QString("TemplateMatcher::finished wrote %1") .arg(debugdata));
535  matches_done = true;
536  }
537  }
538 
539  tmpledges = pgm_set(tmpl, tmplheight);
540  mintmpledges = pick_mintmpledges(matches, nframes);
541 
542  LOG(VB_COMMFLAG, LOG_INFO,
543  QString("TemplateMatcher::finished %1x%2@(%3,%4),"
544  " %5 edge pixels, want %6")
545  .arg(tmplwidth).arg(tmplheight).arg(tmplcol).arg(tmplrow)
546  .arg(tmpledges).arg(mintmpledges));
547 
548  for (long long ii = 0; ii < nframes; ii++)
549  match[ii] = matches[ii] >= mintmpledges ? 1 : 0;
550 
551  if (debugLevel >= 2)
552  {
553  if (final && finishedDebug(nframes, matches, match))
554  goto error;
555  }
556 
557  /*
558  * Construct breaks; find the first logo break.
559  */
560  breakMap.clear();
561  brkb = match[0] ? matchspn(nframes, match, 0, match[0]) : 0;
562  while (brkb < nframes)
563  {
564  /* Skip over logo-less frames; find the next logo frame (brke). */
565  long long brke = matchspn(nframes, match, brkb, match[brkb]);
566  long long brklen = brke - brkb;
567  breakMap.insert(brkb, brklen);
568 
569  /* Find the next logo break. */
570  brkb = matchspn(nframes, match, brke, match[brke]);
571  }
572 
573  /* Clean up the "noise". */
574  minbreaklen = 1;
575  minseglen = 1;
576  for (;;)
577  {
578  bool f1 = false;
579  bool f2 = false;
580  if (minbreaklen <= MINBREAKLEN)
581  {
582  f1 = removeShortBreaks(&breakMap, fps, minbreaklen,
584  minbreaklen++;
585  }
586  if (minseglen <= MINSEGLEN)
587  {
588  f2 = removeShortSegments(&breakMap, nframes, fps, minseglen,
590  minseglen++;
591  }
592  if (minbreaklen > MINBREAKLEN && minseglen > MINSEGLEN)
593  break;
594  if (debug_removerunts && (f1 || f2))
595  frameAnalyzerReportMap(&breakMap, fps, "** TM Break");
596  }
597 
598  /*
599  * Report breaks.
600  */
601  frameAnalyzerReportMap(&breakMap, fps, "TM Break");
602 
603  return 0;
604 
605 error:
606  return -1;
607 }
608 
609 int
611 {
612  if (pgmConverter->reportTime())
613  return -1;
614 
615  LOG(VB_COMMFLAG, LOG_INFO, QString("TM Time: analyze=%1s")
616  .arg(strftimeval(&analyze_time)));
617  return 0;
618 }
619 
620 int
621 TemplateMatcher::templateCoverage(long long nframes, bool final) const
622 {
623  /*
624  * Return <0 for too little logo coverage (some content has no logo), 0 for
625  * "correct" coverage, and >0 for too much coverage (some commercials have
626  * logos).
627  */
628 
629  /*
630  * TUNABLE:
631  *
632  * Expect 20%-45% of total length to be commercials.
633  */
634  const int MINBREAKS = nframes * 20 / 100;
635  const int MAXBREAKS = nframes * 45 / 100;
636 
637  const long long brklen = frameAnalyzerMapSum(&breakMap);
638  const bool good = brklen >= MINBREAKS && brklen <= MAXBREAKS;
639 
640  if (debugLevel >= 1)
641  {
642  if (!tmpl)
643  {
644  LOG(VB_COMMFLAG, LOG_ERR,
645  QString("TemplateMatcher: no template (wanted %2-%3%)")
646  .arg(100 * MINBREAKS / nframes)
647  .arg(100 * MAXBREAKS / nframes));
648  }
649  else if (!final)
650  {
651  LOG(VB_COMMFLAG, LOG_ERR,
652  QString("TemplateMatcher has %1% breaks (real-time flagging)")
653  .arg(100 * brklen / nframes));
654  }
655  else if (good)
656  {
657  LOG(VB_COMMFLAG, LOG_INFO, QString("TemplateMatcher has %1% breaks")
658  .arg(100 * brklen / nframes));
659  }
660  else
661  {
662  LOG(VB_COMMFLAG, LOG_INFO,
663  QString("TemplateMatcher has %1% breaks (wanted %2-%3%)")
664  .arg(100 * brklen / nframes)
665  .arg(100 * MINBREAKS / nframes)
666  .arg(100 * MAXBREAKS / nframes));
667  }
668  }
669 
670  if (!final)
671  return 0; /* real-time flagging */
672 
673  return brklen < MINBREAKS ? 1 : brklen <= MAXBREAKS ? 0 : -1;
674 }
675 
676 int
678  long long nframes)
679 {
680  const FrameAnalyzer::FrameMap *blankMap = blankFrameDetector->getBlanks();
681 
682  /*
683  * TUNABLE:
684  *
685  * Tune the search for blank frames near appearances and disappearances of
686  * the template.
687  *
688  * TEMPLATE_DISAPPEARS_EARLY: Handle the scenario where the template can
689  * disappear "early" before switching to commercial. Delay the beginning of
690  * the commercial break by searching forwards to find a blank frame.
691  *
692  * Setting this value too low can yield false positives. If template
693  * does indeed disappear "early" (common in US broadcast), the end of
694  * the broadcast segment can be misidentified as part of the beginning
695  * of the commercial break.
696  *
697  * Setting this value too high can yield or worsen false negatives. If
698  * the template presence extends at all into the commercial break,
699  * immediate cuts to commercial without any intervening blank frames
700  * can cause the broadcast to "continue" even further into the
701  * commercial break.
702  *
703  * TEMPLATE_DISAPPEARS_LATE: Handle the scenario where the template
704  * disappears "late" after having already switched to commercial (template
705  * presence extends into commercial break). Accelerate the beginning of the
706  * commercial break by searching backwards to find a blank frame.
707  *
708  * Setting this value too low can yield false negatives. If the
709  * template does extend deep into the commercial break, the first part
710  * of the commercial break can be misidentifed as part of the
711  * broadcast.
712  *
713  * Setting this value too high can yield or worsen false positives. If
714  * the template disappears extremely early (too early for
715  * TEMPLATE_DISAPPEARS_EARLY), blank frames before the template
716  * disappears can cause even more of the end of the broadcast segment
717  * to be misidentified as the first part of the commercial break.
718  *
719  * TEMPLATE_REAPPEARS_LATE: Handle the scenario where the template
720  * reappears "late" after having already returned to the broadcast.
721  * Accelerate the beginning of the broadcast by searching backwards to find
722  * a blank frame.
723  *
724  * Setting this value too low can yield false positives. If the
725  * template does indeed reappear "late" (common in US broadcast), the
726  * beginning of the broadcast segment can be misidentified as the end
727  * of the commercial break.
728  *
729  * Setting this value too high can yield or worsen false negatives. If
730  * the template actually reappears early, blank frames before the
731  * template reappears can cause even more of the end of the commercial
732  * break to be misidentified as the first part of the broadcast break.
733  *
734  * TEMPLATE_REAPPEARS_EARLY: Handle the scenario where the template
735  * reappears "early" before resuming the broadcast. Delay the beginning of
736  * the broadcast by searching forwards to find a blank frame.
737  *
738  * Setting this value too low can yield false negatives. If the
739  * template does reappear "early", the the last part of the commercial
740  * break can be misidentified as part of the beginning of the
741  * following broadcast segment.
742  *
743  * Setting this value too high can yield or worsen false positives. If
744  * the template reappears extremely late (too late for
745  * TEMPLATE_REAPPEARS_LATE), blank frames after the template reappears
746  * can cause even more of the broadcast segment can be misidentified
747  * as part of the end of the commercial break.
748  */
749  const int BLANK_NEARBY = (int)roundf(0.5f * fps);
750  const int TEMPLATE_DISAPPEARS_EARLY = (int)roundf(25 * fps);
751  const int TEMPLATE_DISAPPEARS_LATE = (int)roundf(0 * fps);
752  const int TEMPLATE_REAPPEARS_LATE = (int)roundf(35 * fps);
753  const int TEMPLATE_REAPPEARS_EARLY = (int)roundf(1.5f * fps);
754 
755  LOG(VB_COMMFLAG, LOG_INFO, QString("TemplateMatcher adjusting for blanks"));
756 
757  FrameAnalyzer::FrameMap::Iterator ii = breakMap.begin();
758  long long prevbrke = 0;
759  while (ii != breakMap.end())
760  {
761  FrameAnalyzer::FrameMap::Iterator iinext = ii;
762  ++iinext;
763 
764  /*
765  * Where the template disappears, look for nearby blank frames. Only
766  * adjust beginning-of-breaks that are not at the very beginning. Do
767  * not search into adjacent breaks.
768  */
769  const long long brkb = ii.key();
770  const long long brke = brkb + *ii;
771  FrameAnalyzer::FrameMap::const_iterator jj = blankMap->constEnd();
772  if (brkb > 0)
773  {
774  jj = frameMapSearchForwards(blankMap,
775  max(prevbrke,
776  brkb - max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_LATE)),
777  min(brke,
778  brkb + max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_EARLY)));
779  }
780  long long newbrkb = brkb;
781  if (jj != blankMap->constEnd())
782  {
783  newbrkb = jj.key();
784  long long adj = *jj / 2;
785  if (adj > MAX_BLANK_FRAMES)
786  adj = MAX_BLANK_FRAMES;
787  newbrkb += adj;
788  }
789 
790  /*
791  * Where the template reappears, look for nearby blank frames. Do not
792  * search into adjacent breaks.
793  */
794  FrameAnalyzer::FrameMap::const_iterator kk = frameMapSearchBackwards(
795  blankMap,
796  max(newbrkb,
797  brke - max(BLANK_NEARBY, TEMPLATE_REAPPEARS_LATE)),
798  min(iinext == breakMap.end() ? nframes : iinext.key(),
799  brke + max(BLANK_NEARBY, TEMPLATE_REAPPEARS_EARLY)));
800  long long newbrke = brke;
801  if (kk != blankMap->constEnd())
802  {
803  newbrke = kk.key();
804  long long adj = *kk;
805  newbrke += adj;
806  adj /= 2;
807  if (adj > MAX_BLANK_FRAMES)
808  adj = MAX_BLANK_FRAMES;
809  newbrke -= adj;
810  }
811 
812  /*
813  * Adjust breakMap for blank frames.
814  */
815  long long newbrklen = newbrke - newbrkb;
816  if (newbrkb != brkb)
817  {
818  breakMap.erase(ii);
819  if (newbrkb < nframes && newbrklen)
820  breakMap.insert(newbrkb, newbrklen);
821  }
822  else if (newbrke != brke)
823  {
824  if (newbrklen)
825  {
826  breakMap.remove(newbrkb);
827  breakMap.insert(newbrkb, newbrklen);
828  }
829  else
830  breakMap.erase(ii);
831  }
832 
833  prevbrke = newbrke;
834  ii = iinext;
835  }
836 
837  /*
838  * Report breaks.
839  */
840  frameAnalyzerReportMap(&breakMap, fps, "TM Break");
841  return 0;
842 }
843 
844 int
846 {
847  breaks->clear();
848  for (FrameAnalyzer::FrameMap::Iterator bb = breakMap.begin();
849  bb != breakMap.end();
850  ++bb)
851  {
852  breaks->insert(bb.key(), *bb);
853  }
854  return 0;
855 }
856 
857 /* vim: set expandtab tabstop=4 shiftwidth=4: */
bool removeShortBreaks(FrameAnalyzer::FrameMap *breakMap, float fps, int minbreaklen, bool verbose)
#define MAX_BLANK_FRAMES
int reportTime(void) const override
#define PGM_CONVERT_GREYSCALE
Definition: PGMConverter.h:28
Definition: cc.h:13
int adjustForBlanks(const BlankFrameDetector *bf, long long nframes)
static void error(const char *str,...)
Definition: vbi.c:41
#define timeradd(a, b, result)
Definition: compat.h:293
struct AVFrame AVFrame
static const long long NEXTFRAME
Definition: FrameAnalyzer.h:58
static int sort_ascending(const void *aa, const void *bb)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
unsigned char * match
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)
enum analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes) override
#define IMAGE_ALIGN
Definition: mythconfig.h:19
TemplateMatcher(PGMConverter *pgmc, EdgeDetector *ed, TemplateFinder *tf, QString debugdir)
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:85
int computeBreaks(FrameMap *breaks)
int MythPlayerInited(const MythPlayer *player)
MythPlayer * player
const struct AVFrame * getTemplate(int *prow, int *pcol, int *pwidth, int *pheight) const
float GetFrameRate(void) const
Definition: mythplayer.h:176
struct timeval analyze_time
const FrameAnalyzer::FrameMap * getBlanks(void) const
int FILE
Definition: mythburn.py:110
unsigned short * matches
int templateCoverage(long long nframes, bool final) const
void frameAnalyzerReportMap(const FrameAnalyzer::FrameMap *frameMap, float fps, const char *comment)
int GetNumSetting(const QString &key, int defaultval=0)
#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
PGMConverter * pgmConverter
FrameAnalyzer::FrameMap::const_iterator frameMapSearchForwards(const FrameAnalyzer::FrameMap *frameMap, long long mark, long long markend)
QString strftimeval(const struct timeval *tv)
static guint32 * p2
Definition: goom_core.c:35
int finished(long long nframes, bool final) override
const struct AVFrame * tmpl
QMap< long long, long long > FrameMap
Definition: FrameAnalyzer.h:44
FrameAnalyzer::FrameMap breakMap
TemplateFinder * templateFinder
#define timersub(a, b, result)
Definition: compat.h:303
const AVFrame * getImage(const VideoFrame *frame, long long frameno, int *pwidth, int *pheight)
static guint32 * p1
Definition: goom_core.c:35
void createDebugDirectory(QString dirname, QString comment)
int reportTime(void)