MythTV  0.26-pre
TemplateMatcher.cpp
Go to the documentation of this file.
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: */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends