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