MythTV master
DetectLetterbox.cpp
Go to the documentation of this file.
1// MythTV
4#include "DetectLetterbox.h"
5
6#define LOC QString("DetectLetterbox: ")
7
8static constexpr int8_t NUMBER_OF_DETECTION_LINES { 3 }; // How many lines are we looking at
9static constexpr int8_t THRESHOLD { 5 }; // Y component has to not vary more than this in the bars
10static constexpr int8_t HORIZONTAL_THRESHOLD { 4 }; // How tolerant are we that the image has horizontal edges
11
13{
14 int dbAdjustFill = gCoreContext->GetNumSetting("AdjustFill", 0);
17 static_cast<AdjustFillMode>(std::max(static_cast<int>(kAdjustFill_Off),
18 dbAdjustFill - kAdjustFill_AutoDetect_DefaultOff));
19 m_detectLetterboxLimit = gCoreContext->GetNumSetting("DetectLeterboxLimit", 75);
20}
21
29{
31 return false;
32
33 unsigned char *buf = Frame->m_buffer;
34 if (!buf)
35 return false;
36
37 const FramePitches pitches = Frame->m_pitches;
38 const FrameOffsets offsets = Frame->m_offsets;
39 const int height = Frame->m_height;
40
41 // If the black bars is larger than this limit we switch to Half or Full Mode
42 // const int fullLimit = static_cast<int>(((height - width * 9 / 16) / 2) * m_detectLetterboxLimit / 100);
43 // const int halfLimit = (static_cast<int>(((height - width * 9 / 14) / 2) * m_detectLetterboxLimit / 100);
44
45 // If the black bars is larger than this limit we switch to Half or Full Mode
46 const int fullLimit = static_cast<int>((height * (1 - VideoAspect * 9 / 16) / 2) * m_detectLetterboxLimit / 100);
47 const int halfLimit = static_cast<int>((height * (1 - VideoAspect * 9 / 14) / 2) * m_detectLetterboxLimit / 100);
48
49 // Lines to scan for black letterbox edge
50 const std::array<int,3> xPos { Frame->m_width / 4, Frame->m_width / 2, Frame->m_width * 3 / 4} ;
51 int topHits = 0;
52 int bottomHits = 0;
53 int minTop = 0;
54 int minBottom = 0;
55 int maxTop = 0;
56 int maxBottom = 0;
57 std::array<int,3> topHit = { 0, 0, 0 };
58 std::array<int,3> bottomHit = { 0, 0, 0 };
59
60 switch (Frame->m_type)
61 {
62 case FMT_YV12:
63 case FMT_YUV420P9:
64 case FMT_YUV420P10:
65 case FMT_YUV420P12:
66 case FMT_YUV420P14:
67 case FMT_YUV420P16:
68 case FMT_NV12:
69 case FMT_P010:
70 case FMT_P016:
71 if (!m_firstFrameChecked || m_frameType != Frame->m_type)
72 {
73 m_firstFrameChecked = Frame->m_frameNumber;
74 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("'%1' frame format detected")
76 }
77 m_frameType = Frame->m_type;
78 break;
79 default:
80 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("'%1' frame format is not supported")
82 m_isDetectLetterbox = false;
83 return false;
84 }
85
86 if (Frame->m_frameNumber < 0)
87 {
88 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Strange frame number %1").arg(Frame->m_frameNumber));
89 return false;
90 }
91
92 if (VideoAspect > 1.5F)
93 {
95 {
96 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("The source is already in widescreen (aspect: %1)")
97 .arg(static_cast<double>(VideoAspect)));
100 m_detectLetterboxSwitchFrame = Frame->m_frameNumber;
101 }
102 else
103 {
105 }
106 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("The source is already in widescreen (aspect: %1)")
107 .arg(static_cast<double>(VideoAspect)));
108 m_isDetectLetterbox = false;
109 return false;
110 }
111
112 // Establish the level of light in the edge
113 int averageY = 0;
114 for (int pos : xPos)
115 {
116 averageY += buf[offsets[0] + (5 * pitches[0]) + pos];
117 averageY += buf[offsets[0] + ((height - 6) * pitches[0]) + pos];
118 }
119
120 averageY /= NUMBER_OF_DETECTION_LINES * 2;
121 if (averageY > 64) // Too bright to be a letterbox border
122 averageY = 0;
123
124 // Note - for 10/12 bit etc we only sample the most significant byte
127 int leftshift = depth > 8 ? 1 : 0;
128 int rightshift = depth > 8 ? 0 : 1;
129
130 // Scan the detection lines
131 for (int y = 5; y < height / 4; y++) // skip first pixels incase of noise in the edge
132 {
133 for (int detectionLine = 0; detectionLine < NUMBER_OF_DETECTION_LINES; detectionLine++)
134 {
135 int Y = buf[offsets[0] + (y * pitches[0]) + (xPos[detectionLine] << leftshift)];
136 int U = 0;
137 int V = 0;
138 if (triplanar)
139 {
140 U = buf[offsets[1] + ((y>>1) * pitches[1]) + (xPos[detectionLine] >> rightshift)];
141 V = buf[offsets[2] + ((y>>1) * pitches[2]) + (xPos[detectionLine] >> rightshift)];
142 }
143 else
144 {
145 int offset = offsets[1] + ((y >> 1) * pitches[1]) + (xPos[detectionLine & ~0x1] << leftshift);
146 U = buf[offset];
147 V = buf[offset + (1 << leftshift)];
148 }
149
150 if ((!topHit[detectionLine]) &&
151 ( Y > averageY + THRESHOLD || Y < averageY - THRESHOLD ||
152 U < 128 - 32 || U > 128 + 32 ||
153 V < 128 - 32 || V > 128 + 32 ))
154 {
155 topHit[detectionLine] = y;
156 topHits++;
157 if (!minTop)
158 minTop = y;
159 maxTop = y;
160 }
161
162 Y = buf[offsets[0] + ((height-y-1) * pitches[0]) + (xPos[detectionLine] << leftshift)];
163 if (triplanar)
164 {
165 U = buf[offsets[1] + (((height-y-1) >> 1) * pitches[1]) + (xPos[detectionLine] >> rightshift)];
166 V = buf[offsets[2] + (((height-y-1) >> 1) * pitches[2]) + (xPos[detectionLine] >> rightshift)];
167 }
168 else
169 {
170 int offset = offsets[1] + (((height - y -1) >> 1) * pitches[1]) + (xPos[detectionLine & ~0x1] << leftshift);
171 U = buf[offset];
172 V = buf[offset + (1 << leftshift)];
173 }
174
175 if ((!bottomHit[detectionLine]) &&
176 ( Y > averageY + THRESHOLD || Y < averageY - THRESHOLD ||
177 U < 128 - 32 || U > 128 + 32 ||
178 V < 128 - 32 || V > 128 + 32 ))
179 {
180 bottomHit[detectionLine] = y;
181 bottomHits++;
182 if (!minBottom)
183 minBottom = y;
184 maxBottom = y;
185 }
186 }
187
188 if (topHits == NUMBER_OF_DETECTION_LINES && bottomHits == NUMBER_OF_DETECTION_LINES)
189 break;
190 }
191
192 if (topHits != NUMBER_OF_DETECTION_LINES)
193 maxTop = height / 4;
194 if (!minTop)
195 minTop = height / 4;
196 if (bottomHits != NUMBER_OF_DETECTION_LINES)
197 maxBottom = height / 4;
198 if (!minBottom)
199 minBottom = height / 4;
200
201 bool horizontal = (((minTop != 0) && (maxTop - minTop < HORIZONTAL_THRESHOLD)) &&
202 ((minBottom != 0) && (maxBottom - minBottom < HORIZONTAL_THRESHOLD)));
203
204 if (m_detectLetterboxSwitchFrame > Frame->m_frameNumber) // user is reversing
205 {
210 }
211
212 if (minTop < halfLimit || minBottom < halfLimit)
214 if (minTop < fullLimit || minBottom < fullLimit)
216
218 {
219 if (m_detectLetterboxPossibleHalfFrame == -1 && minTop > halfLimit && minBottom > halfLimit)
221 }
222 else
223 {
224 if (m_detectLetterboxPossibleHalfFrame == -1 && minTop < fullLimit && minBottom < fullLimit)
226 }
227
228 if (m_detectLetterboxPossibleFullFrame == -1 && minTop > fullLimit && minBottom > fullLimit)
230
231 if (maxTop < halfLimit || maxBottom < halfLimit) // Not too restrictive when switching to off
232 {
233 // No Letterbox
235 {
236 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Non Letterbox detected on line: %1 (limit: %2)")
237 .arg(std::min(maxTop, maxBottom)).arg(halfLimit));
240 m_detectLetterboxSwitchFrame = Frame->m_frameNumber;
241 }
242 else
243 {
245 }
246 }
247 else if (horizontal && minTop > halfLimit && minBottom > halfLimit &&
248 maxTop < fullLimit && maxBottom < fullLimit)
249 {
250 // Letterbox (with narrow bars)
252 {
253 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Narrow Letterbox detected on line: %1 (limit: %2) frame: %3")
254 .arg(minTop).arg(halfLimit).arg(m_detectLetterboxPossibleHalfFrame));
257 {
258 // Do not change switch frame if switch to Full mode has not been executed yet
259 }
260 else
261 {
263 }
265 }
266 else
267 {
269 }
270 }
271 else if (horizontal && minTop > fullLimit && minBottom > fullLimit)
272 {
273 // Letterbox
276 {
277 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Detected Letterbox on line: %1 (limit: %2) frame: %3")
278 .arg(minTop).arg(fullLimit).arg(m_detectLetterboxPossibleFullFrame));
282 }
283 else
284 {
286 }
287 }
288 else
289 {
292 }
293
295 return false;
296
297 if (m_detectLetterboxSwitchFrame <= Frame->m_frameNumber && m_detectLetterboxConsecutiveCounter > 3)
298 {
299 if (Current != m_detectLetterboxDetectedMode)
300 {
301 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Switched to '%1' on frame %2 (%3)")
303 .arg(Frame->m_frameNumber).arg(m_detectLetterboxSwitchFrame));
305 return true;
306 }
308 }
309 else if (m_detectLetterboxSwitchFrame <= Frame->m_frameNumber)
310 {
311 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Not Switched to '%1' on frame %2 "
312 "(%3) Not enough consecutive detections (%4)")
314 .arg(Frame->m_frameNumber).arg(m_detectLetterboxSwitchFrame)
316 }
317
318 return false;
319}
320
322{
327}
328
330{
331 return m_isDetectLetterbox;
332}
#define LOC
static constexpr int8_t HORIZONTAL_THRESHOLD
static constexpr int8_t NUMBER_OF_DETECTION_LINES
static constexpr int8_t THRESHOLD
void SetDetectLetterbox(bool Detect, AdjustFillMode Mode)
long long m_firstFrameChecked
bool Detect(MythVideoFrame *Frame, float VideoAspect, AdjustFillMode &Current)
Detects if this frame is or is not letterboxed.
VideoFrameType m_frameType
long long m_detectLetterboxPossibleFullFrame
AdjustFillMode m_detectLetterboxDetectedMode
long long m_detectLetterboxPossibleHalfFrame
On which frame was the mode switch detected.
long long m_detectLetterboxSwitchFrame
Which mode was last detected.
AdjustFillMode m_detectLetterboxDefaultMode
bool GetDetectLetterbox() const
int m_detectLetterboxConsecutiveCounter
int GetNumSetting(const QString &key, int defaultval=0)
static QString FormatDescription(VideoFrameType Type)
Definition: mythframe.cpp:368
static bool FormatIs420(VideoFrameType Type)
Definition: mythframe.h:437
static int ColorDepth(int Format)
Definition: mythframe.h:398
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
std::array< int, 3 > FrameOffsets
Definition: mythframe.h:84
@ FMT_YUV420P9
Definition: mythframe.h:24
@ FMT_YUV420P14
Definition: mythframe.h:27
@ FMT_YUV420P12
Definition: mythframe.h:26
@ FMT_YV12
Definition: mythframe.h:23
@ FMT_P016
Definition: mythframe.h:54
@ FMT_YUV420P10
Definition: mythframe.h:25
@ FMT_YUV420P16
Definition: mythframe.h:28
@ FMT_P010
Definition: mythframe.h:53
@ FMT_NV12
Definition: mythframe.h:52
std::array< int, 3 > FramePitches
Definition: mythframe.h:83
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
Mode
Definition: synaesthesia.h:20
AdjustFillMode
Definition: videoouttypes.h:72
@ kAdjustFill_Off
Definition: videoouttypes.h:74
@ kAdjustFill_Full
Definition: videoouttypes.h:76
@ kAdjustFill_Half
Definition: videoouttypes.h:75
@ kAdjustFill_AutoDetect_DefaultOff
Definition: videoouttypes.h:82