MythTV  master
SceneChangeDetector.cpp
Go to the documentation of this file.
1 // ANSI C headers
2 #include <cstdlib>
3 #include <cmath>
4 
5 // MythTV headers
6 #include "mythcorecontext.h" /* gContext */
7 #include "mythplayer.h"
8 #include "mythlogging.h"
9 
10 // Commercial Flagging headers
11 #include "CommDetector2.h"
12 #include "FrameAnalyzer.h"
13 #include "quickselect.h"
14 #include "HistogramAnalyzer.h"
15 #include "SceneChangeDetector.h"
16 
17 using namespace commDetector2;
18 using namespace frameAnalyzer;
19 
20 namespace {
21 
22 int
23 scenechange_data_sort_desc_frequency(const void *aa, const void *bb)
24 {
25  /* Descending by frequency, then ascending by color. */
26  const struct SceneChangeDetector::scenechange_data *sc1 =
27  (const struct SceneChangeDetector::scenechange_data*)aa;
28  const struct SceneChangeDetector::scenechange_data *sc2 =
29  (const struct SceneChangeDetector::scenechange_data*)bb;
30  int freqdiff = sc2->frequency - sc1->frequency;
31  return freqdiff ? freqdiff : sc1->color - sc2->color;
32 }
33 
34 void
35 scenechange_data_init(SceneChangeDetector::SceneChangeData *scdata,
37 {
38  unsigned int ncolors = sizeof(*hh)/sizeof((*hh)[0]);
39 
40  for (unsigned int ii = 0; ii < ncolors; ii++)
41  {
42  (*scdata)[ii].color = ii;
43  (*scdata)[ii].frequency = (*hh)[ii];
44  }
45  qsort(*scdata, sizeof(*scdata)/sizeof((*scdata)[0]), sizeof((*scdata)[0]),
46  scenechange_data_sort_desc_frequency);
47 }
48 
49 unsigned short
50 scenechange_data_diff(const SceneChangeDetector::SceneChangeData *sc1,
52 {
53  /*
54  * Compute a notion of "difference" that takes into account the difference
55  * in relative frequencies of the dominant colors.
56  */
57  unsigned short diff = 0;
58  for (size_t ii = 0; ii < sizeof(*sc1)/sizeof((*sc1)[0]); ii++)
59  diff += abs((*sc1)[ii].frequency - (*sc2)[ii].frequency) +
60  abs((*sc1)[ii].color - (*sc2)[ii].color);
61  return diff;
62 }
63 
64 bool
65 writeData(const QString& filename, const unsigned short *scdiff, long long nframes)
66 {
67  FILE *fp;
68  long long frameno;
69 
70  QByteArray fname = filename.toLocal8Bit();
71  if (!(fp = fopen(fname.constData(), "w")))
72  return false;
73  for (frameno = 0; frameno < nframes; frameno++)
74  (void)fprintf(fp, "%5u\n", scdiff[frameno]);
75  if (fclose(fp))
76  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
77  .arg(filename).arg(strerror(errno)));
78  return true;
79 }
80 
81 void
82 computeChangeMap(FrameAnalyzer::FrameMap *changeMap, long long nframes,
83  const unsigned short *scdiff, unsigned short mindiff)
84 {
85  /*
86  * Look for sudden changes in histogram.
87  */
88  long long frameno;
89 
90  changeMap->clear();
91  for (frameno = 0; frameno < nframes; frameno++)
92  {
93  if (scdiff[frameno] > mindiff)
94  changeMap->insert(frameno, 0);
95  }
96 }
97 
98 }; /* namespace */
99 
101  const QString& debugdir)
102  : m_histogramAnalyzer(ha)
103  , m_debugdata(debugdir + "/SceneChangeDetector.txt")
104 {
105  LOG(VB_COMMFLAG, LOG_INFO, "SceneChangeDetector");
106 
107  /*
108  * debugLevel:
109  * 0: no debugging
110  * 2: extra verbosity [O(nframes)]
111  */
112  m_debugLevel = gCoreContext->GetNumSetting("SceneChangeDetectorDebugLevel", 0);
113 
114  if (m_debugLevel >= 1)
115  {
116  createDebugDirectory(debugdir,
117  QString("SceneChangeDetector debugLevel %1").arg(m_debugLevel));
118  m_debug_scenechange = true;
119  }
120 }
121 
123 {
124  delete []m_scdata;
125  delete []m_scdiff;
126 }
127 
130  long long nframes)
131 {
133  m_histogramAnalyzer->MythPlayerInited(player, nframes);
134 
135  m_fps = player->GetFrameRate();
136 
137  m_scdata = new SceneChangeData[nframes];
138  memset(m_scdata, 0, nframes * sizeof(*m_scdata));
139 
140  m_scdiff = new unsigned short[nframes];
141  memset(m_scdiff, 0, nframes * sizeof(*m_scdiff));
142 
143  QSize video_disp_dim = player->GetVideoSize();
144 
145  LOG(VB_COMMFLAG, LOG_INFO,
146  QString("SceneChangeDetector::MythPlayerInited %1x%2")
147  .arg(video_disp_dim.width())
148  .arg(video_disp_dim.height()));
149 
150  return ares;
151 }
152 
154 SceneChangeDetector::analyzeFrame(const VideoFrame *frame, long long frameno,
155  long long *pNextFrame)
156 {
157  *pNextFrame = NEXTFRAME;
158 
159  if (m_histogramAnalyzer->analyzeFrame(frame, frameno) ==
161  return ANALYZE_OK;
162 
163  LOG(VB_COMMFLAG, LOG_ERR,
164  QString("SceneChangeDetector::analyzeFrame error at frame %1")
165  .arg(frameno));
166  return ANALYZE_ERROR;
167 }
168 
169 int
170 SceneChangeDetector::finished(long long nframes, bool final)
171 {
172  if (m_histogramAnalyzer->finished(nframes, final))
173  return -1;
174 
175  LOG(VB_COMMFLAG, LOG_INFO, QString("SceneChangeDetector::finished(%1)")
176  .arg(nframes));
177 
178  const HistogramAnalyzer::Histogram *histogram =
180  for (long long frameno = 0; frameno < nframes; frameno++)
181  scenechange_data_init(&m_scdata[frameno], &histogram[frameno]);
182  m_scdiff[0] = 0;
183  for (long long frameno = 1; frameno < nframes; frameno++)
184  m_scdiff[frameno] = scenechange_data_diff(&m_scdata[frameno - 1],
185  &m_scdata[frameno]);
186 
188  {
189  if (final && writeData(m_debugdata, m_scdiff, nframes))
190  {
191  LOG(VB_COMMFLAG, LOG_INFO,
192  QString("SceneChangeDetector::finished wrote %1")
193  .arg(m_debugdata));
194  m_scenechange_done = true;
195  }
196  }
197 
198  /* Identify all scene-change frames (changeMap). */
199  unsigned short *scdiffsort = new unsigned short[nframes];
200  memcpy(scdiffsort, m_scdiff, nframes * sizeof(*m_scdiff));
201  unsigned short mindiff = quick_select_ushort(scdiffsort, nframes,
202  (int)(0.979472 * nframes));
203  LOG(VB_COMMFLAG, LOG_INFO,
204  QString("SceneChangeDetector::finished applying threshold value %1")
205  .arg(mindiff));
206  computeChangeMap(&m_changeMap, nframes, m_scdiff, mindiff);
207  delete []scdiffsort;
208  if (m_debugLevel >= 2)
210 
211  return 0;
212 }
213 
214 int
216 {
218 }
219 
220 /* vim: set expandtab tabstop=4 shiftwidth=4: */
SceneChangeDetector(HistogramAnalyzer *ha, const QString &debugdir)
HistogramAnalyzer * m_histogramAnalyzer
const Histogram * getHistograms(void) const
unsigned short * m_scdiff
virtual void deleteLater(void)
int finished(long long nframes, bool final)
static const long long NEXTFRAME
Definition: FrameAnalyzer.h:58
int reportTime(void) const
int reportTime(void) const override
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
enum analyzeFrameResult analyzeFrame(const VideoFrame *frame, long long frameno, long long *pNextFrame) override
enum analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes) override
QSize GetVideoSize(void) const
Definition: mythplayer.h:174
unsigned char Histogram[UCHAR_MAX+1]
struct SceneChangeDetector::scenechange_data SceneChangeData[UCHAR_MAX+1]
float GetFrameRate(void) const
Definition: mythplayer.h:176
SceneChangeData * m_scdata
int FILE
Definition: mythburn.py:110
enum FrameAnalyzer::analyzeFrameResult analyzeFrame(const VideoFrame *frame, long long frameno)
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
unsigned short quick_select_ushort(unsigned short *arr, int nelems, int select)
Definition: quickselect.c:76
int finished(long long nframes, bool final) override
enum FrameAnalyzer::analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes)
FrameAnalyzer::FrameMap m_changeMap
QMap< long long, long long > FrameMap
Definition: FrameAnalyzer.h:44
void frameAnalyzerReportMapms(const FrameAnalyzer::FrameMap *frameMap, float fps, const char *comment)