/*
 * xine_post_autocrop.c: xine post plugin
 *
 * See the main source file 'xineliboutput.c' for copyright information and
 * how to reach the author.
 *
 * $Id$
 *
 */

/*
 * Copyright (C) 2006 the xine project
 * 
 * This file is part of xine, a free video player.
 * 
 * xine is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * xine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *
 * autocrop video filter by Petri Hintukainen 25/03/2006
 *
 * Automatically crop 4:3 letterbox frames to 16:9
 * 
 * based on expand.c
 *
 *
 * TODO: 
 *  - more reliable border detection, including channel logo detection
 *  - OSD re-positioning (?)
 *
 */


#include <stdint.h>

#include <xine/xine_internal.h>
#include <xine/post.h>

#ifndef FABS
#  define FABS(x) ((x) < 0.0 ? -(x) : (x))
#endif

/*
 *  Configuration
 */

/*#define MARK_FRAME       Draw markers on detected boundaries */
/*#define ENABLE_64BIT 1   Force using of 64-bit routines */
/*#undef __MMX__           Disable MMX */
/*#undef __SSE__           Disable SSE */
/*#define FILTER2          Tighter Y-filter */

#undef LOG_MODULE
#define LOG_MODULE      "autocrop"
#define LOG_INFO        (this->debug_level > 0)
#define LOG_DEBUG       (this->debug_level > 1)
#define LOG_TRACE       (this->debug_level > 2)
#define LOG_ACCEL       1

#define DEFAULT_AUTODETECT_RATE          4      /* unit: frames */
#define DEFAULT_STABILIZE_TIME         (5*25)   /* 5 seconds, unit: frames */
#define DEFAULT_SOFT_START_STEP          4      /* unit: lines per frame */
#define DEFAULT_SUBS_DETECT_LIFETIME   (60*25)  /* 1 minute, unit: frames */
#define DEFAULT_SUBS_DETECT_STABILIZE_TIME 12    /* unit: frames */
#define DEFAULT_LOGO_WIDTH              20  /* percentage of frame width */

/* parameters for avards algorithm */
#define DEFAULT_OVERSCAN_COMPENSATE     0   /* percentage of frame height for overscan compensation */
#define DEFAULT_BAR_TONE_TOLERANCE      0   /* 0...255 */

/*
 * Plugin
 */

typedef struct autocrop_parameters_s {
  int    enable_autodetect;
  int    autodetect_rate;
  int    enable_subs_detect;
  int    subs_detect_lifetime;
  int    subs_detect_stabilize_time;
  int    soft_start;
  int    soft_start_step;
  int    stabilize;
  int    stabilize_time;
  int    logo_width;

  int    use_driver_crop;
  int    use_avards_analysis;
  int    overscan_compensate;
  int    bar_tone_tolerance;
  int    debug_level;
} autocrop_parameters_t;

START_PARAM_DESCR(autocrop_parameters_t)
PARAM_ITEM(POST_PARAM_TYPE_BOOL, enable_autodetect, NULL, 0, 1, 0,
  "enable automatic border detecton")
PARAM_ITEM(POST_PARAM_TYPE_INT, autodetect_rate, NULL, 1, 30, 0,
  "rate of automatic letterbox detection")
PARAM_ITEM(POST_PARAM_TYPE_INT, stabilize_time, NULL, 1, 9999, 0,
  "number of frames with equal bars before cropping value changes")
PARAM_ITEM(POST_PARAM_TYPE_BOOL, enable_subs_detect, NULL, 0, 1, 0,
  "enable automatic subtitle detecton")
PARAM_ITEM(POST_PARAM_TYPE_INT, subs_detect_lifetime, NULL, 0, 9999, 0,
  "lifetime of automatic subtitle detection")
PARAM_ITEM(POST_PARAM_TYPE_INT, subs_detect_stabilize_time, NULL, 0, 9999, 0,
  "stabilize time of automatic subtitle detection")
PARAM_ITEM(POST_PARAM_TYPE_BOOL, soft_start, NULL, 0, 1, 0,
  "enable soft start of cropping")
PARAM_ITEM(POST_PARAM_TYPE_INT, soft_start_step, NULL, 1, 999, 0,
  "soft start step width of cropping")
PARAM_ITEM(POST_PARAM_TYPE_BOOL, stabilize, NULL, 0, 1, 0,
  "stabilize cropping to 14:9, 16:9, (16:9+subs), 20:9, (20:9+subs)")
PARAM_ITEM(POST_PARAM_TYPE_INT, logo_width, NULL, 0, 99, 0,
  "maximum width of logo for automatic logo detection (percentage of frame width)")
PARAM_ITEM(POST_PARAM_TYPE_BOOL, use_driver_crop, NULL, 0, 1, 0,
  "use video driver to crop frames (default is to copy frames in post plugin)")
PARAM_ITEM(POST_PARAM_TYPE_BOOL, use_avards_analysis, NULL, 0, 1, 0,
  "use avards algorithm for frame analysis")
PARAM_ITEM(POST_PARAM_TYPE_INT, overscan_compensate, NULL, 0, 100, 0,
  "compensation of output device overscan applied when cropping frames (%1000 of frame height)")
PARAM_ITEM(POST_PARAM_TYPE_INT, bar_tone_tolerance, NULL, 0, 255, 0,
  "tolerance of bar color")
PARAM_ITEM(POST_PARAM_TYPE_INT, debug_level, NULL, 0, 4, 0,
  "debug level")
END_PARAM_DESCR(autocrop_param_descr)


typedef struct autocrop_post_plugin_s
{
  post_plugin_t  post_plugin;

  /* setup */
  int autodetect;
  int autodetect_rate;
  int subs_detect;
  int subs_detect_lifetime;
  int subs_detect_stabilize_time;
  int soft_start;
  int soft_start_step;
  int stabilize;
  int stabilize_time;
  int logo_width;
  int always_use_driver_crop;
  int use_avards_analysis;
  int overscan_compensate;
  int bar_tone_tolerance;
  int debug_level;

  /* Current cropping status */
  int cropping_active;

  /* Detected bars */
  int detected_end_line;
  int prev_detected_end_line;

  /* Stabilized bars */
  int stabilized_start_line;
  int stabilized_end_line;

  /* Active bars */
  int start_line;
  int end_line;
  int crop_total;

  /* Delayed start for cropping */
  int stabilize_timer;

  /* frame counter (used to select frames for analysis) */
  int analyze_timer;

  /* Last seen frame */
  uint32_t prev_height;
  uint32_t prev_width;
  int64_t  prev_pts;

  /* eliminate jumping when there are subtitles inside bottom bar:
     - when cropping is active and one frame has larger end_line
       than previous, we enlarge frame.
     - after this, cropping is not reseted to previous value unless
       bottom bar has been empty for certain time */
  int height_limit_active;  /* true if detected possible subtitles in bottom area */
  int height_limit;         /* do not crop bottom above this value (bottom of subtitles) */
  int height_limit_timer;   /* counter how many following frames must have black
			       bottom bar until returning to full cropping
			       (used to reset height_limit when there are no subtitles) */
  int end_line_stabilize_timer; /* counter for detecting changes of end line */

  int use_driver_crop;  /* true if non standard frame format (e.g. vdpau) used */
  int has_driver_crop;  /* true if driver has cropping capability */
  int has_unscaled_overlay; /* true if driver has unscaled overlay capability */
  int prev_autodetect_rate;

  pthread_mutex_t crop_lock;

  /* retrieval of standard frame format image */
  vo_frame_t frame;
  uint8_t *img;
  int img_size;

} autocrop_post_plugin_t;


#if (XINE_VERSION_CODE < 10190) && defined(VO_CAP_ARGB_LAYER_OVERLAY)
/* xine-lib-vdpau r134+ */
#  define HAVE_PROC_PROVIDE_STANDARD_FRAME_DATA
#endif
#if XINE_VERSION_CODE >= 10190
/* xine-lib 1.2 r10718+ (2008-12-30) */
#  define HAVE_PROC_PROVIDE_STANDARD_FRAME_DATA
#endif

# if defined(__SSE__)
#  include <xmmintrin.h>
# elif defined(__MMX__)
#  include <mmintrin.h>
# endif

#if defined(__WORDSIZE)
#  if __WORDSIZE == 64
#    define ENABLE_64BIT (sizeof(int) > 32)
#  endif
#endif

/*
 * Constants
 */ 

#define YNOISEFILTER    (0xE0U)
#define YSHIFTUP        (0x05U)
#define UVBLACK         (0x80U)
#define UVSHIFTUP       (0x03U)
#define UVNOISEFILTER   (0xF8U)

/* YV12 */
#define YNOISEFILTER32  (YNOISEFILTER  * 0x01010101U)
#define YSHIFTUP32      (YSHIFTUP      * 0x01010101U)
#define UVBLACK32       (UVBLACK       * 0x01010101U)
#define UVSHIFTUP32     (UVSHIFTUP     * 0x01010101U)
#define UVNOISEFILTER32 (UVNOISEFILTER * 0x01010101U)

#define YNOISEFILTER64  (YNOISEFILTER  * UINT64_C(0x0101010101010101))
#define YSHIFTUP64      (YSHIFTUP      * UINT64_C(0x0101010101010101))
#define UVBLACK64       (UVBLACK       * UINT64_C(0x0101010101010101))
#define UVSHIFTUP64     (UVSHIFTUP     * UINT64_C(0x0101010101010101))
#define UVNOISEFILTER64 (UVNOISEFILTER * UINT64_C(0x0101010101010101))

/* YUY2 */
/* TODO: should use normal/inverse order based on endianess */
#if 0
#define YUY2BLACK32       (UVBLACK       * 0x00010001U)
#define YUY2SHIFTUP32     (UVSHIFTUP     * 0x00010001U)
#define YUY2NOISEFILTER32 ((YNOISEFILTER * 0x01000100U)|(UVNOISEFILTER * 0x00010001U))
#else
#define YUY2BLACK32       (UVBLACK       * 0x01000100U)
#define YUY2SHIFTUP32     (UVSHIFTUP     * 0x01000100U)
#define YUY2NOISEFILTER32 ((YNOISEFILTER * 0x00010001U)|(UVNOISEFILTER * 0x01000100U))
#endif

#define YUY2BLACK64       (YUY2BLACK32       * UINT64_C(0x0000000100000001))
#define YUY2SHIFTUP64     (YUY2SHIFTUP32     * UINT64_C(0x0000000100000001))
#define YUY2NOISEFILTER64 (YUY2NOISEFILTER32 * UINT64_C(0x0000000100000001))

#ifdef FILTER2
/* tighter Y-filter: original black threshold is 0x1f ; here it is 0x1f - 0x0b = 0x14 */
# define YUY2SHIFTUP32  ((UVSHIFTUP    * 0x00010001U)|(YSHIFTUP      * 0x01000100U))
# undef __SSE__
#endif


/*
 * Black bar detection
 *
 *  Detect black lines with simple noise filtering.
 *  Line is "black" if Y-valus are less than 0x20 and 
 *  U/V values inside range 0x7d...0x84.
 *  ~ 32 first and last pixels are not checked.
 *
 */

static int blank_line_Y_C(uint8_t *data, int length);
static int blank_line_UV_C(uint8_t *data, int length);
static int blank_line_YUY2_C(uint8_t *data, int length);
#if defined(ENABLE_64BIT)
static int blank_line_Y_C64(uint8_t *data, int length);
static int blank_line_UV_C64(uint8_t *data, int length);
static int blank_line_YUY2_C64(uint8_t *data, int length);
#endif
#if defined(__MMX__)
static int blank_line_Y_mmx(uint8_t *data, int length);
static int blank_line_UV_mmx(uint8_t *data, int length);
static int blank_line_YUY2_mmx(uint8_t *data, int length);
#endif
#if defined(__SSE__)
static int blank_line_Y_sse(uint8_t *data, int length);
static int blank_line_UV_sse(uint8_t *data, int length);
static int blank_line_YUY2_sse(uint8_t *data, int length);
#endif

static int blank_line_Y_INIT(uint8_t *data, int length);
static int blank_line_UV_INIT(uint8_t *data, int length);
static int blank_line_YUY2_INIT(uint8_t *data, int length);

static void autocrop_init_mm_accel(void);

int (*blank_line_Y)(uint8_t *data, int length)  = blank_line_Y_INIT;
int (*blank_line_UV)(uint8_t *data, int length) = blank_line_UV_INIT;
int (*blank_line_YUY2)(uint8_t *data, int length) = blank_line_YUY2_INIT;

static int blank_line_Y_C(uint8_t *data, int length)
{
  uint32_t *data32 = (uint32_t*)((((long int)data) + 32 + 3) & (~3)), r = 0;

  length -= 64; /* skip borders (2 x 32 pixels) */
  length /= 4;  /* 4 bytes / loop */

#ifdef FILTER2
  while(length) {
    /* shiftdown needs saturated unsigned element-wise substraction, available only in MMX ...*/
    /* -> use shiftup and looser noise filter for same result. */
    /*    this needs special handling for large values */
    r = r | data32[--length]; /* this catches large values (0xf9 : 0xf9+0x7=0x100 === black) */
    r = r | (data32[length] + YSHIFTUP32); /* this catches small walues (0x1d...0x1f) */
  }
#else
  while(length)
    r = r | data32[--length];
#endif

  return !(r & YNOISEFILTER32);
}

static int blank_line_UV_C(uint8_t *data, int length)
{
  uint32_t *data32 = (uint32_t*)((((long int)data) + 16 + 3) & (~3));
  uint32_t r1 = 0, r2 = 0;

  length -= 32; /* skip borders (2 x 32 pixels, 2 pix/byte) */
  length /= 4;  /* 2 x 4 bytes / loop */

  while(length>0) {
    r1 = r1 | ((data32[--length] + UVSHIFTUP32) ^ UVBLACK32);
    r2 = r2 | ((data32[--length] + UVSHIFTUP32) ^ UVBLACK32);
  }
  return !((r1|r2) & (UVNOISEFILTER32));
}

#if defined(ENABLE_64BIT)
static int blank_line_Y_C64(uint8_t *data, int length)
{
  uint64_t *data64 = (uint64_t*)((((long int)data) + 32 + 7) & (~7)), r = 0;

  length -= 64; /* skip borders (2 x 32 pixels) */
  length /= 8;  /* 8 bytes / loop */

#ifdef FILTER2
  while(length) {
    r = r | data64[--length];
    r = r | (data64[length] + YSHIFTUP64);
  }
#else
  while(length)
    r = r | data64[--length];
#endif

  return !(r & YNOISEFILTER64);
}
#endif

#if defined(ENABLE_64BIT)
static int blank_line_UV_C64(uint8_t *data, int length)
{
  uint64_t *data64 = (uint64_t*)((((long int)data) + 16 + 7) & (~7));
  uint64_t r1 = UINT64_C(0), r2 = UINT64_C(0);

  length -= 32; /* skip borders (2x32 pixels, 2 pix/byte) */
  length /= 8;  /* 2 x 8 bytes / loop */

  while(length>0) {
    r1 = r1 | ((data64[--length] + UVSHIFTUP64) ^ UVBLACK64);
    r2 = r2 | ((data64[--length] + UVSHIFTUP64) ^ UVBLACK64);
  }
  return !((r1|r2) & (UVNOISEFILTER64));
}
#endif


#if defined(__MMX__)
typedef union {
  uint32_t u32[2];
  __m64    m64;
} __attribute__((__aligned__ (8))) __m64_wrapper;
#endif

#if defined(__MMX__)
 int blank_line_Y_mmx(uint8_t *data, int length)
{
#ifdef FILTER2
  static const __m64_wrapper mask   = {{YNOISEFILTER32, YNOISEFILTER32}};
  static const __m64_wrapper gshift = {{YSHIFTUP32,     YSHIFTUP32}};
  register __m64 sum, sum2, shift = gshift.m64, val;
#else
  static const __m64_wrapper mask  = {{YNOISEFILTER32, YNOISEFILTER32}};
  register __m64 sum;
#endif
  __m64 *data64 = (__m64*)(((long int)(data + 32 + 7)) & (~7));

  /*sum = _m_pxor(sum,sum);*/
  __asm__("pxor %0,%0" : "=y"(sum));

  length -= 64; /* skip borders (2 x 32 pixels) */
  length /= 8;  /* 8 bytes / loop */

#ifdef FILTER2
  __asm__("pxor %0,%0" : "=y"(sum2));
  while(length) {
    val  = data64[--length];
    sum  = _m_por(sum, val);
    sum2 = _m_por(sum2, _m_paddb(val, shift));
  }
  sum = _m_por(sum, sum2);
#else
  while(length)
    sum = _m_por(sum, data64[--length]);
#endif

  sum = _m_pand(sum, mask.m64);
  return 0 == _m_to_int(_m_packsswb(sum, sum));
}
#endif

#if defined(__MMX__)
static int blank_line_UV_mmx(uint8_t *data, int length)
{
  static const __m64_wrapper gm_03 = {{UVSHIFTUP32,     UVSHIFTUP32}};
  static const __m64_wrapper gm_f8 = {{UVNOISEFILTER32, UVNOISEFILTER32}};
  static const __m64_wrapper gm_80 = {{UVBLACK32,       UVBLACK32}};
  __m64 *data64 = (__m64*)(((long int)(data) + 16 + 7) & (~7));
  register __m64 sum1, sum2, m_03, /*m_f8,*/ m_80;

  /*sum1 = _m_pxor(sum1, sum1); sum1 = _mm_setzero_si64(); */
  /*sum2 = _m_pxor(sum2, sum2); sum2 = _mm_setzero_si64(); */
  __asm__("pxor %0,%0" : "=y"(sum1));
  __asm__("pxor %0,%0" : "=y"(sum2));

  /* fetch static data to MMX registers */
  m_03 = gm_03.m64;
  /*m_f8 = gm_f8.m64;*/
  m_80 = gm_80.m64;

  length -= 32; /* skip borders (2 x 32 pixels, 2pix/byte) */
  length /= 8;  /* 8 bytes / vector */

  do {
    /* process two 8-byte vectors */
    sum1 = _m_por(sum1,
		  /* grab every byte that is not black (x ^ 0x80 != 0) */
		  _m_pxor(
			  /* filter noise: U/V of each "black" pixel should be 0x7d..0x84 
			     -> each black pixel should be 0x80 after (x+3) & 0xf8 */
			  /*_m_pand(*/
				  /* each black pixel should be 0x80..0x87 after adding 3 */
				  _m_paddb(
					     data64[length-1], 
					     m_03)/*,
				  m_f8)*/,
			  m_80));
    sum2 = _m_por(sum2, 
		  _m_pxor( 
			  /*_m_pand( */
				  _m_paddb( 
					     data64[length-2], 
					     m_03)/*, 
				  m_f8)*/,
			    m_80));
    length -= 2;
  } while(length>0);
  
  /* combine two result vectors (or), filter noise (and) */
  sum1 = _m_pand(_m_por(sum1,
			sum2), 
		 gm_f8.m64);
  /* result vector of black line is 0 */
  return 0 == _m_to_int(_m_packsswb(sum1, sum1));
}
#endif

#if defined(__SSE__)
typedef  union {
  uint32_t u32[4];
  __m128   m128;
} __attribute((__aligned__ (16))) __m128_wrapper;
#endif

#if defined(__SSE__)
static int blank_line_Y_sse(uint8_t *data, int length)
{
  static const __m128_wrapper gmask = {{YNOISEFILTER32, YNOISEFILTER32,
					YNOISEFILTER32, YNOISEFILTER32}};
  __m128 *data128 = (__m128*)(((long int)(data) + 32 + 15) & (~15));
  register __m128 sum1, sum2, zero;

  length -= 64; /* skip borders (2 x 32 pixels) */
  length /= 16; /* 16 bytes / loop */

  /* Start prefetching data to CPU cache */
  _mm_prefetch(data128+length-1, _MM_HINT_NTA);
  _mm_prefetch(data128+length-3, _MM_HINT_NTA);

  /* 
   * Process in two paraller loops, one 16 byte vector / each sub-loop 
   * - grabs bytes with value larger than treshold
   */

  zero = _mm_setzero_ps();
  sum1 = zero;
  sum2 = zero;

  do {
    _mm_prefetch(data128+length-5, _MM_HINT_NTA);
    sum1 = _mm_or_ps(sum1, data128[--length]);
    sum2 = _mm_or_ps(sum2, data128[--length]);
  } while(length>0);

  return 0x0f == _mm_movemask_ps(_mm_cmpeq_ps(_mm_and_ps(_mm_or_ps(sum1, 
								   sum2), 
							 gmask.m128), 
					      _mm_setzero_ps()));
}
#endif

#if defined(__SSE__)
static int blank_line_UV_sse(uint8_t *data, int length)
{
  uint8_t *top = data + length - 1;
  do {
    _mm_prefetch(top,    _MM_HINT_NTA);
    _mm_prefetch(top-32, _MM_HINT_NTA);
    _mm_prefetch(top-64, _MM_HINT_NTA);
    _mm_prefetch(top-72, _MM_HINT_NTA);
    top -= 128;
  } while(top >= data);

  return blank_line_UV_mmx(data, length);
}
#endif

static int blank_line_YUY2_C(uint8_t *data, int length)
{
  uint32_t *data32 = (uint32_t*)((((long int)data) + 64 + 3) & (~3));
  uint32_t r1 = 0, r2 = 0;

  length -= 128; /* skip borders (2 x 32 pixels, 2 bytes/pixel) */
  length /= 4;   /* 2 x 4 bytes / loop */

  while(length) {
    r1 = r1 | ((data32[--length] + YUY2SHIFTUP32) ^ YUY2BLACK32);
    r2 = r2 | ((data32[--length] + YUY2SHIFTUP32) ^ YUY2BLACK32);
  }
  return !((r1|r2) & YUY2NOISEFILTER32);
}

#if defined(ENABLE_64BIT)
static int blank_line_YUY2_C64(uint8_t *data, int length)
{
  uint64_t *data64 = (uint64_t*)((((long int)data) + 64 + 7) & (~7));
  uint64_t r1 = 0, r2 = 0;

  length -= 128; /* skip borders (2 x 32 pixels, 2 bytes/pixel) */
  length /= 8;   /* 2 x 8 bytes / loop */

  while(length) {
    r1 = r1 | ((data64[--length] + YUY2SHIFTUP64) ^ YUY2BLACK64);
    r2 = r2 | ((data64[--length] + YUY2SHIFTUP64) ^ YUY2BLACK64);
  }
  return !((r1|r2) & YUY2NOISEFILTER64);
}
#endif

#if defined(__MMX__)
static int blank_line_YUY2_mmx(uint8_t *data, int length)
{
  /* not implemented */

# if !defined(ENABLE_64BIT)
  return blank_line_YUY2_C(data, length);
# else
  return blank_line_YUY2_C64(data, length);
# endif
}
#endif

#if defined(__SSE__)
static int blank_line_YUY2_sse(uint8_t *data, int length)
{
  uint8_t *top = data + length - 1;
  do {
    _mm_prefetch(top,    _MM_HINT_NTA);
    _mm_prefetch(top-32, _MM_HINT_NTA);
    _mm_prefetch(top-64, _MM_HINT_NTA);
    _mm_prefetch(top-72, _MM_HINT_NTA);
    top -= 128;
  } while(top >= data);

  return blank_line_YUY2_mmx(data, length);
}
#endif

static void autocrop_init_mm_accel(void)
{
  blank_line_Y  = blank_line_Y_C;
  blank_line_UV = blank_line_UV_C;
  blank_line_YUY2 = blank_line_YUY2_C;

#if defined(__SSE__)
  if(xine_mm_accel() & MM_ACCEL_X86_SSE) {
    llprintf(LOG_ACCEL, "autocrop_init_mm_accel: using SSE\n");
    blank_line_Y  = blank_line_Y_sse;
    blank_line_UV = blank_line_UV_sse;
    blank_line_YUY2 = blank_line_YUY2_sse;
    return;
  }
#endif
#if defined(ENABLE_64BIT)
  if(ENABLE_64BIT) {
    llprintf(LOG_ACCEL, "autocrop_init_mm_accel: using 64-bit integer operations\n");
    blank_line_Y  = blank_line_Y_C64;
    blank_line_UV = blank_line_UV_C64;
    blank_line_YUY2 = blank_line_YUY2_C64;
    return;
  }
#endif
#if defined(__MMX__)
  if(xine_mm_accel() & MM_ACCEL_X86_MMX) {
    /* mmx not faster than normal x64 (?) */
    llprintf(LOG_ACCEL, "autocrop_init_mm_accel: using MMX\n");
    blank_line_Y  = blank_line_Y_mmx;
    blank_line_UV = blank_line_UV_mmx;
    blank_line_YUY2 = blank_line_YUY2_mmx;
    return;
  }
#endif
  llprintf(LOG_ACCEL, "autocrop_init_mm_accel: no compatible acceleration methods found\n");
}

static int blank_line_Y_INIT(uint8_t *data, int length)
{
  autocrop_init_mm_accel();
  return (*blank_line_Y)(data, length);
}

static int blank_line_UV_INIT(uint8_t *data, int length)
{
  autocrop_init_mm_accel();
  return (*blank_line_UV)(data, length);
}

static int blank_line_YUY2_INIT(uint8_t *data, int length)
{
  autocrop_init_mm_accel();
  return (*blank_line_YUY2)(data, length);
}

/*
 * Analyze frame
 *  - if frame needs cropping set crop_top & crop_bottom
 */

#ifdef MARK_FRAME
int dbg_top=0, dbg_bottom=0;
#endif

static int analyze_frame_yv12(autocrop_post_plugin_t *this, vo_frame_t *frame,
                              int *crop_top, int *crop_bottom)
{
  int y;
  int ypitch = frame->pitches[0];
  int upitch = frame->pitches[1];
  int vpitch = frame->pitches[2];
  uint8_t *ydata = frame->base[0];
  uint8_t *udata = frame->base[1];
  uint8_t *vdata = frame->base[2];
  int max_crop = (frame->height / 4) / 2; /* 4:3 --> 16:9 */
  int logo_width      = frame->width  * this->logo_width / 100;
  int check_width     = frame->width - logo_width;

  /* from top -> down */
  ydata += 8 * ypitch;  /* skip 8 first lines */
  udata += 4 * upitch;
  vdata += 4 * vpitch;

  for(y = 8; y <= max_crop   *2 /* *2 = 20:9+subs -> 16:9 */ ; y += 2) {
    if(  ! ( blank_line_UV(udata,                  check_width/2) ||
             blank_line_UV(udata+logo_width/2,     check_width/2)    ) ||
         ! ( blank_line_UV(vdata,                  check_width/2) ||
             blank_line_UV(vdata+logo_width/2,     check_width/2)    ) ||
         ! ( blank_line_Y( ydata,                  check_width  ) ||
             blank_line_Y( ydata+logo_width,       check_width  )    ) ||
         ! ( blank_line_Y( ydata+ypitch,           check_width  ) ||
             blank_line_Y( ydata+ypitch+logo_width,check_width  )    )) {
      break;
    } else {
      ydata += 2 * ypitch;
      udata += upitch;
      vdata += vpitch;
    }
  }
  *crop_top = y>8 ? y : 0;

  /* from bottom -> up */
  ydata = frame->base[0] + ((frame->height-4)   -1 ) * ypitch;
  udata = frame->base[1] + ((frame->height-4)/2 -1 ) * upitch;
  vdata = frame->base[2] + ((frame->height-4)/2 -1 ) * vpitch;
  for(y = frame->height - 5; y >= frame->height-max_crop; y -=2 ) {
    if( ! blank_line_Y(ydata,        frame->width) ||
	! blank_line_Y(ydata-ypitch, frame->width) ||  
	! blank_line_UV(udata,       frame->width/2) ||
	! blank_line_UV(vdata,       frame->width/2)) {
      break;
    } else {
      ydata -= 2*ypitch;
      udata -= upitch;
      vdata -= vpitch;
    }
  }
  *crop_bottom = y;

  /* test for black in center - don't crop if frame is empty */
  if(*crop_top >= max_crop*2 && *crop_bottom <= frame->height-max_crop) {
    int center_line = (frame->height >> 2) << 1;
    ydata = frame->base[0] + (center_line  )*ypitch;
    udata = frame->base[1] + (center_line/2)*upitch;
    vdata = frame->base[2] + (center_line/2)*vpitch;
    if( blank_line_Y(ydata,        frame->width) &&
        blank_line_Y(ydata+ypitch, frame->width) &&
	blank_line_UV(udata,       frame->width/2) &&
	blank_line_UV(vdata,       frame->width/2)) {
      llprintf(LOG_DEBUG, "not cropping black frame\n");
      return 0;
    }
  }
  return 1;
}

static int analyze_frame_yuy2(autocrop_post_plugin_t *this, vo_frame_t *frame,
                              int *crop_top, int *crop_bottom)
{
  int y;
  int pitch = frame->pitches[0];
  uint8_t *data = frame->base[0];
  int max_crop = (frame->height / 4) / 2; /* 4:3 --> 16:9 */
  int logo_width      = frame->width  * this->logo_width / 100;
  int check_width     = frame->width - logo_width;

  /* from top -> down */
  data += 6 * pitch;  /* skip 6 first lines */
  for(y = 6; y <= max_crop  *2 /* *2 = 20:9+subs -> 16:9 */ ; y ++)
    if(  ! ( blank_line_YUY2(data,              check_width*2) ||
             blank_line_YUY2(data+2*logo_width, check_width*2)))
      break;
    else 
      data += pitch;

  *crop_top = y;

  /* from bottom -> up */
  data = frame->base[0] + ((frame->height-4)   -1 ) * pitch;
  for(y = frame->height - 5; y >= frame->height-max_crop; y -- )
    if( ! blank_line_YUY2(data,  frame->width * 2))
      break;
    else 
      data -= pitch;
  
  *crop_bottom = y;
  
  /* test for black in center - don't crop if frame is empty */
  if(*crop_top >= max_crop*2 && *crop_bottom <= frame->height-max_crop) {
    data = frame->base[0] + (frame->height/2)*pitch;
    if( blank_line_YUY2(data, frame->width * 2)) {
      llprintf(LOG_DEBUG, "not cropping black frame\n");
      return 0;
    }
  }

  return 1;
}


#define AVARDS_FN(fn_name, pixel_pitch, top_overscan_compensate) \
static int fn_name (autocrop_post_plugin_t *this, vo_frame_t *frame, int *crop_top, int *crop_bottom) \
{ \
  int i, top, bottom, histogram[256]; \
  uint8_t *left, *right; \
  uint8_t *img = frame->base[0]; \
  const int bottom_overscan_compensate = 4; \
  const int ignored_side_width = 16 * pixel_pitch; \
  const int width = frame->width * pixel_pitch; \
  const int top_logo_width = (frame->width * this->logo_width / 100) * pixel_pitch; \
  const int bottom_logo_width = this->subs_detect ? -1: top_logo_width - 1; \
  const int pitch = frame->pitches[0]; \
  const int half_height = frame->height / 2; \
\
  memset(histogram, 0, sizeof(histogram)); \
  left = img + top_overscan_compensate * pitch; \
  right = left + width - ignored_side_width; \
  left += ignored_side_width; \
  while (left < right) { \
    histogram[*left]++; \
    left += pixel_pitch; \
  } \
\
  int bar_tone = 0; \
  for (i = 1; i < 256; ++i) { \
    if (histogram[i] > histogram[bar_tone]) \
      bar_tone = i; \
  } \
  const uint8_t min_bar_tone = (bar_tone > this->bar_tone_tolerance) ? bar_tone - this->bar_tone_tolerance: 0; \
  const uint8_t max_bar_tone = (bar_tone + this->bar_tone_tolerance < 255) ? bar_tone + this->bar_tone_tolerance: 255; \
\
  for (top = top_overscan_compensate; top < half_height; ++top) { \
    left = img + top * pitch; \
    right = left + width - pixel_pitch - ignored_side_width; \
    left += ignored_side_width; \
    while (left <= right && *left >= min_bar_tone && *left <= max_bar_tone) \
      left += pixel_pitch; \
    while (right > left && *right >= min_bar_tone && *right <= max_bar_tone) \
      right -= pixel_pitch; \
    if ((right - left) > top_logo_width) \
      break; \
  } \
\
  for (bottom = frame->height - 1 - bottom_overscan_compensate; bottom > half_height; --bottom) { \
    left = img + bottom * pitch; \
    right = left + width - pixel_pitch - ignored_side_width; \
    left += ignored_side_width; \
    while (left <= right && *left >= min_bar_tone && *left <= max_bar_tone) \
      left += pixel_pitch; \
    while (right > left && *right >= min_bar_tone && *right <= max_bar_tone) \
      right -= pixel_pitch; \
    if ((right - left) > bottom_logo_width) \
      break; \
  } \
\
  *crop_top = top; \
  *crop_bottom = bottom; \
  return ((bottom - top) > 0); \
}

AVARDS_FN(analyze_frame_yv12_avards, 1, 8)
AVARDS_FN(analyze_frame_yuy2_avards, 2, 6)


static void analyze_frame(vo_frame_t *frame, int *crop_top, int *crop_bottom)
{
  post_video_port_t *port = (post_video_port_t *)frame->port;
  autocrop_post_plugin_t *this = (autocrop_post_plugin_t *)port->post;
  int start_line, end_line;

#ifdef HAVE_PROC_PROVIDE_STANDARD_FRAME_DATA
  if ((frame->format != XINE_IMGFMT_YV12 && frame->format != XINE_IMGFMT_YUY2) && frame->proc_provide_standard_frame_data) {
    xine_current_frame_data_t data;
    memset(&data, 0, sizeof(data));
    frame->proc_provide_standard_frame_data(frame->next, &data);
    if (data.img_size > this->img_size) {
      free(this->img);
      this->img = calloc(1, data.img_size);
      if (!this->img) {
        this->img_size = 0;
        return;
      }
      this->img_size = data.img_size;
    }

    data.img = this->img;
    frame->proc_provide_standard_frame_data(frame->next, &data);

    if (data.format == XINE_IMGFMT_YV12) {
      this->frame.pitches[0] = frame->width;
      this->frame.pitches[1] = frame->width / 2;
      this->frame.pitches[2] = frame->width / 2;
      this->frame.base[0] = this->img;
      this->frame.base[1] = this->img + frame->width * frame->height;
      this->frame.base[2] = this->img + frame->width * frame->height + frame->width * frame->height / 4;
    } else if (data.format == XINE_IMGFMT_YUY2) {
      this->frame.pitches[0] = frame->width * 2;
      this->frame.base[0] = this->img;
    }
    this->frame.format = data.format;
    this->frame.height = frame->height;
    this->frame.width = frame->width;

    frame = &this->frame;
  }
#endif

  int result = 0;

  if (this->use_avards_analysis) {
    if (frame->format == XINE_IMGFMT_YV12)
      result = analyze_frame_yv12_avards(this, frame, &start_line, &end_line);
    else if (frame->format == XINE_IMGFMT_YUY2)
      result = analyze_frame_yuy2_avards(this, frame, &start_line, &end_line);
  } else {
    if(frame->format == XINE_IMGFMT_YV12)
      result = analyze_frame_yv12(this, frame, &start_line, &end_line);
    else if(frame->format == XINE_IMGFMT_YUY2)
      result = analyze_frame_yuy2(this, frame, &start_line, &end_line);

#if defined(__MMX__)
    _mm_empty();
#endif
  }

  /* Ignore empty frames */
  if(!result) {
    llprintf(LOG_TRACE, "not cropping black frame\n");
    return;
  }

#ifdef MARK_FRAME
  dbg_top = start_line; dbg_bottom = end_line;
#endif

  if(this->stabilize) {
    int bottom = frame->height - end_line;
    int wide = 0;

    /* bottom bar size */
    if(bottom < frame->height/32) {
      llprintf(LOG_TRACE, "bottom: %d ->  4:3       ", end_line);
      end_line = frame->height - 1;  /* no cropping */
    } else if(bottom < frame->height*3/32) {
      llprintf(LOG_TRACE, "bottom: %d -> 14:9 (%d) ", end_line, frame->height * 15 / 16 - 1);
      end_line = frame->height * 15 / 16 - 1; /* 14:9 */
    } else if(bottom < frame->height*3/16) {
      llprintf(LOG_TRACE, "bottom: %d -> 16:9 (%d) ", end_line, frame->height * 7 / 8 - 1);
      end_line = frame->height * 7 / 8 - 1;   /* 16:9 */
      wide = 1;
    } else {
      llprintf(LOG_TRACE, "bottom: %d -> 20:9 (%d) ", end_line, frame->height * 3 / 4 - 1);
      end_line = frame->height * 3 / 4 - 1;   /* 20:9 */
      wide = 2;
    }

    /* top bar size */
    if(start_line < frame->height/32) {
      llprintf(LOG_TRACE, "top:    %3d ->  4:3      \n", start_line);
      start_line = 0;        /* no cropping */
    } else if(start_line < frame->height*3/32) {
      llprintf(LOG_TRACE, "top:    %3d -> 14:9 (%d)\n", start_line, frame->height / 16);
      start_line = frame->height / 16; /* 14:9 */
    } else if(start_line < frame->height*3/16 || wide) {
      llprintf(LOG_TRACE, "top:    %3d -> 16:9 (%d)\n", start_line, frame->height / 8);
      start_line = frame->height / 8;   /* 16:9 */
    } else { 
      llprintf(LOG_TRACE, "top:    %3d -> 20:9 (%d)\n", start_line, frame->height / 4);
      start_line = frame->height / 4;   /* 20:9 */
      wide++;
    }
    switch(wide) {
    case 3: start_line -= frame->height / 8;
            if(start_line < 0)
              start_line = 0;
            llprintf(LOG_TRACE, "        wide -> center top\n");
    case 2: end_line += frame->height / 8;
            if(end_line >= frame->height)
              end_line = frame->height-1;
            llprintf(LOG_TRACE, "        wide -> center bottom\n");
    default:;
    }

  } else {

    if(start_line > (frame->height/8  *2))  /* *2 --> 20:9 -> 16:9 + subtitles */
      start_line = frame->height/8  *2 ;
    if(end_line < (frame->height*7/8))
      end_line = frame->height*7/8;

    if(start_line > (frame->height/8)) {
      /* if wider than 16:9, prefer cropping top if subtitles are inside bottom bar */
      if(start_line + (frame->height - end_line) > frame->height/4) {
        int diff = start_line + (frame->height - end_line) - frame->height/4;
	diff &= ~1;
        llprintf(LOG_TRACE, "balance: %d,%d -> %d,%d\n",
              start_line, end_line,
              start_line, end_line + diff);
#if 0
        /* this moves image to top (crop only top) */ 
        end_line += diff;
#endif
#if 0
        /* this moves image to center */ 
	/* may cause problems with subtitles ... */
        start_line -= diff;
#endif
#if 1
        /* this moves image to center when there are no 
	   detected subtitles inside bottom bar */
        if(this->cropping_active && this->height_limit_active) {
          int reserved = this->height_limit - end_line;
	  if(reserved>0) {
            end_line += reserved;
	    diff -= reserved;
	  }
	}
        start_line -= diff;
#endif
#if 0
	/* do nothing - image will be centered in video out.
	   - problems with subtitles using unscaled OSD */
#endif
      }
    }

    /* stay inside frame and forget very small bars */
    if(start_line <= 8)
      start_line = 0;
    if(end_line >= (frame->height-6))
      end_line = frame->height;
 
    if(start_line < frame->height/12 || end_line > frame->height*11/12) {
      /* Small bars -> crop only detected borders */
     if(start_line || end_line < frame->height-1) {
       llprintf(LOG_TRACE, "Small bars -> <16:9 : start_line = %d end_line = %d (%s%d t%d)\n",
	      start_line, end_line,
	      this->height_limit_active ? "height limit " : "",
	      this->height_limit,
	      this->height_limit_active ? this->height_limit_timer : 0);
      }
    } else {
      /* Large bars -> crop to 16:9 */
      llprintf(LOG_TRACE, "Large bars -> 16:9  : start_line = %d end_line = %d (%s%d t%d)\n",
	    start_line, end_line,
	    this->height_limit_active ? "height limit " : "",
	    this->height_limit,
	    this->height_limit_active ? this->height_limit_timer : 0);
      if(start_line < frame->height / 8)
        start_line = frame->height / 8;
      if(end_line < frame->height * 7 / 8)
        end_line = frame->height * 7 / 8;
    }
  }

  if (this->overscan_compensate > 0) {
    int h = (frame->height * this->overscan_compensate) / (2*1000);
    if (start_line < h)
      h = start_line;
    if ((frame->height - end_line) < h)
      h = frame->height - end_line;
    start_line -= h;
    end_line += h;
  }

  /* adjust start and stop to even lines */
  (*crop_top)    = (start_line) & (~1);
  (*crop_bottom) = (end_line + 1) & (~1);
}

#ifdef MARK_FRAME
static void mark_frame_yv12(autocrop_post_plugin_t *this,
			    vo_frame_t *frame, int *crop_top, int *crop_bottom)
{
  int ypitch = frame->pitches[0];
  int upitch = frame->pitches[1];
  int vpitch = frame->pitches[2];
  uint8_t *ydata = frame->base[0];
  uint8_t *udata = frame->base[1];
  uint8_t *vdata = frame->base[2];

  /* draw markers to detected boundaries and expected boundaries */
  if(*crop_top > 4 && *crop_top < 200) {
    ydata = frame->base[0] + ((*crop_top)-2)*ypitch;
    udata = frame->base[1] + ((*crop_top)/2 -1)*upitch;
    memset(ydata, 0xff, frame->width/10);
    memset(ydata+ypitch, 0xff, frame->width/10);
    memset(udata, 0xff, frame->width/2/10);
    
    if(dbg_top < *crop_top) dbg_top = *crop_top;
    ydata = frame->base[0] + ((dbg_top - *crop_top))*ypitch;
    udata = frame->base[1] + ((dbg_top - *crop_top)/2)*upitch;
    memset(ydata, 0x80, frame->width/2);
    memset(ydata+ypitch, 0x80, frame->width/2);
    memset(udata, 0xff, frame->width/2/2);
  }
  if(*crop_bottom > 300) {
    if(*crop_bottom < frame->height - 2) {
      ydata = frame->base[0] + (*crop_bottom + 2)*ypitch;
      udata = frame->base[1] + ((*crop_bottom)/2)*upitch;
      memset(ydata, 0xff, frame->width);
      memset(ydata+ypitch, 0xff, frame->width);
      memset(udata, 0xff, frame->width/2);
    }
    if(dbg_bottom - *crop_top - 2 < frame->height) {
      ydata = frame->base[0] + (dbg_bottom - *crop_top - 2)*ypitch;
      udata = frame->base[1] + (dbg_bottom - *crop_top - 2)/2*upitch;
      memset(ydata, 0x80, frame->width/2);
      memset(ydata+ypitch, 0xff, frame->width/2);
      memset(udata, 0xff, frame->width/2/2);
    }
  }
  if(frame->height > 500) {
    /* TODO: use frame height instead of assuming 576 ... -> 72 */
    vdata = frame->base[2] + ((72-*crop_top)/2)*vpitch;
vdata = frame->base[2] + (72/2)*vpitch;
    memset(vdata, 0xff, frame->width/2);
    vdata = frame->base[2] + ((frame->height-72+(576-*crop_bottom))/2)*vpitch;
vdata = frame->base[2] + ((frame->height-72)/2)*vpitch;
    memset(vdata, 0xff, frame->width/2);
  }
}
#endif

/* 
 * crop frame by copying 
 */

static int crop_copy_yv12(vo_frame_t *frame, xine_stream_t *stream)
{
  post_video_port_t *port = (post_video_port_t *)frame->port;
  autocrop_post_plugin_t *this = (autocrop_post_plugin_t *)port->post;
  vo_frame_t *new_frame;
    
  int y, result;
  int yp = frame->pitches[0], yp2;
  int up = frame->pitches[1], up2;
  int vp = frame->pitches[2], vp2;
  uint8_t *ydata = frame->base[0], *ydata2;
  uint8_t *udata = frame->base[1], *udata2;
  uint8_t *vdata = frame->base[2], *vdata2;

  int   new_height;
  double new_ratio;

  /* top bar */
  ydata += this->start_line * yp;
  udata += (this->start_line/2) * up;
  vdata += (this->start_line/2) * vp;

  new_height = this->end_line - this->start_line;
  new_ratio  = 12.0/9.0 * ((double)frame->height / (double)new_height);

  new_frame = port->original_port->get_frame(port->original_port,
					     frame->width, new_height, 
					     new_ratio, frame->format, 
					     frame->flags | VO_BOTH_FIELDS);

  /* ??? */
  frame->ratio = new_frame->ratio;

  _x_post_frame_copy_down(frame, new_frame);

  yp2 = new_frame->pitches[0];
  up2 = new_frame->pitches[1];
  vp2 = new_frame->pitches[2];
  ydata2 = new_frame->base[0];
  udata2 = new_frame->base[1];
  vdata2 = new_frame->base[2];

  /*
    TODO:
    save channel logo mask (from top)
    - no changes in 3 sec -> next time crop it out ...
  */

  for(y=0; y < new_height/2; y++) {
    xine_fast_memcpy(ydata2, ydata, frame->width);
    ydata += yp;
    ydata2 += yp2;
    xine_fast_memcpy(ydata2, ydata, frame->width);
    ydata += yp;
    ydata2 += yp2;
    xine_fast_memcpy(udata2, udata, frame->width/2);
    udata += up;
    udata2 += up2;
    xine_fast_memcpy(vdata2, vdata, frame->width/2);
    vdata += vp;
    vdata2 += vp2;
  }

#ifdef MARK_FRAME
  mark_frame_yv12(this, new_frame, &this->start_line, &this->end_line);
#endif

  result = new_frame->draw(new_frame, stream);
  _x_post_frame_copy_up(frame, new_frame);
  new_frame->free(new_frame);
  
  return result;
}

static int crop_copy_yuy2(vo_frame_t *frame, xine_stream_t *stream)
{
  post_video_port_t *port = (post_video_port_t *)frame->port;
  autocrop_post_plugin_t *this = (autocrop_post_plugin_t *)port->post;
  vo_frame_t *new_frame;
    
  int y, result;
  int p = frame->pitches[0], p2;
  uint8_t *data = frame->base[0], *data2;

  int   new_height;
  double new_ratio;

  /* top bar */
  data += this->start_line * p;

  new_height = this->end_line - this->start_line;
  new_ratio = 12.0/9.0 * ((double)frame->height / (double)new_height);
  new_frame = port->original_port->get_frame(port->original_port,
					     frame->width, new_height, 
					     new_ratio, frame->format, 
					     frame->flags | VO_BOTH_FIELDS);
  /* ??? */
  frame->ratio = new_frame->ratio;

  _x_post_frame_copy_down(frame, new_frame);

  p2 = new_frame->pitches[0];
  data2 = new_frame->base[0];

  for(y=0; y < new_height; y++) {
    xine_fast_memcpy(data2, data, frame->width);
    data += p;
    data2 += p2;
  }

  result = new_frame->draw(new_frame, stream);
  _x_post_frame_copy_up(frame, new_frame);
  new_frame->free(new_frame);
  
  return result;
}


/*
 *    Frame handling
 */

static int autocrop_draw(vo_frame_t *frame, xine_stream_t *stream)
{
  post_video_port_t *port = (post_video_port_t *)frame->port;
  autocrop_post_plugin_t *this = (autocrop_post_plugin_t *)port->post;
  int result, start_line, end_line;

  if(!this->autodetect) {

    pthread_mutex_lock(&this->crop_lock);
    this->cropping_active = 1;
    this->start_line = frame->height/8;
    this->end_line   = frame->height*7/8;
    this->crop_total = frame->height/4;
    this->use_driver_crop = this->always_use_driver_crop || (frame->format != XINE_IMGFMT_YV12 && frame->format != XINE_IMGFMT_YUY2);
    pthread_mutex_unlock(&this->crop_lock);

    if (frame->bad_frame || this->use_driver_crop) {
      _x_post_frame_copy_down(frame, frame->next);
      result = frame->next->draw(frame->next, stream);
      _x_post_frame_copy_up(frame, frame->next);
    } else if(frame->format == XINE_IMGFMT_YV12)
      result = crop_copy_yv12(frame, stream);
    else /*if(frame->format == XINE_IMGFMT_YUY2)*/
      result = crop_copy_yuy2(frame, stream);

    return result;
  }

  int autodetect_rate = this->autodetect_rate;
  int cropping_active = this->cropping_active;

  /* use pts jumps to track stream changes (and seeks) */
  if(cropping_active && frame->pts > 0) {
    if(this->prev_pts>0) {
      int64_t dpts = frame->pts - this->prev_pts;
      if(dpts < INT64_C(-30*90000) || dpts > INT64_C(30*90000)) { /* 30 sec */
	if(this->height_limit_active) {
          this->height_limit_timer = this->subs_detect_lifetime / 2;
          llprintf(LOG_DEBUG, "short pts jump reseted height limit\n");
	}
      }
      if(dpts < INT64_C(-30*60*90000) || dpts > INT64_C(30*60*90000)) { /* 30 min */ 
        cropping_active = 0;
        llprintf(LOG_DEBUG, "long pts jump reseted cropping\n");
      }
    }
    this->prev_pts = frame->pts;
  }

  if (cropping_active) {
    start_line = this->stabilized_start_line;
    end_line = this->stabilized_end_line;
  } else {
    start_line = 0;
    end_line = frame->height;
  }

  /* Analyze frame for letterbox borders */
  if(!frame->bad_frame && (this->analyze_timer % autodetect_rate) == 0) {
    analyze_frame(frame, &start_line, &end_line);
    int detected_end_line = end_line;

    /* activate cropping if bars are large enough */
    if(!cropping_active && (start_line > 10 || end_line < (frame->height - 10))) {
      cropping_active = 1;
      this->stabilized_start_line = this->start_line = 0;
      this->stabilized_end_line = this->end_line = this->detected_end_line = this->prev_detected_end_line = frame->height;
      this->stabilize_timer = this->stabilize_time;
      this->prev_pts = -1;
      this->height_limit_active = 0;
      this->end_line_stabilize_timer = this->subs_detect_stabilize_time;
    }

    if(cropping_active && this->subs_detect) {
      /* no change unless different values for several frames */
      if (abs(this->detected_end_line - end_line) > 5) {
        this->end_line_stabilize_timer -= autodetect_rate;
        if (this->end_line_stabilize_timer <= 0) {
          this->detected_end_line = end_line;
          this->end_line_stabilize_timer = this->subs_detect_stabilize_time;
        }
      } else
        this->end_line_stabilize_timer = this->subs_detect_stabilize_time;

      if(this->height_limit_active) {
        this->height_limit_timer -= autodetect_rate;
        if (this->height_limit_timer <= 0) {
          this->height_limit_active = 0;
          llprintf(LOG_DEBUG, "height limit timer expired\n");
        }
      }
      /* apply height limit */
      if(this->height_limit_active && end_line < this->height_limit)
        end_line = this->height_limit;
    }

    /* no change unless different values for several frames */
    if(abs(start_line - this->stabilized_start_line) > 5 || abs(end_line - this->stabilized_end_line) > 5) {
      this->stabilize_timer -= autodetect_rate;
      if(this->stabilize_timer > 0) {
        start_line = this->stabilized_start_line;
        end_line = this->stabilized_end_line;
      }
      else {
        /* ignore very small bars */
        if(start_line <= 10 && end_line >= (frame->height - 10))
          cropping_active = 0;
        this->stabilize_timer = this->stabilize_time;
        llprintf(LOG_DEBUG, "stabilized start: %d -> %d, end %d -> %d\n",
              this->stabilized_start_line, start_line,
              this->stabilized_end_line, end_line);
      }
    } else {
      this->stabilize_timer = this->stabilize_time;
      start_line = this->stabilized_start_line;
      end_line = this->stabilized_end_line;
    }

    /* handle fixed subtitles inside bottom bar */
    if(cropping_active && this->subs_detect) {
      if(this->height_limit_active && abs(this->stabilized_start_line - start_line) > 5) {
        /* reset height limit if top bar changes */
        this->height_limit_active = 0;
        end_line = this->detected_end_line = detected_end_line;
        llprintf(LOG_DEBUG, "height limit reset, top bar moved from %d -> %d, bottom now %d\n", this->stabilized_start_line, start_line, end_line);

      } else if (this->detected_end_line > (this->prev_detected_end_line + 5)) {
        if(!this->height_limit_active || this->height_limit < this->detected_end_line) {
          /* start or increase height limit */
          if (this->height_limit_active)
            llprintf(LOG_DEBUG, "height limit %d -> %d, prev bottom %d\n", this->height_limit, this->detected_end_line, this->prev_detected_end_line);
          else
            llprintf(LOG_DEBUG, "activate height limit %d, prev bottom %d\n", this->detected_end_line, this->prev_detected_end_line);
          this->height_limit = this->detected_end_line;
          this->height_limit_timer = this->subs_detect_lifetime;
          this->height_limit_active = 1;

        } else if(this->height_limit_active && this->height_limit_timer < (this->subs_detect_lifetime / 4)) {
          /* keep height limit timer running */
          this->height_limit_timer = this->subs_detect_lifetime / 2;
          llprintf(LOG_DEBUG, "height_limit_timer increment bottom %d;%d -> %d\n", this->prev_detected_end_line, this->detected_end_line, detected_end_line);
        }
      }
      this->prev_detected_end_line = this->detected_end_line;
    }

    this->stabilized_start_line = start_line;
    this->stabilized_end_line = end_line;
  }

  /* update frame counter */
  ++this->analyze_timer;

  /* "soft start" */
  if(cropping_active && this->soft_start) {
    if(this->start_line != start_line) {
      int diff = this->start_line - start_line;
      if(diff < -this->soft_start_step)
        diff = -this->soft_start_step;
      else if(diff > this->soft_start_step)
        diff = this->soft_start_step;
      start_line = this->start_line - diff;
    }
    if(this->end_line != end_line) {
      int diff = this->end_line - end_line;
      if(diff < -this->soft_start_step)
        diff = -this->soft_start_step;
      else if(diff > this->soft_start_step)
        diff = this->soft_start_step;
      end_line = this->end_line - diff;
    }
  }

  this->prev_height = frame->height;
  this->prev_width = frame->width;

  if (cropping_active && (start_line != this->start_line || end_line != this->end_line)) {
    llprintf(LOG_DEBUG, "active start: %d -> %d, end %d -> %d\n",
          this->start_line, start_line,
          this->end_line, end_line);
  }

  if (this->cropping_active != cropping_active)
    llprintf(LOG_DEBUG, "draw: active %d -> %d\n", this->cropping_active, cropping_active);

  pthread_mutex_lock(&this->crop_lock);
  this->cropping_active = cropping_active;
  this->start_line = start_line;
  this->end_line = end_line;
  this->crop_total = start_line + frame->height - end_line;
  this->use_driver_crop = this->always_use_driver_crop || (frame->format != XINE_IMGFMT_YV12 && frame->format != XINE_IMGFMT_YUY2);
  pthread_mutex_unlock(&this->crop_lock);

  /*
   * do cropping 
   *  - using frame->crop_... does not seem to work with my xv and xine-lib-1.1.1. 
   *  - maybe cropping could be done by adjusting y/u/v data pointers 
   *    and height of frame ?
   *    -> no time-consuming copying 
   */
  if(frame->bad_frame || !cropping_active || this->use_driver_crop) {
    _x_post_frame_copy_down(frame, frame->next);
    result = frame->next->draw(frame->next, stream);
    _x_post_frame_copy_up(frame, frame->next);
  } else if(frame->format == XINE_IMGFMT_YV12)
    result = crop_copy_yv12(frame, stream);
  else /*if(frame->format == XINE_IMGFMT_YUY2)*/
    result = crop_copy_yuy2(frame, stream);

  return result;
}

static vo_frame_t *autocrop_get_frame(xine_video_port_t *port_gen, 
				       uint32_t width, uint32_t height, 
				       double ratio, int format, int flags)
{
  post_video_port_t      *port = (post_video_port_t *)port_gen;
  post_plugin_t          *this_gen = port->post;
  autocrop_post_plugin_t *this = (autocrop_post_plugin_t *)this_gen;

  int cropping_active = this->cropping_active;

  if (ratio <= 0.0) {
    if (height > 1)
      ratio = (double)width / (double)height;
  }

  /* Crop only SDTV 4:3 frames ... */
  int intercept = ((format == XINE_IMGFMT_YV12 || format == XINE_IMGFMT_YUY2 || this->has_driver_crop) &&
       FABS(ratio - 4.0/3.0) < 0.1 &&
       width  >= 240 && width  <= 768 &&
       height >= 288 && height <= 576);

  if(cropping_active && !intercept) {
    cropping_active = 0;
    llprintf(LOG_DEBUG, "get_frame: deactivate ratio: %lf width: %d height: %d\n", ratio, width, height);
  }

  /* reset when format changes */
  if (cropping_active && this->autodetect && (height != this->prev_height || width != this->prev_width)) {
    cropping_active = 0;
    llprintf(LOG_DEBUG, "get_frame: deactivate width %d -> %d height %d -> %d\n", this->prev_width, width, this->prev_height, height);
  }

  /* set new ratio when using driver crop */
  if (cropping_active && this->use_driver_crop) {
    if (this->autodetect) {
      int new_height = this->end_line - this->start_line;
      if (new_height > 1 && new_height != (int)height)
        ratio *= (double)height / (double)new_height;
    } else {
      ratio *= 4.0 / 3.0;
    }
  }

  _x_post_rewire(this_gen);
  vo_frame_t *frame = port->original_port->get_frame(port->original_port, width, height, ratio, format, flags);

  if (frame) {
    /* set cropping when using driver crop */
    if (cropping_active && this->use_driver_crop) {
      if (this->autodetect) {
        frame->crop_top = this->start_line;
        frame->crop_bottom = (height - this->end_line);
      } else {
        frame->crop_top = frame->crop_bottom = height / 8;
      }
    }

    /* intercept frame for analysis and crop-by-copy */
    if (intercept && format != XINE_IMGFMT_YV12 && format != XINE_IMGFMT_YUY2) {
#ifdef HAVE_PROC_PROVIDE_STANDARD_FRAME_DATA
      if (!frame->proc_provide_standard_frame_data) {
#endif
      llprintf(LOG_DEBUG, "get_frame: deactivate because missing provide_standard_frame_data feature\n");
      cropping_active = 0;
      intercept = 0;
#ifdef HAVE_PROC_PROVIDE_STANDARD_FRAME_DATA
    }
#endif
    }

    if (intercept) {
      _x_post_inc_usage(port);
      frame = _x_post_intercept_video_frame(frame, port);
    }
  }

  this->cropping_active = cropping_active;

  return frame;
}

static int autocrop_intercept_ovl(post_video_port_t *port)
{
  autocrop_post_plugin_t *this = (autocrop_post_plugin_t *)port->post;

  if (!this->cropping_active) 
    return 0;

  return 1;
}

static int32_t autocrop_overlay_add_event(video_overlay_manager_t *this_gen, void *event_gen)
{
  post_video_port_t      *port  = _x_post_ovl_manager_to_port(this_gen);
  autocrop_post_plugin_t *this  = (autocrop_post_plugin_t *)port->post;
  video_overlay_event_t  *event = (video_overlay_event_t *)event_gen;

  pthread_mutex_lock(&this->crop_lock);
  int cropping_active = this->cropping_active;
  int crop_total = this->crop_total;
  int use_driver_crop = this->use_driver_crop;
  int start_line = this->start_line;
  pthread_mutex_unlock(&this->crop_lock);

  if(cropping_active && crop_total>10) {
    if (event->event_type == OVERLAY_EVENT_SHOW
#ifdef VO_CAP_CUSTOM_EXTENT_OVERLAY
        /* Do not move overlay if video_out has independent video and OSD resolutions */
        &&   event->object.overlay
        && ( event->object.overlay->extent_width  <= 0 ||
             event->object.overlay->extent_height <= 0)
#endif
        ) {
      switch (event->object.object_type) {
      case 0:
	/* regular subtitle */
	/* Subtitle overlays must be coming somewhere inside xine engine */
        if (use_driver_crop) {
          if(this->has_driver_crop) {
            if(!event->object.overlay->unscaled || !this->has_unscaled_overlay) {
              event->object.overlay->y -= crop_total;
	  }
	} else {
	  /* object is moved crop_top amount in video_out */
            if(event->object.overlay->unscaled && this->has_unscaled_overlay) {
	    /* cancel incorrect move that will be done in video_out */
              event->object.overlay->y += start_line;
	  } else {
	    /* move crop_bottom pixels up */
              event->object.overlay->y -= (crop_total - start_line);
	  }
	}

	/* when using cropping overlays are moved in video_out */
	llprintf(LOG_INFO, "autocrop_overlay_add_event: subtitle event untouched\n");
        } else {
	/* when cropping here subtitles coming from inside of xine must be re-positioned */
          if(!event->object.overlay->unscaled || !this->has_unscaled_overlay) {
            event->object.overlay->y -= crop_total;
	  llprintf(LOG_INFO, "autocrop_overlay_add_event: subtitle event moved up\n");
	}
        }
	break;
      case 1:
	/* menu overlay */
	/* All overlays coming from VDR have this type */
	{
#ifdef DVDTEST
	  int dvd_menu = 0;
	  if (stream->input_plugin) {
	    if (stream->input_plugin->get_capabilities (stream->input_plugin) & 
		INPUT_CAP_SPULANG) {
	      *((int *)lang) = 0; /* channel */
	      if (stream->input_plugin->get_optional_data (stream->input_plugin, lang,
							   INPUT_OPTIONAL_DATA_SPULANG)
		  == INPUT_OPTIONAL_SUCCESS)
		if(!strcmp(lang, "menu"))
		  dvd_menu = 1; /* -> cropping off */
	      /* should turn on when not in menu ... -> where ? */
	    }
	  }
#endif
          if (use_driver_crop) {
            if(!event->object.overlay->unscaled || !this->has_unscaled_overlay) {
              event->object.overlay->y += start_line;//crop_total;
            }
	  }
	}
	break;
      default:;
      }
    }
  }

  return port->original_manager->add_event(port->original_manager, event_gen);
}


static void autocrop_video_close(xine_video_port_t *port_gen, xine_stream_t *stream)
{
  post_video_port_t *port = (post_video_port_t *)port_gen;
  autocrop_post_plugin_t *this = (autocrop_post_plugin_t *) port->post;

  if (this->cropping_active) {
    this->cropping_active = 0;
    llprintf(LOG_DEBUG, "deactivate because video close\n");
  }
  port->original_port->close(port->original_port, stream);
  port->stream = NULL;
  _x_post_dec_usage(port);
}


/*
 *    Parameter functions
 */

static xine_post_api_descr_t *autocrop_get_param_descr(void)
{
  return &autocrop_param_descr;
}

#if XINE_VERSION_CODE > 10209 || defined(PLUGIN_XINE_MODULE)
static int autocrop_set_parameters(xine_post_t *this_gen, const void *param_gen)
#else
static int autocrop_set_parameters(xine_post_t *this_gen, void *param_gen)
#endif
{
  const autocrop_parameters_t *param = (const autocrop_parameters_t *)param_gen;
  autocrop_post_plugin_t *this = (autocrop_post_plugin_t *)this_gen;

  this->autodetect  = param->enable_autodetect;
  this->autodetect_rate = param->autodetect_rate;
  this->subs_detect = param->enable_subs_detect;
  this->subs_detect_lifetime = param->subs_detect_lifetime;
  this->subs_detect_stabilize_time = param->subs_detect_stabilize_time;
  this->soft_start  = param->soft_start;
  this->soft_start_step = param->soft_start_step;
  this->stabilize   = param->stabilize;
  this->stabilize_time = param->stabilize_time;
  this->always_use_driver_crop = param->use_driver_crop && this->has_driver_crop;
  this->logo_width = param->logo_width;
  this->use_avards_analysis = param->use_avards_analysis;
  this->overscan_compensate = param->overscan_compensate;
  this->bar_tone_tolerance = param->bar_tone_tolerance;
  this->debug_level = param->debug_level;

  llprintf(LOG_DEBUG, "autocrop_set_parameters: "
        "autodetect=%d  autodetect_rate=%d  logo_width=%d  "
        "subs_detect=%d  subs_detect_lifetime=%d  subs_detect_stabilize_time=%d  "
        "soft_start=%d  soft_start_step=%d  "
        "stabilize=%d  stabilize_time=%d  use_driver_crop=%d  "
        "use_avards_analysis=%d  overscan_compensate=%d  bar_tone_tolerance=%d\n",
        this->autodetect, this->autodetect_rate, this->logo_width,
        this->subs_detect, this->subs_detect_lifetime, this->subs_detect_stabilize_time,
	this->soft_start, this->soft_start_step,
        this->stabilize, this->stabilize_time, this->always_use_driver_crop,
        this->use_avards_analysis, this->overscan_compensate, this->bar_tone_tolerance);
  return 1;
}

static int autocrop_get_parameters(xine_post_t *this_gen, void *param_gen)
{
  autocrop_post_plugin_t *this = (autocrop_post_plugin_t *)this_gen;
  autocrop_parameters_t *param = (autocrop_parameters_t *)param_gen;
  
  param->enable_autodetect  = this->autodetect;
  param->autodetect_rate  = this->autodetect_rate;
  param->enable_subs_detect = this->subs_detect;
  param->subs_detect_lifetime = this->subs_detect_lifetime;
  param->subs_detect_stabilize_time = this->subs_detect_stabilize_time;
  param->soft_start         = this->soft_start;
  param->soft_start_step = this->soft_start_step;
  param->stabilize          = this->stabilize;
  param->stabilize_time = this->stabilize_time;
  param->use_driver_crop    = this->always_use_driver_crop;
  param->logo_width = this->logo_width;
  param->use_avards_analysis = this->use_avards_analysis;
  param->overscan_compensate = this->overscan_compensate;
  param->bar_tone_tolerance = this->bar_tone_tolerance;
  param->debug_level = this->debug_level;

  llprintf(LOG_DEBUG, "autocrop_get_parameters: "
        "autodetect=%d  autodetect_rate=%d  logo_width=%d  "
        "subs_detect=%d  subs_detect_lifetime=%d  subs_detect_stabilize_time=%d  "
        "soft_start=%d  soft_start_step=%d  "
        "stabilize=%d  stabilize_time=%d  use_driver_crop=%d  "
        "use_avards_analysis=%d  overscan_compensate=%d  bar_tone_tolerance=%d\n",
        this->autodetect, this->autodetect_rate, this->logo_width,
        this->subs_detect, this->subs_detect_lifetime, this->subs_detect_stabilize_time,
	this->soft_start, this->soft_start_step,
        this->stabilize, this->stabilize_time, this->always_use_driver_crop,
        this->use_avards_analysis, this->overscan_compensate, this->bar_tone_tolerance);

  return 1;
}

static char *autocrop_get_help(void) {
  return _("The autocrop plugin is meant to take 4:3 letterboxed frames and "
           "convert them to 16:9 by removing black bars on the top and bottom "
	   "of the frame.\n"
           "\n"
           "Parameters\n"
           "  enable_autodetect:          Enable automatic letterbox detection\n"
           "  autodetect_rate:            Rate of automatic letterbox detection\n"
           "  stabilize_time:             Number of frames with equal bars before cropping value changes\n"
           "  enable_subs_detect:         Enable automatic subtitle detection inside bottom bar\n"
           "  subs_detect_lifetime:       Lifetime of automatic subtitle detection\n"
           "  subs_detect_stabilize_time: Stabilize time of automatic subtitle detection\n"
           "  soft_start:                 Enable soft start of cropping\n"
           "  soft_start_step:            Soft start step width of cropping\n"
           "  stabilize:                  Stabilize cropping to 14:9, 16:9, (16:9+subs), 20:9, (20:9+subs)\n"
           "  use_driver_crop:            Always use video driver crop\n"
           "  logo_width:                 Width of logo (percentage of frame width) for automatic logo detection\n"
           "  use_avards_analysis:        Use AVARDS algorithm for frame analysis\n"
           "  overscan_compensate:        Compensation of output device overscan applied when cropping frames (%1000 of frame height)\n"
           "  bar_tone_tolerance:         Tolerance of bar color (avards only)"
           "  debug_level:                Output debug messages. 0=none, 1=info, 2=debug, 3=trace.\n"
           "\n"
         );
}


/*
 *    Open/close
 */

static void autocrop_dispose(post_plugin_t *this_gen)
{
  if (_x_post_dispose(this_gen)) {
    autocrop_post_plugin_t *this = (autocrop_post_plugin_t *) this_gen;
    pthread_mutex_destroy(&this->crop_lock);
    free(this->img);
    free(this);
  }
}

static post_plugin_t *autocrop_open_plugin(post_class_t *class_gen, 
					    int inputs,
					    xine_audio_port_t **audio_target,
					    xine_video_port_t **video_target)
{
  if (video_target && video_target[ 0 ]) {
    autocrop_post_plugin_t *this = calloc(1, sizeof(autocrop_post_plugin_t));
    post_in_t           *input;
    post_out_t          *output;
    post_video_port_t   *port;

    static const xine_post_api_t post_api = {
      .set_parameters  = autocrop_set_parameters,
      .get_parameters  = autocrop_get_parameters,
      .get_param_descr = autocrop_get_param_descr,
      .get_help        = autocrop_get_help,
    };
    static const xine_post_in_t input_param = {
      .name = "parameters",
      .type = XINE_POST_DATA_PARAMETERS,
      .data = (void *)&post_api,
    };

    if (this) {
      _x_post_init(&this->post_plugin, 0, 1);

      port = _x_post_intercept_video_port(&this->post_plugin, 
					  video_target[ 0 ], 
					  &input, &output);

      input->xine_in.name   = "video in";
      output->xine_out.name = "video out";

      port->intercept_ovl          = autocrop_intercept_ovl;
      port->new_manager->add_event = autocrop_overlay_add_event;
      port->new_port.get_frame     = autocrop_get_frame;
      port->new_port.close         = autocrop_video_close;
      port->new_frame->draw        = autocrop_draw;

      this->post_plugin.xine_post.video_input[ 0 ] = &port->new_port;
      this->post_plugin.dispose = autocrop_dispose;

#if XINE_VERSION_CODE >= 10102
      xine_list_push_back(this->post_plugin.input, (void *)&input_param);
#else
      xine_list_append_content(this->post_plugin.input, (void *)&input_param);
#endif
      this->autodetect  = 1;
      this->autodetect_rate = DEFAULT_AUTODETECT_RATE;
      this->stabilize_time = DEFAULT_STABILIZE_TIME;
      this->subs_detect = 1;
      this->subs_detect_lifetime = DEFAULT_SUBS_DETECT_LIFETIME;
      this->subs_detect_stabilize_time = DEFAULT_SUBS_DETECT_STABILIZE_TIME;
      this->soft_start  = 1;
      this->soft_start_step = DEFAULT_SOFT_START_STEP;
      this->stabilize   = 1;
      this->logo_width = DEFAULT_LOGO_WIDTH;
      this->use_avards_analysis = 0;
      this->overscan_compensate = DEFAULT_OVERSCAN_COMPENSATE;
      this->bar_tone_tolerance = DEFAULT_BAR_TONE_TOLERANCE;
      this->debug_level = 1;

      uint64_t caps = port->original_port->get_capabilities(port->original_port);
      this->has_driver_crop = caps & VO_CAP_CROP;
      this->has_unscaled_overlay = caps & VO_CAP_UNSCALED_OVERLAY;

      pthread_mutex_init(&this->crop_lock, NULL);

      return &this->post_plugin;
    }
  }

  return NULL;
}


/*
 *    Plugin class
 */

#if POST_PLUGIN_IFACE_VERSION < 10
static char *autocrop_get_identifier(post_class_t *class_gen)
{
  return "autocrop";
}

static char *autocrop_get_description(post_class_t *class_gen)
{
  return "Crop letterboxed 4:3 video to 16:9";
}

static void autocrop_class_dispose(post_class_t *class_gen)
{
  free(class_gen);
}
#endif

#if XINE_VERSION_CODE > 10209 || defined(PLUGIN_XINE_MODULE)
static void *autocrop_init_plugin(xine_t *xine, const void *data)
#else
static void *autocrop_init_plugin(xine_t *xine, void *data)
#endif
{
  post_class_t *class = calloc(1, sizeof(post_class_t));
  
  if(class) {
    class->open_plugin     = autocrop_open_plugin;
#if POST_PLUGIN_IFACE_VERSION < 10
    class->get_identifier  = autocrop_get_identifier;
    class->get_description = autocrop_get_description;
    class->dispose         = autocrop_class_dispose;
#else
    class->identifier      = "autocrop";
    class->description     = N_("Crop letterboxed 4:3 video to 16:9");
    class->dispose         = default_post_class_dispose;
#endif
  }

  return class;
}


static post_info_t info = { XINE_POST_TYPE_VIDEO_FILTER };

const plugin_info_t xine_plugin_info[] __attribute__((visibility("default"))) =
{
  /* type, API, "name", version, special_info, init_function */  
  { PLUGIN_POST, POST_PLUGIN_IFACE_VERSION, "autocrop", XINE_VERSION_CODE, &info, &autocrop_init_plugin },
  { PLUGIN_NONE, 0, NULL, 0, NULL, NULL }
};
