+void spu_RenderSubpictures( spu_t *p_spu,
+ picture_t *p_pic_dst, const video_format_t *p_fmt_dst,
+ subpicture_t *p_subpic_list,
+ const video_format_t *p_fmt_src, bool b_paused )
+{
+ spu_private_t *p_sys = p_spu->p;
+
+ const int i_source_video_width = p_fmt_src->i_width;
+ const int i_source_video_height = p_fmt_src->i_height;
+ const mtime_t i_current_date = mdate();
+
+ unsigned int i_subpicture;
+ subpicture_t *pp_subpicture[VOUT_MAX_SUBPICTURES];
+
+ unsigned int i_subtitle_region_count;
+ spu_area_t p_subtitle_area_buffer[VOUT_MAX_SUBPICTURES];
+ spu_area_t *p_subtitle_area;
+ int i_subtitle_area;
+
+ vlc_mutex_lock( &p_sys->lock );
+
+ /* Preprocess subpictures */
+ i_subpicture = 0;
+ i_subtitle_region_count = 0;
+ for( subpicture_t * p_subpic = p_subpic_list;
+ p_subpic != NULL;
+ p_subpic = p_subpic->p_next )
+ {
+ /* */
+ if( !b_paused && p_subpic->pf_pre_render )
+ p_subpic->pf_pre_render( p_spu, p_subpic, p_fmt_dst );
+
+ if( !b_paused && p_subpic->pf_update_regions )
+ {
+ video_format_t fmt_org = *p_fmt_dst;
+ fmt_org.i_width =
+ fmt_org.i_visible_width = i_source_video_width;
+ fmt_org.i_height =
+ fmt_org.i_visible_height = i_source_video_height;
+
+ p_subpic->pf_update_regions( p_spu, p_subpic, &fmt_org, i_current_date );
+ }
+
+ /* */
+ if( p_subpic->b_subtitle )
+ {
+ for( subpicture_region_t *r = p_subpic->p_region; r != NULL; r = r->p_next )
+ i_subtitle_region_count++;
+ }
+
+ /* */
+ pp_subpicture[i_subpicture++] = p_subpic;
+ }
+
+ /* Be sure we have at least 1 picture to process */
+ if( i_subpicture <= 0 )
+ {
+ vlc_mutex_unlock( &p_sys->lock );
+ return;
+ }
+
+ /* Now order subpicture array
+ * XXX The order is *really* important for overlap subtitles positionning */
+ qsort( pp_subpicture, i_subpicture, sizeof(*pp_subpicture), SubpictureCmp );
+
+ /* Allocate area array for subtitle overlap */
+ i_subtitle_area = 0;
+ p_subtitle_area = p_subtitle_area_buffer;
+ if( i_subtitle_region_count > sizeof(p_subtitle_area_buffer)/sizeof(*p_subtitle_area_buffer) )
+ p_subtitle_area = calloc( i_subtitle_region_count, sizeof(*p_subtitle_area) );
+
+ /* Create the blending module */
+ if( !p_sys->p_blend )
+ SpuRenderCreateBlend( p_spu, p_fmt_dst->i_chroma, p_fmt_dst->i_aspect );
+
+ /* Process all subpictures and regions (in the right order) */
+ for( unsigned int i_index = 0; i_index < i_subpicture; i_index++ )
+ {
+ subpicture_t *p_subpic = pp_subpicture[i_index];
+ subpicture_region_t *p_region;
+
+ if( !p_subpic->p_region )
+ continue;
+
+ /* FIXME when possible use a better rendering size than source size
+ * (max of display size and source size for example) FIXME */
+ int i_render_width = p_subpic->i_original_picture_width;
+ int i_render_height = p_subpic->i_original_picture_height;
+ if( !i_render_width || !i_render_height )
+ {
+ if( i_render_width != 0 || i_render_height != 0 )
+ msg_Err( p_spu, "unsupported original picture size %dx%d",
+ i_render_width, i_render_height );
+
+ p_subpic->i_original_picture_width = i_render_width = i_source_video_width;
+ p_subpic->i_original_picture_height = i_render_height = i_source_video_height;
+ }
+
+ if( p_sys->p_text )
+ {
+ p_sys->p_text->fmt_out.video.i_width =
+ p_sys->p_text->fmt_out.video.i_visible_width = i_render_width;
+
+ p_sys->p_text->fmt_out.video.i_height =
+ p_sys->p_text->fmt_out.video.i_visible_height = i_render_height;
+ }
+
+ /* Compute scaling from picture to source size */
+ spu_scale_t scale = spu_scale_createq( i_source_video_width, i_render_width,
+ i_source_video_height, i_render_height );
+
+ /* Update scaling from source size to display size(p_fmt_dst) */
+ scale.w = scale.w * p_fmt_dst->i_width / i_source_video_width;
+ scale.h = scale.h * p_fmt_dst->i_height / i_source_video_height;
+
+ /* Set default subpicture aspect ratio
+ * FIXME if we only handle 1 aspect ratio per picture, why is it set per
+ * region ? */
+ p_region = p_subpic->p_region;
+ if( !p_region->fmt.i_sar_num || !p_region->fmt.i_sar_den )
+ {
+ if( p_region->fmt.i_aspect != 0 )
+ {
+ p_region->fmt.i_sar_den = p_region->fmt.i_aspect;
+ p_region->fmt.i_sar_num = VOUT_ASPECT_FACTOR;
+ }
+ else
+ {
+ p_region->fmt.i_sar_den = p_fmt_dst->i_sar_den;
+ p_region->fmt.i_sar_num = p_fmt_dst->i_sar_num;
+ }
+ }
+
+ /* Take care of the aspect ratio */
+ if( p_region->fmt.i_sar_num * p_fmt_dst->i_sar_den !=
+ p_region->fmt.i_sar_den * p_fmt_dst->i_sar_num )
+ {
+ /* FIXME FIXME what about region->i_x/i_y ? */
+ scale.w = scale.w *
+ (int64_t)p_region->fmt.i_sar_num * p_fmt_dst->i_sar_den /
+ p_region->fmt.i_sar_den / p_fmt_dst->i_sar_num;
+ }
+
+ /* Render all regions
+ * We always transform non absolute subtitle into absolute one on the
+ * first rendering to allow good subtitle overlap support.
+ */
+ for( p_region = p_subpic->p_region; p_region != NULL; p_region = p_region->p_next )
+ {
+ spu_area_t area;
+
+ /* Check scale validity */
+ if( scale.w <= 0 || scale.h <= 0 )
+ continue;
+
+ /* */
+ SpuRenderRegion( p_spu, p_pic_dst, &area,
+ p_subpic, p_region, scale, p_fmt_dst,
+ p_subtitle_area, i_subtitle_area );
+
+ if( p_subpic->b_subtitle )
+ {
+ area = spu_area_unscaled( area, scale );
+ if( !p_subpic->b_absolute && area.i_width > 0 && area.i_height > 0 )
+ {
+ p_region->i_x = area.i_x;
+ p_region->i_y = area.i_y;
+ }
+ if( p_subtitle_area )
+ p_subtitle_area[i_subtitle_area++] = area;
+ }
+ }
+ if( p_subpic->b_subtitle )
+ p_subpic->b_absolute = true;
+ }
+
+ /* */
+ if( p_subtitle_area != p_subtitle_area_buffer )
+ free( p_subtitle_area );
+
+ vlc_mutex_unlock( &p_sys->lock );
+}
+
+/*****************************************************************************
+ * spu_SortSubpictures: find the subpictures to display
+ *****************************************************************************
+ * This function parses all subpictures and decides which ones need to be
+ * displayed. If no picture has been selected, display_date will depend on
+ * the subpicture.
+ * We also check for ephemer DVD subpictures (subpictures that have
+ * to be removed if a newer one is available), which makes it a lot
+ * more difficult to guess if a subpicture has to be rendered or not.
+ *****************************************************************************/
+subpicture_t *spu_SortSubpictures( spu_t *p_spu, mtime_t display_date,
+ bool b_paused, bool b_subtitle_only )
+{
+ spu_private_t *p_sys = p_spu->p;
+ int i_channel;
+ subpicture_t *p_subpic = NULL;
+
+ /* Update sub-filter chain */
+ vlc_mutex_lock( &p_sys->lock );
+ char *psz_chain_update = p_sys->psz_chain_update;
+ p_sys->psz_chain_update = NULL;
+ vlc_mutex_unlock( &p_sys->lock );
+
+ if( psz_chain_update )
+ {
+ filter_chain_Reset( p_sys->p_chain, NULL, NULL );
+
+ filter_chain_AppendFromString( p_spu->p->p_chain, psz_chain_update );
+
+ free( psz_chain_update );
+ }
+
+ /* Run subpicture filters */
+ filter_chain_SubFilter( p_sys->p_chain, display_date );
+
+ vlc_mutex_lock( &p_sys->lock );
+
+ /* We get an easily parsable chained list of subpictures which
+ * ends with NULL since p_subpic was initialized to NULL. */
+ for( i_channel = 0; i_channel < p_sys->i_channel; i_channel++ )
+ {
+ subpicture_t *p_available_subpic[VOUT_MAX_SUBPICTURES];
+ bool pb_available_late[VOUT_MAX_SUBPICTURES];
+ int i_available = 0;
+
+ mtime_t start_date = display_date;
+ mtime_t ephemer_date = 0;
+ int i_index;
+
+ /* Select available pictures */
+ for( i_index = 0; i_index < VOUT_MAX_SUBPICTURES; i_index++ )
+ {
+ spu_heap_entry_t *p_entry = &p_sys->heap.p_entry[i_index];
+ subpicture_t *p_current = p_entry->p_subpicture;
+ bool b_stop_valid;
+ bool b_late;
+
+ if( !p_current || p_entry->b_reject )
+ {
+ if( p_entry->b_reject )
+ SpuHeapDeleteAt( &p_sys->heap, i_index );
+ continue;
+ }
+
+ if( p_current->i_channel != i_channel ||
+ ( b_subtitle_only && !p_current->b_subtitle ) )
+ {
+ continue;
+ }
+ if( display_date &&
+ display_date < p_current->i_start )
+ {
+ /* Too early, come back next monday */
+ continue;
+ }
+
+ if( p_current->i_start > ephemer_date )
+ ephemer_date = p_current->i_start;
+
+ b_stop_valid = ( !p_current->b_ephemer || p_current->i_stop > p_current->i_start ) &&
+ ( !p_current->b_subtitle || !b_paused ); /* XXX Assume that subtitle are pausable */
+
+ b_late = b_stop_valid && p_current->i_stop <= display_date;
+
+ /* start_date will be used for correct automatic overlap support
+ * in case picture that should not be displayed anymore (display_time)
+ * overlap with a picture to be displayed (p_current->i_start) */
+ if( !b_late && !p_current->b_ephemer )
+ start_date = p_current->i_start;
+
+ /* */
+ p_available_subpic[i_available] = p_current;
+ pb_available_late[i_available] = b_late;
+ i_available++;
+ }
+
+ /* Only forced old picture display at the transition */
+ if( start_date < p_sys->i_last_sort_date )
+ start_date = p_sys->i_last_sort_date;
+ if( start_date <= 0 )
+ start_date = INT64_MAX;
+
+ /* Select pictures to be displayed */
+ for( i_index = 0; i_index < i_available; i_index++ )
+ {
+ subpicture_t *p_current = p_available_subpic[i_index];
+ bool b_late = pb_available_late[i_index];
+
+ if( ( b_late && p_current->i_stop <= __MAX( start_date, p_sys->i_last_sort_date ) ) ||
+ ( p_current->b_ephemer && p_current->i_start < ephemer_date ) )
+ {
+ /* Destroy late and obsolete ephemer subpictures */
+ SpuHeapDeleteSubpicture( &p_sys->heap, p_current );
+ }
+ else
+ {
+ SubpictureChain( &p_subpic, p_current );
+ }
+ }
+ }
+
+ p_sys->i_last_sort_date = display_date;
+ vlc_mutex_unlock( &p_sys->lock );
+
+ return p_subpic;
+}
+
+void spu_OffsetSubtitleDate( spu_t *p_spu, mtime_t i_duration )
+{
+ spu_private_t *p_sys = p_spu->p;
+
+ vlc_mutex_lock( &p_sys->lock );
+ for( int i = 0; i < VOUT_MAX_SUBPICTURES; i++ )
+ {
+ spu_heap_entry_t *p_entry = &p_sys->heap.p_entry[i];
+ subpicture_t *p_current = p_entry->p_subpicture;
+
+ if( p_current && p_current->b_subtitle )
+ {
+ if( p_current->i_start > 0 )
+ p_current->i_start += i_duration;
+ if( p_current->i_stop > 0 )
+ p_current->i_stop += i_duration;
+ }
+ }
+ vlc_mutex_unlock( &p_sys->lock );
+}
+
+/*****************************************************************************
+ * subpicture_t allocation
+ *****************************************************************************/
+subpicture_t *subpicture_New( void )
+{
+ subpicture_t *p_subpic = calloc( 1, sizeof(*p_subpic) );
+ if( !p_subpic )
+ return NULL;
+
+ p_subpic->i_order = 0;
+ p_subpic->b_absolute = true;
+ p_subpic->b_fade = false;
+ p_subpic->b_subtitle = false;
+ p_subpic->i_alpha = 0xFF;
+ p_subpic->p_region = NULL;
+ p_subpic->pf_render = NULL;
+ p_subpic->pf_destroy = NULL;
+ p_subpic->p_sys = NULL;
+
+ return p_subpic;
+}
+
+void subpicture_Delete( subpicture_t *p_subpic )
+{
+ subpicture_region_ChainDelete( p_subpic->p_region );
+ p_subpic->p_region = NULL;
+
+ if( p_subpic->pf_destroy )
+ {
+ p_subpic->pf_destroy( p_subpic );
+ }
+ free( p_subpic );
+}
+
+static void SubpictureChain( subpicture_t **pp_head, subpicture_t *p_subpic )
+{
+ p_subpic->p_next = *pp_head;
+
+ *pp_head = p_subpic;
+}
+
+/*****************************************************************************
+ * subpicture_region_t allocation
+ *****************************************************************************/
+subpicture_region_t *subpicture_region_New( const video_format_t *p_fmt )