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