MythTV  master
HistogramAnalyzer.cpp
Go to the documentation of this file.
1 // POSIX headers
2 #include <sys/time.h> // for gettimeofday
3 
4 // ANSI C headers
5 #include <cmath>
6 #include <utility>
7 
8 // MythTV headers
9 #include "mythcorecontext.h"
10 #include "mythplayer.h"
11 #include "mythlogging.h"
12 
13 // Commercial Flagging headers
14 #include "CommDetector2.h"
15 #include "FrameAnalyzer.h"
16 #include "PGMConverter.h"
17 #include "BorderDetector.h"
18 #include "quickselect.h"
19 #include "TemplateFinder.h"
20 #include "HistogramAnalyzer.h"
21 
22 using namespace commDetector2;
23 using namespace frameAnalyzer;
24 
25 namespace {
26 
27 bool
28 readData(const QString& filename, float *mean, unsigned char *median, float *stddev,
29  int *frow, int *fcol, int *fwidth, int *fheight,
30  HistogramAnalyzer::Histogram *histogram, unsigned char *monochromatic,
31  long long nframes)
32 {
33  std::array<quint32,UCHAR_MAX + 1> counter {};
34 
35  QByteArray fname = filename.toLocal8Bit();
36  FILE *fp = fopen(fname.constData(), "r");
37  if (fp == nullptr)
38  return false;
39 
40  for (long long frameno = 0; frameno < nframes; frameno++)
41  {
42  int monochromaticval = 0;
43  int medianval = 0;
44  int widthval = 0;
45  int heightval = 0;
46  int colval = 0;
47  int rowval = 0;
48  float meanval = NAN;
49  float stddevval = NAN;
50  int nitems = fscanf(fp, "%20d %20f %20d %20f %20d %20d %20d %20d",
51  &monochromaticval, &meanval, &medianval, &stddevval,
52  &widthval, &heightval, &colval, &rowval);
53  if (nitems != 8)
54  {
55  LOG(VB_COMMFLAG, LOG_ERR,
56  QString("Not enough data in %1: frame %2")
57  .arg(filename).arg(frameno));
58  goto error;
59  }
60  if (monochromaticval < 0 || monochromaticval > 1 ||
61  medianval < 0 || (uint)medianval > UCHAR_MAX ||
62  widthval < 0 || heightval < 0 || colval < 0 || rowval < 0)
63  {
64  LOG(VB_COMMFLAG, LOG_ERR,
65  QString("Data out of range in %1: frame %2")
66  .arg(filename).arg(frameno));
67  goto error;
68  }
69  for (uint & ctr : counter)
70  {
71  if (fscanf(fp, "%20x", &ctr) != 1)
72  {
73  LOG(VB_COMMFLAG, LOG_ERR,
74  QString("Not enough data in %1: frame %2")
75  .arg(filename).arg(frameno));
76  goto error;
77  }
78  if (ctr > UCHAR_MAX)
79  {
80  LOG(VB_COMMFLAG, LOG_ERR,
81  QString("Data out of range in %1: frame %2")
82  .arg(filename).arg(frameno));
83  goto error;
84  }
85  }
86  mean[frameno] = meanval;
87  median[frameno] = medianval;
88  stddev[frameno] = stddevval;
89  frow[frameno] = rowval;
90  fcol[frameno] = colval;
91  fwidth[frameno] = widthval;
92  fheight[frameno] = heightval;
93  for (size_t ii = 0; ii < counter.size(); ii++)
94  histogram[frameno][ii] = counter[ii];
95  monochromatic[frameno] = !widthval || !heightval ? 1 : 0;
96  /*
97  * monochromaticval not used; it's written to file for debugging
98  * convenience
99  */
100  }
101  if (fclose(fp))
102  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
103  .arg(filename).arg(strerror(errno)));
104  return true;
105 
106 error:
107  if (fclose(fp))
108  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
109  .arg(filename).arg(strerror(errno)));
110  return false;
111 }
112 
113 bool
114 writeData(const QString& filename, float *mean, unsigned char *median, float *stddev,
115  int *frow, int *fcol, int *fwidth, int *fheight,
116  HistogramAnalyzer::Histogram *histogram, unsigned char *monochromatic,
117  long long nframes)
118 {
119  QByteArray fname = filename.toLocal8Bit();
120  FILE *fp = fopen(fname, "w");
121  if (fp == nullptr)
122  return false;
123  for (long long frameno = 0; frameno < nframes; frameno++)
124  {
125  (void)fprintf(fp, "%3u %10.6f %3u %10.6f %5d %5d %5d %5d",
126  monochromatic[frameno],
127  static_cast<double>(mean[frameno]), median[frameno],
128  static_cast<double>(stddev[frameno]),
129  fwidth[frameno], fheight[frameno],
130  fcol[frameno], frow[frameno]);
131  for (unsigned int ii = 0; ii < UCHAR_MAX + 1; ii++)
132  (void)fprintf(fp, " %02x", histogram[frameno][ii]);
133  (void)fprintf(fp, "\n");
134  }
135  if (fclose(fp))
136  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
137  .arg(filename).arg(strerror(errno)));
138  return true;
139 }
140 
141 }; /* namespace */
142 
143 HistogramAnalyzer::HistogramAnalyzer(std::shared_ptr<PGMConverter> pgmc,
144  std::shared_ptr<BorderDetector> bd,
145  const QString& debugdir)
146  : m_pgmConverter(std::move(pgmc))
147  , m_borderDetector(std::move(bd))
149  , m_debugdata(debugdir + "/HistogramAnalyzer-pgm.txt")
150 #else /* !PGM_CONVERT_GREYSCALE */
151  , m_debugdata(debugdir + "/HistogramAnalyzer-yuv.txt")
152 #endif /* !PGM_CONVERT_GREYSCALE */
153 {
154  /*
155  * debugLevel:
156  * 0: no debugging
157  * 1: cache frame information into debugdata [1 file]
158  */
159  m_debugLevel = gCoreContext->GetNumSetting("HistogramAnalyzerDebugLevel", 0);
160 
161  if (m_debugLevel >= 1)
162  {
163  createDebugDirectory(debugdir,
164  QString("HistogramAnalyzer debugLevel %1").arg(m_debugLevel));
165  m_debugHistVal = true;
166  }
167 }
168 
170 {
171  delete []m_monochromatic;
172  delete []m_mean;
173  delete []m_median;
174  delete []m_stddev;
175  delete []m_fRow;
176  delete []m_fCol;
177  delete []m_fWidth;
178  delete []m_fHeight;
179  delete []m_histogram;
180  delete []m_buf;
181 }
182 
184 HistogramAnalyzer::MythPlayerInited(MythPlayer *player, long long nframes)
185 {
186  if (m_histValDone)
188 
189  if (m_monochromatic)
191 
192  QSize buf_dim = player->GetVideoBufferSize();
193  unsigned int width = buf_dim.width();
194  unsigned int height = buf_dim.height();
195 
198  {
201  }
202  QString details = m_logo ? QString("logo %1x%2@(%3,%4)")
203  .arg(m_logoWidth).arg(m_logoHeight).arg(m_logoCc1).arg(m_logoRr1) :
204  QString("no logo");
205 
206  LOG(VB_COMMFLAG, LOG_INFO,
207  QString("HistogramAnalyzer::MythPlayerInited %1x%2: %3")
208  .arg(width).arg(height).arg(details));
209 
210  if (m_pgmConverter->MythPlayerInited(player))
212 
213  if (m_borderDetector->MythPlayerInited(player))
215 
216  m_mean = new float[nframes];
217  m_median = new unsigned char[nframes];
218  m_stddev = new float[nframes];
219  m_fRow = new int[nframes];
220  m_fCol = new int[nframes];
221  m_fWidth = new int[nframes];
222  m_fHeight = new int[nframes];
223  m_histogram = new Histogram[nframes];
224  m_monochromatic = new unsigned char[nframes];
225 
226  memset(m_mean, 0, nframes * sizeof(*m_mean));
227  memset(m_median, 0, nframes * sizeof(*m_median));
228  memset(m_stddev, 0, nframes * sizeof(*m_stddev));
229  memset(m_fRow, 0, nframes * sizeof(*m_fRow));
230  memset(m_fCol, 0, nframes * sizeof(*m_fCol));
231  memset(m_fWidth, 0, nframes * sizeof(*m_fWidth));
232  memset(m_fHeight, 0, nframes * sizeof(*m_fHeight));
233  memset(m_histogram, 0, nframes * sizeof(*m_histogram));
234  memset(m_monochromatic, 0, nframes * sizeof(*m_monochromatic));
235 
236  unsigned int npixels = width * height;
237  m_buf = new unsigned char[npixels];
238 
239  if (m_debugHistVal)
240  {
241  if (readData(m_debugdata, m_mean, m_median, m_stddev, m_fRow, m_fCol,
243  {
244  LOG(VB_COMMFLAG, LOG_INFO,
245  QString("HistogramAnalyzer::MythPlayerInited read %1")
246  .arg(m_debugdata));
247  m_histValDone = true;
249  }
250  }
251 
253 }
254 
255 void
257 {
258  m_logoFinder = finder;
259 }
260 
262 HistogramAnalyzer::analyzeFrame(const MythVideoFrame *frame, long long frameno)
263 {
264  /*
265  * Various statistical computations over pixel values: mean, median,
266  * (running) standard deviation over sample population.
267  */
268  static constexpr int kDefaultColor = 0;
269 
270  /*
271  * TUNABLE:
272  *
273  * Sampling coarseness of each frame. Higher values will allow analysis to
274  * proceed faster (lower resolution), but might be less accurate. Lower
275  * values will examine more pixels (higher resolution), but will run
276  * slower.
277  */
278  static constexpr int kRInc = 4;
279  static constexpr int kCInc = 4;
280 #define ROUNDUP(a,b) (((a) + (b) - 1) / (b) * (b))
281 
282  int pgmwidth = 0;
283  int pgmheight = 0;
284  bool ismonochromatic = false;
285  int croprow = 0;
286  int cropcol = 0;
287  int cropwidth = 0;
288  int cropheight = 0;
289  unsigned int borderpixels = 0;
290  unsigned int livepixels = 0;
291  unsigned int npixels = 0;
292  unsigned int halfnpixels = 0;
293  unsigned char *pp = nullptr;
294  unsigned char bordercolor = 0;
295  unsigned long long sumval = 0;
296  unsigned long long sumsquares = 0;
297  int rr1 = 0;
298  int cc1 = 0;
299  int rr2 = 0;
300  int cc2 = 0;
301  int rr3 = 0;
302  int cc3 = 0;
303  struct timeval start {};
304  struct timeval end {};
305  struct timeval elapsed {};
306 
307  if (m_lastFrameNo != kUncached && m_lastFrameNo == frameno)
309 
310  const AVFrame *pgm = m_pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight);
311  if (pgm == nullptr)
312  goto error;
313 
314  ismonochromatic = m_borderDetector->getDimensions(pgm, pgmheight, frameno,
315  &croprow, &cropcol, &cropwidth, &cropheight) != 0;
316 
317  gettimeofday(&start, nullptr);
318 
319  m_fRow[frameno] = croprow;
320  m_fCol[frameno] = cropcol;
321  m_fWidth[frameno] = cropwidth;
322  m_fHeight[frameno] = cropheight;
323 
324  if (ismonochromatic)
325  {
326  /* Optimization for monochromatic frames; just sample center area. */
327  croprow = pgmheight * 3 / 8;
328  cropheight = pgmheight / 4;
329  cropcol = pgmwidth * 3 / 8;
330  cropwidth = pgmwidth / 4;
331  }
332 
333  rr1 = ROUNDUP(croprow, kRInc);
334  cc1 = ROUNDUP(cropcol, kCInc);
335  rr2 = ROUNDUP(croprow + cropheight, kRInc);
336  cc2 = ROUNDUP(cropcol + cropwidth, kCInc);
337  rr3 = ROUNDUP(pgmheight, kRInc);
338  cc3 = ROUNDUP(pgmwidth, kCInc);
339 
340  borderpixels = (rr1 / kRInc) * (cc3 / kCInc) + /* top */
341  ((rr2 - rr1) / kRInc) * (cc1 / kCInc) + /* left */
342  ((rr2 - rr1) / kRInc) * ((cc3 - cc2) / kCInc) + /* right */
343  ((rr3 - rr2) / kRInc) * (cc3 / kCInc); /* bottom */
344 
345  pp = &m_buf[borderpixels];
346  m_histVal.fill(0);
347  m_histVal[kDefaultColor] += borderpixels;
348  for (int rr = rr1; rr < rr2; rr += kRInc)
349  {
350  int rroffset = rr * pgmwidth;
351 
352  for (int cc = cc1; cc < cc2; cc += kCInc)
353  {
354  if (m_logo && rr >= m_logoRr1 && rr <= m_logoRr2 &&
355  cc >= m_logoCc1 && cc <= m_logoCc2)
356  continue; /* Exclude logo area from analysis. */
357 
358  unsigned char val = pgm->data[0][rroffset + cc];
359  *pp++ = val;
360  sumval += val;
361  sumsquares += val * val;
362  livepixels++;
363  m_histVal[val]++;
364  }
365  }
366  npixels = borderpixels + livepixels;
367 
368  /* Scale scores down to [0..255]. */
369  halfnpixels = npixels / 2;
370  for (unsigned int color = 0; color < UCHAR_MAX + 1; color++)
371  m_histogram[frameno][color] =
372  (m_histVal[color] * UCHAR_MAX + halfnpixels) / npixels;
373 
374  bordercolor = 0;
375  if (ismonochromatic && livepixels)
376  {
377  /*
378  * Fake up the margin pixels to be of the same color as the sampled
379  * area.
380  */
381  bordercolor = (sumval + livepixels - 1) / livepixels;
382  sumval += borderpixels * bordercolor;
383  sumsquares += borderpixels * bordercolor * bordercolor;
384  }
385 
386  memset(m_buf, bordercolor, borderpixels * sizeof(*m_buf));
387  m_monochromatic[frameno] = ismonochromatic ? 1 : 0;
388  m_mean[frameno] = (float)sumval / npixels;
389  m_median[frameno] = quick_select_median(m_buf, npixels);
390  m_stddev[frameno] = npixels > 1 ?
391  sqrt((sumsquares - (float)sumval * sumval / npixels) / (npixels - 1)) :
392  0;
393 
394  (void)gettimeofday(&end, nullptr);
395  timersub(&end, &start, &elapsed);
396  timeradd(&m_analyzeTime, &elapsed, &m_analyzeTime);
397 
398  m_lastFrameNo = frameno;
399 
401 
402 error:
403  LOG(VB_COMMFLAG, LOG_ERR,
404  QString("HistogramAnalyzer::analyzeFrame error at frame %1")
405  .arg(frameno));
406 
408 }
409 
410 int
411 HistogramAnalyzer::finished(long long nframes, bool final)
412 {
414  {
415  if (final && writeData(m_debugdata, m_mean, m_median, m_stddev, m_fRow, m_fCol,
417  {
418  LOG(VB_COMMFLAG, LOG_INFO,
419  QString("HistogramAnalyzer::finished wrote %1")
420  .arg(m_debugdata));
421  m_histValDone = true;
422  }
423  }
424 
425  return 0;
426 }
427 
428 int
430 {
431  if (m_pgmConverter->reportTime())
432  return -1;
433 
434  if (m_borderDetector->reportTime())
435  return -1;
436 
437  LOG(VB_COMMFLAG, LOG_INFO, QString("HA Time: analyze=%1s")
439  return 0;
440 }
441 
442 /* vim: set expandtab tabstop=4 shiftwidth=4: */
HistogramAnalyzer::m_logoRr1
int m_logoRr1
Definition: HistogramAnalyzer.h:51
else
else
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:477
HistogramAnalyzer::m_logoCc1
int m_logoCc1
Definition: HistogramAnalyzer.h:52
HistogramAnalyzer::m_logoHeight
int m_logoHeight
Definition: HistogramAnalyzer.h:50
error
static void error(const char *str,...)
Definition: vbi.cpp:42
commDetector2::strftimeval
QString strftimeval(const struct timeval *tv)
Definition: CommDetector2.cpp:270
HistogramAnalyzer::m_debugHistVal
bool m_debugHistVal
Definition: HistogramAnalyzer.h:73
FrameAnalyzer::ANALYZE_OK
@ ANALYZE_OK
Definition: FrameAnalyzer.h:37
CommDetector2.h
HistogramAnalyzer::m_histogram
Histogram * m_histogram
Definition: HistogramAnalyzer.h:64
HistogramAnalyzer::finished
int finished(long long nframes, bool final)
Definition: HistogramAnalyzer.cpp:411
cc
Definition: cc.h:10
timeradd
#define timeradd(a, b, result)
Definition: compat.h:302
arg
arg(title).arg(filename).arg(doDelete))
quick_select_median
unsigned char quick_select_median(unsigned char *arr, int nelems)
Definition: quickselect.cpp:66
quickselect.h
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
mythplayer.h
MythPlayer
Definition: mythplayer.h:83
HistogramAnalyzer::m_pgmConverter
std::shared_ptr< PGMConverter > m_pgmConverter
Definition: HistogramAnalyzer.h:44
HistogramAnalyzer::reportTime
int reportTime(void) const
Definition: HistogramAnalyzer.cpp:429
HistogramAnalyzer::analyzeFrame
enum FrameAnalyzer::analyzeFrameResult analyzeFrame(const MythVideoFrame *frame, long long frameno)
Definition: HistogramAnalyzer.cpp:262
commDetector2::createDebugDirectory
void createDebugDirectory(const QString &dirname, const QString &comment)
Definition: CommDetector2.cpp:230
mythburn.FILE
int FILE
Definition: mythburn.py:139
HistogramAnalyzer::m_logoRr2
int m_logoRr2
Definition: HistogramAnalyzer.h:53
AVFrame
struct AVFrame AVFrame
Definition: BorderDetector.h:15
TemplateFinder
Definition: TemplateFinder.h:31
mythlogging.h
HistogramAnalyzer::m_fCol
int * m_fCol
Definition: HistogramAnalyzer.h:61
PGM_CONVERT_GREYSCALE
#define PGM_CONVERT_GREYSCALE
Definition: PGMConverter.h:28
PGMConverter.h
HistogramAnalyzer::m_logoFinder
TemplateFinder * m_logoFinder
Definition: HistogramAnalyzer.h:47
MythPlayer::GetVideoBufferSize
QSize GetVideoBufferSize(void) const
Definition: mythplayer.h:129
HistogramAnalyzer::m_fRow
int * m_fRow
Definition: HistogramAnalyzer.h:60
ROUNDUP
#define ROUNDUP(a, b)
HistogramAnalyzer::m_analyzeTime
Definition: HistogramAnalyzer.h:75
HistogramAnalyzer::m_histVal
std::array< int, UCHAR_MAX+1 > m_histVal
Definition: HistogramAnalyzer.h:66
filename
QString filename
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:637
timersub
#define timersub(a, b, result)
Definition: compat.h:312
FrameAnalyzer::analyzeFrameResult
analyzeFrameResult
Definition: FrameAnalyzer.h:36
FrameAnalyzer.h
HistogramAnalyzer::m_logo
const struct AVFrame * m_logo
Definition: HistogramAnalyzer.h:48
FrameAnalyzer::ANALYZE_ERROR
@ ANALYZE_ERROR
Definition: FrameAnalyzer.h:38
uint
unsigned int uint
Definition: compat.h:141
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:60
HistogramAnalyzer::m_logoWidth
int m_logoWidth
Definition: HistogramAnalyzer.h:49
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:933
HistogramAnalyzer::m_fWidth
int * m_fWidth
Definition: HistogramAnalyzer.h:62
HistogramAnalyzer::setLogoState
void setLogoState(TemplateFinder *finder)
Definition: HistogramAnalyzer.cpp:256
HistogramAnalyzer::m_debugLevel
int m_debugLevel
Definition: HistogramAnalyzer.h:71
HistogramAnalyzer::m_monochromatic
unsigned char * m_monochromatic
Definition: HistogramAnalyzer.h:65
BorderDetector.h
HistogramAnalyzer::m_fHeight
int * m_fHeight
Definition: HistogramAnalyzer.h:63
HistogramAnalyzer::m_borderDetector
std::shared_ptr< BorderDetector > m_borderDetector
Definition: HistogramAnalyzer.h:45
HistogramAnalyzer::m_histValDone
bool m_histValDone
Definition: HistogramAnalyzer.h:74
HistogramAnalyzer::Histogram
std::array< uint8_t, UCHAR_MAX+1 > Histogram
Definition: HistogramAnalyzer.h:35
HistogramAnalyzer.h
mythcorecontext.h
HistogramAnalyzer::m_stddev
float * m_stddev
Definition: HistogramAnalyzer.h:59
HistogramAnalyzer::m_debugdata
QString m_debugdata
Definition: HistogramAnalyzer.h:72
HistogramAnalyzer::m_logoCc2
int m_logoCc2
Definition: HistogramAnalyzer.h:54
Histogram
Definition: Histogram.h:7
FrameAnalyzer::ANALYZE_FATAL
@ ANALYZE_FATAL
Definition: FrameAnalyzer.h:40
commDetector2
Definition: CommDetector2.cpp:194
FrameAnalyzer::ANALYZE_FINISHED
@ ANALYZE_FINISHED
Definition: FrameAnalyzer.h:39
HistogramAnalyzer::m_median
unsigned char * m_median
Definition: HistogramAnalyzer.h:58
frameAnalyzer
Definition: FrameAnalyzer.cpp:7
HistogramAnalyzer::m_mean
float * m_mean
Definition: HistogramAnalyzer.h:57
HistogramAnalyzer::m_lastFrameNo
long long m_lastFrameNo
Definition: HistogramAnalyzer.h:68
TemplateFinder.h
HistogramAnalyzer::m_buf
unsigned char * m_buf
Definition: HistogramAnalyzer.h:67
MythVideoFrame
Definition: mythframe.h:83
HistogramAnalyzer::~HistogramAnalyzer
~HistogramAnalyzer()
Definition: HistogramAnalyzer.cpp:169
TemplateFinder::getTemplate
const struct AVFrame * getTemplate(int *prow, int *pcol, int *pwidth, int *pheight) const
Definition: TemplateFinder.cpp:1049
HistogramAnalyzer::MythPlayerInited
enum FrameAnalyzer::analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes)
Definition: HistogramAnalyzer.cpp:184
hardwareprofile.distros.mythtv_data.data_mythtv.pp
pp
Definition: data_mythtv.py:561
HistogramAnalyzer::HistogramAnalyzer
HistogramAnalyzer(std::shared_ptr< PGMConverter > pgmc, std::shared_ptr< BorderDetector > bd, const QString &debugdir)
Definition: HistogramAnalyzer.cpp:143
HistogramAnalyzer::kUncached
static const long long kUncached
Definition: HistogramAnalyzer.h:28