/* 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 #include #include #include #include #include #include #include #include #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_ticknext; } /* 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)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; ialloc_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; }