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