/*
	2004.02.01
		first released source code for IOMP
*/
/*
 * Copyright (C) 2000-2003 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 *
<<<<<<< video_out.c
 * $Id: video_out.c,v 1.9 2003/12/25 10:51:40 rwlin Exp $
=======
 * $Id: video_out.c,v 1.11 2004/02/11 09:31:10 rwlin Exp $
>>>>>>> 1.11
 *
 * frame allocation / queuing / scheduling / output functions
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <signal.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <zlib.h>
#include <pthread.h>
#include <assert.h>

#define XINE_ENABLE_EXPERIMENTAL_FEATURES

#include "xine_internal.h"
#include "video_out.h"
#include "metronom.h"
#include "xineutils.h"

/*
#define LOG
*/

extern int XINE_SEG_ERROR;
extern char *XINE_SEG_ERROR_FILE;
//#define LOG_SEG_ERROR sprintf(XINE_SEG_ERROR_FILE,"%s (%d)",__FILE__,__LINE__);
//#define SET_SEG_ERROR sprintf(XINE_SEG_ERROR_FILE,"%s (%d)",__FILE__,__LINE__);
#define LOG_SEG_ERROR 
#define SET_SEG_ERROR 

#define NUM_FRAME_BUFFERS     15

typedef struct {
  
  xine_video_port_t         vo; /* public part */

  vo_driver_t              *driver;
  pthread_mutex_t           driver_lock;
  xine_t                   *xine;
  metronom_clock_t         *clock;
  xine_list_t              *streams;
  pthread_mutex_t           streams_lock;
  
  img_buf_fifo_t           *free_img_buf_queue;
  img_buf_fifo_t           *display_img_buf_queue;

  vo_frame_t               *last_frame;
  vo_frame_t               *img_backup;
  int                       redraw_needed;
  int                       discard_frames;
  
  int                       video_loop_running;
  int                       video_opened;
  pthread_t                 video_thread;

  int                       num_frames_delivered;
  int                       num_frames_skipped;
  int                       num_frames_discarded;

  /* threshold for sending XINE_EVENT_DROPPED_FRAMES */
  int                       warn_skipped_threshold;
  int                       warn_discarded_threshold;
  int                       warn_threshold_exceeded;
  int                       warn_threshold_event_sent;

  /* pts value when decoder delivered last video frame */
  int64_t                   last_delivery_pts; 


  video_overlay_manager_t  *overlay_source;
  int                       overlay_enabled;

  /* do we true real-time output or is this a grab only instance ? */
  int                       grab_only;

  extra_info_t             *extra_info_base; /* used to free mem chunk */

  int                       current_width, current_height;
  int64_t                   current_duration;
  int                       frame_drop_limit;
  int                       frame_drop_cpt;
} vos_t;

/*
 * frame queue (fifo) util functions
 */

struct img_buf_fifo_s {
  vo_frame_t        *first;
  vo_frame_t        *last;
  int                num_buffers;

  int                locked_for_read;
  pthread_mutex_t    mutex;
  pthread_cond_t     not_empty;
} ;

static img_buf_fifo_t *vo_new_img_buf_queue () {

  img_buf_fifo_t *queue;

  queue = (img_buf_fifo_t *) xine_xmalloc (sizeof (img_buf_fifo_t));
  if( queue ) {
    queue->first       = NULL;
    queue->last        = NULL;
    queue->num_buffers = 0;
    queue->locked_for_read = 0;
    pthread_mutex_init (&queue->mutex, NULL);
    pthread_cond_init  (&queue->not_empty, NULL);
  }
  return queue;
}

static void vo_append_to_img_buf_queue_int (img_buf_fifo_t *queue,
					vo_frame_t *img) {

  /* img already enqueue? (serious leak) */
  assert (img->next==NULL);

  img->next = NULL;

  if (!queue->first) {
    queue->first = img;
    queue->last  = img;
    queue->num_buffers = 0;
  }
  else if (queue->last) {
    queue->last->next = img;
    queue->last  = img;
  }

  queue->num_buffers++;

  pthread_cond_signal (&queue->not_empty);
}

static void vo_append_to_img_buf_queue (img_buf_fifo_t *queue,
					vo_frame_t *img) {
  pthread_mutex_lock (&queue->mutex);
  vo_append_to_img_buf_queue_int (queue, img);
  pthread_mutex_unlock (&queue->mutex);
}

static vo_frame_t *vo_remove_from_img_buf_queue_int (img_buf_fifo_t *queue) {
  vo_frame_t *img;

  while (!queue->first || queue->locked_for_read) {
    pthread_cond_wait (&queue->not_empty, &queue->mutex);
  }

  img = queue->first;

  if (img) {
    queue->first = img->next;
    img->next = NULL;
    if (!queue->first) {
      queue->last = NULL;
      queue->num_buffers = 0;
    }
    else {
      queue->num_buffers--;
    }
  }
    
  return img;
}

static vo_frame_t *vo_remove_from_img_buf_queue (img_buf_fifo_t *queue) {
  vo_frame_t *img;

  pthread_mutex_lock (&queue->mutex);
  img = vo_remove_from_img_buf_queue_int(queue);
  pthread_mutex_unlock (&queue->mutex);

  return img;
}

/*
 * functions to maintain lock_counter
 */
static void vo_frame_inc_lock (vo_frame_t *img) {
  
  pthread_mutex_lock (&img->mutex);

  img->lock_counter++;

  pthread_mutex_unlock (&img->mutex);
}

static void vo_frame_dec_lock (vo_frame_t *img) {
  
  pthread_mutex_lock (&img->mutex);

  img->lock_counter--;
  if (!img->lock_counter) {    
    vos_t *this = (vos_t *) img->port;
    vo_append_to_img_buf_queue (this->free_img_buf_queue, img);
  }

  pthread_mutex_unlock (&img->mutex);
}

/* call vo_driver->copy method for the entire frame */
static void vo_frame_driver_copy(vo_frame_t *img)
{ 
  if (img->format == XINE_IMGFMT_YV12) {
    if (img->copy) {
      int height = img->height;
      uint8_t* src[3];
  
      src[0] = img->base[0];
      src[1] = img->base[1];
      src[2] = img->base[2];
      while ((height -= 16) > -16) {
        img->copy(img, src);
        src[0] += 16 * img->pitches[0];
        src[1] +=  8 * img->pitches[1];
        src[2] +=  8 * img->pitches[2];
      }
    }
  } else {
    if (img->copy) {
      int height = img->height;
      uint8_t* src[3];
      
      src[0] = img->base[0];
      
      while ((height -= 16) > -16) {
        img->copy(img, src);
        src[0] += 16 * img->pitches[0];
      }
    }
  }
}

/*
 * 
 * functions called by video decoder:
 *
 * get_frame => alloc frame for rendering
 *
 * frame_draw=> queue finished frame for display
 *
 * frame_free=> frame no longer used as reference frame by decoder
 *
 */

static vo_frame_t *vo_get_frame (xine_video_port_t *this_gen,
				 uint32_t width, uint32_t height,
				 double ratio, int format,
				 int flags) {

  vo_frame_t *img;
  vos_t      *this = (vos_t *) this_gen;

  img = vo_remove_from_img_buf_queue (this->free_img_buf_queue);

  /* some decoders report strange ratios */
  if (ratio <= 0.0)
    ratio = (double)width / (double)height;
  
  pthread_mutex_lock (&img->mutex);
  img->lock_counter   = 1;
  img->width          = width;
  img->height         = height;
  img->ratio          = ratio;
  img->format         = format;
  img->flags          = flags;
  img->copy_called    = 0;
  img->bad_frame      = 0;
  img->progressive_frame  = 0;
  img->repeat_first_field = 0;
  img->top_field_first    = 1;
  extra_info_reset ( img->extra_info );

  /* let driver ensure this image has the right format */

  this->driver->update_frame_format (this->driver, img, width, height, 
				     ratio, format, flags);

  pthread_mutex_unlock (&img->mutex);
  
  return img;
}

static int vo_frame_draw (vo_frame_t *img, xine_stream_t *stream) {

  vos_t         *this = (vos_t *) img->port;
  int64_t        diff;
  int64_t        cur_vpts;
  int64_t        pic_vpts ;
  int            frames_to_skip;

  img->stream = stream;
  extra_info_merge( img->extra_info, stream->video_decoder_extra_info );
  img->still_count=stream->still_count;

  /* by rwlin@avamax.com, we received & transferred the still count */
  if(img->still_count) {
	printf("still count %d copied to img %x\n",img->still_count,img);
	LOG_SEG_ERROR
  	stream->still_count=0;
  }

  this->current_width = img->width;
  this->current_height = img->height;
  
  stream->metronom->got_video_frame (stream->metronom, img);
  this->current_duration = img->duration;

  if (!this->grab_only) {

    pic_vpts = img->vpts;
    img->extra_info->vpts = img->vpts;

    cur_vpts = this->clock->get_current_time(this->clock);
    this->last_delivery_pts = cur_vpts;

#ifdef LOG
    printf ("video_out: got image at master vpts %lld. vpts for picture is %lld (pts was %lld)\n",
	    cur_vpts, pic_vpts, img->pts);
#endif

    this->num_frames_delivered++;

    diff = pic_vpts - cur_vpts;
    /* avoid division by zero */
    if( img->duration <= 0 )
      img->duration = 3000;
	LOG_SEG_ERROR
    
    /* Frame dropping slow start:
     *   The engine starts to drop frames if there is less than frame_drop_limit
     *   frames in advance. There might be a problem just after a seek because
     *   there is no frame in advance yet.
     *   The following code increases progressively the frame_drop_limit (-2 -> 3)
     *   after a seek to give a chance to the engine to display the first frames
     *   smootly before starting to drop frames if the decoder is really too
     *   slow.
     */
    if (stream->first_frame_flag == 2)
      this->frame_drop_cpt = 10;

    if (this->frame_drop_cpt) {
      this->frame_drop_limit = 3 - (this->frame_drop_cpt / 2);
      this->frame_drop_cpt--;
    }
    frames_to_skip = ((-1 * diff) / img->duration + this->frame_drop_limit) * 2;

    if (frames_to_skip<0)
      frames_to_skip = 0;
  } else {
    frames_to_skip = 0;
    if (this->discard_frames) {
#ifdef LOG
      printf ("video_out: i'm in flush mode, not appending this frame to queue\n");
#endif
      return 0;
    }
  }


#ifdef LOG
  printf ("video_out: delivery diff : %lld, current vpts is %lld, %d frames to skip\n",
	  diff, cur_vpts, frames_to_skip);
#endif
  if (img->lock_counter > 1) {
    printf ("video_out: ALERT! frame is already locked for displaying\n");
    return frames_to_skip;
  }

  if (!img->bad_frame) {

	LOG_SEG_ERROR
    /* do not call copy() for frames that will be dropped */
    if( !frames_to_skip && img->copy && !img->copy_called )
      vo_frame_driver_copy(img);
    
    /*
     * put frame into FIFO-Buffer
     */

#ifdef LOG
    printf ("video_out: frame is ok => appending to display buffer\n");
#endif

    /*
     * check for first frame after seek and mark it
     */
    img->is_first = 0;
    pthread_mutex_lock(&this->streams_lock);
	for (stream = xine_list_first_content(this->streams); stream;
         stream = xine_list_next_content(this->streams)) {
      pthread_mutex_lock (&stream->first_frame_lock);
      if (stream->first_frame_flag == 2) {
        stream->first_frame_flag = (this->grab_only)?0:1;
        img->is_first = 1;
#ifdef LOG
        printf ("video_out: get_next_video_frame first_frame_reached\n");
#endif
      }
      pthread_mutex_unlock (&stream->first_frame_lock);
    }
    pthread_mutex_unlock(&this->streams_lock);

    vo_frame_inc_lock( img );
    vo_append_to_img_buf_queue (this->display_img_buf_queue, img);

  } else {
#ifdef LOG
    printf ("video_out: bad_frame\n");
#endif
    pthread_mutex_lock( &stream->current_extra_info_lock );
	extra_info_merge( stream->current_extra_info, img->extra_info );
    pthread_mutex_unlock( &stream->current_extra_info_lock );

    this->num_frames_skipped++;
  }

  /*
   * performance measurement
   */
  if ((this->num_frames_delivered % 200) == 0 && this->num_frames_delivered) {
    int send_event;

    if( (100 * this->num_frames_skipped / this->num_frames_delivered) >
         this->warn_skipped_threshold ||
        (100 * this->num_frames_discarded / this->num_frames_delivered) >
         this->warn_discarded_threshold )
      this->warn_threshold_exceeded++;
    else
      this->warn_threshold_exceeded = 0;

	LOG_SEG_ERROR
    /* make sure threshold has being consistently exceeded - 5 times in a row
     * (that is, this is not just a small burst of dropped frames).
     */
    send_event = (this->warn_threshold_exceeded == 5 && 
                  !this->warn_threshold_event_sent);
    this->warn_threshold_event_sent += send_event;

    pthread_mutex_lock(&this->streams_lock);
    for (stream = xine_list_first_content(this->streams); stream;
         stream = xine_list_next_content(this->streams)) {
      stream->stream_info[XINE_STREAM_INFO_SKIPPED_FRAMES] =
        1000 * this->num_frames_skipped / this->num_frames_delivered;
      stream->stream_info[XINE_STREAM_INFO_DISCARDED_FRAMES] =
        1000 * this->num_frames_discarded / this->num_frames_delivered;

      /* we send XINE_EVENT_DROPPED_FRAMES to frontend to warn that
       * number of skipped or discarded frames is too high.
       */
      if( send_event ) {
         xine_event_t          event;
         xine_dropped_frames_t data;

         event.type        = XINE_EVENT_DROPPED_FRAMES;
         event.stream      = stream;
         event.data        = &data;
         event.data_length = sizeof(data);
         data.skipped_frames = stream->stream_info[XINE_STREAM_INFO_SKIPPED_FRAMES];
         data.skipped_threshold = this->warn_skipped_threshold * 10;
         data.discarded_frames = stream->stream_info[XINE_STREAM_INFO_DISCARDED_FRAMES];
         data.discarded_threshold = this->warn_discarded_threshold * 10;
         xine_event_send(stream, &event);
      }
    }
    pthread_mutex_unlock(&this->streams_lock);

    if( this->num_frames_skipped || this->num_frames_discarded ) {
      xine_log(this->xine, XINE_LOG_MSG,
	       _("%d frames delivered, %d frames skipped, %d frames discarded\n"), 
	       this->num_frames_delivered, 
	       this->num_frames_skipped, this->num_frames_discarded);
    }

    this->num_frames_delivered = 0;
    this->num_frames_discarded = 0;
    this->num_frames_skipped   = 0;
  }
  
	LOG_SEG_ERROR
	XINE_SEG_ERROR = frames_to_skip;
  return frames_to_skip;
}

/*
 *
 * video out loop related functions
 *
 */

/* duplicate_frame(): this function is used to keep playing frames 
 * while video is still or player paused. 
 * 
 * frame allocation inside vo loop is dangerous:
 * we must never wait for a free frame -> deadlock condition.
 * to avoid deadlocks we don't use vo_remove_from_img_buf_queue()
 * and reimplement a slightly modified version here.
 * free_img_buf_queue->mutex must be grabbed prior entering it.
 * (must assure that free frames won't be exhausted by decoder thread).
 */
static vo_frame_t * duplicate_frame( vos_t *this, vo_frame_t *img ) {

  vo_frame_t *dupl;
  int         image_size;

  if( !this->free_img_buf_queue->first)
    return NULL;

  dupl = this->free_img_buf_queue->first;
  this->free_img_buf_queue->first = dupl->next;
  dupl->next = NULL;
  if (!this->free_img_buf_queue->first) {
    this->free_img_buf_queue->last = NULL;
    this->free_img_buf_queue->num_buffers = 0;
  }
  else {
    this->free_img_buf_queue->num_buffers--;
  }
      
  pthread_mutex_lock (&dupl->mutex);
  dupl->lock_counter   = 1;
  dupl->width          = img->width;
  dupl->height         = img->height;
  dupl->ratio          = img->ratio;
  dupl->format         = img->format;
  dupl->flags          = img->flags | VO_BOTH_FIELDS;
  
  this->driver->update_frame_format (this->driver, dupl, dupl->width, dupl->height, 
				     dupl->ratio, dupl->format, dupl->flags);

  pthread_mutex_unlock (&dupl->mutex);
  
  image_size = img->pitches[0] * img->height;

  if (img->format == XINE_IMGFMT_YV12) {
    if (img->base[0])
      xine_fast_memcpy(dupl->base[0], img->base[0], image_size);
    if (img->base[1])
      xine_fast_memcpy(dupl->base[1], img->base[1], img->pitches[1] * ((img->height+1)/2));
    if (img->base[2])
      xine_fast_memcpy(dupl->base[2], img->base[2], img->pitches[2] * ((img->height+1)/2));
  } else {
    if (img->base[0])
      xine_fast_memcpy(dupl->base[0], img->base[0], image_size);
  }  
  
  dupl->bad_frame = 0;
  dupl->pts       = 0;
  dupl->vpts      = 0;
  dupl->copy_called    = 0;

  dupl->duration  = img->duration;

  dupl->stream    = img->stream;
  memcpy( dupl->extra_info, img->extra_info, sizeof(extra_info_t) );
  
  /* delay frame copying for now, we might not even need it (eg. frame will be discarded) */
  /* vo_frame_driver_copy(dupl); */
  
  return dupl;
}


static void expire_frames (vos_t *this, int64_t cur_vpts) {

  int64_t       pts;
  int64_t       diff;
  vo_frame_t   *img;

  pthread_mutex_lock(&this->display_img_buf_queue->mutex);
  
  img = this->display_img_buf_queue->first;

  /*
   * throw away expired frames
   */

  diff = 1000000; /* always enter the while-loop */

  while (img && (diff > img->duration || this->discard_frames)) {

    if (img->is_first) {
#ifdef LOG
      printf("video_out: expire_frames: first_frame !\n");
#endif
      /*
       * before displaying the first frame without
       * "metronom prebuffering" we should make sure it's 
       * not used as a decoder reference anymore.
       */
      if( img->lock_counter == 1 )
        img->vpts = cur_vpts;
      break;
    }

    pts = img->vpts;
    diff = cur_vpts - pts;
      
    if (diff > img->duration || this->discard_frames) {
  
      if( !this->discard_frames ) {
        xine_log(this->xine, XINE_LOG_MSG,
	         _("video_out: throwing away image with pts %lld because "
		   "it's too old (diff : %lld).\n"), pts, diff);

        this->num_frames_discarded++;
      }
      
      img = vo_remove_from_img_buf_queue_int (this->display_img_buf_queue);
  
      pthread_mutex_lock( &img->stream->current_extra_info_lock );
      extra_info_merge( img->stream->current_extra_info, img->extra_info );
      pthread_mutex_unlock( &img->stream->current_extra_info_lock );

      /* when flushing frames, keep the first one as backup */
      if( this->discard_frames ) {
        
        if (!this->img_backup) {
	  this->img_backup = img;
        } else {
	  vo_frame_dec_lock( img );
        }     
           
      } else {
        /*
         * last frame? back it up for
         * still frame creation
         */
        
        if (1 /*!this->display_img_buf_queue->first*/) {
	  
	  if (this->img_backup) {
#ifdef LOG
	    printf("video_out: overwriting frame backup\n");
#endif
	    vo_frame_dec_lock( this->img_backup );
	  }
#ifdef LOG
	  printf("video_out: possible still frame (old)\n");
#endif

	  this->img_backup = img;
	
	  /* wait 4 frames before drawing this one. 
	     this allow slower systems to recover. */
	  this->redraw_needed = 4; 
        } else {
	  vo_frame_dec_lock( img );
        }
      }
      img = this->display_img_buf_queue->first;
    }
  }
  
  pthread_mutex_unlock(&this->display_img_buf_queue->mutex);
}

static vo_frame_t *get_next_frame (vos_t *this, int64_t cur_vpts) {
  
  vo_frame_t   *img;

  pthread_mutex_lock(&this->display_img_buf_queue->mutex);
  
  img = this->display_img_buf_queue->first;

  /* 
   * still frame detection:
   */

  /* no frame? => still frame detection */

  if (!img) {

    pthread_mutex_unlock(&this->display_img_buf_queue->mutex);
#ifdef LOG
    printf ("video_out: no frame\n");
#endif



    if (this->img_backup && (this->redraw_needed==1)) {
dirty_check_again2:
#ifdef LOG
      printf("video_out: generating still frame (cur_vpts = %lld) \n",
	     cur_vpts);
#endif

      /* keep playing still frames */
      pthread_mutex_lock( &this->free_img_buf_queue->mutex );
      img = duplicate_frame (this, this->img_backup );
      pthread_mutex_unlock( &this->free_img_buf_queue->mutex );
      if( img ) {
        img->vpts = cur_vpts;
        /* extra info of the backup is thrown away, because it is not up to date */
        extra_info_reset(img->extra_info);
      }
        
      return img;

    } else {
    
      if( this->redraw_needed )
           this->redraw_needed--;
       
#ifdef LOG
      printf ("video_out: no frame, but no backup frame\n");
#endif
      return NULL;
    }
  } else {
	/* there is image for output */
    int64_t diff;

dirty_check_again:
    diff = cur_vpts - img->vpts;
    /*
     * time to display frame "img" ?
     */

#ifdef LOG
    printf ("video_out: diff %lld\n", diff);
#endif

	if (diff<0)  {
		int counter = 0;
		int64_t previous_tick,next_tick;
  		vo_frame_t   *t, *st=NULL;
                int finded=0;
                  
		if(this->img_backup && (this->redraw_needed==4)) {	
			this->redraw_needed--;
			pthread_mutex_unlock(&this->display_img_buf_queue->mutex);
			goto dirty_check_again2;
		}
		
		//previous_tick = img->vpts;
		t = img->next;
		previous_tick = (int64_t)img->vpts;
		while (t) {
			next_tick = (int64_t)t->vpts;
			if (next_tick<previous_tick) {
				printf("out of order !!\n");
				st = t;	
				finded = 1;		
				break;
			}
			
			t = t->next;
		}
		
		/* FIXME: george added this section but it affects slide show. please georege check these codes again. 
		if(!finded) {
		    img->vpts = cur_vpts;
		    extra_info_reset(img->extra_info);
		    goto dirty_check_again;	
		}
		*/
		
		if (st!=NULL) {
			/* travel to the image which is in correct order */
			/* drop all frames that is not in the correct order */
			while (img!=st) {
				img = vo_remove_from_img_buf_queue_int(this->display_img_buf_queue);
				vo_frame_dec_lock(img);
				img = this->display_img_buf_queue->first;
				//t->vpts = cur_vpts;
			}
			goto dirty_check_again;
		}

		if (st==NULL) {
		    	
			/* not finding out of order frames */
      		pthread_mutex_unlock(&this->display_img_buf_queue->mutex);
      		return NULL;
		}
	}

    if (this->img_backup) {
#ifdef LOG
      printf("video_out: freeing frame backup\n");
#endif
      vo_frame_dec_lock( this->img_backup );
      this->img_backup = NULL;
    }

    /* 
     * last frame? make backup for possible still image 
     */
    pthread_mutex_lock( &this->free_img_buf_queue->mutex );
    if (img && !img->next) {
      
      if (img->stream->stream_info[XINE_STREAM_INFO_VIDEO_HAS_STILL] ||
          img->stream->video_fifo->size(img->stream->video_fifo) < 10) {

#ifdef LOG
        printf ("video_out: possible still frame\n");
#endif
        this->img_backup = duplicate_frame (this, img);
      }
    }
    pthread_mutex_unlock( &this->free_img_buf_queue->mutex );

    /*
     * remove frame from display queue and show it
     */

    img = vo_remove_from_img_buf_queue_int (this->display_img_buf_queue);
    pthread_mutex_unlock(&this->display_img_buf_queue->mutex);

    return img;
  }
}

static time_t	still_end_time=(time_t)0;

static void*
still_count_down_thread(void *arg)
{
 	xine_stream_t	*stream=(xine_stream_t *)arg;

	while(time(NULL)<still_end_time)
		xine_usec_sleep(50000);

	xine_set_speed(stream,XINE_SPEED_NORMAL);
	printf("xine return to normal speed\n");

	fflush(stdout);
}


static void overlay_and_display_frame (vos_t *this,
				       vo_frame_t *img, int64_t vpts) {
  xine_stream_t *stream;

#ifdef LOG
  printf ("video_out: displaying image with vpts = %lld\n",
	  img->vpts);
#endif

  /* no, this is not were copy() is usually called.
   * it's just to catch special cases like late or duplicated frames.
   */
  if( img->copy && !img->copy_called )
    vo_frame_driver_copy(img);

  pthread_mutex_lock( &img->stream->current_extra_info_lock );
  {
    int64_t diff;
    diff = img->extra_info->vpts - img->stream->current_extra_info->vpts;
    if ((diff > 3000) || (diff<-300000))
      extra_info_merge( img->stream->current_extra_info, img->extra_info );
  }
  pthread_mutex_unlock( &img->stream->current_extra_info_lock );

  if (this->overlay_source) {
    this->overlay_source->multiple_overlay_blend (this->overlay_source,
						  vpts,
						  this->driver, img,
						  this->video_loop_running && this->overlay_enabled);
  }

  /* hold current frame for snapshot feature */
  if( this->last_frame ) {
    vo_frame_dec_lock( this->last_frame );
  }
  vo_frame_inc_lock( img );
  this->last_frame = img;
  
  this->driver->display_frame (this->driver, img);

#if 0
  if(img->still_count<0) {
 	img->still_count=0;
	printf("forever still,img=%x\n",img);
	xine_set_speed(img->stream,XINE_SPEED_PAUSE);
  } else if(img->still_count>0) {
	pthread_t	th;

    printf("still,xine set to speed pause,img=%x\n",img);
    still_end_time=time(NULL)+img->still_count;
	pthread_create(&th,NULL,still_count_down_thread,img->stream);
	xine_set_speed(img->stream,XINE_SPEED_PAUSE);
  }  
#endif

  /*
   * Wake up xine_play if it's waiting for a frame
   */
  if( this->last_frame->is_first ) {
    pthread_mutex_lock(&this->streams_lock);
    for (stream = xine_list_first_content(this->streams); stream;
         stream = xine_list_next_content(this->streams)) {
      pthread_mutex_lock (&stream->first_frame_lock);
      if (stream->first_frame_flag) {
        stream->first_frame_flag = 0;
        pthread_cond_broadcast(&stream->first_frame_reached);
      }
      pthread_mutex_unlock (&stream->first_frame_lock);
    }
    pthread_mutex_unlock(&this->streams_lock);
  }

  this->redraw_needed = 0; 
}

static void check_redraw_needed (vos_t *this, int64_t vpts) {

  if (this->overlay_source) {
    if( this->overlay_source->redraw_needed (this->overlay_source, vpts) ) {
      this->redraw_needed = 1; 
    }
  }
  
  if( this->driver->redraw_needed (this->driver) ) {
    this->redraw_needed = 1;
  }

}

/* special loop for paused mode
 * needed to update screen due overlay changes, resize, window
 * movement, brightness adjusting etc.
 */                   
static void paused_loop( vos_t *this, int64_t vpts )
{
  vo_frame_t   *img;
  
  pthread_mutex_lock( &this->free_img_buf_queue->mutex );
  /* prevent decoder thread from allocating new frames */
	LOG_SEG_ERROR
  this->free_img_buf_queue->locked_for_read = 1;
  
  while (this->clock->speed == XINE_SPEED_PAUSE) {
  
	LOG_SEG_ERROR
    /* we need at least one free frame to keep going */
    if( this->display_img_buf_queue->first &&
       !this->free_img_buf_queue->first ) {
    
	LOG_SEG_ERROR
      img = vo_remove_from_img_buf_queue (this->display_img_buf_queue);
      img->next = NULL;
      this->free_img_buf_queue->first = img;
      this->free_img_buf_queue->last  = img;
      this->free_img_buf_queue->num_buffers = 1;
    }
    
    /* set img_backup to play the same frame several times */
    if( this->display_img_buf_queue->first && !this->img_backup ) {
	LOG_SEG_ERROR
      this->img_backup = vo_remove_from_img_buf_queue (this->display_img_buf_queue);
      this->redraw_needed = 1;
    }
    
	LOG_SEG_ERROR
    check_redraw_needed( this, vpts );
    
    if( this->redraw_needed && this->img_backup ) {
      img = duplicate_frame (this, this->img_backup );
      if( img ) {
        /* extra info of the backup is thrown away, because it is not up to date */
        extra_info_reset(img->extra_info);
        pthread_mutex_unlock( &this->free_img_buf_queue->mutex );
        overlay_and_display_frame (this, img, vpts);
        pthread_mutex_lock( &this->free_img_buf_queue->mutex );
      }  
    }
    
	LOG_SEG_ERROR
    pthread_mutex_unlock( &this->free_img_buf_queue->mutex );
    xine_usec_sleep (20000);
    pthread_mutex_lock( &this->free_img_buf_queue->mutex );
  } 
  
	LOG_SEG_ERROR
  this->free_img_buf_queue->locked_for_read = 0;
   
  if( this->free_img_buf_queue->first )
    pthread_cond_signal (&this->free_img_buf_queue->not_empty);
  pthread_mutex_unlock( &this->free_img_buf_queue->mutex );
	LOG_SEG_ERROR
}

static void *video_out_loop (void *this_gen) {

  int64_t            vpts, diff;
  vo_frame_t        *img;
  vos_t             *this = (vos_t *) this_gen;
  int64_t            frame_duration, next_frame_vpts;
  int64_t            usec_to_sleep;
 
#ifndef WIN32
  /* nice(-value) will fail silently for normal users.
   * however when running as root this may provide smoother
   * playback. follow the link for more information:
   * http://cambuca.ldhs.cetuc.puc-rio.br/~miguel/multimedia_sim/
   */
  nice(-2);
#endif /* WIN32 */

  /*
   * here it is - the heart of xine (or rather: one of the hearts
   * of xine) : the video output loop
   */

  frame_duration = 1500; /* default */
	LOG_SEG_ERROR
  next_frame_vpts = this->clock->get_current_time (this->clock);

#ifdef LOG
    printf ("video_out: loop starting...\n");
#endif

  while ( this->video_loop_running ) {

    /*
     * get current time and find frame to display
     */

    vpts = this->clock->get_current_time (this->clock);
#ifdef LOG
    printf ("video_out: loop iteration at %lld\n", vpts);
#endif
	LOG_SEG_ERROR
    expire_frames (this, vpts);
    img = get_next_frame (this, vpts);

    /*
     * if we have found a frame, display it
     */

    if (img) {
#ifdef LOG
      printf ("video_out: displaying frame (id=%d)\n", img->id);
#endif
      overlay_and_display_frame (this, img, vpts);

    } else {
	SET_SEG_ERROR
      check_redraw_needed( this, vpts );
    }

	SET_SEG_ERROR
    {
    	xine_stream_t *stream = img ? img->stream : xine_list_first_content(this->streams);
	    if(stream && stream->vobu_still==2) {
			//fprintf(stderr,"________________PAUSE__________________\n");
			stream->xine->clock->set_speed (stream->xine->clock,XINE_SPEED_PAUSE);
    	}
    }

    /*
     * if we haven't heared from the decoder for some time
     * flush it
     * test display fifo empty to protect from deadlocks
     */

	SET_SEG_ERROR
    diff = vpts - this->last_delivery_pts;
    if (diff > 30000 && !this->display_img_buf_queue->first) {
      xine_stream_t *stream;

      pthread_mutex_lock(&this->streams_lock);
      for (stream = xine_list_first_content(this->streams); stream;
           stream = xine_list_next_content(this->streams)) {
        if (stream->video_decoder_plugin && stream->video_fifo) {
          buf_element_t *buf;

#ifdef LOG
	  printf ("video_out: flushing current video decoder plugin\n");
#endif
			SET_SEG_ERROR
          buf = stream->video_fifo->buffer_pool_try_alloc (stream->video_fifo);
          if( buf ) {
            buf->type = BUF_CONTROL_FLUSH_DECODER;
            stream->video_fifo->insert(stream->video_fifo, buf);
          }
        }
      }
      pthread_mutex_unlock(&this->streams_lock);
	SET_SEG_ERROR
      this->last_delivery_pts = vpts;
    }

    /*
     * wait until it's time to display next frame
     */

	SET_SEG_ERROR
    if (img) {
      frame_duration = img->duration;
      next_frame_vpts = img->vpts + img->duration;
    } else {
      next_frame_vpts += frame_duration;
    }
    
#ifdef LOG
    printf ("video_out: next_frame_vpts is %lld\n", next_frame_vpts);
#endif
    do {
 	  SET_SEG_ERROR
      vpts = this->clock->get_current_time (this->clock);
  
      if (this->clock->speed == XINE_SPEED_PAUSE) {
 		SET_SEG_ERROR
        paused_loop (this, vpts);
	  }

      usec_to_sleep = (next_frame_vpts - vpts) * 100 / 9;

      SET_SEG_ERROR
      //if ( (next_frame_vpts - vpts) > 2*90000 )
      //  printf("video_out: vpts/clock error, next_vpts=%lld cur_vpts=%lld\n",
      //         next_frame_vpts,vpts);
               
      if (usec_to_sleep>0) 
			xine_usec_sleep (usec_to_sleep);

      SET_SEG_ERROR
    } while ( (usec_to_sleep > 0) && this->video_loop_running);
  }
	SET_SEG_ERROR

  /*
   * throw away undisplayed frames
   */
  
  pthread_mutex_lock(&this->display_img_buf_queue->mutex);
  img = this->display_img_buf_queue->first;
  while (img) {

    img = vo_remove_from_img_buf_queue_int (this->display_img_buf_queue);
    vo_frame_dec_lock( img );

    img = this->display_img_buf_queue->first;
  }
  pthread_mutex_unlock(&this->display_img_buf_queue->mutex);

	SET_SEG_ERROR

  if (this->img_backup) {
    vo_frame_dec_lock( this->img_backup );
    this->img_backup = NULL;
  }
	SET_SEG_ERROR
  if (this->last_frame) {
    vo_frame_dec_lock( this->last_frame );
    this->last_frame = NULL;
  }
	SET_SEG_ERROR
  return NULL;
}

/*
 * public function for video processing frontends to manually
 * consume video frames
 */

int xine_get_next_video_frame (xine_video_port_t *this_gen,
			       xine_video_frame_t *frame) {

  vos_t         *this = (vos_t *) this_gen;
  vo_frame_t    *img;
  xine_stream_t *stream=NULL;

  while (!stream) {
    stream = xine_list_first_content(this->streams);
    if (!stream)
      xine_usec_sleep (1000);
  } 

  pthread_mutex_lock(&this->display_img_buf_queue->mutex);
  
  img = this->display_img_buf_queue->first;

  /* FIXME: ugly, use conditions and locks instead */

#ifdef LOG
  printf ("video_out: get_next_video_frame demux status = %d, fifo_size=%d\n",
	  stream->demux_plugin->get_status (stream->demux_plugin),
	  stream->video_fifo->fifo_size);
#endif

  while ( !img && (stream->video_fifo->fifo_size 
		   || (stream->demux_plugin->get_status (stream->demux_plugin)==DEMUX_OK))) {

    pthread_mutex_unlock(&this->display_img_buf_queue->mutex);
    xine_usec_sleep (1000);
    pthread_mutex_lock(&this->display_img_buf_queue->mutex);

    img = this->display_img_buf_queue->first;
    printf ("video_out: get_next_video_frame demux status = %d, fifo_size=%d\n",
	    stream->demux_plugin->get_status (stream->demux_plugin),
	    stream->video_fifo->fifo_size);


  }

  if (!img) {
    pthread_mutex_unlock(&this->display_img_buf_queue->mutex);
    return 0;
  }

  /*
   * remove frame from display queue and show it
   */
    
  img = vo_remove_from_img_buf_queue_int (this->display_img_buf_queue);
  pthread_mutex_unlock(&this->display_img_buf_queue->mutex);

  frame->vpts         = img->vpts;
  frame->duration     = img->duration;
  frame->width        = img->width;
  frame->height       = img->height;
  frame->pos_stream   = img->extra_info->input_pos;
  frame->pos_time     = img->extra_info->input_time;
 // printf("img->ratio[%f] \n",img->ratio); 
  switch ((int)img->ratio) {
  case XINE_VO_ASPECT_ANAMORPHIC:  /* anamorphic     */
  case XINE_VO_ASPECT_PAN_SCAN:    /* we display pan&scan as widescreen */
  case XINE_VO_ASPECT_ANAMORPHIC_FULL:
    frame->aspect_ratio = 16.0 /9.0;
    break;
  case XINE_VO_ASPECT_DVB:         /* 2.11:1 */
    frame->aspect_ratio = 2.11/1.0;
    break;
  case XINE_VO_ASPECT_SQUARE:      /* square pels */
  case XINE_VO_ASPECT_DONT_TOUCH:  /* probably non-mpeg stream => don't touch aspect ratio */
    frame->aspect_ratio = (double) img->width / (double) img->height;
    break;
  case 0:                          /* forbidden -> 4:3 */
  default:
  case XINE_VO_ASPECT_4_3_CUT:
  case XINE_VO_ASPECT_4_3:         /* 4:3             */
    frame->aspect_ratio = 4.0 / 3.0;
    break;
  }
  
  frame->aspect_ratio = img->ratio;
  frame->colorspace   = img->format;
  frame->data         = img->base[0];
  frame->xine_frame   = img;

  return 1;
}

void xine_free_video_frame (xine_video_port_t *port, 
			    xine_video_frame_t *frame) {

  vo_frame_t *img = (vo_frame_t *) frame->xine_frame;

  vo_frame_dec_lock (img);
}


static uint32_t vo_get_capabilities (xine_video_port_t *this_gen) {
  vos_t      *this = (vos_t *) this_gen;
  return this->driver->get_capabilities (this->driver);
}

static void vo_open (xine_video_port_t *this_gen, xine_stream_t *stream) {

  vos_t      *this = (vos_t *) this_gen;

#ifdef LOG
  printf("video_out: vo_open\n");
#endif
  this->video_opened = 1;
  this->discard_frames = 0;
  this->last_delivery_pts = 0;
  this->warn_threshold_event_sent = this->warn_threshold_exceeded = 0;

  if (!this->overlay_enabled && stream->spu_channel_user > -2)
    /* enable overlays if our new stream might want to show some */
    this->overlay_enabled = 1;
  pthread_mutex_lock(&this->streams_lock);
  xine_list_append_content(this->streams, stream);
  pthread_mutex_unlock(&this->streams_lock);
}

static void vo_close (xine_video_port_t *this_gen, xine_stream_t *stream) {

  vos_t      *this = (vos_t *) this_gen;
  xine_stream_t *cur;

  /* this will make sure all hide events were processed */
  if (this->overlay_source)
    this->overlay_source->flush_events (this->overlay_source);

  this->video_opened = 0;
  
  /* unregister stream */
  pthread_mutex_lock(&this->streams_lock);
  for (cur = xine_list_first_content(this->streams); cur;
       cur = xine_list_next_content(this->streams))
    if (cur == stream) {
      xine_list_delete_current(this->streams);
      break;
    }
  pthread_mutex_unlock(&this->streams_lock);
}


static int vo_get_property (xine_video_port_t *this_gen, int property) {
  vos_t *this = (vos_t *) this_gen;
  int ret;

  switch (property) {
  case VO_PROP_DISCARD_FRAMES:
    ret = this->discard_frames;
    break;
    
  /*
   * handle XINE_PARAM_xxx properties (convert from driver's range)
   */
  case XINE_PARAM_VO_HUE:
  case XINE_PARAM_VO_SATURATION:
  case XINE_PARAM_VO_CONTRAST:
  case XINE_PARAM_VO_BRIGHTNESS: {
    int v, min_v, max_v, range_v;

    pthread_mutex_lock( &this->driver_lock );
    this->driver->get_property_min_max (this->driver,
					property & 0xffffff,
					&min_v, &max_v);

    v = this->driver->get_property (this->driver, property & 0xffffff);

    range_v = max_v - min_v;

    if (range_v > 0)
      ret = (v-min_v) * 65535 / range_v;
    else 
      ret = 0;
    pthread_mutex_unlock( &this->driver_lock );
  }
    break;
  
  default:
    pthread_mutex_lock( &this->driver_lock );
    ret = this->driver->get_property(this->driver, property & 0xffffff);
    pthread_mutex_unlock( &this->driver_lock );
  }
  return ret;
}

static int vo_set_property (xine_video_port_t *this_gen, int property, int value) {
  vos_t *this = (vos_t *) this_gen;
  int ret;

  switch (property) {
  
  case VO_PROP_DISCARD_FRAMES:
    /* recursive discard frames setting */
    pthread_mutex_lock(&this->display_img_buf_queue->mutex);
    if(value)
      this->discard_frames++;
    else
      this->discard_frames--;
    pthread_mutex_unlock(&this->display_img_buf_queue->mutex);
    ret = this->discard_frames;
    
    /* discard buffers here because we have no output thread */
    if (this->grab_only && this->discard_frames) {
      vo_frame_t *img;
      
      pthread_mutex_lock(&this->display_img_buf_queue->mutex);
  
      while ((img = this->display_img_buf_queue->first)) {
  
#ifdef LOG
        printf ("video_out: flushing out frame\n");
#endif
  
        img = vo_remove_from_img_buf_queue_int (this->display_img_buf_queue);
  
        vo_frame_dec_lock (img);
      }
      pthread_mutex_unlock(&this->display_img_buf_queue->mutex);
    }
    break;

  /*
   * handle XINE_PARAM_xxx properties (convert to driver's range)
   */
  case XINE_PARAM_VO_HUE:
  case XINE_PARAM_VO_SATURATION:
  case XINE_PARAM_VO_CONTRAST:
  case XINE_PARAM_VO_BRIGHTNESS:
    if (!this->grab_only) {
      int v, min_v, max_v, range_v;
  
      pthread_mutex_lock( &this->driver_lock );
      
      this->driver->get_property_min_max (this->driver,
  					property & 0xffffff,
  					&min_v, &max_v);
  
      range_v = max_v - min_v;
  
      v = (value * range_v) / 65535 + min_v;
  
      this->driver->set_property(this->driver, property & 0xffffff, v);
      pthread_mutex_unlock( &this->driver_lock );
      ret = value;
    } else
      ret = 0;
    break;
    
    
  default:
    if (!this->grab_only) {
      pthread_mutex_lock( &this->driver_lock );
      ret =  this->driver->set_property(this->driver, property & 0xffffff, value);
      pthread_mutex_unlock( &this->driver_lock );
    } else
      ret = 0;
  }

  return ret;
}


static int vo_status (xine_video_port_t *this_gen, xine_stream_t *stream,
                      int *width, int *height, int64_t *img_duration) {

  vos_t      *this = (vos_t *) this_gen;
  xine_stream_t *cur;
  int ret = 0;

  pthread_mutex_lock(&this->streams_lock);
  for (cur = xine_list_first_content(this->streams); cur;
       cur = xine_list_next_content(this->streams))
    if (cur == stream || !stream) {
      *width = this->current_width;
      *height = this->current_height;
      *img_duration = this->current_duration;
      ret = 1;
      break;
    }
  pthread_mutex_unlock(&this->streams_lock);
  
  return ret;
}


static void vo_free_img_buffers (xine_video_port_t *this_gen) {
  vos_t      *this = (vos_t *) this_gen;
  vo_frame_t *img;

  while (this->free_img_buf_queue->first) {
    img = vo_remove_from_img_buf_queue (this->free_img_buf_queue);
    img->dispose (img);
  }

  while (this->display_img_buf_queue->first) {
    img = vo_remove_from_img_buf_queue (this->display_img_buf_queue) ;
    img->dispose (img);
  }

  free (this->extra_info_base);
}

static void vo_exit (xine_video_port_t *this_gen) {

  vos_t      *this = (vos_t *) this_gen;

#ifdef LOG
  printf ("video_out: vo_exit...\n");
#endif

  if (this->video_loop_running) {
    void *p;

    this->video_loop_running = 0;

    pthread_join (this->video_thread, &p);
  }

  vo_free_img_buffers (this_gen);

  this->driver->dispose (this->driver);

#ifdef LOG
  printf ("video_out: vo_exit... done\n");
#endif

  if (this->overlay_source) {
    this->overlay_source->dispose (this->overlay_source);
  }
  
  xine_list_free(this->streams);
  pthread_mutex_destroy(&this->streams_lock);

  free (this->free_img_buf_queue);
  free (this->display_img_buf_queue);

  free (this);
}

static vo_frame_t *vo_get_last_frame (xine_video_port_t *this_gen) {
  vos_t      *this = (vos_t *) this_gen;
  return this->last_frame;
}

/*
 * overlay stuff 
 */

static video_overlay_manager_t *vo_get_overlay_manager (xine_video_port_t *this_gen) {
  vos_t      *this = (vos_t *) this_gen;
  return this->overlay_source;
}

extern int spu_current_enable;
static void vo_enable_overlay (xine_video_port_t *this_gen, int overlay_enabled) {
  vos_t      *this = (vos_t *) this_gen;

 spu_current_enable = 1; 
  if (overlay_enabled) {
    /* we always ENable ... */
    this->overlay_enabled = 1;
  } else {
    /* ... but we only actually DISable, if all associated streams have SPU off */
    xine_stream_t *stream;
    pthread_mutex_lock(&this->streams_lock);
    for (stream = xine_list_first_content(this->streams) ; stream ;
         stream = xine_list_next_content(this->streams)) {
      if (stream->spu_channel_user > -2) {
	pthread_mutex_unlock(&this->streams_lock);
	return;
      }
    }
    pthread_mutex_unlock(&this->streams_lock);
    this->overlay_enabled = 0;
  }
}

/*
 * Flush video_out fifo
 */
static void vo_flush (xine_video_port_t *this_gen) {
  vos_t      *this = (vos_t *) this_gen;
  vo_frame_t *img;

  if( this->video_loop_running ) {
    pthread_mutex_lock(&this->display_img_buf_queue->mutex);
    this->discard_frames++;
    pthread_mutex_unlock(&this->display_img_buf_queue->mutex);
   
    /* do not try this in paused mode */
    while(this->clock->speed != XINE_SPEED_PAUSE) {
      pthread_mutex_lock(&this->display_img_buf_queue->mutex);
      img = this->display_img_buf_queue->first;
      pthread_mutex_unlock(&this->display_img_buf_queue->mutex);
      if(!img)
        break;
      xine_usec_sleep (20000); /* pthread_cond_t could be used here */
    }

    pthread_mutex_lock(&this->display_img_buf_queue->mutex);
    this->discard_frames--;
    pthread_mutex_unlock(&this->display_img_buf_queue->mutex);
  }
}

xine_video_port_t *vo_new_port (xine_t *xine, vo_driver_t *driver,
				int grabonly) {

  vos_t            *this;
  int               i;
  pthread_attr_t    pth_attrs;
  int		    err;
  int               num_frame_buffers;


  this = xine_xmalloc (sizeof (vos_t)) ;

  this->xine                  = xine;
  this->clock                 = xine->clock;
  this->driver                = driver;
  this->streams               = xine_list_new();
  
  pthread_mutex_init(&this->streams_lock, NULL);
  pthread_mutex_init(&this->driver_lock, NULL );

  this->vo.open                  = vo_open;
  this->vo.get_frame             = vo_get_frame;
  this->vo.get_last_frame        = vo_get_last_frame;
  this->vo.close                 = vo_close;
  this->vo.exit                  = vo_exit;
  this->vo.get_capabilities      = vo_get_capabilities;
  this->vo.enable_ovl            = vo_enable_overlay;
  this->vo.get_overlay_manager   = vo_get_overlay_manager;
  this->vo.flush                 = vo_flush;
  this->vo.get_property          = vo_get_property;
  this->vo.set_property          = vo_set_property;
  this->vo.status                = vo_status;
  this->vo.driver                = driver;

  this->num_frames_delivered  = 0;
  this->num_frames_skipped    = 0;
  this->num_frames_discarded  = 0;
  this->free_img_buf_queue    = vo_new_img_buf_queue ();
  this->display_img_buf_queue = vo_new_img_buf_queue ();
  this->video_loop_running    = 0;

  this->last_frame            = NULL;
  this->img_backup            = NULL;
  
  this->overlay_source        = video_overlay_new_manager();
  this->overlay_source->init (this->overlay_source);
  this->overlay_enabled       = 1;

  this->frame_drop_limit      = 3;
  this->frame_drop_cpt        = 0;

  num_frame_buffers = driver->get_property (driver, VO_PROP_MAX_NUM_FRAMES);

  if (!num_frame_buffers)
    num_frame_buffers = NUM_FRAME_BUFFERS; /* default */
  else if (num_frame_buffers<5) 
    num_frame_buffers = 5;

  this->extra_info_base = calloc (num_frame_buffers,
					  sizeof(extra_info_t));

  for (i=0; i<num_frame_buffers; i++) {
    vo_frame_t *img;

    img = driver->alloc_frame (driver) ;
    if (!img) break;

    img->id        = i;

    img->port      = &this->vo;
    img->free      = vo_frame_dec_lock;
    img->lock      = vo_frame_inc_lock;
    img->draw      = vo_frame_draw;

    img->extra_info = &this->extra_info_base[i];

    vo_append_to_img_buf_queue (this->free_img_buf_queue,
				img);
  }

  this->warn_skipped_threshold = 
    xine->config->register_num (xine->config, "video.warn_skipped_threshold", 10,
    "send event to front end if percentage of skipped frames exceed this value",
    NULL, 20, NULL, NULL);
  this->warn_discarded_threshold = 
    xine->config->register_num (xine->config, "video.warn_discarded_threshold", 10,
    "send event to front end if percentage of discarded frames exceed this value",
    NULL, 20, NULL, NULL);


  if (grabonly) {

    this->video_loop_running   = 0;
    this->video_opened         = 0;
    this->grab_only            = 1;

  } else {

    /*
     * start video output thread
     *
     * this thread will alwys be running, displaying the
     * logo when "idle" thus making it possible to have
     * osd when not playing a stream
     */

    this->video_loop_running   = 1;
    this->video_opened         = 0;
    this->grab_only            = 0;
    
    pthread_attr_init(&pth_attrs);
    pthread_attr_setscope(&pth_attrs, PTHREAD_SCOPE_SYSTEM);
    
    if ((err = pthread_create (&this->video_thread,
			       &pth_attrs, video_out_loop, this)) != 0) {

      printf (_("video_out: can't create thread (%s)\n"), 
	      strerror(err));
      /* FIXME: how does this happen ? */
      printf (_("video_out: sorry, this should not happen. please restart xine.\n"));
      abort();
    } else if (xine->verbosity >= XINE_VERBOSITY_DEBUG)
      printf ("video_out: thread created\n");
    
    pthread_attr_destroy(&pth_attrs);
  }

  return &this->vo;
}
