+ for( i = 0; i < p_sys->p_page->i_region_defs; i++ )
+ {
+ p_sys->p_page->p_region_defs[i].i_id = bs_read( s, 8 );
+ bs_skip( s, 8 ); /* Reserved */
+ p_sys->p_page->p_region_defs[i].i_x = bs_read( s, 16 );
+ p_sys->p_page->p_region_defs[i].i_y = bs_read( s, 16 );
+
+#ifdef DEBUG_DVBSUB
+ msg_Dbg( p_dec, "page_composition, region %i (%i,%i)",
+ i, p_sys->p_page->p_region_defs[i].i_x,
+ p_sys->p_page->p_region_defs[i].i_y );
+#endif
+ }
+ }
+}
+
+static void decode_region_composition( decoder_t *p_dec, bs_t *s )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ dvbsub_region_t *p_region, **pp_region = &p_sys->p_regions;
+ int i_segment_length, i_processed_length, i_id, i_version;
+ int i_width, i_height, i_level_comp, i_depth, i_clut;
+ int i_8_bg, i_4_bg, i_2_bg;
+ bool b_fill;
+
+ i_segment_length = bs_read( s, 16 );
+ i_id = bs_read( s, 8 );
+ i_version = bs_read( s, 4 );
+
+ /* Check if we already have this region */
+ for( p_region = p_sys->p_regions; p_region != NULL;
+ p_region = p_region->p_next )
+ {
+ pp_region = &p_region->p_next;
+ if( p_region->i_id == i_id ) break;
+ }
+
+ /* Check version number */
+ if( p_region && ( p_region->i_version == i_version ) )
+ {
+ bs_skip( s, 8 * (i_segment_length - 1) - 4 );
+ return;
+ }
+
+ if( !p_region )
+ {
+#ifdef DEBUG_DVBSUB
+ msg_Dbg( p_dec, "new region: %i", i_id );
+#endif
+ p_region = *pp_region = malloc( sizeof(dvbsub_region_t) );
+ if( p_region )
+ {
+ memset( p_region, 0, sizeof(dvbsub_region_t) );
+ p_region->p_object_defs = NULL;
+ p_region->p_pixbuf = NULL;
+ p_region->p_next = NULL;
+ }
+ }
+
+ /* Region attributes */
+ p_region->i_id = i_id;
+ p_region->i_version = i_version;
+ b_fill = bs_read( s, 1 );
+ bs_skip( s, 3 ); /* Reserved */
+
+ i_width = bs_read( s, 16 );
+ i_height = bs_read( s, 16 );
+#ifdef DEBUG_DVBSUB
+ msg_Dbg( p_dec, " width=%d height=%d", i_width, i_height );
+#endif
+ i_level_comp = bs_read( s, 3 );
+ i_depth = bs_read( s, 3 );
+ bs_skip( s, 2 ); /* Reserved */
+ i_clut = bs_read( s, 8 );
+
+ i_8_bg = bs_read( s, 8 );
+ i_4_bg = bs_read( s, 4 );
+ i_2_bg = bs_read( s, 2 );
+ bs_skip( s, 2 ); /* Reserved */
+
+ /* Free old object defs */
+ while( p_region->i_object_defs )
+ free( p_region->p_object_defs[--p_region->i_object_defs].psz_text );
+
+ free( p_region->p_object_defs );
+ p_region->p_object_defs = NULL;
+
+ /* Extra sanity checks */
+ if( ( p_region->i_width != i_width ) ||
+ ( p_region->i_height != i_height ) )
+ {
+ if( p_region->p_pixbuf )
+ {
+ msg_Dbg( p_dec, "region size changed (%dx%d->%dx%d)",
+ p_region->i_width, p_region->i_height, i_width, i_height );
+ free( p_region->p_pixbuf );
+ }
+
+ p_region->p_pixbuf = malloc( i_height * i_width );
+ p_region->i_depth = 0;
+ b_fill = true;
+ }
+ if( p_region->i_depth &&
+ ( ( p_region->i_depth != i_depth ) ||
+ ( p_region->i_level_comp != i_level_comp ) ||
+ ( p_region->i_clut != i_clut) ) )
+ {
+ msg_Dbg( p_dec, "region parameters changed (not allowed)" );
+ }
+
+ /* Erase background of region */
+ if( b_fill )
+ {
+ int i_background = ( p_region->i_depth == 1 ) ? i_2_bg :
+ ( ( p_region->i_depth == 2 ) ? i_4_bg : i_8_bg );
+ memset( p_region->p_pixbuf, i_background, i_width * i_height );
+ }
+
+ p_region->i_width = i_width;
+ p_region->i_height = i_height;
+ p_region->i_level_comp = i_level_comp;
+ p_region->i_depth = i_depth;
+ p_region->i_clut = i_clut;
+
+ /* List of objects in the region */
+ i_processed_length = 10;
+ while( i_processed_length < i_segment_length )
+ {
+ dvbsub_objectdef_t *p_obj;
+
+ /* We create a new object */
+ p_region->i_object_defs++;
+ p_region->p_object_defs =
+ realloc( p_region->p_object_defs,
+ sizeof(dvbsub_objectdef_t) * p_region->i_object_defs );
+
+ /* We parse object properties */
+ p_obj = &p_region->p_object_defs[p_region->i_object_defs - 1];
+ p_obj->i_id = bs_read( s, 16 );
+ p_obj->i_type = bs_read( s, 2 );
+ bs_skip( s, 2 ); /* Provider */
+ p_obj->i_x = bs_read( s, 12 );
+ bs_skip( s, 4 ); /* Reserved */
+ p_obj->i_y = bs_read( s, 12 );
+ p_obj->psz_text = 0;
+
+ i_processed_length += 6;
+
+ if( ( p_obj->i_type == DVBSUB_OT_BASIC_CHAR ) ||
+ ( p_obj->i_type == DVBSUB_OT_COMPOSITE_STRING ) )
+ {
+ p_obj->i_fg_pc = bs_read( s, 8 );
+ p_obj->i_bg_pc = bs_read( s, 8 );
+ i_processed_length += 2;
+ }
+ }
+}
+
+/* ETSI 300 743 [7.2.1] */
+static void decode_display_definition( decoder_t *p_dec, bs_t *s )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ uint16_t i_segment_length;
+ uint16_t i_processed_length = 40;
+ dvbsub_display_t *p_display;
+ dvbsub_display_t *p_old = p_sys->p_display;
+ int i_version;
+
+ i_segment_length = bs_read( s, 16 );
+ i_version = bs_read( s, 4 );
+
+ /* Check version number */
+ if( p_old && ( p_old->i_version == i_version ) )
+ {
+ /* The definition did not change */
+ bs_skip( s, 8*i_segment_length - 4 );
+ return;
+ }
+
+#ifdef DEBUG_DVBSUB
+ msg_Dbg( p_dec, "new display definition: %i", i_version );
+#endif
+ p_display = malloc( sizeof(dvbsub_display_t) );
+ if( p_display )
+ {
+ /* We don't have this version of the display definition: Parse it */
+ p_display->i_version = i_version;
+ p_display->b_windowed = bs_read( s, 1 );
+ bs_skip( s, 3 ); /* Reserved bits */
+ p_display->i_width = bs_read( s, 16 )+1;
+ p_display->i_height = bs_read( s, 16 )+1;
+
+ if( p_display->b_windowed )
+ {
+#ifdef DEBUG_DVBSUB
+ msg_Dbg( p_dec, "display definition with offsets (windowed)" );
+#endif
+ /* Coordinates are measured from the top left corner */
+ p_display->i_x = bs_read( s, 16 );
+ p_display->i_max_x = bs_read( s, 16 );
+ p_display->i_y = bs_read( s, 16 );
+ p_display->i_max_y = bs_read( s, 16 );
+ i_processed_length += 64;
+ }
+ }
+
+ p_sys->p_display = p_display;
+ free( p_old );
+
+ if( i_processed_length != i_segment_length*8 )
+ {
+ msg_Err( p_dec, "processed length %d != segment length %d",
+ i_processed_length, i_segment_length );
+ }
+
+#ifdef DEBUG_DVBSUB
+ msg_Dbg( p_dec, "version: %d, width: %d, height: %d",
+ p_display->i_version, p_display->i_width, p_display->i_height );
+ if( p_display->b_windowed )
+ msg_Dbg( p_dec, "xmin: %d, xmax: %d, ymin: %d, ymax: %d",
+ p_display->i_x, p_display->i_max_x, p_display->i_y, p_display->i_max_y );
+#endif
+}
+
+static void dvbsub_render_pdata( decoder_t *, dvbsub_region_t *, int, int,
+ uint8_t *, int );
+static void dvbsub_pdata2bpp( bs_t *, uint8_t *, int, int * );
+static void dvbsub_pdata4bpp( bs_t *, uint8_t *, int, int * );
+static void dvbsub_pdata8bpp( bs_t *, uint8_t *, int, int * );
+
+static void decode_object( decoder_t *p_dec, bs_t *s )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ dvbsub_region_t *p_region;
+ int i_segment_length, i_coding_method, i_version, i_id, i;
+ bool b_non_modify_color;
+
+ /* ETSI 300-743 paragraph 7.2.4
+ * sync_byte, segment_type and page_id have already been processed.
+ */
+ i_segment_length = bs_read( s, 16 );
+ i_id = bs_read( s, 16 );
+ i_version = bs_read( s, 4 );
+ i_coding_method = bs_read( s, 2 );
+
+ if( i_coding_method > 1 )
+ {
+ msg_Dbg( p_dec, "unknown DVB subtitling coding %d is not handled!", i_coding_method );
+ bs_skip( s, 8 * (i_segment_length - 2) - 6 );
+ return;
+ }
+
+ /* Check if the object needs to be rendered in at least one
+ * of the regions */
+ for( p_region = p_sys->p_regions; p_region != NULL;
+ p_region = p_region->p_next )
+ {
+ for( i = 0; i < p_region->i_object_defs; i++ )
+ if( p_region->p_object_defs[i].i_id == i_id ) break;
+
+ if( i != p_region->i_object_defs ) break;
+ }
+ if( !p_region )
+ {
+ bs_skip( s, 8 * (i_segment_length - 2) - 6 );
+ return;
+ }
+
+#ifdef DEBUG_DVBSUB
+ msg_Dbg( p_dec, "new object: %i", i_id );
+#endif
+
+ b_non_modify_color = bs_read( s, 1 );
+ bs_skip( s, 1 ); /* Reserved */
+
+ if( i_coding_method == 0x00 )
+ {
+ int i_topfield, i_bottomfield;
+ uint8_t *p_topfield, *p_bottomfield;
+
+ i_topfield = bs_read( s, 16 );
+ i_bottomfield = bs_read( s, 16 );
+ p_topfield = s->p_start + bs_pos( s ) / 8;
+ p_bottomfield = p_topfield + i_topfield;
+
+ bs_skip( s, 8 * (i_segment_length - 7) );
+
+ /* Sanity check */
+ if( ( i_segment_length < ( i_topfield + i_bottomfield + 7 ) ) ||
+ ( ( p_topfield + i_topfield + i_bottomfield ) > s->p_end ) )
+ {
+ msg_Dbg( p_dec, "corrupted object data" );
+ return;
+ }
+
+ for( p_region = p_sys->p_regions; p_region != NULL;
+ p_region = p_region->p_next )
+ {
+ for( i = 0; i < p_region->i_object_defs; i++ )
+ {
+ if( p_region->p_object_defs[i].i_id != i_id ) continue;
+
+ dvbsub_render_pdata( p_dec, p_region,
+ p_region->p_object_defs[i].i_x,
+ p_region->p_object_defs[i].i_y,
+ p_topfield, i_topfield );
+
+ if( i_bottomfield )
+ {
+ dvbsub_render_pdata( p_dec, p_region,
+ p_region->p_object_defs[i].i_x,
+ p_region->p_object_defs[i].i_y + 1,
+ p_bottomfield, i_bottomfield );
+ }
+ else
+ {
+ /* Duplicate the top field */
+ dvbsub_render_pdata( p_dec, p_region,
+ p_region->p_object_defs[i].i_x,
+ p_region->p_object_defs[i].i_y + 1,
+ p_topfield, i_topfield );
+ }
+ }
+ }
+ }
+ else
+ {
+ /* DVB subtitling as characters */
+ int i_number_of_codes = bs_read( s, 8 );
+ uint8_t* p_start = s->p_start + bs_pos( s ) / 8;
+
+ /* Sanity check */
+ if( ( i_segment_length < ( i_number_of_codes*2 + 4 ) ) ||
+ ( ( p_start + i_number_of_codes*2 ) > s->p_end ) )
+ {
+ msg_Dbg( p_dec, "corrupted object data" );
+ return;
+ }
+
+ for( p_region = p_sys->p_regions; p_region != NULL;
+ p_region = p_region->p_next )
+ {
+ for( i = 0; i < p_region->i_object_defs; i++ )
+ {
+ int j;
+
+ if( p_region->p_object_defs[i].i_id != i_id ) continue;
+
+ p_region->p_object_defs[i].psz_text =
+ realloc( p_region->p_object_defs[i].psz_text,
+ i_number_of_codes + 1 );
+
+ /* FIXME 16bits -> char ??? See Preamble */
+ for( j = 0; j < i_number_of_codes; j++ )
+ {
+ p_region->p_object_defs[i].psz_text[j] = (char)(bs_read( s, 16 ) & 0xFF);
+ }
+ /* Null terminate the string */
+ p_region->p_object_defs[i].psz_text[j] = 0;
+ }
+ }
+ }
+
+#ifdef DEBUG_DVBSUB
+ msg_Dbg( p_dec, "end object: %i", i_id );
+#endif
+}
+
+static void dvbsub_render_pdata( decoder_t *p_dec, dvbsub_region_t *p_region,
+ int i_x, int i_y,
+ uint8_t *p_field, int i_field )
+{
+ uint8_t *p_pixbuf;
+ int i_offset = 0;
+ bs_t bs;
+
+ /* Sanity check */
+ if( !p_region->p_pixbuf )
+ {
+ msg_Err( p_dec, "region %i has no pixel buffer!", p_region->i_id );
+ return;
+ }
+ if( i_y < 0 || i_x < 0 || i_y >= p_region->i_height ||
+ i_x >= p_region->i_width )
+ {
+ msg_Dbg( p_dec, "invalid offset (%i,%i)", i_x, i_y );
+ return;
+ }
+
+ p_pixbuf = p_region->p_pixbuf + i_y * p_region->i_width;
+ bs_init( &bs, p_field, i_field );
+
+ while( !bs_eof( &bs ) )
+ {
+ /* Sanity check */
+ if( i_y >= p_region->i_height ) return;
+
+ switch( bs_read( &bs, 8 ) )
+ {
+ case 0x10:
+ dvbsub_pdata2bpp( &bs, p_pixbuf + i_x, p_region->i_width - i_x,
+ &i_offset );
+ break;
+
+ case 0x11:
+ dvbsub_pdata4bpp( &bs, p_pixbuf + i_x, p_region->i_width - i_x,
+ &i_offset );
+ break;
+
+ case 0x12:
+ dvbsub_pdata8bpp( &bs, p_pixbuf + i_x, p_region->i_width - i_x,
+ &i_offset );
+ break;
+
+ case 0x20:
+ case 0x21:
+ case 0x22:
+ /* We don't use map tables */
+ break;
+
+ case 0xf0: /* End of line code */
+ p_pixbuf += 2*p_region->i_width;
+ i_offset = 0; i_y += 2;
+ break;
+ }
+ }
+}
+
+static void dvbsub_pdata2bpp( bs_t *s, uint8_t *p, int i_width, int *pi_off )
+{
+ bool b_stop = false;
+
+ while( !b_stop && !bs_eof( s ) )
+ {
+ int i_count = 0, i_color = 0;
+
+ i_color = bs_read( s, 2 );
+ if( i_color != 0x00 )
+ {
+ i_count = 1;
+ }
+ else
+ {
+ if( bs_read( s, 1 ) == 0x01 ) // Switch1
+ {
+ i_count = 3 + bs_read( s, 3 );
+ i_color = bs_read( s, 2 );
+ }
+ else
+ {
+ if( bs_read( s, 1 ) == 0x00 ) //Switch2
+ {
+ switch( bs_read( s, 2 ) ) //Switch3
+ {
+ case 0x00:
+ b_stop = true;
+ break;
+ case 0x01:
+ i_count = 2;
+ break;
+ case 0x02:
+ i_count = 12 + bs_read( s, 4 );
+ i_color = bs_read( s, 2 );
+ break;
+ case 0x03:
+ i_count = 29 + bs_read( s, 8 );
+ i_color = bs_read( s, 2 );
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ /* 1 pixel color 0 */
+ i_count = 1;
+ }
+ }
+ }
+
+ if( !i_count ) continue;
+
+ /* Sanity check */
+ if( ( i_count + *pi_off ) > i_width ) break;
+
+ if( i_count == 1 ) p[*pi_off] = i_color;
+ else memset( ( p + *pi_off ), i_color, i_count );
+
+ (*pi_off) += i_count;
+ }
+
+ bs_align( s );
+}
+
+static void dvbsub_pdata4bpp( bs_t *s, uint8_t *p, int i_width, int *pi_off )
+{
+ bool b_stop = false;
+
+ while( !b_stop && !bs_eof( s ) )
+ {
+ int i_count = 0, i_color = 0;
+
+ i_color = bs_read( s, 4 );
+ if( i_color != 0x00 )
+ {
+ /* Add 1 pixel */
+ i_count = 1;
+ }
+ else
+ {
+ if( bs_read( s, 1 ) == 0x00 ) // Switch1
+ {
+ if( bs_show( s, 3 ) != 0x00 )
+ {
+ i_count = 2 + bs_read( s, 3 );
+ }
+ else
+ {
+ bs_skip( s, 3 );
+ b_stop = true;
+ }
+ }
+ else
+ {
+ if( bs_read( s, 1 ) == 0x00) //Switch2
+ {
+ i_count = 4 + bs_read( s, 2 );
+ i_color = bs_read( s, 4 );
+ }
+ else
+ {
+ switch ( bs_read( s, 2 ) ) //Switch3
+ {
+ case 0x0:
+ i_count = 1;
+ break;
+ case 0x1:
+ i_count = 2;
+ break;
+ case 0x2:
+ i_count = 9 + bs_read( s, 4 );
+ i_color = bs_read( s, 4 );
+ break;
+ case 0x3:
+ i_count= 25 + bs_read( s, 8 );
+ i_color = bs_read( s, 4 );
+ break;
+ }
+ }
+ }
+ }
+
+ if( !i_count ) continue;
+
+ /* Sanity check */
+ if( ( i_count + *pi_off ) > i_width ) break;
+
+ if( i_count == 1 ) p[*pi_off] = i_color;
+ else memset( ( p + *pi_off ), i_color, i_count );
+
+ (*pi_off) += i_count;
+ }
+
+ bs_align( s );
+}
+
+static void dvbsub_pdata8bpp( bs_t *s, uint8_t *p, int i_width, int *pi_off )
+{
+ bool b_stop = false;
+
+ while( !b_stop && !bs_eof( s ) )
+ {
+ int i_count = 0, i_color = 0;
+
+ i_color = bs_read( s, 8 );
+ if( i_color != 0x00 )
+ {
+ /* Add 1 pixel */
+ i_count = 1;
+ }
+ else
+ {
+ if( bs_read( s, 1 ) == 0x00 ) // Switch1
+ {
+ if( bs_show( s, 7 ) != 0x00 )
+ {
+ i_count = bs_read( s, 7 );
+ }
+ else
+ {
+ bs_skip( s, 7 );
+ b_stop = true;
+ }
+ }
+ else
+ {
+ i_count = bs_read( s, 7 );
+ i_color = bs_read( s, 8 );
+ }
+ }
+
+ if( !i_count ) continue;
+
+ /* Sanity check */
+ if( ( i_count + *pi_off ) > i_width ) break;
+
+ if( i_count == 1 ) p[*pi_off] = i_color;
+ else memset( ( p + *pi_off ), i_color, i_count );
+
+ (*pi_off) += i_count;
+ }
+
+ bs_align( s );
+}
+
+static void free_all( decoder_t *p_dec )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ dvbsub_region_t *p_reg, *p_reg_next;
+ dvbsub_clut_t *p_clut, *p_clut_next;
+
+ free( p_sys->p_display );
+
+ for( p_clut = p_sys->p_cluts; p_clut != NULL; p_clut = p_clut_next )
+ {
+ p_clut_next = p_clut->p_next;
+ free( p_clut );
+ }
+ p_sys->p_cluts = NULL;
+
+ for( p_reg = p_sys->p_regions; p_reg != NULL; p_reg = p_reg_next )
+ {
+ int i;
+
+ p_reg_next = p_reg->p_next;
+ for( i = 0; i < p_reg->i_object_defs; i++ )
+ free( p_reg->p_object_defs[i].psz_text );
+ if( p_reg->i_object_defs ) free( p_reg->p_object_defs );
+ free( p_reg->p_pixbuf );
+ free( p_reg );
+ }
+ p_sys->p_regions = NULL;
+
+ if( p_sys->p_page )
+ {
+ if( p_sys->p_page->i_region_defs )
+ free( p_sys->p_page->p_region_defs );
+ free( p_sys->p_page );
+ }
+ p_sys->p_page = NULL;
+}
+
+static subpicture_t *render( decoder_t *p_dec )
+{
+ decoder_sys_t *p_sys = p_dec->p_sys;
+ subpicture_t *p_spu;
+ subpicture_region_t **pp_spu_region;
+ int i, j, i_timeout = 0;
+
+ /* Allocate the subpicture internal data. */
+ p_spu = p_dec->pf_spu_buffer_new( p_dec );
+ if( !p_spu ) return NULL;
+
+ pp_spu_region = &p_spu->p_region;
+
+ /* Loop on region definitions */
+#ifdef DEBUG_DVBSUB
+ if( p_sys->p_page )
+ msg_Dbg( p_dec, "rendering %i regions", p_sys->p_page->i_region_defs );
+#endif
+
+ for( i = 0; p_sys->p_page && ( i < p_sys->p_page->i_region_defs ); i++ )
+ {
+ dvbsub_region_t *p_region;
+ dvbsub_regiondef_t *p_regiondef;
+ dvbsub_clut_t *p_clut;
+ dvbsub_color_t *p_color;
+ subpicture_region_t *p_spu_region;
+ uint8_t *p_src, *p_dst;
+ video_format_t fmt;
+ int i_pitch;
+
+ i_timeout = p_sys->p_page->i_timeout;
+
+ p_regiondef = &p_sys->p_page->p_region_defs[i];
+
+#ifdef DEBUG_DVBSUB
+ msg_Dbg( p_dec, "rendering region %i (%i,%i)", i,
+ p_regiondef->i_x, p_regiondef->i_y );
+#endif
+
+ /* Find associated region */
+ for( p_region = p_sys->p_regions; p_region != NULL;
+ p_region = p_region->p_next )
+ {
+ if( p_regiondef->i_id == p_region->i_id ) break;
+ }
+
+ if( !p_region )
+ {
+ msg_Dbg( p_dec, "region %i not found", p_regiondef->i_id );
+ continue;
+ }
+
+ /* Find associated CLUT */
+ for( p_clut = p_sys->p_cluts; p_clut != NULL; p_clut = p_clut->p_next )
+ {
+ if( p_region->i_clut == p_clut->i_id ) break;
+ }
+ if( !p_clut )
+ {
+ msg_Dbg( p_dec, "clut %i not found", p_region->i_clut );
+ continue;
+ }
+
+ /* FIXME: don't create a subpicture region with VLC_FOURCC YUVP
+ * when it actually is a TEXT region */
+
+ /* Create new SPU region */
+ memset( &fmt, 0, sizeof(video_format_t) );
+ fmt.i_chroma = VLC_FOURCC('Y','U','V','P');
+ fmt.i_aspect = 0; /* 0 means use aspect ratio of background video */
+ fmt.i_width = fmt.i_visible_width = p_region->i_width;
+ fmt.i_height = fmt.i_visible_height = p_region->i_height;
+ fmt.i_x_offset = fmt.i_y_offset = 0;
+ p_spu_region = p_spu->pf_create_region( VLC_OBJECT(p_dec), &fmt );
+ if( !p_spu_region )
+ {
+ msg_Err( p_dec, "cannot allocate SPU region" );
+ continue;
+ }
+ p_spu_region->i_x = p_regiondef->i_x;
+ p_spu_region->i_y = p_regiondef->i_y;
+ p_spu_region->i_align = p_sys->i_spu_position;
+ *pp_spu_region = p_spu_region;
+ pp_spu_region = &p_spu_region->p_next;
+
+ /* Build palette */
+ fmt.p_palette->i_entries = ( p_region->i_depth == 1 ) ? 4 :
+ ( ( p_region->i_depth == 2 ) ? 16 : 256 );
+ p_color = ( p_region->i_depth == 1 ) ? p_clut->c_2b :
+ ( ( p_region->i_depth == 2 ) ? p_clut->c_4b : p_clut->c_8b );
+ for( j = 0; j < fmt.p_palette->i_entries; j++ )
+ {
+ fmt.p_palette->palette[j][0] = p_color[j].Y;
+ fmt.p_palette->palette[j][1] = p_color[j].Cb; /* U == Cb */
+ fmt.p_palette->palette[j][2] = p_color[j].Cr; /* V == Cr */
+ fmt.p_palette->palette[j][3] = 0xff - p_color[j].T;
+ }
+
+ p_src = p_region->p_pixbuf;
+ p_dst = p_spu_region->picture.Y_PIXELS;
+ i_pitch = p_spu_region->picture.Y_PITCH;
+
+ /* Copy pixel buffer */
+ for( j = 0; j < p_region->i_height; j++ )
+ {
+ memcpy( p_dst, p_src, p_region->i_width );
+ p_src += p_region->i_width;
+ p_dst += i_pitch;
+ }
+
+ /* Check subtitles encoded as strings of characters
+ * (since there are not rendered in the pixbuffer) */
+ for( j = 0; j < p_region->i_object_defs; j++ )
+ {
+ dvbsub_objectdef_t *p_object_def = &p_region->p_object_defs[j];
+
+ if( ( p_object_def->i_type != 1 ) || !p_object_def->psz_text )
+ continue;
+
+ /* Create new SPU region */
+ memset( &fmt, 0, sizeof(video_format_t) );
+ fmt.i_chroma = VLC_FOURCC('T','E','X','T');
+ fmt.i_aspect = VOUT_ASPECT_FACTOR;
+ fmt.i_width = fmt.i_visible_width = p_region->i_width;
+ fmt.i_height = fmt.i_visible_height = p_region->i_height;
+ fmt.i_x_offset = fmt.i_y_offset = 0;
+ p_spu_region = p_spu->pf_create_region( VLC_OBJECT(p_dec), &fmt );
+ if( !p_region )
+ {
+ msg_Err( p_dec, "cannot allocate SPU region" );
+ continue;
+ }
+
+ p_spu_region->psz_text = strdup( p_object_def->psz_text );
+ p_spu_region->i_x = p_regiondef->i_x + p_object_def->i_x;
+ p_spu_region->i_y = p_regiondef->i_y + p_object_def->i_y;
+ p_spu_region->i_align = p_sys->i_spu_position;
+ *pp_spu_region = p_spu_region;
+ pp_spu_region = &p_spu_region->p_next;
+ }
+ }
+
+ /* Set the pf_render callback */
+ p_spu->i_start = (mtime_t) p_sys->i_pts;
+ //p_spu->i_stop = (mtime_t) 0;
+ p_spu->b_ephemer = true;
+ p_spu->b_pausable = true;
+ //p_spu->b_fade = true;
+ //p_spu->i_stop = p_spu->i_start + (mtime_t) (i_timeout * 1000000);
+
+ /* Correct positioning of SPU */
+ p_spu->b_absolute = p_sys->b_absolute;
+ p_spu->i_x = p_sys->i_spu_x;
+ p_spu->i_y = p_sys->i_spu_y;
+ p_spu->i_original_picture_width = 720;
+ p_spu->i_original_picture_height = 576;
+
+ if( p_sys->p_display )
+ {
+ p_spu->i_original_picture_width = p_sys->p_display->i_width;
+ p_spu->i_original_picture_height = p_sys->p_display->i_height;
+
+ if( p_sys->p_display->b_windowed )
+ {
+ /* TODO: check that this actually works */
+ p_spu->i_original_picture_width = p_sys->p_display->i_max_x - p_sys->p_display->i_x;
+ p_spu->i_original_picture_height = p_sys->p_display->i_max_y - p_sys->p_display->i_y;
+ p_spu->i_x += p_sys->p_display->i_x;
+ p_spu->i_y += p_sys->p_display->i_y;
+ }
+ }
+
+ return p_spu;
+}
+
+/*****************************************************************************
+ * encoder_sys_t : encoder descriptor
+ *****************************************************************************/
+typedef struct encoder_region_t
+{
+ int i_width;
+ int i_height;
+
+} encoder_region_t;
+
+struct encoder_sys_t
+{
+ unsigned int i_page_ver;
+ unsigned int i_region_ver;
+ unsigned int i_clut_ver;
+
+ int i_regions;
+ encoder_region_t *p_regions;
+
+ mtime_t i_pts;
+
+ /* subpicture positioning */
+ int i_offset_x;
+ int i_offset_y;
+};
+
+static void encode_page_composition( encoder_t *, bs_t *, subpicture_t * );
+static void encode_clut( encoder_t *, bs_t *, subpicture_t * );
+static void encode_region_composition( encoder_t *, bs_t *, subpicture_t * );
+static void encode_object( encoder_t *, bs_t *, subpicture_t * );
+
+/*****************************************************************************
+ * OpenEncoder: probe the encoder and return score
+ *****************************************************************************/
+static int OpenEncoder( vlc_object_t *p_this )
+{
+ encoder_t *p_enc = (encoder_t *)p_this;
+ encoder_sys_t *p_sys;
+ vlc_value_t val;
+
+ if( ( p_enc->fmt_out.i_codec != VLC_FOURCC('d','v','b','s') ) &&
+ !p_enc->b_force )
+ {
+ return VLC_EGENERIC;