| 2755 | /** \fn NuppelVideoPlayer::DetectLetterbox(VideoFrame*) |
| 2756 | * \brief Detects if this frame is or is not letterboxed |
| 2757 | * |
| 2758 | * If a change is detected detectLetterboxSwitchFrame and |
| 2759 | * detectLetterboxDetectedMode are set. |
| 2760 | */ |
| 2761 | void NuppelVideoPlayer::DetectLetterbox(VideoFrame *frame) |
| 2762 | { |
| 2763 | const unsigned char *buf = frame->buf; |
| 2764 | const int width = frame->width; |
| 2765 | const int height = frame->height; |
| 2766 | const int *pitches = frame->pitches; |
| 2767 | const int *offsets = frame->offsets; |
| 2768 | const long long frameNumber = frame->frameNumber; |
| 2769 | |
| 2770 | const int NUMBER_OF_DETECTION_LINES = 3; // How many lines are we looking at |
| 2771 | const int THRESHOLD = 5; // Y component has to not vary more than this in the bars |
| 2772 | const int HORIZONTAL_THRESHOLD = 4; // How tolerant are we that the image has horizontal edges |
| 2773 | |
| 2774 | // If the black bars is larger than this limit we switch to Half or Full Mode |
| 2775 | // const int fullLimit = (int) (((height - width * 9 / 16) / 2) * detectLetterboxLimit / 100); |
| 2776 | // const int halfLimit = (int) (((height - width * 9 / 14) / 2) * detectLetterboxLimit / 100); |
| 2777 | // If the black bars is larger than this limit we switch to Half or Full Mode |
| 2778 | const int fullLimit = (int) ((height * (1 - video_aspect * 9 / 16) / 2) * detectLetterboxLimit / 100); |
| 2779 | const int halfLimit = (int) ((height * (1 - video_aspect * 9 / 14) / 2) * detectLetterboxLimit / 100); |
| 2780 | |
| 2781 | |
| 2782 | const int xPos[] = {width / 4, width / 2, width * 3 / 4}; // Lines to scan for black letterbox edge |
| 2783 | int topHits = 0, bottomHits = 0, minTop = 0, minBottom = 0, maxTop = 0, maxBottom = 0; |
| 2784 | int topHit[] = {0, 0, 0}, bottomHit[] = {0, 0, 0}; |
| 2785 | |
| 2786 | if (!videoOutput) |
| 2787 | return; |
| 2788 | |
| 2789 | |
| 2790 | if (frame->codec != FMT_YV12) |
| 2791 | { |
| 2792 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: The source is not in YV12 (was %1)").arg(frame->codec)); |
| 2793 | isDetectLetterbox = false; |
| 2794 | return; |
| 2795 | } |
| 2796 | |
| 2797 | if (frameNumber < 0) |
| 2798 | { |
| 2799 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Strange frame number %1").arg(frameNumber)); |
| 2800 | return; |
| 2801 | } |
| 2802 | |
| 2803 | if (video_aspect > 1.5) |
| 2804 | { |
| 2805 | if (detectLetterboxDetectedMode != detectLetterboxDefaultMode) |
| 2806 | { |
| 2807 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: The source is already in widescreen (aspect: %1)").arg(video_aspect)); |
| 2808 | detectLetterboxLock.lock(); |
| 2809 | detectLetterboxConsecutiveCounter = 0; |
| 2810 | detectLetterboxDetectedMode = detectLetterboxDefaultMode; |
| 2811 | detectLetterboxSwitchFrame = frameNumber; |
| 2812 | detectLetterboxLock.unlock(); |
| 2813 | } |
| 2814 | else |
| 2815 | { |
| 2816 | detectLetterboxConsecutiveCounter++; |
| 2817 | } |
| 2818 | //VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: The source is already in widescreen (aspect: %1)").arg(video_aspect)); |
| 2819 | //isDetectLetterbox = false; |
| 2820 | return; |
| 2821 | } |
| 2822 | |
| 2823 | // Establish the level of light in the edge |
| 2824 | int averageY = 0; |
| 2825 | for (int detectionLine = 0; detectionLine < NUMBER_OF_DETECTION_LINES; detectionLine++) |
| 2826 | { |
| 2827 | averageY += buf[offsets[0] + 5 * pitches[0] + xPos[detectionLine]]; |
| 2828 | averageY += buf[offsets[0] + (height - 6) * pitches[0] + xPos[detectionLine]]; |
| 2829 | } |
| 2830 | averageY /= NUMBER_OF_DETECTION_LINES * 2; |
| 2831 | if (averageY > 64) // To bright to be a letterbox border |
| 2832 | averageY = 0; |
| 2833 | |
| 2834 | // Scan the detection lines |
| 2835 | for (int y = 5; y < height / 4; y++) // skip first pixels incase of noise in the edge |
| 2836 | { |
| 2837 | for (int detectionLine = 0; detectionLine < NUMBER_OF_DETECTION_LINES; detectionLine++) |
| 2838 | { |
| 2839 | int Y = buf[offsets[0] + y * pitches[0] + xPos[detectionLine]]; |
| 2840 | int U = buf[offsets[1] + (y>>1) * pitches[1] + (xPos[detectionLine]>>1)]; |
| 2841 | int V = buf[offsets[2] + (y>>1) * pitches[2] + (xPos[detectionLine]>>1)]; |
| 2842 | if ((!topHit[detectionLine]) && |
| 2843 | ( Y > averageY + THRESHOLD || Y < averageY - THRESHOLD || |
| 2844 | U < 128 - 32 || U > 128 + 32 || |
| 2845 | V < 128 - 32 || V > 128 + 32 )) |
| 2846 | { |
| 2847 | topHit[detectionLine] = y; |
| 2848 | topHits++; |
| 2849 | if (!minTop) |
| 2850 | minTop = y; |
| 2851 | maxTop = y; |
| 2852 | } |
| 2853 | |
| 2854 | Y = buf[offsets[0] + (height-y-1 ) * pitches[0] + xPos[detectionLine]]; |
| 2855 | U = buf[offsets[1] + (height-y-1 >> 1) * pitches[1] + (xPos[detectionLine]>>1)]; |
| 2856 | V = buf[offsets[2] + (height-y-1 >> 1) * pitches[2] + (xPos[detectionLine]>>1)]; |
| 2857 | if ((!bottomHit[detectionLine]) && |
| 2858 | ( Y > averageY + THRESHOLD || Y < averageY - THRESHOLD || |
| 2859 | U < 128 - 32 || U > 128 + 32 || |
| 2860 | V < 128 - 32 || V > 128 + 32 )) |
| 2861 | { |
| 2862 | bottomHit[detectionLine] = y; |
| 2863 | bottomHits++; |
| 2864 | if (!minBottom) |
| 2865 | minBottom = y; |
| 2866 | maxBottom = y; |
| 2867 | } |
| 2868 | } |
| 2869 | |
| 2870 | if (topHits == NUMBER_OF_DETECTION_LINES && bottomHits == NUMBER_OF_DETECTION_LINES) |
| 2871 | { |
| 2872 | break; |
| 2873 | } |
| 2874 | } |
| 2875 | if (topHits != NUMBER_OF_DETECTION_LINES) maxTop = height / 4; |
| 2876 | if (!minTop) minTop = height / 4; |
| 2877 | if (bottomHits != NUMBER_OF_DETECTION_LINES) maxBottom = height / 4; |
| 2878 | if (!minBottom) minBottom = height / 4; |
| 2879 | |
| 2880 | bool horizontal = ((minTop && maxTop - minTop < HORIZONTAL_THRESHOLD) && |
| 2881 | (minBottom && maxBottom - minBottom < HORIZONTAL_THRESHOLD)); |
| 2882 | |
| 2883 | if (detectLetterboxSwitchFrame > frameNumber) // user is reversing |
| 2884 | { |
| 2885 | detectLetterboxLock.lock(); |
| 2886 | detectLetterboxDetectedMode = GetAdjustFill(); |
| 2887 | detectLetterboxSwitchFrame = -1; |
| 2888 | detectLetterboxPossibleHalfFrame = -1; |
| 2889 | detectLetterboxPossibleFullFrame = -1; |
| 2890 | detectLetterboxLock.unlock(); |
| 2891 | } |
| 2892 | |
| 2893 | if (minTop < halfLimit || minBottom < halfLimit) |
| 2894 | detectLetterboxPossibleHalfFrame = -1; |
| 2895 | if (minTop < fullLimit || minBottom < fullLimit) |
| 2896 | detectLetterboxPossibleFullFrame = -1; |
| 2897 | |
| 2898 | if (detectLetterboxDetectedMode != kAdjustFill_Full) |
| 2899 | { |
| 2900 | if (detectLetterboxPossibleHalfFrame == -1 && |
| 2901 | minTop > halfLimit && minBottom > halfLimit) { |
| 2902 | detectLetterboxPossibleHalfFrame = frameNumber; |
| 2903 | } |
| 2904 | } |
| 2905 | else |
| 2906 | { |
| 2907 | if (detectLetterboxPossibleHalfFrame == -1 && |
| 2908 | minTop < fullLimit && minBottom < fullLimit) { |
| 2909 | detectLetterboxPossibleHalfFrame = frameNumber; |
| 2910 | } |
| 2911 | } |
| 2912 | if (detectLetterboxPossibleFullFrame == -1 && minTop > fullLimit && minBottom > fullLimit) |
| 2913 | detectLetterboxPossibleFullFrame = frameNumber; |
| 2914 | |
| 2915 | if ( maxTop < halfLimit || maxBottom < halfLimit) // Not to restrictive when switching to off |
| 2916 | { |
| 2917 | // No Letterbox |
| 2918 | if (detectLetterboxDetectedMode != detectLetterboxDefaultMode) |
| 2919 | { |
| 2920 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Non Letterbox detected on line: %1 (limit: %2)").arg(min(maxTop, maxBottom)).arg(halfLimit)); |
| 2921 | detectLetterboxLock.lock(); |
| 2922 | detectLetterboxConsecutiveCounter = 0; |
| 2923 | detectLetterboxDetectedMode = detectLetterboxDefaultMode; |
| 2924 | detectLetterboxSwitchFrame = frameNumber; |
| 2925 | detectLetterboxLock.unlock(); |
| 2926 | } |
| 2927 | else |
| 2928 | { |
| 2929 | detectLetterboxConsecutiveCounter++; |
| 2930 | } |
| 2931 | } |
| 2932 | else if (horizontal && minTop > halfLimit && minBottom > halfLimit && |
| 2933 | maxTop < fullLimit && maxBottom < fullLimit) |
| 2934 | { |
| 2935 | // Letterbox (with narrow bars) |
| 2936 | if (detectLetterboxDetectedMode != kAdjustFill_Half) |
| 2937 | { |
| 2938 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Narrow Letterbox detected on line: %1 (limit: %2) frame: %3").arg(minTop).arg(halfLimit).arg(detectLetterboxPossibleHalfFrame)); |
| 2939 | detectLetterboxLock.lock(); |
| 2940 | detectLetterboxConsecutiveCounter = 0; |
| 2941 | if (detectLetterboxDetectedMode == kAdjustFill_Full && |
| 2942 | detectLetterboxSwitchFrame != -1) { |
| 2943 | // Do not change switch frame if switch to Full mode has not been executed yet |
| 2944 | } |
| 2945 | else |
| 2946 | detectLetterboxSwitchFrame = detectLetterboxPossibleHalfFrame; |
| 2947 | detectLetterboxDetectedMode = kAdjustFill_Half; |
| 2948 | detectLetterboxLock.unlock(); |
| 2949 | } |
| 2950 | else |
| 2951 | { |
| 2952 | detectLetterboxConsecutiveCounter++; |
| 2953 | } |
| 2954 | } |
| 2955 | else if (horizontal && minTop > fullLimit && minBottom > fullLimit) |
| 2956 | { |
| 2957 | // Letterbox |
| 2958 | detectLetterboxPossibleHalfFrame = -1; |
| 2959 | if (detectLetterboxDetectedMode != kAdjustFill_Full) |
| 2960 | { |
| 2961 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Detected Letterbox on line: %1 (limit: %2) frame: %2").arg(minTop).arg(fullLimit).arg(detectLetterboxPossibleFullFrame)); |
| 2962 | detectLetterboxLock.lock(); |
| 2963 | detectLetterboxConsecutiveCounter = 0; |
| 2964 | detectLetterboxDetectedMode = kAdjustFill_Full; |
| 2965 | detectLetterboxSwitchFrame = detectLetterboxPossibleFullFrame; |
| 2966 | detectLetterboxLock.unlock(); |
| 2967 | } |
| 2968 | else |
| 2969 | { |
| 2970 | detectLetterboxConsecutiveCounter++; |
| 2971 | } |
| 2972 | } |
| 2973 | else |
| 2974 | { |
| 2975 | if (detectLetterboxConsecutiveCounter <= 3) |
| 2976 | detectLetterboxConsecutiveCounter = 0; |
| 2977 | } |
| 2978 | } |
| 2979 | |
| 2980 | /** \fn NuppelVideoPlayer::SwitchToDetectedLetterbox(VideoFrame*) |
| 2981 | * \brief Switch to the mode detected by DetectLetterbox |
| 2982 | * |
| 2983 | * Switch fill mode if a switch was detected for this frame. |
| 2984 | */ |
| 2985 | void NuppelVideoPlayer::SwitchToDetectedLetterbox(VideoFrame *frame) |
| 2986 | { |
| 2987 | if (detectLetterboxSwitchFrame != -1) |
| 2988 | { |
| 2989 | detectLetterboxLock.lock(); |
| 2990 | if (detectLetterboxSwitchFrame <= frame->frameNumber && |
| 2991 | detectLetterboxConsecutiveCounter > 3) |
| 2992 | { |
| 2993 | if (GetAdjustFill() != detectLetterboxDetectedMode) |
| 2994 | { |
| 2995 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Switched to %1 on frame %2 (%3)").arg(detectLetterboxDetectedMode).arg(frame->frameNumber).arg(detectLetterboxSwitchFrame)); |
| 2996 | videoOutput->ToggleAdjustFill(detectLetterboxDetectedMode); |
| 2997 | ReinitOSD(); |
| 2998 | } |
| 2999 | detectLetterboxSwitchFrame = -1; |
| 3000 | } |
| 3001 | else if (detectLetterboxSwitchFrame <= frame->frameNumber) |
| 3002 | 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)); |
| 3003 | |
| 3004 | detectLetterboxLock.unlock(); |
| 3005 | } |
| 3006 | } |
| 3007 | |