| | 1 | // -*- Mode: c++ -*- |
| | 2 | |
| | 3 | // Qt headers |
| | 4 | #include <QApplication> |
| | 5 | #include <QKeyEvent> |
| | 6 | |
| | 7 | // MythTV headers |
| | 8 | #include "DetectLetterbox.h" |
| | 9 | #include "NuppelVideoPlayer.h" |
| | 10 | #include "videoouttypes.h" |
| | 11 | #include "videoout_xv.h" |
| | 12 | |
| | 13 | #ifdef USING_VDPAU |
| | 14 | #include "util-vdpau.h" |
| | 15 | #endif // USING_VDPAU |
| | 16 | |
| | 17 | DetectLetterbox::DetectLetterbox(NuppelVideoPlayer* const nvp) |
| | 18 | { |
| | 19 | int dbAdjustFill = gContext->GetNumSetting("AdjustFill", 0); |
| | 20 | isDetectLetterbox = dbAdjustFill >= kAdjustFill_AutoDetect_DefaultOff; |
| | 21 | detectLetterboxDefaultMode = (AdjustFillMode) max((int) kAdjustFill_Off, |
| | 22 | dbAdjustFill - kAdjustFill_AutoDetect_DefaultOff); |
| | 23 | detectLetterboxSwitchFrame = -1; |
| | 24 | detectLetterboxPossibleHalfFrame = -1; |
| | 25 | detectLetterboxPossibleFullFrame = -1; |
| | 26 | detectLetterboxDetectedMode = nvp->GetAdjustFill(); |
| | 27 | detectLetterboxLimit = gContext->GetNumSetting("DetectLeterboxLimit", 75); |
| | 28 | nupple_video_player = nvp; |
| | 29 | } |
| | 30 | |
| | 31 | DetectLetterbox::~DetectLetterbox() |
| | 32 | { |
| | 33 | } |
| | 34 | |
| | 35 | /** \fn DetectLetterbox::Detect(VideoFrame*) |
| | 36 | * \brief Detects if this frame is or is not letterboxed |
| | 37 | * |
| | 38 | * If a change is detected detectLetterboxSwitchFrame and |
| | 39 | * detectLetterboxDetectedMode are set. |
| | 40 | */ |
| | 41 | void DetectLetterbox::Detect(VideoFrame *frame) |
| | 42 | { |
| | 43 | unsigned char *buf = frame->buf; |
| | 44 | int *pitches = frame->pitches; |
| | 45 | int *offsets = frame->offsets; |
| | 46 | const int width = frame->width; |
| | 47 | const int height = frame->height; |
| | 48 | const long long frameNumber = frame->frameNumber; |
| | 49 | const int NUMBER_OF_DETECTION_LINES = 3; // How many lines are we looking at |
| | 50 | const int THRESHOLD = 5; // Y component has to not vary more than this in the bars |
| | 51 | const int HORIZONTAL_THRESHOLD = 4; // How tolerant are we that the image has horizontal edges |
| | 52 | |
| | 53 | // If the black bars is larger than this limit we switch to Half or Full Mode |
| | 54 | // const int fullLimit = (int) (((height - width * 9 / 16) / 2) * detectLetterboxLimit / 100); |
| | 55 | // const int halfLimit = (int) (((height - width * 9 / 14) / 2) * detectLetterboxLimit / 100); |
| | 56 | // If the black bars is larger than this limit we switch to Half or Full Mode |
| | 57 | const int fullLimit = (int) ((height * (1 - nupple_video_player->GetVideoAspect() * 9 / 16) / 2) * detectLetterboxLimit / 100); |
| | 58 | const int halfLimit = (int) ((height * (1 - nupple_video_player->GetVideoAspect() * 9 / 14) / 2) * detectLetterboxLimit / 100); |
| | 59 | |
| | 60 | const int xPos[] = {width / 4, width / 2, width * 3 / 4}; // Lines to scan for black letterbox edge |
| | 61 | int topHits = 0, bottomHits = 0, minTop = 0, minBottom = 0, maxTop = 0, maxBottom = 0; |
| | 62 | int topHit[] = {0, 0, 0}, bottomHit[] = {0, 0, 0}; |
| | 63 | |
| | 64 | #ifndef USING_VDPAU |
| | 65 | |
| | 66 | #endif // not USING_VDPAU |
| | 67 | |
| | 68 | #ifdef USING_VDPAU |
| | 69 | VdpVideoSurface* vdpau; |
| | 70 | VdpYCbCrFormat vdp_ycbcr_format; |
| | 71 | vdp_ycbcr_format = VDP_YCBCR_FORMAT_YV12; |
| | 72 | VdpStatus vdp_status; |
| | 73 | VdpVideoSurfaceGetBitsYCbCr * vpd_video_serface_get_bits_y_cb_cd; |
| | 74 | VideoOutputXv* video_output; |
| | 75 | #endif // USING_VDPAU |
| | 76 | |
| | 77 | if (!GetDetectLetterbox()) |
| | 78 | return; |
| | 79 | |
| | 80 | if (!nupple_video_player->getVideoOutput()) |
| | 81 | return; |
| | 82 | |
| | 83 | switch (frame->codec) { |
| | 84 | #ifdef USING_VDPAU |
| | 85 | case FMT_VDPAU: |
| | 86 | return; |
| | 87 | if (frameNumber == 1) |
| | 88 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: VDPAU frame format detected")); |
| | 89 | |
| | 90 | memset(buf, 0, width*height*3); |
| | 91 | |
| | 92 | pitches[0] = width; |
| | 93 | pitches[1] = width >> 1; |
| | 94 | pitches[2] = width >> 1 ; |
| | 95 | |
| | 96 | offsets[0] = 0; |
| | 97 | offsets[0] = width*height; |
| | 98 | offsets[0] = (width*height) >> 2; |
| | 99 | |
| | 100 | video_output = (VideoOutputXv*) nupple_video_player->getVideoOutput(); |
| | 101 | vdpau = video_output->getVDPAUContext()->getVdpVideoSurface(); |
| | 102 | vdp_status = vpd_video_serface_get_bits_y_cb_cd(*vdpau, vdp_ycbcr_format, (void* const*)buf, (const uint32_t*) pitches); |
| | 103 | break; |
| | 104 | #endif // USING_VDPAU |
| | 105 | case FMT_YV12: |
| | 106 | if (frameNumber == 1) |
| | 107 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: YV12 frame format detected")); |
| | 108 | break; |
| | 109 | default: |
| | 110 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: The source is not a supported frame format (was %1)").arg(frame->codec)); |
| | 111 | isDetectLetterbox = false; |
| | 112 | return; |
| | 113 | } |
| | 114 | |
| | 115 | if (frameNumber < 0) |
| | 116 | { |
| | 117 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Strange frame number %1").arg(frameNumber)); |
| | 118 | return; |
| | 119 | } |
| | 120 | |
| | 121 | if (nupple_video_player->GetVideoAspect() > 1.5) |
| | 122 | { |
| | 123 | if (detectLetterboxDetectedMode != detectLetterboxDefaultMode) |
| | 124 | { |
| | 125 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: The source is already in widescreen (aspect: %1)").arg(nupple_video_player->GetVideoAspect())); |
| | 126 | detectLetterboxLock.lock(); |
| | 127 | detectLetterboxConsecutiveCounter = 0; |
| | 128 | detectLetterboxDetectedMode = detectLetterboxDefaultMode; |
| | 129 | detectLetterboxSwitchFrame = frameNumber; |
| | 130 | detectLetterboxLock.unlock(); |
| | 131 | } |
| | 132 | else |
| | 133 | { |
| | 134 | detectLetterboxConsecutiveCounter++; |
| | 135 | } |
| | 136 | //VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: The source is already in widescreen (aspect: %1)").arg(video_aspect)); |
| | 137 | //isDetectLetterbox = false; |
| | 138 | return; |
| | 139 | } |
| | 140 | |
| | 141 | // Establish the level of light in the edge |
| | 142 | int averageY = 0; |
| | 143 | for (int detectionLine = 0; detectionLine < NUMBER_OF_DETECTION_LINES; detectionLine++) |
| | 144 | { |
| | 145 | averageY += buf[offsets[0] + 5 * pitches[0] + xPos[detectionLine]]; |
| | 146 | averageY += buf[offsets[0] + (height - 6) * pitches[0] + xPos[detectionLine]]; |
| | 147 | } |
| | 148 | averageY /= NUMBER_OF_DETECTION_LINES * 2; |
| | 149 | if (averageY > 64) // To bright to be a letterbox border |
| | 150 | averageY = 0; |
| | 151 | |
| | 152 | // Scan the detection lines |
| | 153 | for (int y = 5; y < height / 4; y++) // skip first pixels incase of noise in the edge |
| | 154 | { |
| | 155 | for (int detectionLine = 0; detectionLine < NUMBER_OF_DETECTION_LINES; detectionLine++) |
| | 156 | { |
| | 157 | int Y = buf[offsets[0] + y * pitches[0] + xPos[detectionLine]]; |
| | 158 | int U = buf[offsets[1] + (y>>1) * pitches[1] + (xPos[detectionLine]>>1)]; |
| | 159 | int V = buf[offsets[2] + (y>>1) * pitches[2] + (xPos[detectionLine]>>1)]; |
| | 160 | if ((!topHit[detectionLine]) && |
| | 161 | ( Y > averageY + THRESHOLD || Y < averageY - THRESHOLD || |
| | 162 | U < 128 - 32 || U > 128 + 32 || |
| | 163 | V < 128 - 32 || V > 128 + 32 )) |
| | 164 | { |
| | 165 | topHit[detectionLine] = y; |
| | 166 | topHits++; |
| | 167 | if (!minTop) |
| | 168 | minTop = y; |
| | 169 | maxTop = y; |
| | 170 | } |
| | 171 | |
| | 172 | Y = buf[offsets[0] + (height-y-1 ) * pitches[0] + xPos[detectionLine]]; |
| | 173 | U = buf[offsets[1] + (height-y-1 >> 1) * pitches[1] + (xPos[detectionLine]>>1)]; |
| | 174 | V = buf[offsets[2] + (height-y-1 >> 1) * pitches[2] + (xPos[detectionLine]>>1)]; |
| | 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 | { |
| | 190 | break; |
| | 191 | } |
| | 192 | } |
| | 193 | if (topHits != NUMBER_OF_DETECTION_LINES) maxTop = height / 4; |
| | 194 | if (!minTop) minTop = height / 4; |
| | 195 | if (bottomHits != NUMBER_OF_DETECTION_LINES) maxBottom = height / 4; |
| | 196 | if (!minBottom) minBottom = height / 4; |
| | 197 | |
| | 198 | bool horizontal = ((minTop && maxTop - minTop < HORIZONTAL_THRESHOLD) && |
| | 199 | (minBottom && maxBottom - minBottom < HORIZONTAL_THRESHOLD)); |
| | 200 | |
| | 201 | if (detectLetterboxSwitchFrame > frameNumber) // user is reversing |
| | 202 | { |
| | 203 | detectLetterboxLock.lock(); |
| | 204 | detectLetterboxDetectedMode = nupple_video_player->GetAdjustFill(); |
| | 205 | detectLetterboxSwitchFrame = -1; |
| | 206 | detectLetterboxPossibleHalfFrame = -1; |
| | 207 | detectLetterboxPossibleFullFrame = -1; |
| | 208 | detectLetterboxLock.unlock(); |
| | 209 | } |
| | 210 | |
| | 211 | if (minTop < halfLimit || minBottom < halfLimit) |
| | 212 | detectLetterboxPossibleHalfFrame = -1; |
| | 213 | if (minTop < fullLimit || minBottom < fullLimit) |
| | 214 | detectLetterboxPossibleFullFrame = -1; |
| | 215 | |
| | 216 | if (detectLetterboxDetectedMode != kAdjustFill_Full) |
| | 217 | { |
| | 218 | if (detectLetterboxPossibleHalfFrame == -1 && |
| | 219 | minTop > halfLimit && minBottom > halfLimit) { |
| | 220 | detectLetterboxPossibleHalfFrame = frameNumber; |
| | 221 | } |
| | 222 | } |
| | 223 | else |
| | 224 | { |
| | 225 | if (detectLetterboxPossibleHalfFrame == -1 && |
| | 226 | minTop < fullLimit && minBottom < fullLimit) { |
| | 227 | detectLetterboxPossibleHalfFrame = frameNumber; |
| | 228 | } |
| | 229 | } |
| | 230 | if (detectLetterboxPossibleFullFrame == -1 && minTop > fullLimit && minBottom > fullLimit) |
| | 231 | detectLetterboxPossibleFullFrame = frameNumber; |
| | 232 | |
| | 233 | if ( maxTop < halfLimit || maxBottom < halfLimit) // Not to restrictive when switching to off |
| | 234 | { |
| | 235 | // No Letterbox |
| | 236 | if (detectLetterboxDetectedMode != detectLetterboxDefaultMode) |
| | 237 | { |
| | 238 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Non Letterbox detected on line: %1 (limit: %2)").arg(min(maxTop, maxBottom)).arg(halfLimit)); |
| | 239 | detectLetterboxLock.lock(); |
| | 240 | detectLetterboxConsecutiveCounter = 0; |
| | 241 | detectLetterboxDetectedMode = detectLetterboxDefaultMode; |
| | 242 | detectLetterboxSwitchFrame = frameNumber; |
| | 243 | detectLetterboxLock.unlock(); |
| | 244 | } |
| | 245 | else |
| | 246 | { |
| | 247 | detectLetterboxConsecutiveCounter++; |
| | 248 | } |
| | 249 | } |
| | 250 | else if (horizontal && minTop > halfLimit && minBottom > halfLimit && |
| | 251 | maxTop < fullLimit && maxBottom < fullLimit) |
| | 252 | { |
| | 253 | // Letterbox (with narrow bars) |
| | 254 | if (detectLetterboxDetectedMode != kAdjustFill_Half) |
| | 255 | { |
| | 256 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Narrow Letterbox detected on line: %1 (limit: %2) frame: %3").arg(minTop).arg(halfLimit).arg(detectLetterboxPossibleHalfFrame)); |
| | 257 | detectLetterboxLock.lock(); |
| | 258 | detectLetterboxConsecutiveCounter = 0; |
| | 259 | if (detectLetterboxDetectedMode == kAdjustFill_Full && |
| | 260 | detectLetterboxSwitchFrame != -1) { |
| | 261 | // Do not change switch frame if switch to Full mode has not been executed yet |
| | 262 | } |
| | 263 | else |
| | 264 | detectLetterboxSwitchFrame = detectLetterboxPossibleHalfFrame; |
| | 265 | detectLetterboxDetectedMode = kAdjustFill_Half; |
| | 266 | detectLetterboxLock.unlock(); |
| | 267 | } |
| | 268 | else |
| | 269 | { |
| | 270 | detectLetterboxConsecutiveCounter++; |
| | 271 | } |
| | 272 | } |
| | 273 | else if (horizontal && minTop > fullLimit && minBottom > fullLimit) |
| | 274 | { |
| | 275 | // Letterbox |
| | 276 | detectLetterboxPossibleHalfFrame = -1; |
| | 277 | if (detectLetterboxDetectedMode != kAdjustFill_Full) |
| | 278 | { |
| | 279 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Detected Letterbox on line: %1 (limit: %2) frame: %2").arg(minTop).arg(fullLimit).arg(detectLetterboxPossibleFullFrame)); |
| | 280 | detectLetterboxLock.lock(); |
| | 281 | detectLetterboxConsecutiveCounter = 0; |
| | 282 | detectLetterboxDetectedMode = kAdjustFill_Full; |
| | 283 | detectLetterboxSwitchFrame = detectLetterboxPossibleFullFrame; |
| | 284 | detectLetterboxLock.unlock(); |
| | 285 | } |
| | 286 | else |
| | 287 | { |
| | 288 | detectLetterboxConsecutiveCounter++; |
| | 289 | } |
| | 290 | } |
| | 291 | else |
| | 292 | { |
| | 293 | if (detectLetterboxConsecutiveCounter <= 3) |
| | 294 | detectLetterboxConsecutiveCounter = 0; |
| | 295 | } |
| | 296 | } |
| | 297 | |
| | 298 | /** \fn DetectLetterbox::SwitchTo(VideoFrame*) |
| | 299 | * \brief Switch to the mode detected by DetectLetterbox |
| | 300 | * |
| | 301 | * Switch fill mode if a switch was detected for this frame. |
| | 302 | */ |
| | 303 | void DetectLetterbox::SwitchTo(VideoFrame *frame) |
| | 304 | { |
| | 305 | if (!GetDetectLetterbox()) |
| | 306 | return; |
| | 307 | |
| | 308 | if (detectLetterboxSwitchFrame != -1) |
| | 309 | { |
| | 310 | detectLetterboxLock.lock(); |
| | 311 | if (detectLetterboxSwitchFrame <= frame->frameNumber && |
| | 312 | detectLetterboxConsecutiveCounter > 3) |
| | 313 | { |
| | 314 | if (nupple_video_player->GetAdjustFill() != detectLetterboxDetectedMode) |
| | 315 | { |
| | 316 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Switched to %1 on frame %2 (%3)").arg(detectLetterboxDetectedMode).arg(frame->frameNumber).arg(detectLetterboxSwitchFrame)); |
| | 317 | nupple_video_player->getVideoOutput()->ToggleAdjustFill(detectLetterboxDetectedMode); |
| | 318 | nupple_video_player->ReinitOSD(); |
| | 319 | } |
| | 320 | detectLetterboxSwitchFrame = -1; |
| | 321 | } |
| | 322 | else if (detectLetterboxSwitchFrame <= frame->frameNumber) |
| | 323 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Not Switched to %1 on frame %2 (%3) Not enough consecutive detections (%4)").arg(detectLetterboxDetectedMode).arg(frame->frameNumber).arg(detectLetterboxSwitchFrame).arg(detectLetterboxConsecutiveCounter)); |
| | 324 | |
| | 325 | detectLetterboxLock.unlock(); |
| | 326 | } |
| | 327 | } |
| | 328 | |
| | 329 | void DetectLetterbox::SetDetectLetterbox(bool detect) |
| | 330 | { |
| | 331 | isDetectLetterbox = detect; |
| | 332 | detectLetterboxSwitchFrame = -1; |
| | 333 | detectLetterboxDetectedMode = nupple_video_player->GetAdjustFill(); |
| | 334 | } |
| | 335 | |
| | 336 | bool DetectLetterbox::GetDetectLetterbox() |
| | 337 | { |
| | 338 | return isDetectLetterbox; |
| | 339 | } |
| | 340 | |
| | 341 | /* vim: set expandtab tabstop=4 shiftwidth=4: */ |