| | 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 | |