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 auto *sc1 = (const struct SceneChangeDetector::scenechange_data*)aa;
27  const auto *sc2 = (const struct SceneChangeDetector::scenechange_data*)bb;
28  int freqdiff = sc2->frequency - sc1->frequency;
29  return freqdiff ? freqdiff : sc1->color - sc2->color;
30 }
31 
32 void
33 scenechange_data_init(SceneChangeDetector::SceneChangeData *scdata,
35 {
36  unsigned int ncolors = sizeof(*hh)/sizeof((*hh)[0]);
37 
38  for (unsigned int ii = 0; ii < ncolors; ii++)
39  {
40  (*scdata)[ii].color = ii;
41  (*scdata)[ii].frequency = (*hh)[ii];
42  }
43  qsort(*scdata, sizeof(*scdata)/sizeof((*scdata)[0]), sizeof((*scdata)[0]),
44  scenechange_data_sort_desc_frequency);
45 }
46 
47 unsigned short
48 scenechange_data_diff(const SceneChangeDetector::SceneChangeData *sc1,
50 {
51  /*
52  * Compute a notion of "difference" that takes into account the difference
53  * in relative frequencies of the dominant colors.
54  */
55  unsigned short diff = 0;
56  for (size_t ii = 0; ii < sizeof(*sc1)/sizeof((*sc1)[0]); ii++)
57  diff += abs((*sc1)[ii].frequency - (*sc2)[ii].frequency) +
58  abs((*sc1)[ii].color - (*sc2)[ii].color);
59  return diff;
60 }
61 
62 bool
63 writeData(const QString& filename, const unsigned short *scdiff, long long nframes)
64 {
65  QByteArray fname = filename.toLocal8Bit();
66  FILE *fp = fopen(fname.constData(), "w");
67  if (fp == nullptr)
68  return false;
69  for (long long frameno = 0; frameno < nframes; frameno++)
70  (void)fprintf(fp, "%5u\n", scdiff[frameno]);
71  if (fclose(fp))
72  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
73  .arg(filename).arg(strerror(errno)));
74  return true;
75 }
76 
77 void
78 computeChangeMap(FrameAnalyzer::FrameMap *changeMap, long long nframes,
79  const unsigned short *scdiff, unsigned short mindiff)
80 {
81  /*
82  * Look for sudden changes in histogram.
83  */
84  changeMap->clear();
85  for (long long frameno = 0; frameno < nframes; frameno++)
86  {
87  if (scdiff[frameno] > mindiff)
88  changeMap->insert(frameno, 0);
89  }
90 }
91 
92 }; /* namespace */
93 
95  const QString& debugdir)
97  , m_debugdata(debugdir + "/SceneChangeDetector.txt")
98 {
99  LOG(VB_COMMFLAG, LOG_INFO, "SceneChangeDetector");
100 
101  /*
102  * debugLevel:
103  * 0: no debugging
104  * 2: extra verbosity [O(nframes)]
105  */
106  m_debugLevel = gCoreContext->GetNumSetting("SceneChangeDetectorDebugLevel", 0);
107 
108  if (m_debugLevel >= 1)
109  {
110  createDebugDirectory(debugdir,
111  QString("SceneChangeDetector debugLevel %1").arg(m_debugLevel));
112  m_debug_scenechange = true;
113  }
114 }
115 
117 {
118  delete []m_scdata;
119  delete []m_scdiff;
120 }
121 
124  long long nframes)
125 {
127  m_histogramAnalyzer->MythPlayerInited(player, nframes);
128 
129  m_fps = player->GetFrameRate();
130 
131  m_scdata = new SceneChangeData[nframes];
132  memset(m_scdata, 0, nframes * sizeof(*m_scdata));
133 
134  m_scdiff = new unsigned short[nframes];
135  memset(m_scdiff, 0, nframes * sizeof(*m_scdiff));
136 
137  QSize video_disp_dim = player->GetVideoSize();
138 
139  LOG(VB_COMMFLAG, LOG_INFO,
140  QString("SceneChangeDetector::MythPlayerInited %1x%2")
141  .arg(video_disp_dim.width())
142  .arg(video_disp_dim.height()));
143 
144  return ares;
145 }
146 
148 SceneChangeDetector::analyzeFrame(const VideoFrame *frame, long long frameno,
149  long long *pNextFrame)
150 {
151  *pNextFrame = NEXTFRAME;
152 
153  if (m_histogramAnalyzer->analyzeFrame(frame, frameno) ==
155  return ANALYZE_OK;
156 
157  LOG(VB_COMMFLAG, LOG_ERR,
158  QString("SceneChangeDetector::analyzeFrame error at frame %1")
159  .arg(frameno));
160  return ANALYZE_ERROR;
161 }
162 
163 int
164 SceneChangeDetector::finished(long long nframes, bool final)
165 {
166  if (m_histogramAnalyzer->finished(nframes, final))
167  return -1;
168 
169  LOG(VB_COMMFLAG, LOG_INFO, QString("SceneChangeDetector::finished(%1)")
170  .arg(nframes));
171 
172  const HistogramAnalyzer::Histogram *histogram =
174  for (long long frameno = 0; frameno < nframes; frameno++)
175  scenechange_data_init(&m_scdata[frameno], &histogram[frameno]);
176  m_scdiff[0] = 0;
177  for (long long frameno = 1; frameno < nframes; frameno++)
178  m_scdiff[frameno] = scenechange_data_diff(&m_scdata[frameno - 1],
179  &m_scdata[frameno]);
180 
182  {
183  if (final && writeData(m_debugdata, m_scdiff, nframes))
184  {
185  LOG(VB_COMMFLAG, LOG_INFO,
186  QString("SceneChangeDetector::finished wrote %1")
187  .arg(m_debugdata));
188  m_scenechange_done = true;
189  }
190  }
191 
192  /* Identify all scene-change frames (changeMap). */
193  auto *scdiffsort = new unsigned short[nframes];
194  memcpy(scdiffsort, m_scdiff, nframes * sizeof(*m_scdiff));
195  unsigned short mindiff = quick_select_ushort(scdiffsort, nframes,
196  (int)(0.979472 * nframes));
197  LOG(VB_COMMFLAG, LOG_INFO,
198  QString("SceneChangeDetector::finished applying threshold value %1")
199  .arg(mindiff));
200  computeChangeMap(&m_changeMap, nframes, m_scdiff, mindiff);
201  delete []scdiffsort;
202  if (m_debugLevel >= 2)
204 
205  return 0;
206 }
207 
208 int
210 {
212 }
213 
214 /* 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)
unsigned char[UCHAR_MAX+1] Histogram
static const long long NEXTFRAME
Definition: FrameAnalyzer.h:57
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:188
float GetFrameRate(void) const
Definition: mythplayer.h:190
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)
scenechange_data[UCHAR_MAX+1] SceneChangeData
QMap< long long, long long > FrameMap
Definition: FrameAnalyzer.h:43
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:74
int finished(long long nframes, bool final) override
enum FrameAnalyzer::analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes)
FrameAnalyzer::FrameMap m_changeMap
void frameAnalyzerReportMapms(const FrameAnalyzer::FrameMap *frameMap, float fps, const char *comment)