|
MythTV
0.26-pre
|
00001 // POSIX headers 00002 #include <sys/time.h> /* gettimeofday */ 00003 00004 // ANSI C headers 00005 #include <cstdlib> 00006 #include <cmath> 00007 00008 // C++ headers 00009 #include <algorithm> 00010 using namespace std; 00011 00012 // Qt headers 00013 #include <QFile> 00014 #include <QFileInfo> 00015 00016 // MythTV headers 00017 #include "mythplayer.h" 00018 #include "mythcorecontext.h" 00019 #include "mythlogging.h" 00020 00021 // Commercial Flagging headers 00022 #include "CommDetector2.h" 00023 #include "FrameAnalyzer.h" 00024 #include "pgm.h" 00025 #include "PGMConverter.h" 00026 #include "EdgeDetector.h" 00027 #include "BlankFrameDetector.h" 00028 #include "TemplateFinder.h" 00029 #include "TemplateMatcher.h" 00030 00031 using namespace commDetector2; 00032 using namespace frameAnalyzer; 00033 00034 namespace { 00035 00036 int pgm_set(const AVPicture *pict, int height) 00037 { 00038 const int width = pict->linesize[0]; 00039 const int size = height * width; 00040 int score, ii; 00041 00042 score = 0; 00043 for (ii = 0; ii < size; ii++) 00044 if (pict->data[0][ii]) 00045 score++; 00046 return score; 00047 } 00048 00049 int pgm_match(const AVPicture *tmpl, const AVPicture *test, int height, 00050 int radius, unsigned short *pscore) 00051 { 00052 /* Return the number of matching "edge" and non-edge pixels. */ 00053 const int width = tmpl->linesize[0]; 00054 int score, rr, cc; 00055 00056 if (width != test->linesize[0]) 00057 { 00058 LOG(VB_COMMFLAG, LOG_ERR, 00059 QString("pgm_match widths don't match: %1 != %2") 00060 .arg(width).arg(test->linesize[0])); 00061 return -1; 00062 } 00063 00064 score = 0; 00065 for (rr = 0; rr < height; rr++) 00066 { 00067 for (cc = 0; cc < width; cc++) 00068 { 00069 int r2min, r2max, r2, c2min, c2max, c2; 00070 00071 if (!tmpl->data[0][rr * width + cc]) 00072 continue; 00073 00074 r2min = max(0, rr - radius); 00075 r2max = min(height, rr + radius); 00076 00077 c2min = max(0, cc - radius); 00078 c2max = min(width, cc + radius); 00079 00080 for (r2 = r2min; r2 <= r2max; r2++) 00081 { 00082 for (c2 = c2min; c2 <= c2max; c2++) 00083 { 00084 if (test->data[0][r2 * width + c2]) 00085 { 00086 score++; 00087 goto next_pixel; 00088 } 00089 } 00090 } 00091 next_pixel: 00092 ; 00093 } 00094 } 00095 00096 *pscore = score; 00097 return 0; 00098 } 00099 00100 bool readMatches(QString filename, unsigned short *matches, long long nframes) 00101 { 00102 FILE *fp; 00103 long long frameno; 00104 00105 QByteArray fname = filename.toLocal8Bit(); 00106 if (!(fp = fopen(fname.constData(), "r"))) 00107 return false; 00108 00109 for (frameno = 0; frameno < nframes; frameno++) 00110 { 00111 int nitems = fscanf(fp, "%20hu", &matches[frameno]); 00112 if (nitems != 1) 00113 { 00114 LOG(VB_COMMFLAG, LOG_ERR, 00115 QString("Not enough data in %1: frame %2") 00116 .arg(filename).arg(frameno)); 00117 goto error; 00118 } 00119 } 00120 00121 if (fclose(fp)) 00122 LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2") 00123 .arg(filename).arg(strerror(errno))); 00124 return true; 00125 00126 error: 00127 if (fclose(fp)) 00128 LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2") 00129 .arg(filename).arg(strerror(errno))); 00130 return false; 00131 } 00132 00133 bool writeMatches(QString filename, unsigned short *matches, long long nframes) 00134 { 00135 FILE *fp; 00136 long long frameno; 00137 00138 QByteArray fname = filename.toLocal8Bit(); 00139 if (!(fp = fopen(fname.constData(), "w"))) 00140 return false; 00141 00142 for (frameno = 0; frameno < nframes; frameno++) 00143 (void)fprintf(fp, "%hu\n", matches[frameno]); 00144 00145 if (fclose(fp)) 00146 LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2") 00147 .arg(filename).arg(strerror(errno))); 00148 return true; 00149 } 00150 00151 int finishedDebug(long long nframes, const unsigned short *matches, 00152 const unsigned char *match) 00153 { 00154 unsigned short low, high, score; 00155 long long startframe; 00156 00157 score = matches[0]; 00158 low = score; 00159 high = score; 00160 startframe = 0; 00161 00162 for (long long frameno = 1; frameno < nframes; frameno++) 00163 { 00164 score = matches[frameno]; 00165 if (match[frameno - 1] == match[frameno]) 00166 { 00167 if (score < low) 00168 low = score; 00169 if (score > high) 00170 high = score; 00171 continue; 00172 } 00173 00174 LOG(VB_COMMFLAG, LOG_INFO, QString("Frame %1-%2: %3 L-H: %4-%5 (%6)") 00175 .arg(startframe, 6).arg(frameno - 1, 6) 00176 .arg(match[frameno - 1] ? "logo " : " no-logo") 00177 .arg(low, 4).arg(high, 4).arg(frameno - startframe, 5)); 00178 00179 low = score; 00180 high = score; 00181 startframe = frameno; 00182 } 00183 00184 return 0; 00185 } 00186 00187 int sort_ascending(const void *aa, const void *bb) 00188 { 00189 return *(unsigned short*)aa - *(unsigned short*)bb; 00190 } 00191 00192 long long matchspn(long long nframes, unsigned char *match, long long frameno, 00193 unsigned char acceptval) 00194 { 00195 /* 00196 * strspn(3)-style interface: return the first frame number that does not 00197 * match "acceptval". 00198 */ 00199 while (frameno < nframes && match[frameno] == acceptval) 00200 frameno++; 00201 return frameno; 00202 } 00203 00204 unsigned int range_area(const unsigned short *freq, unsigned short start, 00205 unsigned short end) 00206 { 00207 /* Return the integrated area under the curve of the plotted histogram. */ 00208 const unsigned short width = end - start; 00209 unsigned short matchcnt; 00210 unsigned int sum, nsamples; 00211 00212 sum = 0; 00213 nsamples = 0; 00214 for (matchcnt = start; matchcnt < end; matchcnt++) 00215 { 00216 if (freq[matchcnt]) 00217 { 00218 sum += freq[matchcnt]; 00219 nsamples++; 00220 } 00221 } 00222 if (!nsamples) 00223 return 0; 00224 return width * sum / nsamples; 00225 } 00226 00227 unsigned short pick_mintmpledges(const unsigned short *matches, 00228 long long nframes) 00229 { 00230 /* 00231 * Most frames either match the template very well, or don't match 00232 * very well at all. This allows us to assume a bimodal 00233 * distribution of the values in a frequency histogram of the 00234 * "matches" array. 00235 * 00236 * Return a local minima between the two modes representing the 00237 * threshold value that decides whether or not a frame matches the 00238 * template. 00239 * 00240 * See "mythcommflag-analyze" and its output. 00241 */ 00242 00243 /* 00244 * TUNABLE: 00245 * 00246 * Given a point to test, require the integrated area to the left 00247 * of the point to be greater than some (larger) area to the right 00248 * of the point. 00249 */ 00250 static const float LEFTWIDTH = 0.04; 00251 static const float MIDDLEWIDTH = 0.04; 00252 static const float RIGHTWIDTH = 0.04; 00253 00254 static const float MATCHSTART = 0.20; 00255 static const float MATCHEND = 0.80; 00256 00257 unsigned short matchrange, matchstart, matchend; 00258 unsigned short leftwidth, middlewidth, rightwidth; 00259 unsigned short *sorted, minmatch, maxmatch, *freq; 00260 int nfreq, matchcnt, local_minimum; 00261 unsigned int maxdelta; 00262 00263 sorted = new unsigned short[nframes]; 00264 memcpy(sorted, matches, nframes * sizeof(*matches)); 00265 qsort(sorted, nframes, sizeof(*sorted), sort_ascending); 00266 minmatch = sorted[0]; 00267 maxmatch = sorted[nframes - 1]; 00268 matchrange = maxmatch - minmatch; 00269 /* degenerate minmatch==maxmatch case is gracefully handled */ 00270 00271 leftwidth = (unsigned short)(LEFTWIDTH * matchrange); 00272 middlewidth = (unsigned short)(MIDDLEWIDTH * matchrange); 00273 rightwidth = (unsigned short)(RIGHTWIDTH * matchrange); 00274 00275 nfreq = maxmatch + 1; 00276 freq = new unsigned short[nfreq]; 00277 memset(freq, 0, nfreq * sizeof(*freq)); 00278 for (long long frameno = 0; frameno < nframes; frameno++) 00279 freq[matches[frameno]]++; /* freq[<matchcnt>] = <framecnt> */ 00280 00281 matchstart = minmatch + (unsigned short)(MATCHSTART * matchrange); 00282 matchend = minmatch + (unsigned short)(MATCHEND * matchrange); 00283 00284 local_minimum = matchstart; 00285 maxdelta = 0; 00286 for (matchcnt = matchstart + leftwidth + middlewidth / 2; 00287 matchcnt < matchend - rightwidth - middlewidth / 2; 00288 matchcnt++) 00289 { 00290 unsigned short p0, p1, p2, p3; 00291 unsigned int leftscore, middlescore, rightscore; 00292 00293 p0 = matchcnt - leftwidth - middlewidth / 2; 00294 p1 = p0 + leftwidth; 00295 p2 = p1 + middlewidth; 00296 p3 = p2 + rightwidth; 00297 00298 leftscore = range_area(freq, p0, p1); 00299 middlescore = range_area(freq, p1, p2); 00300 rightscore = range_area(freq, p2, p3); 00301 if (middlescore < leftscore && middlescore < rightscore) 00302 { 00303 unsigned int delta = (leftscore - middlescore) + 00304 (rightscore - middlescore); 00305 if (delta > maxdelta) 00306 { 00307 local_minimum = matchcnt; 00308 maxdelta = delta; 00309 } 00310 } 00311 } 00312 00313 LOG(VB_COMMFLAG, LOG_INFO, 00314 QString("pick_mintmpledges minmatch=%1 maxmatch=%2" 00315 " matchstart=%3 matchend=%4 widths=%5,%6,%7 local_minimum=%8") 00316 .arg(minmatch).arg(maxmatch).arg(matchstart).arg(matchend) 00317 .arg(leftwidth).arg(middlewidth).arg(rightwidth) 00318 .arg(local_minimum)); 00319 00320 delete []freq; 00321 delete []sorted; 00322 return local_minimum; 00323 } 00324 00325 }; /* namespace */ 00326 00327 TemplateMatcher::TemplateMatcher(PGMConverter *pgmc, EdgeDetector *ed, 00328 TemplateFinder *tf, QString debugdir) : 00329 FrameAnalyzer(), pgmConverter(pgmc), 00330 edgeDetector(ed), templateFinder(tf), 00331 tmpl(0), 00332 tmplrow(-1), tmplcol(-1), 00333 tmplwidth(-1), tmplheight(-1), 00334 matches(NULL), match(NULL), 00335 fps(0.0f), 00336 debugLevel(0), debugdir(debugdir), 00337 #ifdef PGM_CONVERT_GREYSCALE 00338 debugdata(debugdir + "/TemplateMatcher-pgm.txt"), 00339 #else /* !PGM_CONVERT_GREYSCALE */ 00340 debugdata(debugdir + "/TemplateMatcher-yuv.txt"), 00341 #endif /* !PGM_CONVERT_GREYSCALE */ 00342 player(NULL), 00343 debug_matches(false), debug_removerunts(false), 00344 matches_done(false) 00345 { 00346 memset(&cropped, 0, sizeof(cropped)); 00347 memset(&analyze_time, 0, sizeof(analyze_time)); 00348 00349 /* 00350 * debugLevel: 00351 * 0: no debugging 00352 * 1: cache frame edge counts into debugdir [1 file] 00353 * 2: extra verbosity [O(nframes)] 00354 */ 00355 debugLevel = gCoreContext->GetNumSetting("TemplateMatcherDebugLevel", 0); 00356 00357 if (debugLevel >= 1) 00358 { 00359 createDebugDirectory(debugdir, 00360 QString("TemplateMatcher debugLevel %1").arg(debugLevel)); 00361 debug_matches = true; 00362 if (debugLevel >= 2) 00363 debug_removerunts = true; 00364 } 00365 } 00366 00367 TemplateMatcher::~TemplateMatcher(void) 00368 { 00369 if (matches) 00370 delete []matches; 00371 if (match) 00372 delete []match; 00373 avpicture_free(&cropped); 00374 } 00375 00376 enum FrameAnalyzer::analyzeFrameResult 00377 TemplateMatcher::MythPlayerInited(MythPlayer *_player, 00378 long long nframes) 00379 { 00380 player = _player; 00381 fps = player->GetFrameRate(); 00382 00383 if (!(tmpl = templateFinder->getTemplate(&tmplrow, &tmplcol, 00384 &tmplwidth, &tmplheight))) 00385 { 00386 LOG(VB_COMMFLAG, LOG_ERR, 00387 QString("TemplateMatcher::MythPlayerInited: no template")); 00388 return ANALYZE_FATAL; 00389 } 00390 00391 if (avpicture_alloc(&cropped, PIX_FMT_GRAY8, tmplwidth, tmplheight)) 00392 { 00393 LOG(VB_COMMFLAG, LOG_ERR, 00394 QString("TemplateMatcher::MythPlayerInited " 00395 "avpicture_alloc cropped (%1x%2) failed") 00396 .arg(tmplwidth).arg(tmplheight)); 00397 return ANALYZE_FATAL; 00398 } 00399 00400 if (pgmConverter->MythPlayerInited(player)) 00401 goto free_cropped; 00402 00403 matches = new unsigned short[nframes]; 00404 memset(matches, 0, nframes * sizeof(*matches)); 00405 00406 match = new unsigned char[nframes]; 00407 00408 if (debug_matches) 00409 { 00410 if (readMatches(debugdata, matches, nframes)) 00411 { 00412 LOG(VB_COMMFLAG, LOG_INFO, 00413 QString("TemplateMatcher::MythPlayerInited read %1") 00414 .arg(debugdata)); 00415 matches_done = true; 00416 } 00417 } 00418 00419 if (matches_done) 00420 return ANALYZE_FINISHED; 00421 00422 return ANALYZE_OK; 00423 00424 free_cropped: 00425 avpicture_free(&cropped); 00426 return ANALYZE_FATAL; 00427 } 00428 00429 enum FrameAnalyzer::analyzeFrameResult 00430 TemplateMatcher::analyzeFrame(const VideoFrame *frame, long long frameno, 00431 long long *pNextFrame) 00432 { 00433 /* 00434 * TUNABLE: 00435 * 00436 * The matching area should be a lot smaller than the area used by 00437 * TemplateFinder, so use a smaller percentile than the TemplateFinder 00438 * (intensity requirements to be considered an "edge" are lower, because 00439 * there should be less pollution from non-template edges). 00440 * 00441 * Higher values mean fewer edge pixels in the candidate template area; 00442 * occluded or faint templates might be missed. 00443 * 00444 * Lower values mean more edge pixels in the candidate template area; 00445 * non-template edges can be picked up and cause false identification of 00446 * matches. 00447 */ 00448 const int FRAMESGMPCTILE = 70; 00449 00450 /* 00451 * TUNABLE: 00452 * 00453 * The per-pixel search radius for matching the template. Meant to deal 00454 * with template edges that might minimally slide around (such as for 00455 * animated lighting effects). 00456 * 00457 * Higher values will pick up more pixels as matching the template 00458 * (possibly false matches). 00459 * 00460 * Lower values will require more exact template matches, possibly missing 00461 * true matches. 00462 * 00463 * The TemplateMatcher accumulates all its state in the "matches" array to 00464 * be processed later by TemplateMatcher::finished. 00465 */ 00466 const int JITTER_RADIUS = 0; 00467 00468 const AVPicture *pgm; 00469 const AVPicture *edges; 00470 int pgmwidth, pgmheight; 00471 struct timeval start, end, elapsed; 00472 00473 *pNextFrame = NEXTFRAME; 00474 00475 if (!(pgm = pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight))) 00476 goto error; 00477 00478 (void)gettimeofday(&start, NULL); 00479 00480 if (pgm_crop(&cropped, pgm, pgmheight, tmplrow, tmplcol, 00481 tmplwidth, tmplheight)) 00482 goto error; 00483 00484 if (!(edges = edgeDetector->detectEdges(&cropped, tmplheight, 00485 FRAMESGMPCTILE))) 00486 goto error; 00487 00488 if (pgm_match(tmpl, edges, tmplheight, JITTER_RADIUS, &matches[frameno])) 00489 goto error; 00490 00491 (void)gettimeofday(&end, NULL); 00492 timersub(&end, &start, &elapsed); 00493 timeradd(&analyze_time, &elapsed, &analyze_time); 00494 00495 return ANALYZE_OK; 00496 00497 error: 00498 LOG(VB_COMMFLAG, LOG_ERR, 00499 QString("TemplateMatcher::analyzeFrame error at frame %1 of %2") 00500 .arg(frameno)); 00501 return ANALYZE_ERROR; 00502 } 00503 00504 int 00505 TemplateMatcher::finished(long long nframes, bool final) 00506 { 00507 /* 00508 * TUNABLE: 00509 * 00510 * Eliminate false negatives and false positives by eliminating breaks and 00511 * segments shorter than these periods, subject to maximum bounds. 00512 * 00513 * Higher values could eliminate real breaks or segments entirely. 00514 * Lower values can yield more false "short" breaks or segments. 00515 */ 00516 const int MINBREAKLEN = (int)roundf(45 * fps); /* frames */ 00517 const int MINSEGLEN = (int)roundf(105 * fps); /* frames */ 00518 00519 int tmpledges, mintmpledges; 00520 int minbreaklen, minseglen; 00521 long long brkb; 00522 FrameAnalyzer::FrameMap::Iterator bb; 00523 00524 if (!matches_done && debug_matches) 00525 { 00526 if (final && writeMatches(debugdata, matches, nframes)) 00527 { 00528 LOG(VB_COMMFLAG, LOG_INFO, 00529 QString("TemplateMatcher::finished wrote %1") .arg(debugdata)); 00530 matches_done = true; 00531 } 00532 } 00533 00534 tmpledges = pgm_set(tmpl, tmplheight); 00535 mintmpledges = pick_mintmpledges(matches, nframes); 00536 00537 LOG(VB_COMMFLAG, LOG_INFO, 00538 QString("TemplateMatcher::finished %1x%2@(%3,%4)," 00539 " %5 edge pixels, want %6") 00540 .arg(tmplwidth).arg(tmplheight).arg(tmplcol).arg(tmplrow) 00541 .arg(tmpledges).arg(mintmpledges)); 00542 00543 for (long long ii = 0; ii < nframes; ii++) 00544 match[ii] = matches[ii] >= mintmpledges ? 1 : 0; 00545 00546 if (debugLevel >= 2) 00547 { 00548 if (final && finishedDebug(nframes, matches, match)) 00549 goto error; 00550 } 00551 00552 /* 00553 * Construct breaks; find the first logo break. 00554 */ 00555 breakMap.clear(); 00556 brkb = match[0] ? matchspn(nframes, match, 0, match[0]) : 0; 00557 while (brkb < nframes) 00558 { 00559 /* Skip over logo-less frames; find the next logo frame (brke). */ 00560 long long brke = matchspn(nframes, match, brkb, match[brkb]); 00561 long long brklen = brke - brkb; 00562 breakMap.insert(brkb, brklen); 00563 00564 /* Find the next logo break. */ 00565 brkb = matchspn(nframes, match, brke, match[brke]); 00566 } 00567 00568 /* Clean up the "noise". */ 00569 minbreaklen = 1; 00570 minseglen = 1; 00571 for (;;) 00572 { 00573 bool f1 = false; 00574 bool f2 = false; 00575 if (minbreaklen <= MINBREAKLEN) 00576 { 00577 f1 = removeShortBreaks(&breakMap, fps, minbreaklen, 00578 debug_removerunts); 00579 minbreaklen++; 00580 } 00581 if (minseglen <= MINSEGLEN) 00582 { 00583 f2 = removeShortSegments(&breakMap, nframes, fps, minseglen, 00584 debug_removerunts); 00585 minseglen++; 00586 } 00587 if (minbreaklen > MINBREAKLEN && minseglen > MINSEGLEN) 00588 break; 00589 if (debug_removerunts && (f1 || f2)) 00590 frameAnalyzerReportMap(&breakMap, fps, "** TM Break"); 00591 } 00592 00593 /* 00594 * Report breaks. 00595 */ 00596 frameAnalyzerReportMap(&breakMap, fps, "TM Break"); 00597 00598 return 0; 00599 00600 error: 00601 return -1; 00602 } 00603 00604 int 00605 TemplateMatcher::reportTime(void) const 00606 { 00607 if (pgmConverter->reportTime()) 00608 return -1; 00609 00610 LOG(VB_COMMFLAG, LOG_INFO, QString("TM Time: analyze=%1s") 00611 .arg(strftimeval(&analyze_time))); 00612 return 0; 00613 } 00614 00615 int 00616 TemplateMatcher::templateCoverage(long long nframes, bool final) const 00617 { 00618 /* 00619 * Return <0 for too little logo coverage (some content has no logo), 0 for 00620 * "correct" coverage, and >0 for too much coverage (some commercials have 00621 * logos). 00622 */ 00623 00624 /* 00625 * TUNABLE: 00626 * 00627 * Expect 20%-45% of total length to be commercials. 00628 */ 00629 const int MINBREAKS = nframes * 20 / 100; 00630 const int MAXBREAKS = nframes * 45 / 100; 00631 00632 const long long brklen = frameAnalyzerMapSum(&breakMap); 00633 const bool good = brklen >= MINBREAKS && brklen <= MAXBREAKS; 00634 00635 if (debugLevel >= 1) 00636 { 00637 if (!tmpl) 00638 { 00639 LOG(VB_COMMFLAG, LOG_ERR, 00640 QString("TemplateMatcher: no template (wanted %2-%3%)") 00641 .arg(100 * MINBREAKS / nframes) 00642 .arg(100 * MAXBREAKS / nframes)); 00643 } 00644 else if (!final) 00645 { 00646 LOG(VB_COMMFLAG, LOG_ERR, 00647 QString("TemplateMatcher has %1% breaks (real-time flagging)") 00648 .arg(100 * brklen / nframes)); 00649 } 00650 else if (good) 00651 { 00652 LOG(VB_COMMFLAG, LOG_INFO, QString("TemplateMatcher has %1% breaks") 00653 .arg(100 * brklen / nframes)); 00654 } 00655 else 00656 { 00657 LOG(VB_COMMFLAG, LOG_INFO, 00658 QString("TemplateMatcher has %1% breaks (wanted %2-%3%)") 00659 .arg(100 * brklen / nframes) 00660 .arg(100 * MINBREAKS / nframes) 00661 .arg(100 * MAXBREAKS / nframes)); 00662 } 00663 } 00664 00665 if (!final) 00666 return 0; /* real-time flagging */ 00667 00668 return brklen < MINBREAKS ? 1 : brklen <= MAXBREAKS ? 0 : -1; 00669 } 00670 00671 int 00672 TemplateMatcher::adjustForBlanks(const BlankFrameDetector *blankFrameDetector, 00673 long long nframes) 00674 { 00675 const FrameAnalyzer::FrameMap *blankMap = blankFrameDetector->getBlanks(); 00676 00677 /* 00678 * TUNABLE: 00679 * 00680 * Tune the search for blank frames near appearances and disappearances of 00681 * the template. 00682 * 00683 * TEMPLATE_DISAPPEARS_EARLY: Handle the scenario where the template can 00684 * disappear "early" before switching to commercial. Delay the beginning of 00685 * the commercial break by searching forwards to find a blank frame. 00686 * 00687 * Setting this value too low can yield false positives. If template 00688 * does indeed disappear "early" (common in US broadcast), the end of 00689 * the broadcast segment can be misidentified as part of the beginning 00690 * of the commercial break. 00691 * 00692 * Setting this value too high can yield or worsen false negatives. If 00693 * the template presence extends at all into the commercial break, 00694 * immediate cuts to commercial without any intervening blank frames 00695 * can cause the broadcast to "continue" even further into the 00696 * commercial break. 00697 * 00698 * TEMPLATE_DISAPPEARS_LATE: Handle the scenario where the template 00699 * disappears "late" after having already switched to commercial (template 00700 * presence extends into commercial break). Accelerate the beginning of the 00701 * commercial break by searching backwards to find a blank frame. 00702 * 00703 * Setting this value too low can yield false negatives. If the 00704 * template does extend deep into the commercial break, the first part 00705 * of the commercial break can be misidentifed as part of the 00706 * broadcast. 00707 * 00708 * Setting this value too high can yield or worsen false positives. If 00709 * the template disappears extremely early (too early for 00710 * TEMPLATE_DISAPPEARS_EARLY), blank frames before the template 00711 * disappears can cause even more of the end of the broadcast segment 00712 * to be misidentified as the first part of the commercial break. 00713 * 00714 * TEMPLATE_REAPPEARS_LATE: Handle the scenario where the template 00715 * reappears "late" after having already returned to the broadcast. 00716 * Accelerate the beginning of the broadcast by searching backwards to find 00717 * a blank frame. 00718 * 00719 * Setting this value too low can yield false positives. If the 00720 * template does indeed reappear "late" (common in US broadcast), the 00721 * beginning of the broadcast segment can be misidentified as the end 00722 * of the commercial break. 00723 * 00724 * Setting this value too high can yield or worsen false negatives. If 00725 * the template actually reappears early, blank frames before the 00726 * template reappears can cause even more of the end of the commercial 00727 * break to be misidentified as the first part of the broadcast break. 00728 * 00729 * TEMPLATE_REAPPEARS_EARLY: Handle the scenario where the template 00730 * reappears "early" before resuming the broadcast. Delay the beginning of 00731 * the broadcast by searching forwards to find a blank frame. 00732 * 00733 * Setting this value too low can yield false negatives. If the 00734 * template does reappear "early", the the last part of the commercial 00735 * break can be misidentified as part of the beginning of the 00736 * following broadcast segment. 00737 * 00738 * Setting this value too high can yield or worsen false positives. If 00739 * the template reappears extremely late (too late for 00740 * TEMPLATE_REAPPEARS_LATE), blank frames after the template reappears 00741 * can cause even more of the broadcast segment can be misidentified 00742 * as part of the end of the commercial break. 00743 */ 00744 const int BLANK_NEARBY = (int)roundf(0.5 * fps); 00745 const int TEMPLATE_DISAPPEARS_EARLY = (int)roundf(25 * fps); 00746 const int TEMPLATE_DISAPPEARS_LATE = (int)roundf(0 * fps); 00747 const int TEMPLATE_REAPPEARS_LATE = (int)roundf(35 * fps); 00748 const int TEMPLATE_REAPPEARS_EARLY = (int)roundf(1.5 * fps); 00749 00750 LOG(VB_COMMFLAG, LOG_INFO, QString("TemplateMatcher adjusting for blanks")); 00751 00752 FrameAnalyzer::FrameMap::Iterator ii = breakMap.begin(); 00753 long long prevbrke = 0; 00754 while (ii != breakMap.end()) 00755 { 00756 FrameAnalyzer::FrameMap::Iterator iinext = ii; 00757 ++iinext; 00758 00759 /* 00760 * Where the template disappears, look for nearby blank frames. Only 00761 * adjust beginning-of-breaks that are not at the very beginning. Do 00762 * not search into adjacent breaks. 00763 */ 00764 const long long brkb = ii.key(); 00765 const long long brke = brkb + *ii; 00766 FrameAnalyzer::FrameMap::const_iterator jj = blankMap->constEnd(); 00767 if (brkb > 0) 00768 { 00769 jj = frameMapSearchForwards(blankMap, 00770 max(prevbrke, 00771 brkb - max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_LATE)), 00772 min(brke, 00773 brkb + max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_EARLY))); 00774 } 00775 long long newbrkb = brkb; 00776 if (jj != blankMap->constEnd()) 00777 { 00778 newbrkb = jj.key(); 00779 long long adj = *jj / 2; 00780 if (adj > MAX_BLANK_FRAMES) 00781 adj = MAX_BLANK_FRAMES; 00782 newbrkb += adj; 00783 } 00784 00785 /* 00786 * Where the template reappears, look for nearby blank frames. Do not 00787 * search into adjacent breaks. 00788 */ 00789 FrameAnalyzer::FrameMap::const_iterator kk = frameMapSearchBackwards( 00790 blankMap, 00791 max(newbrkb, 00792 brke - max(BLANK_NEARBY, TEMPLATE_REAPPEARS_LATE)), 00793 min(iinext == breakMap.end() ? nframes : iinext.key(), 00794 brke + max(BLANK_NEARBY, TEMPLATE_REAPPEARS_EARLY))); 00795 long long newbrke = brke; 00796 if (kk != blankMap->constEnd()) 00797 { 00798 newbrke = kk.key(); 00799 long long adj = *kk; 00800 newbrke += adj; 00801 adj /= 2; 00802 if (adj > MAX_BLANK_FRAMES) 00803 adj = MAX_BLANK_FRAMES; 00804 newbrke -= adj; 00805 } 00806 00807 /* 00808 * Adjust breakMap for blank frames. 00809 */ 00810 long long newbrklen = newbrke - newbrkb; 00811 if (newbrkb != brkb) 00812 { 00813 breakMap.erase(ii); 00814 if (newbrkb < nframes && newbrklen) 00815 breakMap.insert(newbrkb, newbrklen); 00816 } 00817 else if (newbrke != brke) 00818 { 00819 if (newbrklen) 00820 { 00821 breakMap.remove(newbrkb); 00822 breakMap.insert(newbrkb, newbrklen); 00823 } 00824 else 00825 breakMap.erase(ii); 00826 } 00827 00828 prevbrke = newbrke; 00829 ii = iinext; 00830 } 00831 00832 /* 00833 * Report breaks. 00834 */ 00835 frameAnalyzerReportMap(&breakMap, fps, "TM Break"); 00836 return 0; 00837 } 00838 00839 int 00840 TemplateMatcher::computeBreaks(FrameAnalyzer::FrameMap *breaks) 00841 { 00842 breaks->clear(); 00843 for (FrameAnalyzer::FrameMap::Iterator bb = breakMap.begin(); 00844 bb != breakMap.end(); 00845 ++bb) 00846 { 00847 breaks->insert(bb.key(), *bb); 00848 } 00849 return 0; 00850 } 00851 00852 /* vim: set expandtab tabstop=4 shiftwidth=4: */
1.7.6.1