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 
7 // MythTV headers
8 #include "mythcorecontext.h"
9 #include "mythplayer.h"
10 #include "mythlogging.h"
11 
12 // Commercial Flagging headers
13 #include "CommDetector2.h"
14 #include "FrameAnalyzer.h"
15 #include "PGMConverter.h"
16 #include "BorderDetector.h"
17 #include "quickselect.h"
18 #include "TemplateFinder.h"
19 #include "HistogramAnalyzer.h"
20 
21 using namespace commDetector2;
22 using namespace frameAnalyzer;
23 
24 namespace {
25 
26 bool
27 readData(const QString& filename, float *mean, unsigned char *median, float *stddev,
28  int *frow, int *fcol, int *fwidth, int *fheight,
29  HistogramAnalyzer::Histogram *histogram, unsigned char *monochromatic,
30  long long nframes)
31 {
32  quint32 counter[UCHAR_MAX + 1];
33 
34  QByteArray fname = filename.toLocal8Bit();
35  FILE *fp = fopen(fname.constData(), "r");
36  if (fp == nullptr)
37  return false;
38 
39  for (long long frameno = 0; frameno < nframes; frameno++)
40  {
41  int monochromaticval = 0;
42  int medianval = 0;
43  int widthval = 0;
44  int heightval = 0;
45  int colval = 0;
46  int rowval = 0;
47  float meanval = NAN;
48  float stddevval = NAN;
49  int nitems = fscanf(fp, "%20d %20f %20d %20f %20d %20d %20d %20d",
50  &monochromaticval, &meanval, &medianval, &stddevval,
51  &widthval, &heightval, &colval, &rowval);
52  if (nitems != 8)
53  {
54  LOG(VB_COMMFLAG, LOG_ERR,
55  QString("Not enough data in %1: frame %2")
56  .arg(filename).arg(frameno));
57  goto error;
58  }
59  if (monochromaticval < 0 || monochromaticval > 1 ||
60  medianval < 0 || (uint)medianval > UCHAR_MAX ||
61  widthval < 0 || heightval < 0 || colval < 0 || rowval < 0)
62  {
63  LOG(VB_COMMFLAG, LOG_ERR,
64  QString("Data out of range in %1: frame %2")
65  .arg(filename).arg(frameno));
66  goto error;
67  }
68  for (size_t ii = 0; ii < sizeof(counter)/sizeof(*counter); ii++)
69  {
70  if (fscanf(fp, "%20x", &counter[ii]) != 1)
71  {
72  LOG(VB_COMMFLAG, LOG_ERR,
73  QString("Not enough data in %1: frame %2")
74  .arg(filename).arg(frameno));
75  goto error;
76  }
77  if (counter[ii] > UCHAR_MAX)
78  {
79  LOG(VB_COMMFLAG, LOG_ERR,
80  QString("Data out of range in %1: frame %2")
81  .arg(filename).arg(frameno));
82  goto error;
83  }
84  }
85  mean[frameno] = meanval;
86  median[frameno] = medianval;
87  stddev[frameno] = stddevval;
88  frow[frameno] = rowval;
89  fcol[frameno] = colval;
90  fwidth[frameno] = widthval;
91  fheight[frameno] = heightval;
92  for (size_t ii = 0; ii < sizeof(counter)/sizeof(*counter); ii++)
93  histogram[frameno][ii] = counter[ii];
94  monochromatic[frameno] = !widthval || !heightval ? 1 : 0;
95  /*
96  * monochromaticval not used; it's written to file for debugging
97  * convenience
98  */
99  }
100  if (fclose(fp))
101  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
102  .arg(filename).arg(strerror(errno)));
103  return true;
104 
105 error:
106  if (fclose(fp))
107  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
108  .arg(filename).arg(strerror(errno)));
109  return false;
110 }
111 
112 bool
113 writeData(const QString& filename, float *mean, unsigned char *median, float *stddev,
114  int *frow, int *fcol, int *fwidth, int *fheight,
115  HistogramAnalyzer::Histogram *histogram, unsigned char *monochromatic,
116  long long nframes)
117 {
118  QByteArray fname = filename.toLocal8Bit();
119  FILE *fp = fopen(fname, "w");
120  if (fp == nullptr)
121  return false;
122  for (long long frameno = 0; frameno < nframes; frameno++)
123  {
124  (void)fprintf(fp, "%3u %10.6f %3u %10.6f %5d %5d %5d %5d",
125  monochromatic[frameno],
126  static_cast<double>(mean[frameno]), median[frameno],
127  static_cast<double>(stddev[frameno]),
128  fwidth[frameno], fheight[frameno],
129  fcol[frameno], frow[frameno]);
130  for (unsigned int ii = 0; ii < UCHAR_MAX + 1; ii++)
131  (void)fprintf(fp, " %02x", histogram[frameno][ii]);
132  (void)fprintf(fp, "\n");
133  }
134  if (fclose(fp))
135  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
136  .arg(filename).arg(strerror(errno)));
137  return true;
138 }
139 
140 }; /* namespace */
141 
143  const QString& debugdir)
144  : m_pgmConverter(pgmc)
145  , m_borderDetector(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_debug_histval = 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_histval_done)
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_debug_histval)
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_histval_done = true;
247  }
248  }
249 
251 }
252 
253 void
255 {
256  m_logoFinder = finder;
257 }
258 
260 HistogramAnalyzer::analyzeFrame(const VideoFrame *frame, long long frameno)
261 {
262  /*
263  * Various statistical computations over pixel values: mean, median,
264  * (running) standard deviation over sample population.
265  */
266  static constexpr int kDefaultColor = 0;
267 
268  /*
269  * TUNABLE:
270  *
271  * Sampling coarseness of each frame. Higher values will allow analysis to
272  * proceed faster (lower resolution), but might be less accurate. Lower
273  * values will examine more pixels (higher resolution), but will run
274  * slower.
275  */
276  static constexpr int kRInc = 4;
277  static constexpr int kCInc = 4;
278 #define ROUNDUP(a,b) (((a) + (b) - 1) / (b) * (b))
279 
280  int pgmwidth = 0;
281  int pgmheight = 0;
282  bool ismonochromatic = false;
283  int croprow = 0;
284  int cropcol = 0;
285  int cropwidth = 0;
286  int cropheight = 0;
287  unsigned int borderpixels = 0;
288  unsigned int livepixels = 0;
289  unsigned int npixels = 0;
290  unsigned int halfnpixels = 0;
291  unsigned char *pp = nullptr;
292  unsigned char bordercolor = 0;
293  unsigned long long sumval = 0;
294  unsigned long long sumsquares = 0;
295  int rr1 = 0;
296  int cc1 = 0;
297  int rr2 = 0;
298  int cc2 = 0;
299  int rr3 = 0;
300  int cc3 = 0;
301  struct timeval start {};
302  struct timeval end {};
303  struct timeval elapsed {};
304 
305  if (m_lastframeno != UNCACHED && 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  gettimeofday(&start, nullptr);
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  memset(m_histval, 0, sizeof(m_histval));
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 += 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 += borderpixels * bordercolor;
381  sumsquares += 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  (void)gettimeofday(&end, nullptr);
393  timersub(&end, &start, &elapsed);
394  timeradd(&m_analyze_time, &elapsed, &m_analyze_time);
395 
396  m_lastframeno = frameno;
397 
399 
400 error:
401  LOG(VB_COMMFLAG, LOG_ERR,
402  QString("HistogramAnalyzer::analyzeFrame error at frame %1")
403  .arg(frameno));
404 
406 }
407 
408 int
409 HistogramAnalyzer::finished(long long nframes, bool final)
410 {
412  {
413  if (final && writeData(m_debugdata, m_mean, m_median, m_stddev, m_frow, m_fcol,
415  {
416  LOG(VB_COMMFLAG, LOG_INFO,
417  QString("HistogramAnalyzer::finished wrote %1")
418  .arg(m_debugdata));
419  m_histval_done = true;
420  }
421  }
422 
423  return 0;
424 }
425 
426 int
428 {
429  if (m_pgmConverter->reportTime())
430  return -1;
431 
433  return -1;
434 
435  LOG(VB_COMMFLAG, LOG_INFO, QString("HA Time: analyze=%1s")
436  .arg(strftimeval(&m_analyze_time)));
437  return 0;
438 }
439 
440 /* vim: set expandtab tabstop=4 shiftwidth=4: */
unsigned char * m_buf
#define PGM_CONVERT_GREYSCALE
Definition: PGMConverter.h:27
Definition: cc.h:13
Histogram * m_histogram
int finished(long long nframes, bool final)
static void error(const char *str,...)
Definition: vbi.c:42
#define timeradd(a, b, result)
Definition: compat.h:300
unsigned char[UCHAR_MAX+1] Histogram
PGMConverter * m_pgmConverter
struct AVFrame AVFrame
int reportTime(void) const
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
TemplateFinder * m_logoFinder
#define ROUNDUP(a, b)
int m_histval[UCHAR_MAX+1]
int MythPlayerInited(const MythPlayer *player)
HistogramAnalyzer(PGMConverter *pgmc, BorderDetector *bd, const QString &debugdir)
const struct AVFrame * getTemplate(int *prow, int *pcol, int *pwidth, int *pheight) const
const struct AVFrame * m_logo
static const long long UNCACHED
unsigned int uint
Definition: compat.h:140
void setLogoState(TemplateFinder *finder)
int MythPlayerInited(const MythPlayer *player)
unsigned char * m_monochromatic
int FILE
Definition: mythburn.py:110
enum FrameAnalyzer::analyzeFrameResult analyzeFrame(const VideoFrame *frame, long long frameno)
BorderDetector * m_borderDetector
int GetNumSetting(const QString &key, int defaultval=0)
void createDebugDirectory(const QString &dirname, const QString &comment)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QSize GetVideoBufferSize(void) const
Definition: mythplayer.h:187
unsigned char quick_select_median(unsigned char *arr, int nelems)
Definition: quickselect.c:66
QString strftimeval(const struct timeval *tv)
enum FrameAnalyzer::analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes)
int reportTime(void)
#define timersub(a, b, result)
Definition: compat.h:310
int getDimensions(const AVFrame *pgm, int pgmheight, long long frameno, int *prow, int *pcol, int *pwidth, int *pheight)
const AVFrame * getImage(const VideoFrame *frame, long long frameno, int *pwidth, int *pheight)
unsigned char * m_median
int reportTime(void)