| | 2625 | // Debug method for DetectLetterbox |
| | 2626 | // RED: 76, 109, 207 |
| | 2627 | // GREEN: 150, 91, 62 |
| | 2628 | void draw(VideoFrame *frame,int x, int y, unsigned char Y, unsigned char U, unsigned char V) { |
| | 2629 | unsigned char *buf = frame->buf; |
| | 2630 | int *pitches = frame->pitches; // Y, U, & V pitches |
| | 2631 | int *offsets = frame->offsets; // Y, U, & V offsets |
| | 2632 | |
| | 2633 | buf[offsets[0] + y * pitches[0] + x] = Y; |
| | 2634 | buf[offsets[1] + (y>>1) * pitches[1] + (x>>1)] = U; |
| | 2635 | buf[offsets[2] + (y>>1) * pitches[2] + (x>>1)] = V; |
| | 2636 | } |
| | 2637 | |
| | 2638 | void NuppelVideoPlayer::DetectLetterbox(VideoFrame *frame, bool debug) |
| | 2639 | { |
| | 2640 | const unsigned char *buf = frame->buf; |
| | 2641 | const int width = frame->width; |
| | 2642 | const int height = frame->height; |
| | 2643 | const int *pitches = frame->pitches; |
| | 2644 | const int *offsets = frame->offsets; |
| | 2645 | const long long frameNumber = frame->frameNumber; |
| | 2646 | |
| | 2647 | const int NUMBER_OF_DETECTION_LINES = 3; // How many lines are we looking at |
| | 2648 | const int THRESHOLD = 5; // Y component has to not vary more than this in the bars |
| | 2649 | const int HORIZONTAL_THRESHOLD = 4; // How tolerant are we that the image has horizontal edges |
| | 2650 | |
| | 2651 | // If the black bars is larger than this limit we switch to Half or Full Mode |
| | 2652 | // const int fullLimit = (int) (((height - width * 9 / 16) / 2) * detectLetterboxLimit / 100); |
| | 2653 | // const int halfLimit = (int) (((height - width * 9 / 14) / 2) * detectLetterboxLimit / 100); |
| | 2654 | // If the black bars is larger than this limit we switch to Half or Full Mode |
| | 2655 | const int fullLimit = (int) ((height * (1 - video_aspect * 9 / 16) / 2) * detectLetterboxLimit / 100); |
| | 2656 | const int halfLimit = (int) ((height * (1 - video_aspect * 9 / 14) / 2) * detectLetterboxLimit / 100); |
| | 2657 | |
| | 2658 | |
| | 2659 | const int xPos[] = {width / 4, width / 2, width * 3 / 4}; // Lines to scan for black letterbox edge |
| | 2660 | int topHits = 0, bottomHits = 0, minTop = 0, minBottom = 0, maxTop = 0, maxBottom = 0; |
| | 2661 | int topHit[] = {0, 0, 0}, bottomHit[] = {0, 0, 0}; |
| | 2662 | |
| | 2663 | if (!videoOutput) |
| | 2664 | return; |
| | 2665 | |
| | 2666 | |
| | 2667 | if (frame->codec != FMT_YV12) |
| | 2668 | { |
| | 2669 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: The source is not in YV12")); |
| | 2670 | isDetectLetterbox = false; |
| | 2671 | return; |
| | 2672 | } |
| | 2673 | |
| | 2674 | if (frameNumber < 0) |
| | 2675 | { |
| | 2676 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Strange frame number %1").arg(frameNumber)); |
| | 2677 | return; |
| | 2678 | } |
| | 2679 | |
| | 2680 | if (video_aspect > 1.5) |
| | 2681 | { |
| | 2682 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: The source is already in widescreen (aspect: %1)").arg(video_aspect)); |
| | 2683 | isDetectLetterbox = false; |
| | 2684 | return; |
| | 2685 | } |
| | 2686 | |
| | 2687 | // Establish the level of light in the edge |
| | 2688 | int averageY = 0; |
| | 2689 | for (int detectionLine = 0; detectionLine < NUMBER_OF_DETECTION_LINES; detectionLine++) |
| | 2690 | { |
| | 2691 | averageY += buf[offsets[0] + 5 * pitches[0] + xPos[detectionLine]]; |
| | 2692 | averageY += buf[offsets[0] + (height - 6) * pitches[0] + xPos[detectionLine]]; |
| | 2693 | } |
| | 2694 | averageY /= NUMBER_OF_DETECTION_LINES * 2; |
| | 2695 | if (averageY > 64) // To bright to be a letterbox border |
| | 2696 | averageY = 0; |
| | 2697 | |
| | 2698 | // Scan the detection lines |
| | 2699 | for (int y = 5; y < height / 4; y++) // skip first pixels incase of noise in the edge |
| | 2700 | { |
| | 2701 | for (int detectionLine = 0; detectionLine < NUMBER_OF_DETECTION_LINES; detectionLine++) |
| | 2702 | { |
| | 2703 | int Y = buf[offsets[0] + y * pitches[0] + xPos[detectionLine]]; |
| | 2704 | int U = buf[offsets[1] + (y>>1) * pitches[1] + (xPos[detectionLine]>>1)]; |
| | 2705 | int V = buf[offsets[2] + (y>>1) * pitches[2] + (xPos[detectionLine]>>1)]; |
| | 2706 | if ((!topHit[detectionLine]) && |
| | 2707 | ( Y > averageY + THRESHOLD || Y < averageY - THRESHOLD || |
| | 2708 | U < 128 - 32 || U > 128 + 32 || |
| | 2709 | V < 128 - 32 || V > 128 + 32 )) |
| | 2710 | { |
| | 2711 | topHit[detectionLine] = y; |
| | 2712 | topHits++; |
| | 2713 | if (!minTop) |
| | 2714 | minTop = y; |
| | 2715 | maxTop = y; |
| | 2716 | } |
| | 2717 | |
| | 2718 | Y = buf[offsets[0] + (height-y-1 ) * pitches[0] + xPos[detectionLine]]; |
| | 2719 | U = buf[offsets[1] + (height-y-1 >> 1) * pitches[1] + (xPos[detectionLine]>>1)]; |
| | 2720 | V = buf[offsets[2] + (height-y-1 >> 1) * pitches[2] + (xPos[detectionLine]>>1)]; |
| | 2721 | if ((!bottomHit[detectionLine]) && |
| | 2722 | ( Y > averageY + THRESHOLD || Y < averageY - THRESHOLD || |
| | 2723 | U < 128 - 32 || U > 128 + 32 || |
| | 2724 | V < 128 - 32 || V > 128 + 32 )) |
| | 2725 | { |
| | 2726 | bottomHit[detectionLine] = y; |
| | 2727 | bottomHits++; |
| | 2728 | if (!minBottom) |
| | 2729 | minBottom = y; |
| | 2730 | maxBottom = y; |
| | 2731 | } |
| | 2732 | } |
| | 2733 | |
| | 2734 | if (topHits == NUMBER_OF_DETECTION_LINES && bottomHits == NUMBER_OF_DETECTION_LINES) |
| | 2735 | { |
| | 2736 | break; |
| | 2737 | } |
| | 2738 | } |
| | 2739 | if (topHits != NUMBER_OF_DETECTION_LINES) maxTop = height / 4; |
| | 2740 | if (!minTop) minTop = height / 4; |
| | 2741 | if (bottomHits != NUMBER_OF_DETECTION_LINES) maxBottom = height / 4; |
| | 2742 | if (!minBottom) minBottom = height / 4; |
| | 2743 | |
| | 2744 | if (!debug) |
| | 2745 | { |
| | 2746 | bool horizontal = ((minTop && maxTop - minTop < HORIZONTAL_THRESHOLD) && |
| | 2747 | (minBottom && maxBottom - minBottom < HORIZONTAL_THRESHOLD)); |
| | 2748 | |
| | 2749 | if (detectLetterboxSwitchFrame > frameNumber) // user is reversing |
| | 2750 | { |
| | 2751 | detectLetterboxLock.lock(); |
| | 2752 | detectLetterboxDetectedMode = GetAdjustFill(); |
| | 2753 | detectLetterboxSwitchFrame = -1; |
| | 2754 | detectLetterboxPossibleHalfFrame = -1; |
| | 2755 | detectLetterboxPossibleFullFrame = -1; |
| | 2756 | detectLetterboxLock.unlock(); |
| | 2757 | } |
| | 2758 | |
| | 2759 | if (minTop < halfLimit || minBottom < halfLimit) |
| | 2760 | detectLetterboxPossibleHalfFrame = -1; |
| | 2761 | if (minTop < fullLimit || minBottom < fullLimit) |
| | 2762 | detectLetterboxPossibleFullFrame = -1; |
| | 2763 | |
| | 2764 | if (detectLetterboxDetectedMode != kAdjustFill_Full) |
| | 2765 | { |
| | 2766 | if (detectLetterboxPossibleHalfFrame == -1 && |
| | 2767 | minTop > halfLimit && minBottom > halfLimit) { |
| | 2768 | detectLetterboxPossibleHalfFrame = frameNumber; |
| | 2769 | } |
| | 2770 | } |
| | 2771 | else |
| | 2772 | { |
| | 2773 | if (detectLetterboxPossibleHalfFrame == -1 && |
| | 2774 | minTop < fullLimit && minBottom < fullLimit) { |
| | 2775 | detectLetterboxPossibleHalfFrame = frameNumber; |
| | 2776 | } |
| | 2777 | } |
| | 2778 | if (detectLetterboxPossibleFullFrame == -1 && minTop > fullLimit && minBottom > fullLimit) |
| | 2779 | detectLetterboxPossibleFullFrame = frameNumber; |
| | 2780 | |
| | 2781 | if ( maxTop < halfLimit || maxBottom < halfLimit) // Not to restrictive when switching to off |
| | 2782 | { |
| | 2783 | // No Letterbox |
| | 2784 | if (detectLetterboxDetectedMode != detectLetterboxDefaultMode) |
| | 2785 | { |
| | 2786 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Non Letterbox detected on line: %1 (limit: %2)").arg(min(maxTop, maxBottom)).arg(halfLimit)); |
| | 2787 | detectLetterboxLock.lock(); |
| | 2788 | detectLetterboxConsecutiveCounter = 0; |
| | 2789 | detectLetterboxDetectedMode = detectLetterboxDefaultMode; |
| | 2790 | detectLetterboxSwitchFrame = frameNumber; |
| | 2791 | detectLetterboxLock.unlock(); |
| | 2792 | } |
| | 2793 | else |
| | 2794 | { |
| | 2795 | detectLetterboxConsecutiveCounter++; |
| | 2796 | } |
| | 2797 | } |
| | 2798 | else if (horizontal && minTop > halfLimit && minBottom > halfLimit && |
| | 2799 | maxTop < fullLimit && maxBottom < fullLimit) |
| | 2800 | { |
| | 2801 | // Letterbox (with narrow bars) |
| | 2802 | if (detectLetterboxDetectedMode != kAdjustFill_Half) |
| | 2803 | { |
| | 2804 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Narrow Letterbox detected on line: %1 (limit: %2) frame: %3").arg(minTop).arg(halfLimit).arg(detectLetterboxPossibleHalfFrame)); |
| | 2805 | detectLetterboxLock.lock(); |
| | 2806 | detectLetterboxConsecutiveCounter = 0; |
| | 2807 | if (detectLetterboxDetectedMode == kAdjustFill_Full && |
| | 2808 | detectLetterboxSwitchFrame != -1) { |
| | 2809 | // Do not change switch frame if switch to Full mode has not been executed yet |
| | 2810 | } |
| | 2811 | else |
| | 2812 | detectLetterboxSwitchFrame = detectLetterboxPossibleHalfFrame; |
| | 2813 | detectLetterboxDetectedMode = kAdjustFill_Half; |
| | 2814 | detectLetterboxLock.unlock(); |
| | 2815 | } |
| | 2816 | else |
| | 2817 | { |
| | 2818 | detectLetterboxConsecutiveCounter++; |
| | 2819 | } |
| | 2820 | } |
| | 2821 | else if (horizontal && minTop > fullLimit && minBottom > fullLimit) |
| | 2822 | { |
| | 2823 | // Letterbox |
| | 2824 | detectLetterboxPossibleHalfFrame = -1; |
| | 2825 | if (detectLetterboxDetectedMode != kAdjustFill_Full) |
| | 2826 | { |
| | 2827 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Detected Letterbox on line: %1 (limit: %2) frame: %2").arg(minTop).arg(fullLimit).arg(detectLetterboxPossibleFullFrame)); |
| | 2828 | detectLetterboxLock.lock(); |
| | 2829 | detectLetterboxConsecutiveCounter = 0; |
| | 2830 | detectLetterboxDetectedMode = kAdjustFill_Full; |
| | 2831 | detectLetterboxSwitchFrame = detectLetterboxPossibleFullFrame; |
| | 2832 | detectLetterboxLock.unlock(); |
| | 2833 | } |
| | 2834 | else |
| | 2835 | { |
| | 2836 | detectLetterboxConsecutiveCounter++; |
| | 2837 | } |
| | 2838 | } |
| | 2839 | else |
| | 2840 | { |
| | 2841 | if (detectLetterboxConsecutiveCounter <= 3) |
| | 2842 | detectLetterboxConsecutiveCounter = 0; |
| | 2843 | } |
| | 2844 | } |
| | 2845 | else |
| | 2846 | { |
| | 2847 | // draw debug output |
| | 2848 | for (int x = 0; x < width; x++) |
| | 2849 | { |
| | 2850 | // detection lines |
| | 2851 | if (topHits == NUMBER_OF_DETECTION_LINES && maxTop - minTop <= HORIZONTAL_THRESHOLD) |
| | 2852 | { |
| | 2853 | // horizontal line |
| | 2854 | draw (frame, x, halfLimit, 150, 91, 62); |
| | 2855 | draw (frame, x, fullLimit, 150, 91, 62); |
| | 2856 | } |
| | 2857 | else |
| | 2858 | { |
| | 2859 | draw (frame, x, halfLimit, 255, 128, 128); |
| | 2860 | draw (frame, x, fullLimit, 255, 128, 128); |
| | 2861 | } |
| | 2862 | |
| | 2863 | if (bottomHits == NUMBER_OF_DETECTION_LINES && maxBottom - minBottom <= HORIZONTAL_THRESHOLD) |
| | 2864 | { |
| | 2865 | // horizontal line |
| | 2866 | draw (frame, x, height - 1 - halfLimit, 150, 91, 62); |
| | 2867 | draw (frame, x, height - 1 - fullLimit, 150, 91, 62); |
| | 2868 | } |
| | 2869 | else |
| | 2870 | { |
| | 2871 | draw (frame, x, height - 1 - halfLimit, 255, 128, 128); |
| | 2872 | draw (frame, x, height - 1 - fullLimit, 255, 128, 128); |
| | 2873 | } |
| | 2874 | |
| | 2875 | // if (GetAdjustFill() == kAdjustFill_Full) { |
| | 2876 | // draw (frame, x, (height - width * 9 / 16) / 2, 128, 255, 255); |
| | 2877 | // draw (frame, x, height - (height - width * 9 / 16) / 2, 128, 255, 255); |
| | 2878 | // } else if (GetAdjustFill() == kAdjustFill_Half) { |
| | 2879 | // draw (frame, x, (height - width * 9 / 14) / 2, 128, 255, 255); |
| | 2880 | // draw (frame, x, height - (height - width * 9 / 14) / 2, 128, 255, 255); |
| | 2881 | // } |
| | 2882 | } |
| | 2883 | |
| | 2884 | for (int y = 5; y < height / 4; y++) |
| | 2885 | { |
| | 2886 | int line; |
| | 2887 | for (line = 0; line < NUMBER_OF_DETECTION_LINES; line++) |
| | 2888 | { |
| | 2889 | if (topHit[line] > y || !topHit[line]) |
| | 2890 | draw (frame, xPos[line], y, 150, 91, 62); |
| | 2891 | else |
| | 2892 | draw (frame, xPos[line], y, 76, 109, 207); |
| | 2893 | |
| | 2894 | if (bottomHit[line] > y || !bottomHit[line]) |
| | 2895 | draw (frame, xPos[line], height - 1 - y, 150, 91, 62); |
| | 2896 | else |
| | 2897 | draw (frame, xPos[line], height - 1 - y, 76, 109, 207); |
| | 2898 | } |
| | 2899 | |
| | 2900 | } |
| | 2901 | |
| | 2902 | for (int x = 0; x < 32; x ++) |
| | 2903 | { |
| | 2904 | for (int y = 0; y < 32; y++) |
| | 2905 | { |
| | 2906 | draw (frame, x , y, averageY, 128, 128); |
| | 2907 | draw (frame, x + 48, y, averageY, 128 - 32 + x*2, 128 - 32 + y*2); |
| | 2908 | draw (frame, x , y + 48, 64, 128, 128); |
| | 2909 | draw (frame, x + 48, y + 48, 64, 128 - 32 + x*2, 128 - 32 + y*2); |
| | 2910 | } |
| | 2911 | } |
| | 2912 | } |
| | 2913 | } |
| | 2914 | |
| | 2915 | void NuppelVideoPlayer::SwitchToDetectedLetterbox(VideoFrame *frame) |
| | 2916 | { |
| | 2917 | if (detectLetterboxSwitchFrame != -1) |
| | 2918 | { |
| | 2919 | detectLetterboxLock.lock(); |
| | 2920 | if (detectLetterboxSwitchFrame <= frame->frameNumber && |
| | 2921 | detectLetterboxConsecutiveCounter > 3) |
| | 2922 | { |
| | 2923 | if (GetAdjustFill() != detectLetterboxDetectedMode) |
| | 2924 | { |
| | 2925 | VERBOSE(VB_PLAYBACK, QString("Detect Letterbox: Switched to %1 on frame %2 (%3)").arg(detectLetterboxDetectedMode).arg(frame->frameNumber).arg(detectLetterboxSwitchFrame)); |
| | 2926 | videoOutput->ToggleAdjustFill(detectLetterboxDetectedMode); |
| | 2927 | ReinitOSD(); |
| | 2928 | } |
| | 2929 | detectLetterboxSwitchFrame = -1; |
| | 2930 | } |
| | 2931 | else if (detectLetterboxSwitchFrame <= frame->frameNumber) |
| | 2932 | 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)); |
| | 2933 | |
| | 2934 | detectLetterboxLock.unlock(); |
| | 2935 | } |
| | 2936 | |
| | 2937 | if (isDetectLetterboxDebugging) |
| | 2938 | DetectLetterbox(frame, true); |
| | 2939 | } |
| | 2940 | |