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 
194  if (m_logoFinder != nullptr)
195  {
198  if (m_logo != nullptr)
199  {
202  }
203  }
204  QString details = m_logo ? QString("logo %1x%2@(%3,%4)")
205  .arg(m_logoWidth).arg(m_logoHeight).arg(m_logoCc1).arg(m_logoRr1) :
206  QString("no logo");
207 
208  LOG(VB_COMMFLAG, LOG_INFO,
209  QString("HistogramAnalyzer::MythPlayerInited %1x%2: %3")
210  .arg(width).arg(height).arg(details));
211 
212  if (m_pgmConverter->MythPlayerInited(player))
214 
215  if (m_borderDetector->MythPlayerInited(player))
217 
218  m_mean = new float[nframes];
219  m_median = new unsigned char[nframes];
220  m_stddev = new float[nframes];
221  m_fRow = new int[nframes];
222  m_fCol = new int[nframes];
223  m_fWidth = new int[nframes];
224  m_fHeight = new int[nframes];
225  m_histogram = new Histogram[nframes];
226  m_monochromatic = new unsigned char[nframes];
227 
228  memset(m_mean, 0, nframes * sizeof(*m_mean));
229  memset(m_median, 0, nframes * sizeof(*m_median));
230  memset(m_stddev, 0, nframes * sizeof(*m_stddev));
231  memset(m_fRow, 0, nframes * sizeof(*m_fRow));
232  memset(m_fCol, 0, nframes * sizeof(*m_fCol));
233  memset(m_fWidth, 0, nframes * sizeof(*m_fWidth));
234  memset(m_fHeight, 0, nframes * sizeof(*m_fHeight));
235  memset(m_histogram, 0, nframes * sizeof(*m_histogram));
236  memset(m_monochromatic, 0, nframes * sizeof(*m_monochromatic));
237 
238  unsigned int npixels = width * height;
239  m_buf = new unsigned char[npixels];
240 
241  if (m_debugHistVal)
242  {
245  {
246  LOG(VB_COMMFLAG, LOG_INFO,
247  QString("HistogramAnalyzer::MythPlayerInited read %1")
248  .arg(m_debugdata));
249  m_histValDone = true;
251  }
252  }
253 
255 }
256 
257 void
259 {
260  m_logoFinder = finder;
261 }
262 
263 static constexpr int ROUNDUP(int a, int b) { return (a + b - 1) / b * b; }
264 
266 HistogramAnalyzer::analyzeFrame(const MythVideoFrame *frame, long long frameno)
267 {
268  /*
269  * Various statistical computations over pixel values: mean, median,
270  * (running) standard deviation over sample population.
271  */
272  static constexpr int kDefaultColor = 0;
273 
274  /*
275  * TUNABLE:
276  *
277  * Sampling coarseness of each frame. Higher values will allow analysis to
278  * proceed faster (lower resolution), but might be less accurate. Lower
279  * values will examine more pixels (higher resolution), but will run
280  * slower.
281  */
282  static constexpr int kRInc = 4;
283  static constexpr int kCInc = 4;
284 
285  int pgmwidth = 0;
286  int pgmheight = 0;
287  bool ismonochromatic = false;
288  int croprow = 0;
289  int cropcol = 0;
290  int cropwidth = 0;
291  int cropheight = 0;
292  unsigned int borderpixels = 0;
293  unsigned int livepixels = 0;
294  unsigned int npixels = 0;
295  unsigned int halfnpixels = 0;
296  unsigned char *pp = nullptr;
297  unsigned char bordercolor = 0;
298  unsigned long long sumval = 0;
299  unsigned long long sumsquares = 0;
300  int rr1 = 0;
301  int cc1 = 0;
302  int rr2 = 0;
303  int cc2 = 0;
304  int rr3 = 0;
305  int cc3 = 0;
306  std::chrono::microseconds start {0us};
307  std::chrono::microseconds end {0us};
308 
309  if (m_lastFrameNo != kUncached && m_lastFrameNo == frameno)
311 
312  const AVFrame *pgm = m_pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight);
313  if (pgm == nullptr)
314  goto error;
315 
316  ismonochromatic = m_borderDetector->getDimensions(pgm, pgmheight, frameno,
317  &croprow, &cropcol, &cropwidth, &cropheight) != 0;
318 
319  start = nowAsDuration<std::chrono::microseconds>();
320 
321  m_fRow[frameno] = croprow;
322  m_fCol[frameno] = cropcol;
323  m_fWidth[frameno] = cropwidth;
324  m_fHeight[frameno] = cropheight;
325 
326  if (ismonochromatic)
327  {
328  /* Optimization for monochromatic frames; just sample center area. */
329  croprow = pgmheight * 3 / 8;
330  cropheight = pgmheight / 4;
331  cropcol = pgmwidth * 3 / 8;
332  cropwidth = pgmwidth / 4;
333  }
334 
335  rr1 = ROUNDUP(croprow, kRInc);
336  cc1 = ROUNDUP(cropcol, kCInc);
337  rr2 = ROUNDUP(croprow + cropheight, kRInc);
338  cc2 = ROUNDUP(cropcol + cropwidth, kCInc);
339  rr3 = ROUNDUP(pgmheight, kRInc);
340  cc3 = ROUNDUP(pgmwidth, kCInc);
341 
342  borderpixels = (rr1 / kRInc) * (cc3 / kCInc) + /* top */
343  ((rr2 - rr1) / kRInc) * (cc1 / kCInc) + /* left */
344  ((rr2 - rr1) / kRInc) * ((cc3 - cc2) / kCInc) + /* right */
345  ((rr3 - rr2) / kRInc) * (cc3 / kCInc); /* bottom */
346 
347  pp = &m_buf[borderpixels];
348  m_histVal.fill(0);
349  m_histVal[kDefaultColor] += borderpixels;
350  for (int rr = rr1; rr < rr2; rr += kRInc)
351  {
352  int rroffset = rr * pgmwidth;
353 
354  for (int cc = cc1; cc < cc2; cc += kCInc)
355  {
356  if (m_logo && rr >= m_logoRr1 && rr <= m_logoRr2 &&
357  cc >= m_logoCc1 && cc <= m_logoCc2)
358  continue; /* Exclude logo area from analysis. */
359 
360  unsigned char val = pgm->data[0][rroffset + cc];
361  *pp++ = val;
362  sumval += val;
363  sumsquares += 1ULL * val * val;
364  livepixels++;
365  m_histVal[val]++;
366  }
367  }
368  npixels = borderpixels + livepixels;
369 
370  /* Scale scores down to [0..255]. */
371  halfnpixels = npixels / 2;
372  for (unsigned int color = 0; color < UCHAR_MAX + 1; color++)
373  m_histogram[frameno][color] =
374  (m_histVal[color] * UCHAR_MAX + halfnpixels) / npixels;
375 
376  bordercolor = 0;
377  if (ismonochromatic && livepixels)
378  {
379  /*
380  * Fake up the margin pixels to be of the same color as the sampled
381  * area.
382  */
383  bordercolor = (sumval + livepixels - 1) / livepixels;
384  sumval += 1ULL * borderpixels * bordercolor;
385  sumsquares += 1ULL * borderpixels * bordercolor * bordercolor;
386  }
387 
388  memset(m_buf, bordercolor, borderpixels * sizeof(*m_buf));
389  m_monochromatic[frameno] = ismonochromatic ? 1 : 0;
390  m_mean[frameno] = (float)sumval / npixels;
391  m_median[frameno] = quick_select_median(m_buf, npixels);
392  m_stddev[frameno] = npixels > 1 ?
393  sqrt((sumsquares - (float)sumval * sumval / npixels) / (npixels - 1)) :
394  0;
395 
396  end = nowAsDuration<std::chrono::microseconds>();
397  m_analyzeTime += (end - start);
398 
399  m_lastFrameNo = frameno;
400 
402 
403 error:
404  LOG(VB_COMMFLAG, LOG_ERR,
405  QString("HistogramAnalyzer::analyzeFrame error at frame %1")
406  .arg(frameno));
407 
409 }
410 
411 int
412 HistogramAnalyzer::finished(long long nframes, bool final)
413 {
415  {
418  {
419  LOG(VB_COMMFLAG, LOG_INFO,
420  QString("HistogramAnalyzer::finished wrote %1")
421  .arg(m_debugdata));
422  m_histValDone = true;
423  }
424  }
425 
426  return 0;
427 }
428 
429 int
431 {
432  if (m_pgmConverter->reportTime())
433  return -1;
434 
435  if (m_borderDetector->reportTime())
436  return -1;
437 
438  LOG(VB_COMMFLAG, LOG_INFO, QString("HA Time: analyze=%1s")
439  .arg(strftimeval(m_analyzeTime)));
440  return 0;
441 }
442 
443 /* 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
FrameAnalyzer::ANALYZE_ERROR
@ ANALYZE_ERROR
Definition: FrameAnalyzer.h:38
error
static void error(const char *str,...)
Definition: vbi.cpp:37
HistogramAnalyzer::m_debugHistVal
bool m_debugHistVal
Definition: HistogramAnalyzer.h:73
anonymous_namespace{HistogramAnalyzer.cpp}::writeData
bool writeData(const QString &filename, float *mean, unsigned char *median, float *stddev, int *frow, int *fcol, int *fwidth, int *fheight, HistogramAnalyzer::Histogram *histogram, unsigned char *monochromatic, long long nframes)
Definition: HistogramAnalyzer.cpp:112
CommDetector2.h
HistogramAnalyzer::m_histogram
Histogram * m_histogram
Definition: HistogramAnalyzer.h:64
HistogramAnalyzer::finished
int finished(long long nframes, bool final)
Definition: HistogramAnalyzer.cpp:412
cc
Definition: cc.h:9
quick_select_median
unsigned char quick_select_median(unsigned char *arr, int nelems)
Definition: quickselect.cpp:70
quickselect.h
FrameAnalyzer::ANALYZE_OK
@ ANALYZE_OK
Definition: FrameAnalyzer.h:37
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
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:430
HistogramAnalyzer::analyzeFrame
enum FrameAnalyzer::analyzeFrameResult analyzeFrame(const MythVideoFrame *frame, long long frameno)
Definition: HistogramAnalyzer.cpp:266
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:131
ROUNDUP
static constexpr int ROUNDUP(int a, int b)
Definition: HistogramAnalyzer.cpp:263
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_FATAL
@ ANALYZE_FATAL
Definition: FrameAnalyzer.h:40
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
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:916
HistogramAnalyzer::m_fWidth
int * m_fWidth
Definition: HistogramAnalyzer.h:62
HistogramAnalyzer::setLogoState
void setLogoState(TemplateFinder *finder)
Definition: HistogramAnalyzer.cpp:258
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
commDetector2
Definition: CommDetector2.cpp:189
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:87
FrameAnalyzer::ANALYZE_FINISHED
@ ANALYZE_FINISHED
Definition: FrameAnalyzer.h:39
HistogramAnalyzer::~HistogramAnalyzer
~HistogramAnalyzer()
Definition: HistogramAnalyzer.cpp:167
build_compdb.filename
filename
Definition: build_compdb.py:21
anonymous_namespace{HistogramAnalyzer.cpp}::readData
bool readData(const QString &filename, float *mean, unsigned char *median, float *stddev, int *frow, int *fcol, int *fwidth, int *fheight, HistogramAnalyzer::Histogram *histogram, unsigned char *monochromatic, long long nframes)
Definition: HistogramAnalyzer.cpp:26
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: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
uint
unsigned int uint
Definition: freesurround.h:24
HistogramAnalyzer::kUncached
static const long long kUncached
Definition: HistogramAnalyzer.h:28