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