MythTV  master
DetectLetterbox.cpp
Go to the documentation of this file.
1 // MythTV
2 #include "mythcorecontext.h"
3 #include "DetectLetterbox.h"
4 
5 #define LOC QString("DetectLetterbox: ")
6 
7 #define NUMBER_OF_DETECTION_LINES 3 // How many lines are we looking at
8 #define THRESHOLD 5 // Y component has to not vary more than this in the bars
9 #define HORIZONTAL_THRESHOLD 4 // How tolerant are we that the image has horizontal edges
10 
12 {
13  int dbAdjustFill = gCoreContext->GetNumSetting("AdjustFill", 0);
16  static_cast<AdjustFillMode>(max(static_cast<int>(kAdjustFill_Off),
17  dbAdjustFill - kAdjustFill_AutoDetect_DefaultOff));
18  m_detectLetterboxDetectedMode = Player->GetAdjustFill();
19  m_detectLetterboxLimit = gCoreContext->GetNumSetting("DetectLeterboxLimit", 75);
20  m_player = Player;
21 }
22 
30 {
31  if (!Frame || !GetDetectLetterbox())
32  return;
33 
34  if (!m_player->GetVideoOutput())
35  return;
36 
37  unsigned char *buf = Frame->buf;
38  int *pitches = Frame->pitches;
39  int *offsets = Frame->offsets;
40  const int height = Frame->height;
41 
42  // If the black bars is larger than this limit we switch to Half or Full Mode
43  // const int fullLimit = static_cast<int>(((height - width * 9 / 16) / 2) * m_detectLetterboxLimit / 100);
44  // const int halfLimit = (static_cast<int>(((height - width * 9 / 14) / 2) * m_detectLetterboxLimit / 100);
45 
46  // If the black bars is larger than this limit we switch to Half or Full Mode
47  const int fullLimit = static_cast<int>((height * (1 - m_player->GetVideoAspect() * 9 / 16) / 2) * m_detectLetterboxLimit / 100);
48  const int halfLimit = static_cast<int>((height * (1 - m_player->GetVideoAspect() * 9 / 14) / 2) * m_detectLetterboxLimit / 100);
49 
50  // Lines to scan for black letterbox edge
51  const int xPos[] = { Frame->width / 4, Frame->width / 2, Frame->width * 3 / 4} ;
52  int topHits = 0;
53  int bottomHits = 0;
54  int minTop = 0;
55  int minBottom = 0;
56  int maxTop = 0;
57  int maxBottom = 0;
58  int topHit[] = { 0, 0, 0 };
59  int bottomHit[] = { 0, 0, 0 };
60 
61  switch (Frame->codec)
62  {
63  case FMT_YV12:
64  case FMT_YUV420P9:
65  case FMT_YUV420P10:
66  case FMT_YUV420P12:
67  case FMT_YUV420P14:
68  case FMT_YUV420P16:
69  case FMT_NV12:
70  case FMT_P010:
71  case FMT_P016:
72  if (!m_firstFrameChecked || m_frameType != Frame->codec)
73  {
74  m_firstFrameChecked = Frame->frameNumber;
75  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("'%1' frame format detected")
76  .arg(format_description(Frame->codec)));
77  }
78  m_frameType = Frame->codec;
79  break;
80  default:
81  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("'%1' frame format is not supported")
82  .arg(format_description(Frame->codec)));
83  m_isDetectLetterbox = false;
84  return;
85  }
86 
87  if (Frame->frameNumber < 0)
88  {
89  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Strange frame number %1")
90  .arg(Frame->frameNumber));
91  return;
92  }
93 
94  if (m_player->GetVideoAspect() > 1.5F)
95  {
97  {
98  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("The source is already in widescreen (aspect: %1)")
99  .arg(static_cast<double>(m_player->GetVideoAspect())));
100  m_detectLetterboxLock.lock();
103  m_detectLetterboxSwitchFrame = Frame->frameNumber;
104  m_detectLetterboxLock.unlock();
105  }
106  else
107  {
109  }
110  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("The source is already in widescreen (aspect: %1)")
111  .arg(static_cast<double>(m_player->GetVideoAspect())));
112  m_isDetectLetterbox = false;
113  return;
114  }
115 
116  // Establish the level of light in the edge
117  int averageY = 0;
118  for (int pos : xPos)
119  {
120  averageY += buf[offsets[0] + 5 * pitches[0] + pos];
121  averageY += buf[offsets[0] + (height - 6) * pitches[0] + pos];
122  }
123 
124  averageY /= NUMBER_OF_DETECTION_LINES * 2;
125  if (averageY > 64) // Too bright to be a letterbox border
126  averageY = 0;
127 
128  // Note - for 10/12 bit etc we only sample the most significant byte
129  bool triplanar = format_is_420(m_frameType);
130  int depth = ColorDepth(m_frameType);
131  int leftshift = depth > 8 ? 1 : 0;
132  int rightshift = depth > 8 ? 0 : 1;
133 
134  // Scan the detection lines
135  for (int y = 5; y < height / 4; y++) // skip first pixels incase of noise in the edge
136  {
137  for (int detectionLine = 0; detectionLine < NUMBER_OF_DETECTION_LINES; detectionLine++)
138  {
139  int Y = buf[offsets[0] + y * pitches[0] + (xPos[detectionLine] << leftshift)];
140  int U = 0;
141  int V = 0;
142  if (triplanar)
143  {
144  U = buf[offsets[1] + (y>>1) * pitches[1] + (xPos[detectionLine] >> rightshift)];
145  V = buf[offsets[2] + (y>>1) * pitches[2] + (xPos[detectionLine] >> rightshift)];
146  }
147  else
148  {
149  int offset = offsets[1] + ((y >> 1) * pitches[1]) + (xPos[detectionLine & ~0x1] << leftshift);
150  U = buf[offset];
151  V = buf[offset + (1 << leftshift)];
152  }
153 
154  if ((!topHit[detectionLine]) &&
155  ( Y > averageY + THRESHOLD || Y < averageY - THRESHOLD ||
156  U < 128 - 32 || U > 128 + 32 ||
157  V < 128 - 32 || V > 128 + 32 ))
158  {
159  topHit[detectionLine] = y;
160  topHits++;
161  if (!minTop)
162  minTop = y;
163  maxTop = y;
164  }
165 
166  Y = buf[offsets[0] + (height-y-1) * pitches[0] + (xPos[detectionLine] << leftshift)];
167  if (triplanar)
168  {
169  U = buf[offsets[1] + ((height-y-1) >> 1) * pitches[1] + (xPos[detectionLine] >> rightshift)];
170  V = buf[offsets[2] + ((height-y-1) >> 1) * pitches[2] + (xPos[detectionLine] >> rightshift)];
171  }
172  else
173  {
174  int offset = offsets[1] + (((height - y -1) >> 1) * pitches[1]) + (xPos[detectionLine & ~0x1] << leftshift);
175  U = buf[offset];
176  V = buf[offset + (1 << leftshift)];
177  }
178 
179  if ((!bottomHit[detectionLine]) &&
180  ( Y > averageY + THRESHOLD || Y < averageY - THRESHOLD ||
181  U < 128 - 32 || U > 128 + 32 ||
182  V < 128 - 32 || V > 128 + 32 ))
183  {
184  bottomHit[detectionLine] = y;
185  bottomHits++;
186  if (!minBottom)
187  minBottom = y;
188  maxBottom = y;
189  }
190  }
191 
192  if (topHits == NUMBER_OF_DETECTION_LINES && bottomHits == NUMBER_OF_DETECTION_LINES)
193  break;
194  }
195 
196  if (topHits != NUMBER_OF_DETECTION_LINES)
197  maxTop = height / 4;
198  if (!minTop)
199  minTop = height / 4;
200  if (bottomHits != NUMBER_OF_DETECTION_LINES)
201  maxBottom = height / 4;
202  if (!minBottom)
203  minBottom = height / 4;
204 
205  bool horizontal = (((minTop != 0) && (maxTop - minTop < HORIZONTAL_THRESHOLD)) &&
206  ((minBottom != 0) && (maxBottom - minBottom < HORIZONTAL_THRESHOLD)));
207 
208  if (m_detectLetterboxSwitchFrame > Frame->frameNumber) // user is reversing
209  {
210  m_detectLetterboxLock.lock();
215  m_detectLetterboxLock.unlock();
216  }
217 
218  if (minTop < halfLimit || minBottom < halfLimit)
220  if (minTop < fullLimit || minBottom < fullLimit)
222 
224  {
226  minTop > halfLimit && minBottom > halfLimit)
227  {
229  }
230  }
231  else
232  {
234  minTop < fullLimit && minBottom < fullLimit)
235  {
237  }
238  }
240  minTop > fullLimit && minBottom > fullLimit)
241  {
243  }
244 
245  if (maxTop < halfLimit || maxBottom < halfLimit) // Not too restrictive when switching to off
246  {
247  // No Letterbox
249  {
250  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Non Letterbox detected on line: %1 (limit: %2)")
251  .arg(min(maxTop, maxBottom)).arg(halfLimit));
252  m_detectLetterboxLock.lock();
255  m_detectLetterboxSwitchFrame = Frame->frameNumber;
256  m_detectLetterboxLock.unlock();
257  }
258  else
259  {
261  }
262  }
263  else if (horizontal && minTop > halfLimit && minBottom > halfLimit &&
264  maxTop < fullLimit && maxBottom < fullLimit)
265  {
266  // Letterbox (with narrow bars)
268  {
269  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Narrow Letterbox detected on line: %1 (limit: %2) frame: %3")
270  .arg(minTop).arg(halfLimit)
272  m_detectLetterboxLock.lock();
276  {
277  // Do not change switch frame if switch to Full mode has not been executed yet
278  }
279  else
280  {
282  }
284  m_detectLetterboxLock.unlock();
285  }
286  else
287  {
289  }
290  }
291  else if (horizontal && minTop > fullLimit && minBottom > fullLimit)
292  {
293  // Letterbox
296  {
297  LOG(VB_PLAYBACK, LOG_INFO, LOC +
298  QString("Detected Letterbox on line: %1 (limit: %2) frame: %3").arg(minTop)
299  .arg(fullLimit).arg(m_detectLetterboxPossibleFullFrame));
300  m_detectLetterboxLock.lock();
304  m_detectLetterboxLock.unlock();
305  }
306  else
307  {
309  }
310  }
311  else
312  {
315  }
316 }
317 
324 {
325  if (!GetDetectLetterbox())
326  return;
327 
329  return;
330 
331  m_detectLetterboxLock.lock();
332  if (m_detectLetterboxSwitchFrame <= Frame->frameNumber &&
334  {
336  {
337  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Switched to '%1' on frame %2 (%3)")
339  .arg(Frame->frameNumber)
342  m_player->ReinitOSD();
343  }
345  }
346  else if (m_detectLetterboxSwitchFrame <= Frame->frameNumber)
347  {
348  LOG(VB_PLAYBACK, LOG_INFO, LOC +
349  QString("Not Switched to '%1' on frame %2 "
350  "(%3) Not enough consecutive detections (%4)")
352  .arg(Frame->frameNumber).arg(m_detectLetterboxSwitchFrame)
354  }
355 
356  m_detectLetterboxLock.unlock();
357 }
358 
360 {
365 }
366 
368 {
369  return m_isDetectLetterbox;
370 }
371 
372 /* vim: set expandtab tabstop=4 shiftwidth=4: */
DetectLetterbox(MythPlayer *Player)
void SetDetectLetterbox(bool Detect)
float GetVideoAspect(void) const
Definition: mythplayer.h:218
#define THRESHOLD
static bool format_is_420(VideoFrameType Type)
Definition: mythframe.h:85
void Detect(VideoFrame *Frame)
Detects if this frame is or is not letterboxed.
#define NUMBER_OF_DETECTION_LINES
void SwitchTo(VideoFrame *Frame)
Switch to the mode detected by DetectLetterbox.
QString toString(MarkTypes type)
#define LOC
long long m_detectLetterboxSwitchFrame
Which mode was last detected.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
bool GetDetectLetterbox() const
int ColorDepth(int Format)
Return the color depth for the given MythTV frame format.
Definition: mythframe.cpp:808
AdjustFillMode GetAdjustFill(void) const
QMutex m_detectLetterboxLock
AdjustFillMode m_detectLetterboxDefaultMode
VideoFrameType m_frameType
MythVideoOutput * GetVideoOutput(void)
Definition: mythplayer.h:281
long long m_firstFrameChecked
virtual void ToggleAdjustFill(AdjustFillMode FillMode=kAdjustFill_Toggle)
int GetNumSetting(const QString &key, int defaultval=0)
MythPlayer * m_player
void ReinitOSD(void)
Definition: mythplayer.cpp:399
AdjustFillMode m_detectLetterboxDetectedMode
const char * format_description(VideoFrameType Type)
Definition: mythframe.cpp:33
#define HORIZONTAL_THRESHOLD
long long m_detectLetterboxPossibleHalfFrame
On which frame was the mode switch detected.
int m_detectLetterboxConsecutiveCounter
long long m_detectLetterboxPossibleFullFrame
static int x1
Definition: mythsocket.cpp:60
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23