/*
	2004.02.01
		first released source code for IOMP
*/
/*
 * Copyright (C) 2000-2002 the xine project and Fredrik Noring
 * 
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 *
 * $Id: video_out_fb.c,v 1.5 2003/12/10 03:30:01 kevin Exp $
 * 
 * video_out_fb.c, frame buffer xine driver by Miguel Freitas
 *
 * Contributors:
 *
 *     Fredrik Noring <noring@nocrew.org>:  Zero copy buffers and clean up.
 *
 * based on xine's video_out_xshm.c...
 * ...based on mpeg2dec code from
 * Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
 *
 * ideas from ppmtofb - Display P?M graphics on framebuffer devices
 *            by Geert Uytterhoeven and Chris Lawrence
 *
 * Note: Use this with fbxine. It may work with the regular xine too,
 * provided the visual type is changed (see below).
 *
 * TODO: VT switching (configurable)
 */

/* #define USE_X11_VISUAL */

#define RECOMMENDED_NUM_BUFFERS  5
#define MAXIMUM_NUM_BUFFERS     25

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> 
             
#include "xine.h"
#include "video_out.h"

#include <errno.h>

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#include <pthread.h>
#include <netinet/in.h>

#include <linux/fb.h>
#include <linux/kd.h>
#include <linux/vt.h>

#include "xine_internal.h"
#include "alphablend.h"
#include "yuv2rgb.h"
#include "xineutils.h"
#include "vo_scale.h"

#define MYDEBUG(x) printf x
/*
#define LOG
*/
typedef struct fb_frame_s
{
  vo_frame_t         vo_frame;

  int                format;
  int                flags;
  
  vo_scale_t         sc;

  uint8_t           *chunk[3]; /* mem alloc by xmalloc_aligned           */

  yuv2rgb_t         *yuv2rgb;  /* yuv2rgb converter for this frame */
  uint8_t           *rgb_dst;
  int                yuv_stride;
  int                stripe_height, stripe_inc;
  
  int                bytes_per_line;

  uint8_t*           video_mem;            /* mmapped video memory */
  uint8_t*           data;
  int                yoffset;

  struct fb_driver_s *this;
} fb_frame_t;

typedef struct fb_driver_s
{
  vo_driver_t        vo_driver;

  int                fd;
  int                mem_size;
  uint8_t*           video_mem_base;       /* mmapped video memory */
  
  int                depth, bpp, bytes_per_pixel;
  
  int                total_num_native_buffers;
  int                used_num_buffers;
	
  int                yuv2rgb_mode;
  int                yuv2rgb_swap;
  int                yuv2rgb_gamma;
  uint8_t           *yuv2rgb_cmap;
  yuv2rgb_factory_t *yuv2rgb_factory;

  vo_overlay_t      *overlay;

  /* size / aspect ratio calculations */
  vo_scale_t         sc;
  
  int                fb_bytes_per_line;

  fb_frame_t        *cur_frame, *old_frame;
	
  struct fb_var_screeninfo fb_var;
  struct fb_fix_screeninfo fb_fix;

  int                use_zero_copy;
  int                frame_mode;
} fb_driver_t;

typedef struct
{
  video_driver_class_t driver_class;
  config_values_t     *config;
} fb_class_t;

static int shmid_fb = -1;
static void *shared_memory_fb = (void *)-1;
int Count=0;

static uint32_t fb_get_capabilities(vo_driver_t *this_gen)
{
  return VO_CAP_COPIES_IMAGE |
    VO_CAP_YV12         |
    VO_CAP_YUY2         |
    VO_CAP_BRIGHTNESS;
}

static void fb_frame_copy(vo_frame_t *vo_img, uint8_t **src)
{
  fb_frame_t *frame = (fb_frame_t *)vo_img ;
  
  vo_img->copy_called = 1;
  if(frame->format == XINE_IMGFMT_YV12) {
    frame->yuv2rgb->yuv2rgb_fun(frame->yuv2rgb, frame->rgb_dst,
				 src[0], src[1], src[2]);
  }
  else {
    frame->yuv2rgb->yuy22rgb_fun(frame->yuv2rgb,
				 frame->rgb_dst, src[0]);
  }
  frame->rgb_dst += frame->stripe_inc; 
}

static void fb_frame_field(vo_frame_t *vo_img, int which_field)
{
  fb_frame_t *frame = (fb_frame_t *)vo_img ;
  
  
  switch(which_field)
  {
  case VO_TOP_FIELD:
      frame->rgb_dst    = frame->data;
      frame->stripe_inc = 2*frame->stripe_height *
			  frame->bytes_per_line;
    break;
			
  case VO_BOTTOM_FIELD:
      frame->rgb_dst    = frame->data +
			  frame->bytes_per_line ;
      frame->stripe_inc = 2*frame->stripe_height *
			  frame->bytes_per_line;
    break;
			
  case VO_BOTH_FIELDS:
      frame->rgb_dst    = frame->data;
    break;
  }
}

static void fb_frame_dispose(vo_frame_t *vo_img)
{
  fb_frame_t *frame = (fb_frame_t *)vo_img;

  if(!frame->this->use_zero_copy)
     free(frame->data);
  free(frame);
}

static vo_frame_t *fb_alloc_frame(vo_driver_t *this_gen)
{
  fb_driver_t *this = (fb_driver_t *)this_gen;
  fb_frame_t *frame;

  if(this->use_zero_copy &&
     this->total_num_native_buffers <= this->used_num_buffers)
    return 0;

  frame = (fb_frame_t *)malloc(sizeof(fb_frame_t));
  if(!frame)
  {
    fprintf(stderr, "fb_alloc_frame: Out of memory.\n");
    return 0;
  }

  memset(frame, 0, sizeof(fb_frame_t));
  memcpy(&frame->sc, &this->sc, sizeof(vo_scale_t));

  pthread_mutex_init(&frame->vo_frame.mutex, NULL);
  
  /* supply required functions */
  frame->vo_frame.copy    = fb_frame_copy;
  frame->vo_frame.field   = fb_frame_field; 
  frame->vo_frame.dispose = fb_frame_dispose;
  frame->vo_frame.driver  = this_gen;
  
  frame->this = this;
  
  /* colorspace converter for this frame */
  frame->yuv2rgb =
    this->yuv2rgb_factory->create_converter(this->yuv2rgb_factory);

  if(this->use_zero_copy)
  {
    frame->yoffset = this->used_num_buffers * this->fb_var.yres;
    frame->video_mem = this->video_mem_base +
		       this->used_num_buffers * this->fb_var.yres *
		       this->fb_bytes_per_line;

    memset(frame->video_mem, 0,
	   this->fb_var.yres * this->fb_bytes_per_line);
  }
  else
    frame->video_mem = this->video_mem_base;

  this->used_num_buffers++;

  return (vo_frame_t *)frame;
}

static void fb_compute_ideal_size(fb_driver_t *this, fb_frame_t *frame)
{
  vo_scale_compute_ideal_size(&frame->sc);
}

static void fb_compute_rgb_size(fb_driver_t *this, fb_frame_t *frame)
{
  vo_scale_compute_output_size(&frame->sc);
  /* avoid problems in yuv2rgb */
  if(frame->sc.output_height < (frame->sc.delivered_height+15) >> 4)
    frame->sc.output_height = (frame->sc.delivered_height+15) >> 4;

  if (frame->sc.output_width < 8)
    frame->sc.output_width = 8;

  /* yuv2rgb_mlib needs an even YUV2 width */
  if (frame->sc.output_width & 1) 
    frame->sc.output_width++;

#ifdef LOG
  printf("video_out_fb: frame source %d x %d => screen output %d x %d%s\n",
	 frame->sc.delivered_width, frame->sc.delivered_height,
	 frame->sc.output_width, frame->sc.output_height,
	 (frame->sc.delivered_width != frame->sc.output_width ||
	  frame->sc.delivered_height != frame->sc.output_height ?
	  ", software scaling" : ""));
#endif
}

static void setup_colorspace_converter(fb_frame_t *frame, int flags)
{
  switch(flags)
  {
    case VO_TOP_FIELD:
    case VO_BOTTOM_FIELD:
      frame->yuv2rgb->
	configure(frame->yuv2rgb,
		  frame->sc.delivered_width,
		  16,
		  2 * frame->vo_frame.pitches[0],
		  2 * frame->vo_frame.pitches[1],
		  frame->sc.output_width,
		  frame->stripe_height,
		  frame->bytes_per_line * 2);
      frame->yuv_stride = frame->bytes_per_line * 2;
      break;
    
    case VO_BOTH_FIELDS:
      frame->yuv2rgb->
	configure(frame->yuv2rgb,
		  frame->sc.delivered_width,
		  16,
		  frame->vo_frame.pitches[0],
		  frame->vo_frame.pitches[1],
		  frame->sc.output_width,
		  frame->stripe_height,
		  frame->bytes_per_line);
      frame->yuv_stride = frame->bytes_per_line;
      break;
  }
}

static void reset_dest_pointers(fb_frame_t *frame, int flags)
{
  switch(flags)
  {
    case VO_TOP_FIELD:
      frame->rgb_dst = frame->data;
      frame->stripe_inc = 2 * frame->stripe_height *
			  frame->bytes_per_line;
      break;

    case VO_BOTTOM_FIELD:
      frame->rgb_dst = frame->data +
		       frame->bytes_per_line ;
      frame->stripe_inc = 2 * frame->stripe_height *
			  frame->bytes_per_line;
      break;

    case VO_BOTH_FIELDS:
      frame->rgb_dst = frame->data;
      frame->stripe_inc = frame->stripe_height *
			  frame->bytes_per_line;
      break;
  }
}

static void frame_reallocate(fb_driver_t *this, fb_frame_t *frame,
			     uint32_t width, uint32_t height, int format)
{
  if(frame->chunk[0])
  {
    free(frame->chunk[0]);
        frame->chunk[0] = NULL;
  }
  if(frame->chunk[1])
  {
    free(frame->chunk[1]);
        frame->chunk[1] = NULL;
  }
  if(frame->chunk[2])
  {
    free(frame->chunk[2]);
        frame->chunk[2] = NULL;
  }
      
  if(this->use_zero_copy)
  {
    frame->data = frame->video_mem +
		  frame->sc.output_yoffset*this->fb_bytes_per_line+
		  frame->sc.output_xoffset*this->bytes_per_pixel;
  }
  else
  {
    if(frame->data)
      free(frame->data);
    frame->data = xine_xmalloc(frame->sc.output_width *
			       frame->sc.output_height *
			       this->bytes_per_pixel);
  }

  if(format == XINE_IMGFMT_YV12)
  {
      frame->vo_frame.pitches[0] = 8*((width + 7) / 8);
      frame->vo_frame.pitches[1] = 8*((width + 15) / 16);
      frame->vo_frame.pitches[2] = 8*((width + 15) / 16);
		
    frame->vo_frame.base[0] =
      xine_xmalloc_aligned(16,
			   frame->vo_frame.pitches[0] *
			   height,
                                                      (void **)&frame->chunk[0]);
		
    frame->vo_frame.base[1] =
      xine_xmalloc_aligned(16,
			   frame->vo_frame.pitches[1] *
			   ((height+1)/2),
                                                      (void **)&frame->chunk[1]);
		
    frame->vo_frame.base[2] =
      xine_xmalloc_aligned(16,
			   frame->vo_frame.pitches[2] *
			   ((height+1)/2),
                                                      (void **)&frame->chunk[2]);
  }
  else
  {
    frame->vo_frame.pitches[0] = 8 * ((width + 3) / 4);
		
    frame->vo_frame.base[0] =
      xine_xmalloc_aligned(16,
			   frame->vo_frame.pitches[0] *
			   height,
                                                      (void **)&frame->chunk[0]);
      frame->chunk[1] = NULL;
      frame->chunk[2] = NULL;
  }
}
    
static void fb_update_frame_format(vo_driver_t *this_gen,
				   vo_frame_t *frame_gen,
				   uint32_t width, uint32_t height,
				   double ratio, int format, int flags)
{
  fb_driver_t *this = (fb_driver_t *)this_gen;
  fb_frame_t *frame = (fb_frame_t *)frame_gen;
  
  flags &= VO_BOTH_FIELDS;

  /* Find out if we need to adapt this frame. */
  if (width != frame->sc.delivered_width    ||
      height != frame->sc.delivered_height  ||
      ratio != frame->sc.delivered_ratio    ||
      flags != frame->flags                 ||
      format != frame->format               ||
//	  this->last_frame_output_width != frame->sc.output_width  ||		/* removed by rwlin@avamax.com */
//	  this->last_frame_output_height != frame->sc.output_height ||		/* removed by rwlin@avamax.com */
	  this->sc.gui_width != frame->sc.gui_width ||						/* added by rwlin@avamax.com */
	  this->sc.gui_height != frame->sc.gui_height ||					/* added by rwlin@avamax.com */
      this->sc.user_ratio != frame->sc.user_ratio)
  {
#ifdef LOG
    printf("video_out_fb: frame format (from decoder) "
	   "has changed => adapt\n");
#endif

    frame->sc.delivered_width  = width;
    frame->sc.delivered_height = height;
    frame->sc.delivered_ratio  = ratio;
    frame->flags               = flags;
    frame->format              = format;
    frame->sc.user_ratio       = this->sc.user_ratio;
    frame->sc.gui_width			   = this->sc.gui_width;		/* added by rwlin@avamax.com */
    frame->sc.gui_height		   = this->sc.gui_height;		/* added by rwlin@avamax.com */

    fb_compute_ideal_size(this, frame);
	/* added by rwlin@avamax.com */
	if (this->sc.frame_output_cb) {
		frame->sc.gui_pixel_aspect = frame->sc.video_pixel_aspect;
		vo_scale_redraw_needed(&frame->sc);
	}

    fb_compute_rgb_size(this, frame);

    frame_reallocate(this, frame, width, height, format);
    frame->stripe_height = 16 * frame->sc.output_height /
			   frame->sc.delivered_height;
    if(this->use_zero_copy)
      frame->bytes_per_line = this->fb_bytes_per_line;
    else
      frame->bytes_per_line = frame->sc.output_width *
			      this->bytes_per_pixel;
    
    setup_colorspace_converter(frame, flags);

  }

  reset_dest_pointers(frame, flags);

}

static void fb_overlay_clut_yuv2rgb(fb_driver_t *this,
				    vo_overlay_t *overlay, fb_frame_t *frame)
{
  int i;
  clut_t* clut = (clut_t*)overlay->color;
	
  if(!overlay->rgb_clut)
  {
    for(i = 0;
	i < sizeof(overlay->color)/sizeof(overlay->color[0]);
	i++)
    {
      *((uint32_t *)&clut[i]) =
	frame->yuv2rgb->
	yuv2rgb_single_pixel_fun(frame->yuv2rgb,
				 clut[i].y,
				 clut[i].cb,
				 clut[i].cr);
    }
    overlay->rgb_clut++;
  }
	
  if(!overlay->clip_rgb_clut)
  {
    clut = (clut_t*) overlay->clip_color;
		
    for(i = 0;
	i < sizeof(overlay->color)/sizeof(overlay->color[0]);
	i++)
    {
      *((uint32_t *)&clut[i]) =
	frame->yuv2rgb->
	yuv2rgb_single_pixel_fun(frame->yuv2rgb,
				 clut[i].y,
				 clut[i].cb,
				 clut[i].cr);
    }
    overlay->clip_rgb_clut++;
  }
}

static void fb_overlay_blend (vo_driver_t *this_gen, vo_frame_t *frame_gen,
			      vo_overlay_t *overlay)
{
  fb_driver_t *this = (fb_driver_t *)this_gen;
  fb_frame_t *frame = (fb_frame_t *)frame_gen;

  /* Alpha Blend here */
  if(overlay->rle)
  {
    if(!overlay->rgb_clut || !overlay->clip_rgb_clut) {
        fb_overlay_clut_yuv2rgb(this,overlay,frame);
    }

    switch(this->bpp)
    {
       case 16:
	blend_rgb16(frame->data,
		    overlay,
		    frame->sc.output_width,
		    frame->sc.output_height,
		    frame->sc.delivered_width,
		    frame->sc.delivered_height);
        break;
				
       case 24:
	blend_rgb24(frame->data,
		    overlay,
		    frame->sc.output_width,
		    frame->sc.output_height,
		    frame->sc.delivered_width,
		    frame->sc.delivered_height);
        break;
				
       case 32:
	blend_rgb32(frame->data,
		    overlay,
		    frame->sc.output_width,
		    frame->sc.output_height,
		    frame->sc.delivered_width,
		    frame->sc.delivered_height);
	break;
     }        
   }
}

static int fb_redraw_needed(vo_driver_t *this_gen)
{
  /* added by rwlin@avamax.com */		
  fb_driver_t  *this = (fb_driver_t *) this_gen;
  int ret = 0;

  if( this->cur_frame ) {

    this->sc.delivered_height   = this->cur_frame->sc.delivered_height;
    this->sc.delivered_width    = this->cur_frame->sc.delivered_width;
    this->sc.video_pixel_aspect = this->cur_frame->sc.video_pixel_aspect;
    if( vo_scale_redraw_needed( &this->sc ) ) {

    	//fprintf(stderr,"============== redraw_needed\n");
      //clean_output_area (this, this->cur_frame);
      ret = 1;
    }
  } 
  else
    ret = 1;
      
  return ret;
}

extern int avamax_gui;
extern int avamax_skip;
extern int Program_File;

#include <sys/io.h>
#define INP_STATUS 0x03DA // VGA card input status port
#define V_RETRACE_MASK 0x08 // = 00000100 binary--just bit #3
struct timespec rtsleep = {0,10};
struct timespec rtsleep1;
 
//#define MYSLEEP nanosleep(&rtsleep,&rtsleep1);

//#define MYSLEEP pthread_yield(NULL);

extern void *avamax_shm1234;
static void SendReDrawFrame()
{
    struct share_memory_data {
        int shm_flag;	
        char shm_data[512];
    } *send_shm_buff;

	if (avamax_shm1234 == 0) {
   if(shmid_fb == -1) {
      	// "clear frame" and "get Program File name"  share memory
     shmid_fb = shmget((key_t)1234,sizeof(struct share_memory_data),SHM_R |SHM_W);  	  
  	}
	} else {
		shmid_fb = 0;
		shared_memory_fb = avamax_shm1234;
	}
  	if(shmid_fb != -1) {
  	    if(shared_memory_fb == (void *) -1) {
  	        shared_memory_fb = shmat(shmid_fb,(void *)0,0);
  	    }
  	    if(shared_memory_fb != (void *)-1) {
  	        send_shm_buff = (struct share_memory_data *)shared_memory_fb;	
  	        if(!send_shm_buff->shm_flag) {
  	            send_shm_buff = (struct share_memory_data *)shared_memory_fb;
  	            send_shm_buff->shm_data[1]=(char)1;
  	            send_shm_buff->shm_flag = 1;
  	        }
  	        else if(send_shm_buff->shm_flag) {
  	            //send_shm_buff = (struct share_memory_data *)shared_memory_fb;	
  	            switch(send_shm_buff->shm_data[0]) {
  	            case 0x01:  // is demo Program File
  	                Program_File = 1;
  	                break;
  	            case 0x02:  // is cmdxine Program File
  	            	Program_File = 2;
  	                break;
  	            default:    // other Program File
  	                Program_File = 0;
  	                break;
  	            }
 
  	            send_shm_buff = (struct share_memory_data *)shared_memory_fb;
  	            send_shm_buff->shm_data[1]=(char)1;
  	            send_shm_buff->shm_flag = 1;
  	        } 
  	    }
  	    /* ----- george add ----(end) */  	    	  		
  	}
  
    	
}

#define MYSLEEP 

static void wait_for_retrace() {
#if 1
    static int first = 1;
    if (first) {
        first = 0;
        if (ioperm(INP_STATUS,1,1)) {
            exit(0);
        }
    }
    // Bit 3 of the input status port on the VGA card tells the
	// status of the vertical retrace: 1 if it is in the "vertical
	// blank time.
	while (inb(INP_STATUS) & V_RETRACE_MASK) {
	    MYSLEEP;
	}
	 
	// NOW we wait for a blank.
	while (!(inb(INP_STATUS) & V_RETRACE_MASK)) {
	    MYSLEEP;
	}
#endif 
}

extern void eat_one_spu(); // kevin
static void fb_display_frame(vo_driver_t *this_gen, vo_frame_t *frame_gen)
{
  fb_driver_t  *this = (fb_driver_t *) this_gen;
  fb_frame_t   *frame = (fb_frame_t *) frame_gen;
  uint8_t	*dst, *src;
  int y;  
  int format;
  int runtime=0;
  int has_update=0;
  int IsPause=0;
  int def=0;
  
  eat_one_spu(); // kevin 
  /*
  printf("before this=(%d,%d,%d,%d,%d,%d), frame=(%d,%d,%d,%d,%d) frame->sc.user_ratio[%d] this->sc.user_ratio[%d]\n",
		this->sc.gui_x,this->sc.gui_y,this->sc.gui_width,this->sc.gui_height,this->fb_bytes_per_line,this->bytes_per_pixel,
		frame->sc.gui_x,frame->sc.gui_y,frame->sc.gui_width,frame->sc.gui_height,frame->bytes_per_line,frame->sc.user_ratio,this->sc.user_ratio);
  */

#if 1  
  /*----- george add  ---- */
  if((frame->sc.user_ratio != this->sc.user_ratio) || ((frame->sc.gui_width > this->sc.gui_width) && ((frame->sc.gui_width == 800)))) {
       	//¦b¤Á´«frame size®É,³Ì«á¤@­Óframe·|¨S¥hupdate frame,©Ò¥H­n¤@µo²{¦¹frame´N¥hupdate frameµM«á¤~¯àdisplay,¥H§K¨ä«e«áframe ªºsize»P¦¹frame¤£¤@¼Ë¦Ó¨Ïµe­±¦b¤Á´«®É°{1~2¤U                  	        
          runtime = 1;
      //    has_update=1;
          fb_update_frame_format(this_gen,frame_gen,this->sc.gui_width,this->sc.gui_height,this->sc.user_ratio,format , 1); 
  }  
  if((frame->sc.gui_width > this->sc.gui_width) && (((frame->sc.gui_width == 800) || (frame->sc.gui_width == 640 && this->sc.gui_width == 532) || (frame->sc.gui_width == 666)) || ((frame->sc.gui_width == 1066 && this->sc.gui_width == 854)))) {
     //¹LÂo¤Á´«·|¦³¤@±iframe¬OÃa±¼ªº.©Ò¥H­n§â¨º±N²¤¹L¤£¨q¥X¨Ó
      fb_update_frame_format(this_gen,frame_gen,this->sc.gui_width,this->sc.gui_height,this->sc.user_ratio,format , 1);
      runtime=1;
      has_update=1;
  }  
  /*----- george add ----(end) */
#endif 

  if(frame->sc.output_width  != this->sc.output_width ||
     frame->sc.output_height != this->sc.output_height)
  {
  	/* clear the output by rwlin@avamax.com*/
  	/*
  	if(has_update){
  	    printf("this->cur_frame \n");
  	if(this->cur_frame) {
	    dst = this->cur_frame->video_mem+
			this->cur_frame->sc.output_yoffset * this->fb_bytes_per_line +
        	this->cur_frame->sc.output_xoffset * this->bytes_per_pixel;

		if(dst>=this->video_mem_base && dst<=this->video_mem_base+this->mem_size) {
		  	for(y=0;y<this->cur_frame->sc.output_height;y++,dst+=this->fb_bytes_per_line)
				memset(dst,0,this->cur_frame->bytes_per_line);
		}
	}
        }
        */

        SendReDrawFrame();
        
        this->sc.output_width    = frame->sc.output_width;
        this->sc.output_height   = frame->sc.output_height;
 
        printf("video_out_fb: gui size %d x %d, frame size %d x %d\n",
            this->sc.gui_width, this->sc.gui_height,
            frame->sc.output_width, frame->sc.output_height);
        if((this->sc.gui_width == 1066) && (frame->sc.output_width == 800)) {
            runtime=1;
            has_update=1;	
        }
        
    // removed by rwlin@avamax.com
    //memset(this->video_mem_base, 0, this->mem_size);
  }


  if(!runtime) {
     if (this->sc.frame_output_cb) {
        this->sc.delivered_height   = frame->sc.delivered_height;
        this->sc.delivered_width    = frame->sc.delivered_width;     
        vo_scale_redraw_needed( &this->sc );
     }
  }
  else {
  	/*----- george add ---- */
      if(!has_update) {	
          frame->vo_frame.free(&frame->vo_frame);
          return;	
      }
      else {
          vo_scale_redraw_needed( &this->sc );
      }
      /*----- george add ----(end) */
  }

  if(this->use_zero_copy)
  {
    if(this->old_frame)
      this->old_frame->vo_frame.free
	(&this->old_frame->vo_frame);
    this->old_frame = this->cur_frame;
    this->cur_frame = frame;
		
    this->fb_var.yoffset = frame->yoffset;
    if(ioctl(this->fd, FBIOPAN_DISPLAY, &this->fb_var) == -1)
      perror("video_out_fb: ioctl FBIOPAN_DISPLAY failed");
  }
  else
  {
  	/* rendering algorithm rewritten by rwlin@avamax.com */
  	int		start_x,start_y,height,linelength;
  	int 	tmp;

//	printf("before this=(%d,%d,%d,%d,%d,%d), frame=(%d,%d,%d,%d,%d)\n",
//		this->sc.gui_x,this->sc.gui_y,this->sc.gui_width,this->sc.gui_height,this->fb_bytes_per_line,this->bytes_per_pixel,
//		frame->sc.gui_x,frame->sc.gui_y,frame->sc.gui_width,frame->sc.gui_height,frame->bytes_per_line);
        
        /* ---- george add code 2004/1/6 ---- begin --- */
        // when change size then to clear screen
        if((frame->sc.gui_width > this->sc.gui_width) && (frame->sc.user_ratio == 2)) {
            fb_update_frame_format(this_gen,frame_gen,this->sc.gui_width,this->sc.gui_height,this->sc.user_ratio,format , 1);
	//    this->cur_frame=frame;
        }
        if(frame->sc.gui_width > this->sc.gui_width) {
	    if(frame->sc.gui_width > 800) {
	    	printf("A\n");
	    	fb_update_frame_format(this_gen,frame_gen,this->sc.gui_width,this->sc.gui_height,this->sc.user_ratio,format , 1);
	        //this->cur_frame=frame;
	    	height=frame->sc.output_height;
  	        if(height>this->sc.gui_height)
  		    height=this->sc.gui_height;
  		
	    	memset(frame->video_mem + this->sc.gui_y * this->fb_bytes_per_line + this->sc.gui_x * this->bytes_per_pixel,0,frame->sc.gui_width*((frame->sc.gui_height-this->sc.gui_height)/2)*2);
	        memset((frame->video_mem + (this->sc.gui_y + this->sc.gui_height) * this->fb_bytes_per_line),0,frame->sc.gui_width*((frame->sc.gui_height-this->sc.gui_height)/2)*2);
	        for(y = 0; y < height; y++)
                {
                    if(y <= this->sc.gui_height) {
                        memset(frame->video_mem + ((this->sc.gui_y + y) * this->fb_bytes_per_line)-((frame->sc.gui_width - this->sc.gui_width)),0,(frame->sc.gui_width - this->sc.gui_width) * this->bytes_per_pixel);
                    }
                    else {
                        break;	
                    } 
                }
	    }
	    else {
	    	fb_update_frame_format(this_gen,frame_gen,this->sc.gui_width,this->sc.gui_height,this->sc.user_ratio,format , 1);
	        this->cur_frame=frame;
	        memset(frame->video_mem,0,800*((600-this->sc.gui_height)/2)*2);
	        memset((frame->video_mem + (this->sc.gui_y + this->sc.gui_height) * this->fb_bytes_per_line),0,800*((600-this->sc.gui_height)/2)*2);
	        for(y = 0; y < height; y++)
                {
                    if(y <= this->sc.gui_height) {
                        memset(frame->video_mem + ((this->sc.gui_y + y) * this->fb_bytes_per_line)-((800 - this->sc.gui_width)),0,(800 - this->sc.gui_width) * this->bytes_per_pixel);
                    }
                    else {
                        break;
                    } 
                }
            }         
            frame->vo_frame.free(&frame->vo_frame);
            return;  	   
        }
        else {
            if((this->sc.gui_width == 1066) && (frame->sc.output_width == 800)) {	        
	        memset(frame->video_mem,0,800*((600-frame->sc.gui_height)/2)*2);
	        memset((frame->video_mem + (frame->sc.gui_y + frame->sc.gui_height) * this->fb_bytes_per_line),0,800*((600-frame->sc.gui_height)/2)*2);
	        for(y = 0; y < height; y++)
                {
                    if(y <= frame->sc.gui_height) {
                        memset(frame->video_mem + ((frame->sc.gui_y + y) * this->fb_bytes_per_line)-((800 - frame->sc.gui_width)),0,(800 - frame->sc.gui_width) * this->bytes_per_pixel);
                    }
                    else {
                        break;	
                    } 
                }
	        frame->vo_frame.free(&frame->vo_frame);
                return;  	
            }	
        }         
   
        /* ---- george add code 2004/1/6 ---- end --- */
        this->cur_frame=frame;

	/* adjust y/height */
	start_y=frame->sc.output_yoffset;
	if(start_y<this->sc.gui_y)
		start_y=this->sc.gui_y;
  	height=frame->sc.output_height;
  	if(height>this->sc.gui_height)
  		height=this->sc.gui_height;
  	tmp=(start_y+height)-(this->sc.gui_y+this->sc.gui_height);
  	if(tmp>0)
		start_y-=tmp;

	/* adjust x/width */
	start_x=frame->sc.output_xoffset;
	if(start_x<this->sc.gui_x)
		start_x=this->sc.gui_x;
	linelength=frame->bytes_per_line;
	tmp=this->sc.gui_width*this->bytes_per_pixel;
	if(linelength>tmp)
		linelength=tmp;
	tmp=(start_x+linelength/this->bytes_per_pixel)-(this->sc.gui_x+this->sc.gui_width);
	if(tmp>0)
		start_x-=tmp;

    dst = frame->video_mem + start_y * this->fb_bytes_per_line + start_x * this->bytes_per_pixel;
    src = frame->data;
    /*----- george add  2003/11/28 ---- */
    if(!has_update) {
        
        if(Program_File != 1) {
            avamax_skip = 0;
        }
    	if (avamax_skip > 0) {
    		avamax_skip--;
    	}	
    	else {	
    	        wait_for_retrace();
                if(this->frame_mode == 1) {
    	            //  cut img  	          
    	            if(frame->sc.output_width == 854) {
    	                def = (854 - 640);
    	            }
    	            else if(frame->sc.output_width == 1066) {
    	                def = (1066 - 800);
    	            }
		    for(y = 0; y < height; y++)
                    {
                        xine_fast_memcpy(dst, src+((def/2) * this->bytes_per_pixel), linelength - (def * this->bytes_per_pixel));
   /*----- george add  ----(end) */                   
                        src += linelength;
                        dst += this->fb_bytes_per_line;
                    }               	   	
                }
                else {
                	/*----- george add  2003/11/28 ---- */
                 //  if(!((frame->sc.output_width == 854) && (this->sc.user_ratio != frame->sc.user_ratio))) {                   
                        // ¦b¥Ñ4:3¤Á´«¦¨16:9®É·|­è¦n¦³±i4:3ªºframe³Qupdate¦¨¤@¥b,¦ýdata,output«oÁÙ¬O4:3ªº¤j¤p,©Ò¥H­n¹L¼{±¼¤£¯àÅý¥¦¦b»\±¼¿Ã¹õ¤Wªºframe
                        /*----- george add  2003/11/28 ----(end) */
                        for(y = 0; y < height; y++)
                        {
                            xine_fast_memcpy(dst, src, linelength);
                            src += linelength;
                            dst += this->fb_bytes_per_line;
                        } 
                }
        }
    } 
  
    frame->vo_frame.free(&frame->vo_frame);
    
  }
}

static int fb_get_property(vo_driver_t *this_gen, int property)
{
  fb_driver_t *this = (fb_driver_t *)this_gen;

  switch(property)
  {
    case VO_PROP_ASPECT_RATIO:
      return this->sc.user_ratio;

    case VO_PROP_BRIGHTNESS:
    return this->yuv2rgb_gamma;

    default:
      printf("video_out_fb: tried to get unsupported "
	     "property %d\n", property);
  }

  return 0;
}

static int fb_set_property(vo_driver_t *this_gen, int property, int value)
{
  fb_driver_t *this = (fb_driver_t *)this_gen;

  switch(property)
  {
    case VO_PROP_ASPECT_RATIO:
      if(value>=XINE_VO_ASPECT_NUM_RATIOS)
      value = XINE_VO_ASPECT_AUTO;
    
    this->sc.user_ratio = value;
    
      if(value == XINE_VO_ASPECT_4_3_CUT)
         this->frame_mode = 1;
      else
         this->frame_mode = 0;  
    printf("video_out_fb: aspect ratio changed to %s\n",
           vo_scale_aspect_ratio_name(value));
      break;

    case VO_PROP_BRIGHTNESS:
    this->yuv2rgb_gamma = value;
      this->yuv2rgb_factory->
	set_csc_levels(this->yuv2rgb_factory, value, 128, 128);
    printf("video_out_fb: gamma changed to %d\n",value);
      break;

    default:
      printf("video_out_fb: tried to set unsupported "
	     "property %d\n", property);
  }

  return value;
}

static void fb_get_property_min_max(vo_driver_t *this_gen,
				    int property, int *min, int *max)
{
  /* fb_driver_t *this = (fb_driver_t *) this_gen;  */
	
  if(property == VO_PROP_BRIGHTNESS)
  {
    *min = -100;
    *max = +100;
  }
  else
  {
    *min = 0;
    *max = 0;
  }
}

static int fb_gui_data_exchange(vo_driver_t *this_gen,
				int data_type, void *data)
{
  return 0;
}

static void fb_dispose(vo_driver_t *this_gen)
{
  fb_driver_t *this = (fb_driver_t *)this_gen;
  
  munmap(0, this->mem_size);   
  close(this->fd);
}

static int get_fb_var_screeninfo(int fd, struct fb_var_screeninfo *var)
{
  int i;
  
  if(ioctl(fd, FBIOGET_VSCREENINFO, var))
  {
    perror("video_out_fb: ioctl FBIOGET_VSCREENINFO");
    return 0;
  }

  var->xres_virtual = var->xres;
  var->xoffset      = 0;
  var->yoffset      = 0;
  var->nonstd       = 0;
  var->vmode       &= ~FB_VMODE_YWRAP;

  /* Maximize virtual yres to fit as many buffers as possible. */
  for(i = MAXIMUM_NUM_BUFFERS; i > 0; i--)
  {
    var->yres_virtual = i * var->yres;
    if(ioctl(fd, FBIOPUT_VSCREENINFO, var) == -1)
      continue;
    break;
  }
    
  /* Get proper value for maximized var->yres_virtual. */
  if(ioctl(fd, FBIOGET_VSCREENINFO, var) == -1)
  {
    perror("video_out_fb: ioctl FBIOGET_VSCREENINFO");
    return 0;
  }

  return 1;
}

static int get_fb_fix_screeninfo(int fd, struct fb_fix_screeninfo *fix)
{
  if(ioctl(fd, FBIOGET_FSCREENINFO, fix))
  {
    perror("video_out_fb: ioctl FBIOGET_FSCREENINFO");
    return 0;
  }

  if((fix->visual != FB_VISUAL_TRUECOLOR &&
      fix->visual != FB_VISUAL_DIRECTCOLOR) ||
     fix->type != FB_TYPE_PACKED_PIXELS)
  {
    fprintf(stderr, "video_out_fb: only packed truecolor/directcolor is supported (%d).\n"
	    "     Check 'fbset -i' or try 'fbset -depth 16'.\n",
	    fix->visual);
    return 0;
  }

  return 1;
}

static void register_callbacks(fb_driver_t *this)
{
  this->vo_driver.get_capabilities     = fb_get_capabilities;
  this->vo_driver.alloc_frame          = fb_alloc_frame;
  this->vo_driver.update_frame_format  = fb_update_frame_format;
  this->vo_driver.overlay_begin        = 0; /* not used */
  this->vo_driver.overlay_blend        = fb_overlay_blend;
  this->vo_driver.overlay_end          = 0; /* not used */
  this->vo_driver.display_frame        = fb_display_frame;
  this->vo_driver.get_property         = fb_get_property;
  this->vo_driver.set_property         = fb_set_property;
  this->vo_driver.get_property_min_max = fb_get_property_min_max;
  this->vo_driver.gui_data_exchange    = fb_gui_data_exchange;
  this->vo_driver.dispose              = fb_dispose;
  this->vo_driver.redraw_needed        = fb_redraw_needed;
}

static int open_fb_device(config_values_t *config)
{
  static char devkey[] = "video.fb_device";   /* Why static? */
  char *device_name;
  int fd;

  device_name = config->register_string(config, devkey, "",
					_("framebuffer device"),
					NULL, 10, NULL, NULL);
  if(strlen(device_name) > 3)
  {
    fd = open(device_name, O_RDWR);
  }
  else
  {
    device_name = "/dev/fb1";
    fd = open(device_name, O_RDWR);
    
    if(fd < 0)
    {  
      device_name = "/dev/fb0";
      fd = open(device_name, O_RDWR);
    }
  }
  
  if(fd < 0)
  {
    fprintf(stderr, "video_out_fb: Unable to open device \"%s\", aborting: %s\n",
	    device_name, strerror(errno));
    return -1;
  }
  
  config->update_string(config, devkey, device_name);

  return fd;
}
  
static int mode_visual(fb_driver_t *this, config_values_t *config,
		       struct fb_var_screeninfo *var,
		       struct fb_fix_screeninfo *fix)
{
  switch(fix->visual)
  {
    case FB_VISUAL_TRUECOLOR:
    case FB_VISUAL_DIRECTCOLOR:
      switch(this->depth)
      {
	case 24:
	  if(this->bpp == 32)
	  {
	    if(!var->blue.offset)
	      return MODE_32_RGB;
	    return MODE_32_BGR;
	  }
	  if(!var->blue.offset)
	    return MODE_24_RGB;
	  return MODE_24_BGR;

	case 16:
	  if(!var->blue.offset)
	    return MODE_16_RGB;
	  return MODE_16_BGR;
  
	case 15:
	  if(!var->blue.offset)
	    return MODE_15_RGB;
	  return MODE_15_BGR;

	case 8:
	  if(!var->blue.offset)
	    return MODE_8_RGB; 
	  return MODE_8_BGR; 

      }
  }
  
  fprintf(stderr, "video_out_fb: Your video mode was not recognized, sorry.\n");
  return 0;
}
    
static int setup_yuv2rgb(fb_driver_t *this, config_values_t *config,
			 struct fb_var_screeninfo *var,
			 struct fb_fix_screeninfo *fix)
{
  this->yuv2rgb_mode = mode_visual(this, config, var, fix);
  if(!this->yuv2rgb_mode)
    return 0;

  this->yuv2rgb_swap  = 0;
  this->yuv2rgb_gamma =
    config->register_range(config, "video.fb_gamma", 0,
			   -100, 100, 
			   "gamma correction for fb driver",
			   NULL, 0, NULL, NULL);

  this->yuv2rgb_factory = yuv2rgb_factory_init(this->yuv2rgb_mode,
					       this->yuv2rgb_swap, 
					       this->yuv2rgb_cmap);
  this->yuv2rgb_factory->set_csc_levels(this->yuv2rgb_factory,
					this->yuv2rgb_gamma, 128, 128);
  
  return 1;
}

static void setup_buffers(fb_driver_t *this,
			  struct fb_var_screeninfo *var)
{
  /*
   * depth in X11 terminology land is the number of bits used to
   * actually represent the colour.
   *
   * bpp in X11 land means how many bits in the frame buffer per
   * pixel.
   *
   * ex. 15 bit color is 15 bit depth and 16 bpp. Also 24 bit
   *     color is 24 bit depth, but can be 24 bpp or 32 bpp.
   *
   * fb assumptions: bpp % 8   = 0 (e.g. 8, 16, 24, 32 bpp)
   *                 bpp      <= 32
   *                 msb_right = 0
   */
   
  this->bytes_per_pixel = (this->fb_var.bits_per_pixel + 7)/8;
  this->bpp = this->bytes_per_pixel * 8;
  this->depth = this->fb_var.red.length +
		this->fb_var.green.length +
		this->fb_var.blue.length;

  this->total_num_native_buffers = var->yres_virtual / var->yres;
  this->used_num_buffers = 0;
	
  this->cur_frame = this->old_frame = 0;
	
  printf("video_out_fb: %d video RAM buffers are available.\n",
	 this->total_num_native_buffers);

  if(this->total_num_native_buffers < RECOMMENDED_NUM_BUFFERS)
  {
    this->use_zero_copy = 0;
    printf("WARNING: video_out_fb: Zero copy buffers are DISABLED because only %d buffers\n"
	   "     are available which is less than the recommended %d buffers. Lowering\n"
	   "     the frame buffer resolution might help.\n",
	   this->total_num_native_buffers,
	   RECOMMENDED_NUM_BUFFERS);
  }
  else
  {
    /* test if FBIOPAN_DISPLAY works */
    this->fb_var.yoffset = this->fb_var.yres;
    if(ioctl(this->fd, FBIOPAN_DISPLAY, &this->fb_var) == -1) {
      printf("WARNING: video_out_fb: Zero copy buffers are DISABLED because kernel driver\n"
	     "     do not support screen panning (used for frame flips).\n");
    } else {
      this->fb_var.yoffset = 0;
      ioctl(this->fd, FBIOPAN_DISPLAY, &this->fb_var);

      this->use_zero_copy = 1;
      printf("video_out_fb: Using zero copy buffers.\n");
    }
  }
}

static vo_driver_t *fb_open_plugin(video_driver_class_t *class_gen,
				   const void *visual_gen)
{
  config_values_t *config;
  fb_driver_t *this;
  fb_class_t *class;
  fb_visual_t *visual = NULL;
  
  if (visual_gen) {
    visual = (fb_visual_t *) visual_gen;
  }
          
  class = (fb_class_t *)class_gen;
  config = class->config;

  /* allocate plugin struct */
  this = malloc(sizeof(fb_driver_t));
  if(!this)
  {
    fprintf(stderr, "video_out_fb: malloc failed\n");
    return 0;
  }
  memset(this, 0, sizeof(fb_driver_t));
  
  register_callbacks(this);

  this->fd = open_fb_device(config);
  if(this->fd == -1)
    goto error;
  if(!get_fb_var_screeninfo(this->fd, &this->fb_var))
    goto error;
  if(!get_fb_fix_screeninfo(this->fd, &this->fb_fix))
    goto error;
   
  if(this->fb_fix.line_length)
    this->fb_bytes_per_line = this->fb_fix.line_length;
  else
    this->fb_bytes_per_line =
      (this->fb_var.xres_virtual *
       this->fb_var.bits_per_pixel)/8;
    
  vo_scale_init(&this->sc, 0, 0, config);
  this->sc.gui_width  = this->fb_var.xres;
  this->sc.gui_height = this->fb_var.yres;
  this->sc.user_ratio = XINE_VO_ASPECT_SQUARE;
 // this->sc.user_ratio = XINE_VO_ASPECT_AUTO;

  if (visual) {
    this->sc.frame_output_cb = visual->frame_output_cb;
    this->sc.user_data       = visual->user_data;
  }

  this->sc.scaling_disabled =
    config->register_bool(config, "video.disable_scaling", 0,
			  _("disable all video scaling (faster!)"),
			  NULL, 10, NULL, NULL);
  
  setup_buffers(this, &this->fb_var);

  if(this->depth > 16)
    printf("WARNING: video_out_fb: current display depth is %d. For better performance\n"
	   "     a depth of 16 bpp is recommended!\n\n",
	   this->depth);

  printf("video_out_fb: video mode depth is %d (%d bpp),\n"
	 "     red: %d/%d, green: %d/%d, blue: %d/%d\n",
	 this->depth, this->bpp,
	 this->fb_var.red.length, this->fb_var.red.offset,
	 this->fb_var.green.length, this->fb_var.green.offset,
	 this->fb_var.blue.length, this->fb_var.blue.offset);
                                  
  if(!setup_yuv2rgb(this, config, &this->fb_var, &this->fb_fix))
    goto error;
	
  /* mmap whole video memory */
  this->mem_size = this->fb_fix.smem_len;
  this->video_mem_base = mmap(0, this->mem_size, PROT_READ | PROT_WRITE,
			      MAP_SHARED, this->fd, 0);
  return &this->vo_driver;
error:
  free(this);
  return 0;
}

static char* fb_get_identifier(video_driver_class_t *this_gen)
{
  return "fb";
}

static char* fb_get_description(video_driver_class_t *this_gen)
{
  return _("Xine video output plugin using the Linux frame buffer device");
}

static void fb_dispose_class(video_driver_class_t *this_gen)
{
  fb_class_t *this = (fb_class_t *)this_gen;
  free(this);
}

static void *fb_init_class(xine_t *xine, void *visual_gen)
{
  fb_class_t *this = (fb_class_t *)malloc(sizeof(fb_class_t));

  this->driver_class.open_plugin     = fb_open_plugin;
  this->driver_class.get_identifier  = fb_get_identifier;
  this->driver_class.get_description = fb_get_description;
  this->driver_class.dispose         = fb_dispose_class;

  
  this->config          = xine->config;

  return this;
}

static vo_info_t vo_info_fb =
{
  1,                    /* priority    */
#ifdef USE_X11_VISUAL
  XINE_VISUAL_TYPE_X11  /* visual type */
#else
  XINE_VISUAL_TYPE_FB   /* visual type */
#endif
};

/* exported plugin catalog entry */
plugin_info_t xine_plugin_info[] =
{
  /* type, API, "name", version, special_info, init_function */  
  {
    PLUGIN_VIDEO_OUT,
    16,
    "fb",
    XINE_VERSION_CODE,
    &vo_info_fb, fb_init_class
  },
  {
    PLUGIN_NONE,
    0,
    "",
    0,
    NULL,
    NULL
  }
};