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  FILE *fp;
33  long long frameno;
34  quint32 counter[UCHAR_MAX + 1];
35 
36  QByteArray fname = filename.toLocal8Bit();
37  if (!(fp = fopen(fname.constData(), "r")))
38  return false;
39 
40  for (frameno = 0; frameno < nframes; frameno++)
41  {
42  int monochromaticval, medianval, widthval, heightval, colval, rowval;
43  float meanval, stddevval;
44  int nitems = fscanf(fp, "%20d %20f %20d %20f %20d %20d %20d %20d",
45  &monochromaticval, &meanval, &medianval, &stddevval,
46  &widthval, &heightval, &colval, &rowval);
47  if (nitems != 8)
48  {
49  LOG(VB_COMMFLAG, LOG_ERR,
50  QString("Not enough data in %1: frame %2")
51  .arg(filename).arg(frameno));
52  goto error;
53  }
54  if (monochromaticval < 0 || monochromaticval > 1 ||
55  medianval < 0 || (uint)medianval > UCHAR_MAX ||
56  widthval < 0 || heightval < 0 || colval < 0 || rowval < 0)
57  {
58  LOG(VB_COMMFLAG, LOG_ERR,
59  QString("Data out of range in %1: frame %2")
60  .arg(filename).arg(frameno));
61  goto error;
62  }
63  for (size_t ii = 0; ii < sizeof(counter)/sizeof(*counter); ii++)
64  {
65  if ((nitems = fscanf(fp, "%20x", &counter[ii])) != 1)
66  {
67  LOG(VB_COMMFLAG, LOG_ERR,
68  QString("Not enough data in %1: frame %2")
69  .arg(filename).arg(frameno));
70  goto error;
71  }
72  if (counter[ii] > UCHAR_MAX)
73  {
74  LOG(VB_COMMFLAG, LOG_ERR,
75  QString("Data out of range in %1: frame %2")
76  .arg(filename).arg(frameno));
77  goto error;
78  }
79  }
80  mean[frameno] = meanval;
81  median[frameno] = medianval;
82  stddev[frameno] = stddevval;
83  frow[frameno] = rowval;
84  fcol[frameno] = colval;
85  fwidth[frameno] = widthval;
86  fheight[frameno] = heightval;
87  for (size_t ii = 0; ii < sizeof(counter)/sizeof(*counter); ii++)
88  histogram[frameno][ii] = counter[ii];
89  monochromatic[frameno] = !widthval || !heightval ? 1 : 0;
90  /*
91  * monochromaticval not used; it's written to file for debugging
92  * convenience
93  */
94  }
95  if (fclose(fp))
96  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
97  .arg(filename).arg(strerror(errno)));
98  return true;
99 
100 error:
101  if (fclose(fp))
102  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
103  .arg(filename).arg(strerror(errno)));
104  return false;
105 }
106 
107 bool
108 writeData(const QString& filename, float *mean, unsigned char *median, float *stddev,
109  int *frow, int *fcol, int *fwidth, int *fheight,
110  HistogramAnalyzer::Histogram *histogram, unsigned char *monochromatic,
111  long long nframes)
112 {
113  FILE *fp;
114  long long frameno;
115 
116  QByteArray fname = filename.toLocal8Bit();
117  if (!(fp = fopen(fname, "w")))
118  return false;
119  for (frameno = 0; frameno < nframes; frameno++)
120  {
121  (void)fprintf(fp, "%3u %10.6f %3u %10.6f %5d %5d %5d %5d",
122  monochromatic[frameno],
123  static_cast<double>(mean[frameno]), median[frameno],
124  static_cast<double>(stddev[frameno]),
125  fwidth[frameno], fheight[frameno],
126  fcol[frameno], frow[frameno]);
127  for (unsigned int ii = 0; ii < UCHAR_MAX + 1; ii++)
128  (void)fprintf(fp, " %02x", histogram[frameno][ii]);
129  (void)fprintf(fp, "\n");
130  }
131  if (fclose(fp))
132  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
133  .arg(filename).arg(strerror(errno)));
134  return true;
135 }
136 
137 }; /* namespace */
138 
140  const QString& debugdir)
141  : m_pgmConverter(pgmc)
142  , m_borderDetector(bd)
144  , m_debugdata(debugdir + "/HistogramAnalyzer-pgm.txt")
145 #else /* !PGM_CONVERT_GREYSCALE */
146  , m_debugdata(debugdir + "/HistogramAnalyzer-yuv.txt")
147 #endif /* !PGM_CONVERT_GREYSCALE */
148 {
149  memset(m_histval, 0, sizeof(int) * (UCHAR_MAX + 1));
150  memset(&m_analyze_time, 0, sizeof(m_analyze_time));
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 const int DEFAULT_COLOR = 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 const int RINC = 4;
277  static const int CINC = 4;
278 #define ROUNDUP(a,b) (((a) + (b) - 1) / (b) * (b))
279 
280  const AVFrame *pgm;
281  int pgmwidth, pgmheight;
282  bool ismonochromatic;
283  int croprow, cropcol, cropwidth, cropheight;
284  unsigned int borderpixels, livepixels, npixels, halfnpixels;
285  unsigned char *pp, bordercolor;
286  unsigned long long sumval, sumsquares;
287  int rr, cc, rr1, cc1, rr2, cc2, rr3, cc3;
288  struct timeval start, end, elapsed;
289 
290  if (m_lastframeno != UNCACHED && m_lastframeno == frameno)
292 
293  if (!(pgm = m_pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight)))
294  goto error;
295 
296  ismonochromatic = m_borderDetector->getDimensions(pgm, pgmheight, frameno,
297  &croprow, &cropcol, &cropwidth, &cropheight) != 0;
298 
299  gettimeofday(&start, nullptr);
300 
301  m_frow[frameno] = croprow;
302  m_fcol[frameno] = cropcol;
303  m_fwidth[frameno] = cropwidth;
304  m_fheight[frameno] = cropheight;
305 
306  if (ismonochromatic)
307  {
308  /* Optimization for monochromatic frames; just sample center area. */
309  croprow = pgmheight * 3 / 8;
310  cropheight = pgmheight / 4;
311  cropcol = pgmwidth * 3 / 8;
312  cropwidth = pgmwidth / 4;
313  }
314 
315  rr1 = ROUNDUP(croprow, RINC);
316  cc1 = ROUNDUP(cropcol, CINC);
317  rr2 = ROUNDUP(croprow + cropheight, RINC);
318  cc2 = ROUNDUP(cropcol + cropwidth, CINC);
319  rr3 = ROUNDUP(pgmheight, RINC);
320  cc3 = ROUNDUP(pgmwidth, CINC);
321 
322  borderpixels = (rr1 / RINC) * (cc3 / CINC) + /* top */
323  ((rr2 - rr1) / RINC) * (cc1 / CINC) + /* left */
324  ((rr2 - rr1) / RINC) * ((cc3 - cc2) / CINC) + /* right */
325  ((rr3 - rr2) / RINC) * (cc3 / CINC); /* bottom */
326 
327  sumval = 0;
328  sumsquares = 0;
329  livepixels = 0;
330  pp = &m_buf[borderpixels];
331  memset(m_histval, 0, sizeof(m_histval));
332  m_histval[DEFAULT_COLOR] += borderpixels;
333  for (rr = rr1; rr < rr2; rr += RINC)
334  {
335  int rroffset = rr * pgmwidth;
336 
337  for (cc = cc1; cc < cc2; cc += CINC)
338  {
339  if (m_logo && rr >= m_logorr1 && rr <= m_logorr2 &&
340  cc >= m_logocc1 && cc <= m_logocc2)
341  continue; /* Exclude logo area from analysis. */
342 
343  unsigned char val = pgm->data[0][rroffset + cc];
344  *pp++ = val;
345  sumval += val;
346  sumsquares += val * val;
347  livepixels++;
348  m_histval[val]++;
349  }
350  }
351  npixels = borderpixels + livepixels;
352 
353  /* Scale scores down to [0..255]. */
354  halfnpixels = npixels / 2;
355  for (unsigned int color = 0; color < UCHAR_MAX + 1; color++)
356  m_histogram[frameno][color] =
357  (m_histval[color] * UCHAR_MAX + halfnpixels) / npixels;
358 
359  bordercolor = 0;
360  if (ismonochromatic && livepixels)
361  {
362  /*
363  * Fake up the margin pixels to be of the same color as the sampled
364  * area.
365  */
366  bordercolor = (sumval + livepixels - 1) / livepixels;
367  sumval += borderpixels * bordercolor;
368  sumsquares += borderpixels * bordercolor * bordercolor;
369  }
370 
371  memset(m_buf, bordercolor, borderpixels * sizeof(*m_buf));
372  m_monochromatic[frameno] = ismonochromatic ? 1 : 0;
373  m_mean[frameno] = (float)sumval / npixels;
374  m_median[frameno] = quick_select_median(m_buf, npixels);
375  m_stddev[frameno] = npixels > 1 ?
376  sqrt((sumsquares - (float)sumval * sumval / npixels) / (npixels - 1)) :
377  0;
378 
379  (void)gettimeofday(&end, nullptr);
380  timersub(&end, &start, &elapsed);
381  timeradd(&m_analyze_time, &elapsed, &m_analyze_time);
382 
383  m_lastframeno = frameno;
384 
386 
387 error:
388  LOG(VB_COMMFLAG, LOG_ERR,
389  QString("HistogramAnalyzer::analyzeFrame error at frame %1")
390  .arg(frameno));
391 
393 }
394 
395 int
396 HistogramAnalyzer::finished(long long nframes, bool final)
397 {
399  {
400  if (final && writeData(m_debugdata, m_mean, m_median, m_stddev, m_frow, m_fcol,
402  {
403  LOG(VB_COMMFLAG, LOG_INFO,
404  QString("HistogramAnalyzer::finished wrote %1")
405  .arg(m_debugdata));
406  m_histval_done = true;
407  }
408  }
409 
410  return 0;
411 }
412 
413 int
415 {
416  if (m_pgmConverter->reportTime())
417  return -1;
418 
420  return -1;
421 
422  LOG(VB_COMMFLAG, LOG_INFO, QString("HA Time: analyze=%1s")
423  .arg(strftimeval(&m_analyze_time)));
424  return 0;
425 }
426 
427 /* vim: set expandtab tabstop=4 shiftwidth=4: */
unsigned char * m_buf
#define PGM_CONVERT_GREYSCALE
Definition: PGMConverter.h:28
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
struct AVFrame AVFrame
PGMConverter * m_pgmConverter
int reportTime(void) const
unsigned int uint
Definition: compat.h:140
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
TemplateFinder * m_logoFinder
#define ROUNDUP(a, b)
unsigned char Histogram[UCHAR_MAX+1]
int m_histval[UCHAR_MAX+1]
int MythPlayerInited(const MythPlayer *player)
static int pp(VideoFilter *vf, VideoFrame *frame, int field)
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
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)
struct timeval m_analyze_time
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:173
unsigned char quick_select_median(unsigned char *arr, int nelems)
Definition: quickselect.c:68
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)