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