00001
00002 #include <cstdlib>
00003 #include <cmath>
00004
00005
00006 #include "mythcorecontext.h"
00007 #include "mythplayer.h"
00008
00009
00010 #include "CommDetector2.h"
00011 #include "FrameAnalyzer.h"
00012 #include "quickselect.h"
00013 #include "HistogramAnalyzer.h"
00014 #include "BlankFrameDetector.h"
00015 #include "TemplateMatcher.h"
00016
00017 using namespace commDetector2;
00018 using namespace frameAnalyzer;
00019
00020 namespace {
00021
00022 bool
00023 isBlank(unsigned char median, float stddev, unsigned char maxmedian,
00024 float maxstddev)
00025 {
00026 return ((median < maxmedian) ||
00027 ((median == maxmedian) && (stddev <= maxstddev)));
00028 }
00029
00030 int
00031 sort_ascending_uchar(const void *aa, const void *bb)
00032 {
00033 return *(unsigned char*)aa - *(unsigned char*)bb;
00034 }
00035
00036 int
00037 sort_ascending_float(const void *aa, const void *bb)
00038 {
00039 float faa = *(float*)aa;
00040 float fbb = *(float*)bb;
00041 return faa < fbb ? -1 : faa == fbb ? 0 : 1;
00042 }
00043
00044 bool
00045 pickmedian(const unsigned char medianval,
00046 unsigned char minval, unsigned char maxval)
00047 {
00048 return medianval >= minval && medianval <= maxval;
00049 }
00050
00051 void
00052 computeBlankMap(FrameAnalyzer::FrameMap *blankMap, long long nframes,
00053 const unsigned char *median, const float *stddev,
00054 const unsigned char *monochromatic)
00055 {
00056
00057
00058
00059
00060 const unsigned char MINBLANKMEDIAN = 1;
00061 const unsigned char MAXBLANKMEDIAN = 96;
00062 const float MEDIANPCTILE = 0.95;
00063 const float STDDEVPCTILE = 0.85;
00064
00065 long long frameno, segb, sege, nblanks;
00066 long long blankno, blankno1, blankno2;
00067 long long stddevno, stddevno1, stddevno2;
00068 unsigned char *blankmedian, maxmedian;
00069 float *blankstddev, maxstddev;
00070
00071
00072
00073 nblanks = 0;
00074 for (frameno = 0; frameno < nframes; frameno++)
00075 {
00076 if (monochromatic[frameno] && pickmedian(median[frameno],
00077 MINBLANKMEDIAN, MAXBLANKMEDIAN))
00078 nblanks++;
00079 }
00080
00081 if (!nblanks)
00082 {
00083
00084 LOG(VB_COMMFLAG, LOG_INFO,
00085 "BlankFrameDetector::computeBlankMap: No blank frames.");
00086 return;
00087 }
00088
00089
00090
00091 blankmedian = new unsigned char[nblanks];
00092 blankstddev = new float[nblanks];
00093 blankno = 0;
00094 for (frameno = 0; frameno < nframes; frameno++)
00095 {
00096 if (monochromatic[frameno] && pickmedian(median[frameno],
00097 MINBLANKMEDIAN, MAXBLANKMEDIAN))
00098 {
00099 blankmedian[blankno] = median[frameno];
00100 blankstddev[blankno] = stddev[frameno];
00101 blankno++;
00102 }
00103 }
00104
00105 qsort(blankmedian, nblanks, sizeof(*blankmedian), sort_ascending_uchar);
00106 blankno = min(nblanks - 1, (long long)roundf(nblanks * MEDIANPCTILE));
00107 maxmedian = blankmedian[blankno];
00108
00109 qsort(blankstddev, nblanks, sizeof(*blankstddev), sort_ascending_float);
00110 stddevno = min(nblanks - 1, (long long)roundf(nblanks * STDDEVPCTILE));
00111 maxstddev = blankstddev[stddevno];
00112
00113
00114
00115 blankno1 = blankno;
00116 blankno2 = blankno;
00117 while (blankno1 > 0 && blankmedian[blankno1] == maxmedian)
00118 blankno1--;
00119 if (blankmedian[blankno1] != maxmedian)
00120 blankno1++;
00121 while (blankno2 < nblanks && blankmedian[blankno2] == maxmedian)
00122 blankno2++;
00123 if (blankno2 == nblanks)
00124 blankno2--;
00125
00126 stddevno1 = stddevno;
00127 stddevno2 = stddevno;
00128 while (stddevno1 > 0 && blankstddev[stddevno1] == maxstddev)
00129 stddevno1--;
00130 if (blankstddev[stddevno1] != maxstddev)
00131 stddevno1++;
00132 while (stddevno2 < nblanks && blankstddev[stddevno2] == maxstddev)
00133 stddevno2++;
00134 if (stddevno2 == nblanks)
00135 stddevno2--;
00136
00137 LOG(VB_COMMFLAG, LOG_INFO,
00138 QString("Blanks selecting median<=%1 (%2-%3%), stddev<=%4 (%5-%6%)")
00139 .arg(maxmedian)
00140 .arg(blankno1 * 100 / nblanks).arg(blankno2 * 100 / nblanks)
00141 .arg(maxstddev)
00142 .arg(stddevno1 * 100 / nblanks).arg(stddevno2 * 100 / nblanks));
00143
00144 delete []blankmedian;
00145 delete []blankstddev;
00146
00147 blankMap->clear();
00148 if (monochromatic[0] && isBlank(median[0], stddev[0], maxmedian, maxstddev))
00149 {
00150 segb = 0;
00151 sege = 0;
00152 }
00153 else
00154 {
00155
00156 blankMap->insert(0, 0);
00157 segb = -1;
00158 sege = -1;
00159 }
00160 for (frameno = 1; frameno < nframes; frameno++)
00161 {
00162 if (monochromatic[frameno] && isBlank(median[frameno], stddev[frameno],
00163 maxmedian, maxstddev))
00164 {
00165
00166 if (sege < frameno - 1)
00167 {
00168
00169 segb = frameno;
00170 sege = frameno;
00171 }
00172 else
00173 {
00174
00175 sege = frameno;
00176 }
00177 }
00178 else if (sege == frameno - 1)
00179 {
00180
00181 long long seglen = frameno - segb;
00182 blankMap->insert(segb, seglen);
00183 }
00184 }
00185 if (sege == frameno - 1)
00186 {
00187
00188 long long seglen = frameno - segb;
00189 blankMap->insert(segb, seglen);
00190 }
00191
00192 FrameAnalyzer::FrameMap::Iterator iiblank = blankMap->end();
00193 --iiblank;
00194 if (iiblank.key() + *iiblank < nframes)
00195 {
00196
00197
00198
00199
00200 blankMap->insert(nframes - 1, 0);
00201 }
00202 }
00203
00204 void
00205 computeBreakMap(FrameAnalyzer::FrameMap *breakMap,
00206 const FrameAnalyzer::FrameMap *blankMap, float fps,
00207 int debugLevel)
00208 {
00209
00210
00211
00212
00213
00214 static const struct {
00215 int len;
00216 int delta;
00217 } breaktype[] = {
00218
00219 { 15, 2 },
00220 { 20, 2 },
00221 { 30, 5 },
00222 { 60, 5 },
00223 };
00224 static const unsigned int nbreaktypes =
00225 sizeof(breaktype)/sizeof(*breaktype);
00226
00227
00228
00229
00230
00231
00232
00233 static const int MINCONTENTLEN = (int)roundf(10 * fps);
00234
00235 breakMap->clear();
00236 for (FrameAnalyzer::FrameMap::const_iterator iiblank = blankMap->begin();
00237 iiblank != blankMap->end();
00238 ++iiblank)
00239 {
00240 long long brkb = iiblank.key();
00241 long long iilen = *iiblank;
00242 long long start = brkb + iilen / 2;
00243
00244 for (unsigned int ii = 0; ii < nbreaktypes; ii++)
00245 {
00246
00247 FrameAnalyzer::FrameMap::const_iterator jjblank = iiblank;
00248 for (++jjblank; jjblank != blankMap->end(); ++jjblank)
00249 {
00250 long long brke = jjblank.key();
00251 long long jjlen = *jjblank;
00252 long long end = brke + jjlen / 2;
00253
00254 long long testlen = (long long)roundf((end - start) / fps);
00255 if (testlen > breaktype[ii].len + breaktype[ii].delta)
00256 break;
00257
00258 long long delta = testlen - breaktype[ii].len;
00259 if (delta < 0)
00260 delta = 0 - delta;
00261
00262 if (delta > breaktype[ii].delta)
00263 continue;
00264
00265
00266 bool inserted = false;
00267 for (unsigned int jj = 0;; jj++)
00268 {
00269 long long newbrkb = brkb + jj;
00270 if (newbrkb >= brke)
00271 {
00272 LOG(VB_COMMFLAG, LOG_INFO,
00273 QString("BF [%1,%2] ran out of slots")
00274 .arg(brkb).arg(brke - 1));
00275 break;
00276 }
00277 if (breakMap->find(newbrkb) == breakMap->end())
00278 {
00279 breakMap->insert(newbrkb, brke - newbrkb);
00280 inserted = true;
00281 break;
00282 }
00283 }
00284 if (inserted)
00285 break;
00286 }
00287 }
00288 }
00289
00290 if (debugLevel >= 1)
00291 {
00292 frameAnalyzerReportMap(breakMap, fps, "BF Break");
00293 LOG(VB_COMMFLAG, LOG_INFO,
00294 "BF coalescing overlapping/nearby breaks ...");
00295 }
00296
00297
00298
00299
00300
00301 for (;;)
00302 {
00303 bool coalesced = false;
00304 FrameAnalyzer::FrameMap::iterator iibreak = breakMap->begin();
00305 while (iibreak != breakMap->end())
00306 {
00307 long long iib = iibreak.key();
00308 long long iie = iib + *iibreak;
00309
00310 FrameAnalyzer::FrameMap::iterator jjbreak = iibreak;
00311 ++jjbreak;
00312 if (jjbreak == breakMap->end())
00313 break;
00314
00315 long long jjb = jjbreak.key();
00316 long long jje = jjb + *jjbreak;
00317
00318 if (jjb < iib)
00319 {
00320
00321 ++iibreak;
00322 continue;
00323 }
00324
00325 if (iie + MINCONTENTLEN < jjb)
00326 {
00327
00328 ++iibreak;
00329 continue;
00330 }
00331
00332
00333 if (jje > iie)
00334 {
00335 breakMap->remove(iib);
00336 breakMap->insert(iib, jje - iib);
00337 }
00338 breakMap->erase(jjbreak);
00339 coalesced = true;
00340 iibreak = breakMap->find(iib);
00341 }
00342 if (!coalesced)
00343 break;
00344 }
00345
00346
00347 FrameAnalyzer::FrameMap::iterator iibreak = breakMap->begin();
00348 while (iibreak != breakMap->end())
00349 {
00350 long long iib = iibreak.key();
00351 long long iie = iib + *iibreak;
00352 FrameAnalyzer::FrameMap::iterator jjbreak = iibreak;
00353 ++jjbreak;
00354 breakMap->erase(iibreak);
00355
00356
00357 long long addb = *blankMap->find(iib);
00358 addb = addb / 2;
00359 if (addb > MAX_BLANK_FRAMES)
00360 addb = MAX_BLANK_FRAMES;
00361 iib += addb;
00362
00363 long long adde = *blankMap->find(iie);
00364 iie += adde;
00365 long long sube = adde / 2;
00366 if (sube > MAX_BLANK_FRAMES)
00367 sube = MAX_BLANK_FRAMES;
00368 iie -= sube;
00369 breakMap->insert(iib, iie - iib);
00370 iibreak = jjbreak;
00371 }
00372 }
00373
00374 };
00375
00376 BlankFrameDetector::BlankFrameDetector(HistogramAnalyzer *ha, QString debugdir)
00377 : FrameAnalyzer()
00378 , histogramAnalyzer(ha)
00379 , fps(0.0f)
00380 , debugLevel(0)
00381 {
00382
00383
00384
00385
00386
00387 debugLevel = gCoreContext->GetNumSetting("BlankFrameDetectorDebugLevel", 0);
00388
00389 if (debugLevel >= 1)
00390 createDebugDirectory(debugdir,
00391 QString("BlankFrameDetector debugLevel %1").arg(debugLevel));
00392 }
00393
00394 enum FrameAnalyzer::analyzeFrameResult
00395 BlankFrameDetector::MythPlayerInited(MythPlayer *player, long long nframes)
00396 {
00397 FrameAnalyzer::analyzeFrameResult ares =
00398 histogramAnalyzer->MythPlayerInited(player, nframes);
00399
00400 fps = player->GetFrameRate();
00401
00402 QSize video_disp_dim = player->GetVideoSize();
00403
00404 LOG(VB_COMMFLAG, LOG_INFO,
00405 QString("BlankFrameDetector::MythPlayerInited %1x%2")
00406 .arg(video_disp_dim.width())
00407 .arg(video_disp_dim.height()));
00408
00409 return ares;
00410 }
00411
00412 enum FrameAnalyzer::analyzeFrameResult
00413 BlankFrameDetector::analyzeFrame(const VideoFrame *frame, long long frameno,
00414 long long *pNextFrame)
00415 {
00416 *pNextFrame = NEXTFRAME;
00417
00418 if (histogramAnalyzer->analyzeFrame(frame, frameno) ==
00419 FrameAnalyzer::ANALYZE_OK)
00420 return ANALYZE_OK;
00421
00422 LOG(VB_COMMFLAG, LOG_INFO,
00423 QString("BlankFrameDetector::analyzeFrame error at frame %1")
00424 .arg(frameno));
00425 return ANALYZE_ERROR;
00426 }
00427
00428 int
00429 BlankFrameDetector::finished(long long nframes, bool final)
00430 {
00431 if (histogramAnalyzer->finished(nframes, final))
00432 return -1;
00433
00434 LOG(VB_COMMFLAG, LOG_INFO, QString("BlankFrameDetector::finished(%1)")
00435 .arg(nframes));
00436
00437
00438 computeBlankMap(&blankMap, nframes,
00439 histogramAnalyzer->getMedians(), histogramAnalyzer->getStdDevs(),
00440 histogramAnalyzer->getMonochromatics());
00441 if (debugLevel >= 2)
00442 frameAnalyzerReportMapms(&blankMap, fps, "BF Blank");
00443
00444 return 0;
00445 }
00446
00447 int
00448 BlankFrameDetector::computeForLogoSurplus(
00449 const TemplateMatcher *templateMatcher)
00450 {
00451
00452
00453
00454
00455
00456 const FrameAnalyzer::FrameMap *logoBreakMap = templateMatcher->getBreaks();
00457
00458
00459 const int MAXBLANKADJUSTMENT = (int)roundf(5 * fps);
00460
00461 LOG(VB_COMMFLAG, LOG_INFO, "BlankFrameDetector adjusting for logo surplus");
00462
00463
00464
00465
00466
00467 for (FrameAnalyzer::FrameMap::const_iterator ii =
00468 logoBreakMap->constBegin();
00469 ii != logoBreakMap->constEnd();
00470 ++ii)
00471 {
00472
00473 long long iikey = ii.key();
00474 long long iibb = iikey - MAXBLANKADJUSTMENT;
00475 long long iiee = iikey + MAXBLANKADJUSTMENT;
00476 FrameAnalyzer::FrameMap::Iterator jjfound = blankMap.end();
00477
00478
00479 for (FrameAnalyzer::FrameMap::Iterator jj = blankMap.begin();
00480 jj != blankMap.end();
00481 ++jj)
00482 {
00483 long long jjbb = jj.key();
00484 long long jjee = jjbb + *jj;
00485
00486 if (iiee < jjbb)
00487 break;
00488
00489 if (jjee < iibb)
00490 continue;
00491
00492 jjfound = jj;
00493 if (iikey <= jjbb)
00494 {
00495
00496
00497
00498
00499 break;
00500 }
00501 }
00502
00503
00504 if (jjfound != blankMap.end())
00505 {
00506 long long jjee = jjfound.key() + *jjfound;
00507 blankMap.erase(jjfound);
00508 if (jjee <= iikey)
00509 {
00510
00511 blankMap.remove(iikey);
00512 blankMap.insert(iikey, 1);
00513 }
00514 else
00515 {
00516
00517 blankMap.insert(iikey, jjee - iikey);
00518 }
00519 }
00520
00521
00522 long long kkkey = ii.key() + *ii;
00523 long long kkbb = kkkey - MAXBLANKADJUSTMENT;
00524 long long kkee = kkkey + MAXBLANKADJUSTMENT;
00525 FrameAnalyzer::FrameMap::Iterator mmfound = blankMap.end();
00526
00527
00528 for (FrameAnalyzer::FrameMap::Iterator mm = blankMap.begin();
00529 mm != blankMap.end();
00530 ++mm)
00531 {
00532 long long mmbb = mm.key();
00533 long long mmee = mmbb + *mm;
00534
00535 if (kkee < mmbb)
00536 break;
00537
00538 if (mmee < kkbb)
00539 continue;
00540
00541
00542 if (mmee < kkkey || mmfound == blankMap.end())
00543 mmfound = mm;
00544 if (mmee >= kkkey)
00545 break;
00546 }
00547
00548
00549 if (mmfound != blankMap.end())
00550 {
00551 long long mmbb = mmfound.key();
00552 if (mmbb < kkkey)
00553 {
00554
00555 blankMap.remove(mmbb);
00556 blankMap.insert(mmbb, kkkey - mmbb);
00557 }
00558 else
00559 {
00560
00561 blankMap.erase(mmfound);
00562 blankMap.remove(kkkey - 1);
00563 blankMap.insert(kkkey - 1, 1);
00564 }
00565 }
00566 }
00567
00568
00569
00570
00571 computeBreakMap(&breakMap, &blankMap, fps, debugLevel);
00572
00573
00574
00575
00576
00577 for (FrameAnalyzer::FrameMap::const_iterator ii =
00578 logoBreakMap->constBegin();
00579 ii != logoBreakMap->constEnd();
00580 ++ii)
00581 {
00582 long long iibb = ii.key();
00583 long long iiee = iibb + *ii;
00584 bool overlap = false;
00585
00586 for (FrameAnalyzer::FrameMap::Iterator jj = breakMap.begin();
00587 jj != breakMap.end();
00588 )
00589 {
00590 long long jjbb = jj.key();
00591 long long jjee = jjbb + *jj;
00592 FrameAnalyzer::FrameMap::Iterator jjnext = jj;
00593 ++jjnext;
00594
00595 if (iiee < jjbb)
00596 {
00597 if (!overlap)
00598 {
00599
00600 breakMap.insert(iibb, iiee - iibb);
00601 }
00602 break;
00603 }
00604
00605 if (iibb < jjbb && jjbb < iiee)
00606 {
00607
00608 overlap = true;
00609 breakMap.erase(jj);
00610 breakMap.insert(iibb, max(iiee, jjee) - iibb);
00611 }
00612 else if (jjbb < iibb && iibb < jjee)
00613 {
00614
00615 overlap = true;
00616 if (jjee < iiee)
00617 {
00618 breakMap.remove(jjbb);
00619 breakMap.insert(jjbb, iiee - jjbb);
00620 }
00621 }
00622
00623 jj = jjnext;
00624 }
00625 }
00626
00627 frameAnalyzerReportMap(&breakMap, fps, "BF Break");
00628 return 0;
00629 }
00630
00631 int
00632 BlankFrameDetector::computeForLogoDeficit(
00633 const TemplateMatcher *templateMatcher)
00634 {
00635 (void)templateMatcher;
00636
00637 LOG(VB_COMMFLAG, LOG_INFO, "BlankFrameDetector adjusting for "
00638 "too little logo coverage (unimplemented)");
00639 return 0;
00640 }
00641
00642 int
00643 BlankFrameDetector::computeBreaks(FrameAnalyzer::FrameMap *breaks)
00644 {
00645 if (breakMap.empty())
00646 {
00647
00648 computeBreakMap(&breakMap, &blankMap, fps, debugLevel);
00649 frameAnalyzerReportMap(&breakMap, fps, "BF Break");
00650 }
00651
00652 breaks->clear();
00653 for (FrameAnalyzer::FrameMap::Iterator bb = breakMap.begin();
00654 bb != breakMap.end();
00655 ++bb)
00656 breaks->insert(bb.key(), *bb);
00657
00658 return 0;
00659 }
00660
00661 int
00662 BlankFrameDetector::reportTime(void) const
00663 {
00664 return histogramAnalyzer->reportTime();
00665 }
00666
00667