+ p_input = vlc_object_find( p_this, VLC_OBJECT_INPUT, FIND_PARENT );
+ if( !p_input )
+ return;
+
+ if( b_attach )
+ {
+ UpdateSPU( p_spu, VLC_OBJECT(p_input) );
+ var_AddCallback( p_input, "highlight", CropCallback, p_spu );
+ var_AddCallback( p_input, "sub-margin", MarginCallback, p_spu->p );
+
+ vlc_mutex_lock( &p_spu->p->lock );
+ p_spu->p->i_margin = var_GetInteger( p_input, "sub-margin" );
+ vlc_mutex_unlock( &p_spu->p->lock );
+
+ vlc_object_release( p_input );
+ }
+ else
+ {
+ /* Delete callback */
+ var_DelCallback( p_input, "highlight", CropCallback, p_spu );
+ var_DelCallback( p_input, "sub-margin", MarginCallback, p_spu->p );
+ vlc_object_release( p_input );
+ }
+}
+
+/**
+ * Display a subpicture
+ *
+ * Remove the reservation flag of a subpicture, which will cause it to be
+ * ready for display.
+ * \param p_spu the subpicture unit object
+ * \param p_subpic the subpicture to display
+ */
+void spu_DisplaySubpicture( spu_t *p_spu, subpicture_t *p_subpic )
+{
+ spu_private_t *p_sys = p_spu->p;
+
+ /* DEFAULT_CHAN always reset itself */
+ if( p_subpic->i_channel == DEFAULT_CHAN )
+ SpuClearChannel( p_spu, DEFAULT_CHAN );
+
+ /* p_private is for spu only and cannot be non NULL here */
+ for( subpicture_region_t *r = p_subpic->p_region; r != NULL; r = r->p_next )
+ assert( r->p_private == NULL );
+
+ /* */
+ vlc_mutex_lock( &p_sys->lock );
+ if( SpuHeapPush( &p_sys->heap, p_subpic ) )
+ {
+ vlc_mutex_unlock( &p_sys->lock );
+ msg_Err( p_spu, "subpicture heap full" );
+ subpicture_Delete( p_subpic );
+ return;
+ }
+ vlc_mutex_unlock( &p_sys->lock );
+}
+
+/**
+ * This function renders all sub picture units in the list.
+ */
+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,
+ mtime_t render_subtitle_date )
+{
+ spu_private_t *p_sys = p_spu->p;
+
+ const mtime_t render_osd_date = mdate();
+
+ const int i_source_video_width = p_fmt_src->i_width;
+ const int i_source_video_height = p_fmt_src->i_height;
+
+ 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( 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,
+ p_subpic->b_subtitle ? render_subtitle_date : render_osd_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 )
+ p_spu->p->p_blend = filter_NewBlend( VLC_OBJECT(p_spu), p_fmt_dst );
+
+ /* 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 )
+ {
+ 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,
+ p_subpic->b_subtitle ? render_subtitle_date : render_osd_date );
+
+ 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 render_subtitle_date,
+ bool b_subtitle_only )
+{
+ spu_private_t *p_sys = p_spu->p;
+ int i_channel;
+ subpicture_t *p_subpic = NULL;
+ const mtime_t render_osd_date = mdate();
+
+ /* 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, render_osd_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 = render_subtitle_date;
+ mtime_t ephemer_subtitle_date = 0;
+ mtime_t ephemer_osd_date = 0;
+ int64_t i_ephemer_subtitle_order = INT64_MIN;
+ int64_t i_ephemer_system_order = INT64_MIN;
+ 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;
+ }
+ const mtime_t render_date = p_current->b_subtitle ? render_subtitle_date : render_osd_date;
+ if( render_date &&
+ render_date < p_current->i_start )
+ {
+ /* Too early, come back next monday */
+ continue;
+ }
+
+ mtime_t *pi_ephemer_date = p_current->b_subtitle ? &ephemer_subtitle_date : &ephemer_osd_date;
+ int64_t *pi_ephemer_order = p_current->b_subtitle ? &i_ephemer_subtitle_order : &i_ephemer_system_order;
+ if( p_current->i_start >= *pi_ephemer_date )
+ {
+ *pi_ephemer_date = p_current->i_start;
+ if( p_current->i_order > *pi_ephemer_order )
+ *pi_ephemer_order = p_current->i_order;
+ }
+
+ b_stop_valid = !p_current->b_ephemer || p_current->i_stop > p_current->i_start;
+
+ b_late = b_stop_valid && p_current->i_stop <= render_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( p_current->b_subtitle && !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];
+
+ const mtime_t stop_date = p_current->b_subtitle ? __MAX( start_date, p_sys->i_last_sort_date ) : render_osd_date;
+ const mtime_t ephemer_date = p_current->b_subtitle ? ephemer_subtitle_date : ephemer_osd_date;
+ const int64_t i_ephemer_order = p_current->b_subtitle ? i_ephemer_subtitle_order : i_ephemer_system_order;
+
+ /* Destroy late and obsolete ephemer subpictures */
+ bool b_rejet = b_late && p_current->i_stop <= stop_date;
+ if( p_current->b_ephemer )
+ {
+ if( p_current->i_start < ephemer_date )
+ b_rejet = true;
+ else if( p_current->i_start == ephemer_date &&
+ p_current->i_order < i_ephemer_order )
+ b_rejet = true;
+ }
+
+ if( b_rejet )
+ SpuHeapDeleteSubpicture( &p_sys->heap, p_current );
+ else
+ SubpictureChain( &p_subpic, p_current );
+ }
+ }
+
+ p_sys->i_last_sort_date = render_subtitle_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_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_t *subpicture_NewFromPicture( vlc_object_t *p_obj,
+ picture_t *p_picture, vlc_fourcc_t i_chroma )
+{
+ /* */
+ video_format_t fmt_in = p_picture->format;
+
+ /* */
+ video_format_t fmt_out;
+ fmt_out = fmt_in;
+ fmt_out.i_chroma = i_chroma;
+
+ /* */
+ image_handler_t *p_image = image_HandlerCreate( p_obj );
+ if( !p_image )
+ return NULL;
+
+ picture_t *p_pip = image_Convert( p_image, p_picture, &fmt_in, &fmt_out );
+
+ image_HandlerDelete( p_image );