Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
vlc-2-2
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Redmine
Redmine
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
videolan
vlc-2-2
Commits
8d92c1ea
Commit
8d92c1ea
authored
Dec 12, 2010
by
Laurent Aimar
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
No functionnal changes (spu).
Reorder the function declarations to avoid forward declarations.
parent
96b23172
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
1126 additions
and
1172 deletions
+1126
-1172
src/video_output/vout_subpictures.c
src/video_output/vout_subpictures.c
+1126
-1172
No files found.
src/video_output/vout_subpictures.c
View file @
8d92c1ea
...
...
@@ -65,12 +65,6 @@ typedef struct
}
spu_heap_t
;
static
void
SpuHeapInit
(
spu_heap_t
*
);
static
int
SpuHeapPush
(
spu_heap_t
*
,
subpicture_t
*
);
static
void
SpuHeapDeleteAt
(
spu_heap_t
*
,
int
i_index
);
static
int
SpuHeapDeleteSubpicture
(
spu_heap_t
*
,
subpicture_t
*
);
static
void
SpuHeapClean
(
spu_heap_t
*
p_heap
);
struct
spu_private_t
{
vlc_mutex_t
lock
;
/* lock to protect all followings fields */
...
...
@@ -98,416 +92,526 @@ struct spu_private_t
mtime_t
i_last_sort_date
;
};
/* */
typedef
struct
/* Subpicture rendered flag
* FIXME ? it could be moved to private ? */
#define SUBPICTURE_RENDERED (0x1000)
#if SUBPICTURE_RENDERED < SUBPICTURE_ALIGN_MASK
# error SUBPICTURE_RENDERED too low
#endif
/*****************************************************************************
* heap managment
*****************************************************************************/
static
void
SpuHeapInit
(
spu_heap_t
*
p_heap
)
{
int
w
;
int
h
;
}
spu_scale_t
;
static
spu_scale_t
spu_scale_create
(
int
w
,
int
h
);
static
spu_scale_t
spu_scale_unit
(
void
);
static
spu_scale_t
spu_scale_createq
(
int
wn
,
int
wd
,
int
hn
,
int
hd
);
static
int
spu_scale_w
(
int
v
,
const
spu_scale_t
s
);
static
int
spu_scale_h
(
int
v
,
const
spu_scale_t
s
);
static
int
spu_invscale_w
(
int
v
,
const
spu_scale_t
s
);
static
int
spu_invscale_h
(
int
v
,
const
spu_scale_t
s
);
for
(
int
i
=
0
;
i
<
VOUT_MAX_SUBPICTURES
;
i
++
)
{
spu_heap_entry_t
*
e
=
&
p_heap
->
p_entry
[
i
];
typedef
struct
e
->
p_subpicture
=
NULL
;
e
->
b_reject
=
false
;
}
}
static
int
SpuHeapPush
(
spu_heap_t
*
p_heap
,
subpicture_t
*
p_subpic
)
{
int
i_x
;
int
i_y
;
int
i_width
;
int
i_height
;
for
(
int
i
=
0
;
i
<
VOUT_MAX_SUBPICTURES
;
i
++
)
{
spu_heap_entry_t
*
e
=
&
p_heap
->
p_entry
[
i
];
spu_scale_t
scale
;
}
spu_area_t
;
if
(
e
->
p_subpicture
)
continue
;
static
spu_area_t
spu_area_create
(
int
x
,
int
y
,
int
w
,
int
h
,
spu_scale_t
);
static
spu_area_t
spu_area_scaled
(
spu_area_t
);
static
spu_area_t
spu_area_unscaled
(
spu_area_t
,
spu_scale_t
);
static
bool
spu_area_overlap
(
spu_area_t
,
spu_area_t
);
e
->
p_subpicture
=
p_subpic
;
e
->
b_reject
=
false
;
return
VLC_SUCCESS
;
}
return
VLC_EGENERIC
;
}
static
void
SpuHeapDeleteAt
(
spu_heap_t
*
p_heap
,
int
i_index
)
{
spu_heap_entry_t
*
e
=
&
p_heap
->
p_entry
[
i_index
];
/* Subpicture rendered flag
* FIXME ? it could be moved to private ? */
#define SUBPICTURE_RENDERED (0x1000)
#if SUBPICTURE_RENDERED < SUBPICTURE_ALIGN_MASK
# error SUBPICTURE_RENDERED too low
#endif
if
(
e
->
p_subpicture
)
subpicture_Delete
(
e
->
p_subpicture
);
#define SCALE_UNIT (1000)
e
->
p_subpicture
=
NULL
;
}
static
void
SubpictureChain
(
subpicture_t
**
pp_head
,
subpicture_t
*
p_subpic
);
static
int
SubpictureCmp
(
const
void
*
s0
,
const
void
*
s1
);
static
int
SpuHeapDeleteSubpicture
(
spu_heap_t
*
p_heap
,
subpicture_t
*
p_subpic
)
{
for
(
int
i
=
0
;
i
<
VOUT_MAX_SUBPICTURES
;
i
++
)
{
spu_heap_entry_t
*
e
=
&
p_heap
->
p_entry
[
i
];
static
void
SpuRenderRegion
(
spu_t
*
,
subpicture_region_t
**
,
spu_area_t
*
,
subpicture_t
*
,
subpicture_region_t
*
,
const
spu_scale_t
scale_size
,
const
video_format_t
*
p_fmt
,
const
spu_area_t
*
p_subtitle_area
,
int
i_subtitle_area
,
mtime_t
render_date
);
if
(
e
->
p_subpicture
!=
p_subpic
)
continue
;
SpuHeapDeleteAt
(
p_heap
,
i
);
return
VLC_SUCCESS
;
}
return
VLC_EGENERIC
;
}
static
void
UpdateSPU
(
spu_t
*
,
vlc_object_t
*
);
static
int
CropCallback
(
vlc_object_t
*
,
char
const
*
,
vlc_value_t
,
vlc_value_t
,
void
*
);
static
void
SpuHeapClean
(
spu_heap_t
*
p_heap
)
{
for
(
int
i
=
0
;
i
<
VOUT_MAX_SUBPICTURES
;
i
++
)
{
spu_heap_entry_t
*
e
=
&
p_heap
->
p_entry
[
i
];
if
(
e
->
p_subpicture
)
subpicture_Delete
(
e
->
p_subpicture
);
}
}
/* Buffer allocation for SPU filter (blend, scale, ...) */
struct
filter_owner_sys_t
{
spu_t
*
p_spu
;
int
i_channel
;
};
static
int
spu_get_attachments
(
filter_t
*
,
input_attachment_t
***
,
int
*
);
static
picture_t
*
spu_new_video_buffer
(
filter_t
*
);
static
void
spu_del_video_buffer
(
filter_t
*
,
picture_t
*
);
/* Buffer aloccation fir SUB filter */
static
int
SubFilterAllocationInit
(
filter_t
*
,
void
*
);
static
void
SubFilterAllocationClean
(
filter_t
*
);
static
void
FilterRelease
(
filter_t
*
p_filter
)
{
if
(
p_filter
->
p_module
)
module_unneed
(
p_filter
,
p_filter
->
p_module
);
if
(
p_filter
->
p_owner
)
free
(
p_filter
->
p_owner
);
/* */
static
void
SpuRenderCreateAndLoadText
(
spu_t
*
);
static
void
SpuRenderCreateAndLoadScale
(
spu_t
*
);
static
void
FilterRelease
(
filter_t
*
p_filter
);
vlc_object_release
(
p_filter
);
}
/*****************************************************************************
* Public API
*****************************************************************************/
static
picture_t
*
spu_new_video_buffer
(
filter_t
*
p_filter
)
{
const
video_format_t
*
p_fmt
=
&
p_filter
->
fmt_out
.
video
;
#undef spu_Create
/**
* Creates the subpicture unit
*
* \param p_this the parent object which creates the subpicture unit
*/
spu_t
*
spu_Create
(
vlc_object_t
*
p_this
)
VLC_UNUSED
(
p_filter
);
return
picture_NewFromFormat
(
p_fmt
);
}
static
void
spu_del_video_buffer
(
filter_t
*
p_filter
,
picture_t
*
p_picture
)
{
spu_t
*
p_spu
;
spu_private_t
*
p_sys
;
VLC_UNUSED
(
p_filter
);
picture_Release
(
p_picture
);
}
p_spu
=
vlc_custom_create
(
p_this
,
sizeof
(
spu_t
)
+
sizeof
(
spu_private_t
)
,
VLC_OBJECT_GENERIC
,
"subpicture"
);
if
(
!
p_spu
)
return
NULL
;
vlc_object_attach
(
p_spu
,
p_this
)
;
static
int
spu_get_attachments
(
filter_t
*
p_filter
,
input_attachment_t
***
ppp_attachment
,
int
*
pi_attachment
)
{
spu_t
*
p_spu
=
p_filter
->
p_owner
->
p_spu
;
/* Initialize spu fields */
p_spu
->
p
=
p_sys
=
(
spu_private_t
*
)
&
p_spu
[
1
];
int
i_ret
=
VLC_EGENERIC
;
if
(
p_spu
->
p
->
p_input
)
i_ret
=
input_Control
(
(
input_thread_t
*
)
p_spu
->
p
->
p_input
,
INPUT_GET_ATTACHMENTS
,
ppp_attachment
,
pi_attachment
);
return
i_ret
;
}
/* Initialize private fields */
vlc_mutex_init
(
&
p_sys
->
lock
);
static
void
SpuRenderCreateAndLoadText
(
spu_t
*
p_spu
)
{
filter_t
*
p_text
;
SpuHeapInit
(
&
p_sys
->
heap
);
assert
(
!
p_spu
->
p
->
p_text
);
p_sys
->
p_text
=
NULL
;
p_sys
->
p_scale
=
NULL
;
p_sys
->
p_scale_yuvp
=
NULL
;
p_spu
->
p
->
p_text
=
p_text
=
vlc_custom_create
(
p_spu
,
sizeof
(
filter_t
),
VLC_OBJECT_GENERIC
,
"spu text"
);
if
(
!
p_text
)
return
;
p_sys
->
i_margin
=
var_InheritInteger
(
p_spu
,
"sub-margin"
);
p_text
->
p_owner
=
xmalloc
(
sizeof
(
*
p_text
->
p_owner
)
);
p_text
->
p_owner
->
p_spu
=
p_spu
;
/* Register the default subpicture channel */
p_sys
->
i_channel
=
SPU_DEFAULT_CHANNEL
+
1
;
es_format_Init
(
&
p_text
->
fmt_in
,
VIDEO_ES
,
0
);
p_sys
->
psz_chain_update
=
NULL
;
vlc_mutex_init
(
&
p_sys
->
chain_lock
);
p_sys
->
p_chain
=
filter_chain_New
(
p_spu
,
"sub filter"
,
false
,
SubFilterAllocationInit
,
SubFilterAllocationClean
,
p_spu
);
es_format_Init
(
&
p_text
->
fmt_out
,
VIDEO_ES
,
0
);
p_text
->
fmt_out
.
video
.
i_width
=
p_text
->
fmt_out
.
video
.
i_visible_width
=
32
;
p_text
->
fmt_out
.
video
.
i_height
=
p_text
->
fmt_out
.
video
.
i_visible_height
=
32
;
/* Load text and scale module */
SpuRenderCreateAndLoadText
(
p_spu
);
SpuRenderCreateAndLoadScale
(
p_spu
);
p_text
->
pf_get_attachments
=
spu_get_attachments
;
/* */
p_sys
->
i_last_sort_date
=
-
1
;
vlc_object_attach
(
p_text
,
p_spu
);
return
p_spu
;
/* FIXME TOCHECK shouldn't module_need( , , psz_modulename, false ) do the
* same than these 2 calls ? */
char
*
psz_modulename
=
var_CreateGetString
(
p_spu
,
"text-renderer"
);
if
(
psz_modulename
&&
*
psz_modulename
)
{
p_text
->
p_module
=
module_need
(
p_text
,
"text renderer"
,
psz_modulename
,
true
);
}
free
(
psz_modulename
);
if
(
!
p_text
->
p_module
)
p_text
->
p_module
=
module_need
(
p_text
,
"text renderer"
,
NULL
,
false
);
/* Create a few variables used for enhanced text rendering */
var_Create
(
p_text
,
"spu-duration"
,
VLC_VAR_TIME
);
var_Create
(
p_text
,
"spu-elapsed"
,
VLC_VAR_TIME
);
var_Create
(
p_text
,
"text-rerender"
,
VLC_VAR_BOOL
);
var_Create
(
p_text
,
"scale"
,
VLC_VAR_INTEGER
);
}
/**
* Destroy the subpicture unit
*
* \param p_this the parent object which destroys the subpicture unit
*/
void
spu_Destroy
(
spu_t
*
p_spu
)
static
filter_t
*
CreateAndLoadScale
(
vlc_object_t
*
p_obj
,
vlc_fourcc_t
i_src_chroma
,
vlc_fourcc_t
i_dst_chroma
,
bool
b_resize
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
if
(
p_sys
->
p_text
)
FilterRelease
(
p_sys
->
p_text
);
filter_t
*
p_scale
;
if
(
p_sys
->
p_scale_yuvp
)
FilterRelease
(
p_sys
->
p_scale_yuvp
);
p_scale
=
vlc_custom_create
(
p_obj
,
sizeof
(
filter_t
),
VLC_OBJECT_GENERIC
,
"scale"
);
if
(
!
p_scale
)
return
NULL
;
if
(
p_sys
->
p_scale
)
FilterRelease
(
p_sys
->
p_scale
);
es_format_Init
(
&
p_scale
->
fmt_in
,
VIDEO_ES
,
0
);
p_scale
->
fmt_in
.
video
.
i_chroma
=
i_src_chroma
;
p_scale
->
fmt_in
.
video
.
i_width
=
p_scale
->
fmt_in
.
video
.
i_height
=
32
;
filter_chain_Delete
(
p_sys
->
p_chain
);
vlc_mutex_destroy
(
&
p_sys
->
chain_lock
);
free
(
p_sys
->
psz_chain_update
);
es_format_Init
(
&
p_scale
->
fmt_out
,
VIDEO_ES
,
0
);
p_scale
->
fmt_out
.
video
.
i_chroma
=
i_dst_chroma
;
p_scale
->
fmt_out
.
video
.
i_width
=
p_scale
->
fmt_out
.
video
.
i_height
=
b_resize
?
16
:
32
;
/* Destroy all remaining subpictures */
SpuHeapClean
(
&
p_sys
->
heap
)
;
p_scale
->
pf_video_buffer_new
=
spu_new_video_buffer
;
p_scale
->
pf_video_buffer_del
=
spu_del_video_buffer
;
vlc_mutex_destroy
(
&
p_sys
->
lock
);
vlc_object_attach
(
p_scale
,
p_obj
);
p_scale
->
p_module
=
module_need
(
p_scale
,
"video filter2"
,
NULL
,
false
);
vlc_object_release
(
p_spu
);
return
p_scale
;
}
static
void
SpuRenderCreateAndLoadScale
(
spu_t
*
p_spu
)
{
assert
(
!
p_spu
->
p
->
p_scale
);
assert
(
!
p_spu
->
p
->
p_scale_yuvp
);
/* XXX p_spu->p_scale is used for all conversion/scaling except yuvp to
* yuva/rgba */
p_spu
->
p
->
p_scale
=
CreateAndLoadScale
(
VLC_OBJECT
(
p_spu
),
VLC_CODEC_YUVA
,
VLC_CODEC_YUVA
,
true
);
/* This one is used for YUVP to YUVA/RGBA without scaling
* FIXME rename it */
p_spu
->
p
->
p_scale_yuvp
=
CreateAndLoadScale
(
VLC_OBJECT
(
p_spu
),
VLC_CODEC_YUVP
,
VLC_CODEC_YUVA
,
false
);
}
/**
* Attach/Detach the SPU from any input
*
* \param p_this the object in which to destroy the subpicture unit
* \param b_attach to select attach or detach
*/
void
spu_Attach
(
spu_t
*
p_spu
,
vlc_object_t
*
p_input
,
bool
b_attach
)
static
void
SpuRenderText
(
spu_t
*
p_spu
,
bool
*
pb_rerender_text
,
subpicture_t
*
p_subpic
,
subpicture_region_t
*
p_region
,
int
i_min_scale_ratio
,
mtime_t
render_date
)
{
if
(
b_attach
)
{
UpdateSPU
(
p_spu
,
p_input
);
var_Create
(
p_input
,
"highlight"
,
VLC_VAR_BOOL
);
var_AddCallback
(
p_input
,
"highlight"
,
CropCallback
,
p_spu
);
filter_t
*
p_text
=
p_spu
->
p
->
p_text
;
vlc_mutex_lock
(
&
p_spu
->
p
->
lock
);
p_spu
->
p
->
p_input
=
p_input
;
assert
(
p_region
->
fmt
.
i_chroma
==
VLC_CODEC_TEXT
);
FilterRelease
(
p_spu
->
p
->
p_text
);
p_spu
->
p
->
p_text
=
NULL
;
SpuRenderCreateAndLoadText
(
p_spu
);
if
(
!
p_text
||
!
p_text
->
p_module
)
goto
exit
;
vlc_mutex_unlock
(
&
p_spu
->
p
->
lock
);
/* Setup 3 variables which can be used to render
* time-dependent text (and effects). The first indicates
* the total amount of time the text will be on screen,
* the second the amount of time it has already been on
* screen (can be a negative value as text is layed out
* before it is rendered) and the third is a feedback
* variable from the renderer - if the renderer sets it
* then this particular text is time-dependent, eg. the
* visual progress bar inside the text in karaoke and the
* text needs to be rendered multiple times in order for
* the effect to work - we therefore need to return the
* region to its original state at the end of the loop,
* instead of leaving it in YUVA or YUVP.
* Any renderer which is unaware of how to render
* time-dependent text can happily ignore the variables
* and render the text the same as usual - it should at
* least show up on screen, but the effect won't change
* the text over time.
*/
var_SetTime
(
p_text
,
"spu-duration"
,
p_subpic
->
i_stop
-
p_subpic
->
i_start
);
var_SetTime
(
p_text
,
"spu-elapsed"
,
render_date
);
var_SetBool
(
p_text
,
"text-rerender"
,
false
);
var_SetInteger
(
p_text
,
"scale"
,
i_min_scale_ratio
);
if
(
p_text
->
pf_render_html
&&
p_region
->
psz_html
)
{
p_text
->
pf_render_html
(
p_text
,
p_region
,
p_region
);
}
else
else
if
(
p_text
->
pf_render_text
)
{
vlc_mutex_lock
(
&
p_spu
->
p
->
lock
);
p_spu
->
p
->
p_input
=
NULL
;
vlc_mutex_unlock
(
&
p_spu
->
p
->
lock
);
/* Delete callbacks */
var_DelCallback
(
p_input
,
"highlight"
,
CropCallback
,
p_spu
);
var_Destroy
(
p_input
,
"highlight"
);
p_text
->
pf_render_text
(
p_text
,
p_region
,
p_region
);
}
*
pb_rerender_text
=
var_GetBool
(
p_text
,
"text-rerender"
);
exit:
p_region
->
i_align
|=
SUBPICTURE_RENDERED
;
}
/**
*
Inform the SPU filters of mouse event
*
A few scale functions helpers.
*/
int
spu_ProcessMouse
(
spu_t
*
p_spu
,
const
vlc_mouse_t
*
p_mouse
,
const
video_format_t
*
p_fmt
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
vlc_mutex_lock
(
&
p_sys
->
chain_lock
);
filter_chain_MouseEvent
(
p_sys
->
p_chain
,
p_mouse
,
p_fmt
);
vlc_mutex_unlock
(
&
p_sys
->
chain_lock
);
#define SCALE_UNIT (1000)
typedef
struct
{
int
w
;
int
h
;
}
spu_scale_t
;
return
VLC_SUCCESS
;
static
spu_scale_t
spu_scale_create
(
int
w
,
int
h
)
{
spu_scale_t
s
=
{
.
w
=
w
,
.
h
=
h
};
if
(
s
.
w
<=
0
)
s
.
w
=
SCALE_UNIT
;
if
(
s
.
h
<=
0
)
s
.
h
=
SCALE_UNIT
;
return
s
;
}
static
spu_scale_t
spu_scale_unit
(
void
)
{
return
spu_scale_create
(
SCALE_UNIT
,
SCALE_UNIT
);
}
static
spu_scale_t
spu_scale_createq
(
int
wn
,
int
wd
,
int
hn
,
int
hd
)
{
return
spu_scale_create
(
wn
*
SCALE_UNIT
/
wd
,
hn
*
SCALE_UNIT
/
hd
);
}
static
int
spu_scale_w
(
int
v
,
const
spu_scale_t
s
)
{
return
v
*
s
.
w
/
SCALE_UNIT
;
}
static
int
spu_scale_h
(
int
v
,
const
spu_scale_t
s
)
{
return
v
*
s
.
h
/
SCALE_UNIT
;
}
static
int
spu_invscale_w
(
int
v
,
const
spu_scale_t
s
)
{
return
v
*
SCALE_UNIT
/
s
.
w
;
}
static
int
spu_invscale_h
(
int
v
,
const
spu_scale_t
s
)
{
return
v
*
SCALE_UNIT
/
s
.
h
;
}
/**
* 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
* A few area functions helpers
*/
void
spu_PutSubpicture
(
spu_t
*
p_spu
,
subpicture_t
*
p_subpic
)
typedef
struct
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
/* SPU_DEFAULT_CHANNEL always reset itself */
if
(
p_subpic
->
i_channel
==
SPU_DEFAULT_CHANNEL
)
spu_ClearChannel
(
p_spu
,
SPU_DEFAULT_CHANNEL
);
int
i_x
;
int
i_y
;
int
i_width
;
int
i_height
;
/* 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
);
spu_scale_t
scale
;
}
spu_area_t
;
/* */
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
);
static
spu_area_t
spu_area_create
(
int
x
,
int
y
,
int
w
,
int
h
,
spu_scale_t
s
)
{
spu_area_t
a
=
{
.
i_x
=
x
,
.
i_y
=
y
,
.
i_width
=
w
,
.
i_height
=
h
,
.
scale
=
s
};
return
a
;
}
/**
* This function renders all sub picture units in the list.
*/
static
subpicture_t
*
SpuRenderSubpictures
(
spu_t
*
p_spu
,
const
video_format_t
*
p_fmt_dst
,
subpicture_t
*
p_subpic_list
,
const
video_format_t
*
p_fmt_src
,
mtime_t
render_subtitle_date
,
mtime_t
render_osd_date
)
static
spu_area_t
spu_area_scaled
(
spu_area_t
a
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
if
(
a
.
scale
.
w
==
SCALE_UNIT
&&
a
.
scale
.
h
==
SCALE_UNIT
)
return
a
;
const
int
i_source_video_width
=
p_fmt_src
->
i_width
;
const
int
i_source_video_height
=
p_fmt_src
->
i_height
;
a
.
i_x
=
spu_scale_w
(
a
.
i_x
,
a
.
scale
)
;
a
.
i_y
=
spu_scale_h
(
a
.
i_y
,
a
.
scale
)
;
unsigned
int
i_subpicture
;
subpicture_t
*
pp_subpicture
[
VOUT_MAX_SUBPICTURES
]
;
a
.
i_width
=
spu_scale_w
(
a
.
i_width
,
a
.
scale
)
;
a
.
i_height
=
spu_scale_h
(
a
.
i_height
,
a
.
scale
)
;
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
;
a
.
scale
=
spu_scale_unit
();
return
a
;
}
static
spu_area_t
spu_area_unscaled
(
spu_area_t
a
,
spu_scale_t
s
)
{
if
(
a
.
scale
.
w
==
s
.
w
&&
a
.
scale
.
h
==
s
.
h
)
return
a
;
/* 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
)
{
subpicture_Update
(
p_subpic
,
p_fmt_src
,
p_fmt_dst
,
p_subpic
->
b_subtitle
?
render_subtitle_date
:
render_osd_date
);
a
=
spu_area_scaled
(
a
);
/* */
if
(
p_subpic
->
b_subtitle
)
{
for
(
subpicture_region_t
*
r
=
p_subpic
->
p_region
;
r
!=
NULL
;
r
=
r
->
p_next
)
i_subtitle_region_count
++
;
}
a
.
i_x
=
spu_invscale_w
(
a
.
i_x
,
s
);
a
.
i_y
=
spu_invscale_h
(
a
.
i_y
,
s
);
/* */
pp_subpicture
[
i_subpicture
++
]
=
p_subpic
;
}
a
.
i_width
=
spu_invscale_w
(
a
.
i_width
,
s
);
a
.
i_height
=
spu_invscale_h
(
a
.
i_height
,
s
);
/* Be sure we have at least 1 picture to process */
if
(
i_subpicture
<=
0
)
return
NULL
;
a
.
scale
=
s
;
return
a
;
}
static
bool
spu_area_overlap
(
spu_area_t
a
,
spu_area_t
b
)
{
const
int
i_dx
=
0
;
const
int
i_dy
=
0
;
subpicture_t
*
p_output
=
subpicture_New
(
NULL
);
a
=
spu_area_scaled
(
a
);
b
=
spu_area_scaled
(
b
);
/* Now order subpicture array
* XXX The order is *really* important for overlap subtitles positionning */
qsort
(
pp_subpicture
,
i_subpicture
,
sizeof
(
*
pp_subpicture
),
SubpictureCmp
);
return
__MAX
(
a
.
i_x
-
i_dx
,
b
.
i_x
)
<
__MIN
(
a
.
i_x
+
a
.
i_width
+
i_dx
,
b
.
i_x
+
b
.
i_width
)
&&
__MAX
(
a
.
i_y
-
i_dy
,
b
.
i_y
)
<
__MIN
(
a
.
i_y
+
a
.
i_height
+
i_dy
,
b
.
i_y
+
b
.
i_height
);
}
/* 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
)
);
/**
* Avoid area overlapping
*/
static
void
SpuAreaFixOverlap
(
spu_area_t
*
p_dst
,
const
spu_area_t
*
p_sub
,
int
i_sub
,
int
i_align
)
{
spu_area_t
a
=
spu_area_scaled
(
*
p_dst
);
bool
b_moved
=
false
;
bool
b_ok
;
/* Process all subpictures and regions (in the right order) */
for
(
unsigned
int
i_index
=
0
;
i_index
<
i_subpicture
;
i_index
++
)
/* Check for overlap
* XXX It is not fast O(n^2) but we should not have a lot of region */
do
{
subpicture_t
*
p_subpic
=
pp_subpicture
[
i_index
];
subpicture_region_t
*
p_region
;
b_ok
=
true
;
for
(
int
i
=
0
;
i
<
i_sub
;
i
++
)
{
spu_area_t
sub
=
spu_area_scaled
(
p_sub
[
i
]
);
if
(
!
p_subpic
->
p_region
)
if
(
!
spu_area_overlap
(
a
,
sub
)
)
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_align
&
SUBPICTURE_ALIGN_TOP
)
{
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
;
/* We go down */
int
i_y
=
sub
.
i_y
+
sub
.
i_height
;
a
.
i_y
=
i_y
;
b_moved
=
true
;
}
if
(
p_sys
->
p_text
)
else
if
(
i_align
&
SUBPICTURE_ALIGN_BOTTOM
)
{
p_sys
->
p_text
->
fmt_out
.
video
.
i_width
=
p_sys
->
p_text
->
fmt_out
.
video
.
i_visible_width
=
i_render_width
;
/* We go up */
int
i_y
=
sub
.
i_y
-
a
.
i_height
;
a
.
i_y
=
i_y
;
b_moved
=
true
;
}
else
{
/* TODO what to do in this case? */
//fprintf( stderr, "Overlap with unsupported alignment\n" );
break
;
}
p_sys
->
p_text
->
fmt_out
.
video
.
i_height
=
p_sys
->
p_text
->
fmt_out
.
video
.
i_visible_height
=
i_render_height
;
b_ok
=
false
;
break
;
}
}
while
(
!
b_ok
);
/* 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
);
if
(
b_moved
)
*
p_dst
=
spu_area_unscaled
(
a
,
p_dst
->
scale
);
}
/* 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
;
}
static
void
SpuAreaFitInside
(
spu_area_t
*
p_area
,
const
spu_area_t
*
p_boundary
)
{
spu_area_t
a
=
spu_area_scaled
(
*
p_area
);
/* 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
;
}
const
int
i_error_x
=
(
a
.
i_x
+
a
.
i_width
)
-
p_boundary
->
i_width
;
if
(
i_error_x
>
0
)
a
.
i_x
-=
i_error_x
;
if
(
a
.
i_x
<
0
)
a
.
i_x
=
0
;
/* 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
;
const
int
i_error_y
=
(
a
.
i_y
+
a
.
i_height
)
-
p_boundary
->
i_height
;
if
(
i_error_y
>
0
)
a
.
i_y
-=
i_error_y
;
if
(
a
.
i_y
<
0
)
a
.
i_y
=
0
;
/* Check scale validity */
if
(
scale
.
w
<=
0
||
scale
.
h
<=
0
)
continue
;
*
p_area
=
spu_area_unscaled
(
a
,
p_area
->
scale
);
}
/* */
subpicture_region_t
*
p_render
;
SpuRenderRegion
(
p_spu
,
&
p_render
,
&
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_render
)
/**
* Place a region
*/
static
void
SpuRegionPlace
(
int
*
pi_x
,
int
*
pi_y
,
const
subpicture_t
*
p_subpic
,
const
subpicture_region_t
*
p_region
)
{
const
int
i_delta_x
=
p_region
->
i_x
;
const
int
i_delta_y
=
p_region
->
i_y
;
int
i_x
,
i_y
;
assert
(
p_region
->
i_x
!=
INT_MAX
&&
p_region
->
i_y
!=
INT_MAX
);
if
(
p_region
->
i_align
&
SUBPICTURE_ALIGN_TOP
)
{
subpicture_region_t
**
pp_last
=
&
p_output
->
p_region
;
while
(
*
pp_last
)
pp_last
=
&
(
*
pp_last
)
->
p_next
;
*
pp_last
=
p_render
;
i_y
=
i_delta_y
;
}
if
(
p_subpic
->
b_subtitle
)
else
if
(
p_region
->
i_align
&
SUBPICTURE_ALIGN_BOTTOM
)
{
area
=
spu_area_unscaled
(
area
,
scale
);
if
(
!
p_subpic
->
b_absolute
&&
area
.
i_width
>
0
&&
area
.
i_height
>
0
)
i_y
=
p_subpic
->
i_original_picture_height
-
p_region
->
fmt
.
i_height
-
i_delta_y
;
}
else
{
p_region
->
i_x
=
area
.
i_x
;
p_region
->
i_y
=
area
.
i_y
;
i_y
=
p_subpic
->
i_original_picture_height
/
2
-
p_region
->
fmt
.
i_height
/
2
;
}
if
(
p_subtitle_area
)
p_subtitle_area
[
i_subtitle_area
++
]
=
area
;
if
(
p_region
->
i_align
&
SUBPICTURE_ALIGN_LEFT
)
{
i_x
=
i_delta_x
;
}
else
if
(
p_region
->
i_align
&
SUBPICTURE_ALIGN_RIGHT
)
{
i_x
=
p_subpic
->
i_original_picture_width
-
p_region
->
fmt
.
i_width
-
i_delta_x
;
}
if
(
p_subpic
->
b_subtitle
)
p_subpic
->
b_absolute
=
true
;
else
{
i_x
=
p_subpic
->
i_original_picture_width
/
2
-
p_region
->
fmt
.
i_width
/
2
;
}
/* */
if
(
p_subtitle_area
!=
p_subtitle_area_buffer
)
free
(
p_subtitle_area
);
if
(
p_subpic
->
b_absolute
)
{
i_x
=
i_delta_x
;
i_y
=
i_delta_y
;
}
return
p_output
;
*
pi_x
=
i_x
;
*
pi_y
=
i_y
;
}
/**
* This function compares two 64 bits integers.
* It can be used by qsort.
*/
static
int
IntegerCmp
(
int64_t
i0
,
int64_t
i1
)
{
return
i0
<
i1
?
-
1
:
i0
>
i1
?
1
:
0
;
}
/**
* This function compares 2 subpictures using the following properties
* (ordered by priority)
* 1. absolute positionning
* 2. start time
* 3. creation order (per channel)
*
* It can be used by qsort.
*
* XXX spu_RenderSubpictures depends heavily on this order.
*/
static
int
SubpictureCmp
(
const
void
*
s0
,
const
void
*
s1
)
{
subpicture_t
*
p_subpic0
=
*
(
subpicture_t
**
)
s0
;
subpicture_t
*
p_subpic1
=
*
(
subpicture_t
**
)
s1
;
int
r
;
r
=
IntegerCmp
(
!
p_subpic0
->
b_absolute
,
!
p_subpic1
->
b_absolute
);
if
(
!
r
)
r
=
IntegerCmp
(
p_subpic0
->
i_start
,
p_subpic1
->
i_start
);
if
(
!
r
)
r
=
IntegerCmp
(
p_subpic0
->
i_channel
,
p_subpic1
->
i_channel
);
if
(
!
r
)
r
=
IntegerCmp
(
p_subpic0
->
i_order
,
p_subpic1
->
i_order
);
return
r
;
}
static
void
SubpictureChain
(
subpicture_t
**
pp_head
,
subpicture_t
*
p_subpic
)
{
p_subpic
->
p_next
=
*
pp_head
;
*
pp_head
=
p_subpic
;
}
/*****************************************************************************
...
...
@@ -656,1037 +760,887 @@ static subpicture_t *SpuSortSubpictures( spu_t *p_spu,
return
p_subpic
;
}
subpicture_t
*
spu_Render
(
spu_t
*
p_spu
,
const
video_format_t
*
p_fmt_dst
,
const
video_format_t
*
p_fmt_src
,
mtime_t
render_subtitle_date
,
mtime_t
render_osd_date
,
bool
b_subtitle_only
)
/**
* It will transform the provided region into another region suitable for rendering.
*/
static
void
SpuRenderRegion
(
spu_t
*
p_spu
,
subpicture_region_t
**
pp_dst
,
spu_area_t
*
p_dst_area
,
subpicture_t
*
p_subpic
,
subpicture_region_t
*
p_region
,
const
spu_scale_t
scale_size
,
const
video_format_t
*
p_fmt
,
const
spu_area_t
*
p_subtitle_area
,
int
i_subtitle_area
,
mtime_t
render_date
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
/* Update sub-filter chain */
vlc_mutex_lock
(
&
p_sys
->
lock
)
;
char
*
psz_chain_update
=
p_sys
->
psz_chain_updat
e
;
p_sys
->
psz_chain_update
=
NULL
;
vlc_mutex_unlock
(
&
p_sys
->
lock
)
;
video_format_t
fmt_original
=
p_region
->
fmt
;
bool
b_rerender_text
=
false
;
bool
b_restore_format
=
fals
e
;
int
i_x_offset
;
int
i_y_offset
;
vlc_mutex_lock
(
&
p_sys
->
chain_lock
);
if
(
psz_chain_update
)
{
filter_chain_Reset
(
p_sys
->
p_chain
,
NULL
,
NULL
);
video_format_t
region_fmt
;
picture_t
*
p_region_picture
;
filter_chain_AppendFromString
(
p_spu
->
p
->
p_chain
,
psz_chain_update
);
/* Invalidate area by default */
*
p_dst_area
=
spu_area_create
(
0
,
0
,
0
,
0
,
scale_size
);
*
pp_dst
=
NULL
;
free
(
psz_chain_update
);
/* Render text region */
if
(
p_region
->
fmt
.
i_chroma
==
VLC_CODEC_TEXT
)
{
const
int
i_min_scale_ratio
=
SCALE_UNIT
;
/* FIXME what is the right value? (scale_size is not) */
SpuRenderText
(
p_spu
,
&
b_rerender_text
,
p_subpic
,
p_region
,
i_min_scale_ratio
,
render_date
);
b_restore_format
=
b_rerender_text
;
/* Check if the rendering has failed ... */
if
(
p_region
->
fmt
.
i_chroma
==
VLC_CODEC_TEXT
)
goto
exit
;
}
/* Run subpicture filters */
filter_chain_SubFilter
(
p_sys
->
p_chain
,
render_osd_date
);
vlc_mutex_unlock
(
&
p_sys
->
chain_lock
);
/* Get the sorted list of subpicture to render */
vlc_mutex_lock
(
&
p_sys
->
lock
);
/* Force palette if requested
* FIXME b_force_palette and b_force_crop are applied to all subpictures using palette
* instead of only the right one (being the dvd spu).
*/
const
bool
b_using_palette
=
p_region
->
fmt
.
i_chroma
==
VLC_CODEC_YUVP
;
const
bool
b_force_palette
=
b_using_palette
&&
p_sys
->
b_force_palette
;
const
bool
b_force_crop
=
b_force_palette
&&
p_sys
->
b_force_crop
;
bool
b_changed_palette
=
false
;
subpicture_t
*
p_list
=
SpuSortSubpictures
(
p_spu
,
render_subtitle_date
,
render_osd_date
,
b_subtitle_only
);
if
(
!
p_list
)
{
vlc_mutex_unlock
(
&
p_sys
->
lock
);
return
NULL
;
}
/* Render the current list of subpictures */
subpicture_t
*
p_render
=
SpuRenderSubpictures
(
p_spu
,
p_fmt_dst
,
p_list
,
p_fmt_src
,
render_subtitle_date
,
render_osd_date
);
vlc_mutex_unlock
(
&
p_sys
->
lock
);
return
p_render
;
}
/* Compute the margin which is expressed in destination pixel unit
* The margin is applied only to subtitle and when no forced crop is
* requested (dvd menu) */
int
i_margin_y
=
0
;
if
(
!
b_force_crop
&&
p_subpic
->
b_subtitle
)
i_margin_y
=
spu_invscale_h
(
p_sys
->
i_margin
,
scale_size
);
void
spu_OffsetSubtitleDate
(
spu_t
*
p_spu
,
mtime_t
i_duration
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
/* Place the picture
* We compute the position in the rendered size */
SpuRegionPlace
(
&
i_x_offset
,
&
i_y_offset
,
p_subpic
,
p_region
);
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
;
/* Save this position for subtitle overlap support
* it is really important that there are given without scale_size applied */
*
p_dst_area
=
spu_area_create
(
i_x_offset
,
i_y_offset
,
p_region
->
fmt
.
i_width
,
p_region
->
fmt
.
i_height
,
scale_size
)
;
if
(
p_current
&&
p_current
->
b_subtitle
)
/* Handle overlapping subtitles when possible */
if
(
p_subpic
->
b_subtitle
&&
!
p_subpic
->
b_absolute
)
{
if
(
p_current
->
i_start
>
0
)
p_current
->
i_start
+=
i_duration
;
if
(
p_current
->
i_stop
>
0
)
p_current
->
i_stop
+=
i_duration
;
}
SpuAreaFixOverlap
(
p_dst_area
,
p_subtitle_area
,
i_subtitle_area
,
p_region
->
i_align
);
}
vlc_mutex_unlock
(
&
p_sys
->
lock
);
}
int
spu_RegisterChannel
(
spu_t
*
p_spu
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
vlc_mutex_lock
(
&
p_sys
->
lock
);
int
i_channel
=
p_sys
->
i_channel
++
;
vlc_mutex_unlock
(
&
p_sys
->
lock
)
;
/* we copy the area: for the subtitle overlap support we want
* to only save the area without margin applied */
spu_area_t
restrained
=
*
p_dst_area
;
return
i_channel
;
}
/* apply margin to subtitles and correct if they go over the picture edge */
if
(
p_subpic
->
b_subtitle
)
restrained
.
i_y
-=
i_margin_y
;
void
spu_ClearChannel
(
spu_t
*
p_spu
,
int
i_channel
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
spu_area_t
display
=
spu_area_create
(
0
,
0
,
p_fmt
->
i_width
,
p_fmt
->
i_height
,
spu_scale_unit
()
);
SpuAreaFitInside
(
&
restrained
,
&
display
)
;
vlc_mutex_lock
(
&
p_sys
->
lock
);
/* Fix the position for the current scale_size */
i_x_offset
=
spu_scale_w
(
restrained
.
i_x
,
restrained
.
scale
);
i_y_offset
=
spu_scale_h
(
restrained
.
i_y
,
restrained
.
scale
);
for
(
int
i_subpic
=
0
;
i_subpic
<
VOUT_MAX_SUBPICTURES
;
i_subpic
++
)
/* */
if
(
b_force_palette
)
{
spu_heap_entry_t
*
p_entry
=
&
p_sys
->
heap
.
p_entry
[
i_subpic
]
;
subpicture_t
*
p_subpic
=
p_entry
->
p_subpictur
e
;
video_palette_t
*
p_palette
=
p_region
->
fmt
.
p_palette
;
video_palette_t
palett
e
;
if
(
!
p_subpic
)
continue
;
if
(
p_subpic
->
i_channel
!=
i_channel
&&
(
i_channel
!=
-
1
||
p_subpic
->
i_channel
==
SPU_DEFAULT_CHANNEL
)
)
continue
;
/* We suppose DVD palette here */
palette
.
i_entries
=
4
;
for
(
int
i
=
0
;
i
<
4
;
i
++
)
for
(
int
j
=
0
;
j
<
4
;
j
++
)
palette
.
palette
[
i
][
j
]
=
p_sys
->
palette
[
i
][
j
];
/* You cannot delete subpicture outside of spu_SortSubpictures */
p_entry
->
b_reject
=
true
;
if
(
p_palette
->
i_entries
==
palette
.
i_entries
)
{
for
(
int
i
=
0
;
i
<
p_palette
->
i_entries
;
i
++
)
for
(
int
j
=
0
;
j
<
4
;
j
++
)
b_changed_palette
|=
p_palette
->
palette
[
i
][
j
]
!=
palette
.
palette
[
i
][
j
];
}
else
{
b_changed_palette
=
true
;
}
*
p_palette
=
palette
;
}
vlc_mutex_unlock
(
&
p_sys
->
lock
);
}
void
spu_ChangeFilters
(
spu_t
*
p_spu
,
const
char
*
psz_filters
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
vlc_mutex_lock
(
&
p_sys
->
lock
);
free
(
p_sys
->
psz_chain_update
);
p_sys
->
psz_chain_update
=
strdup
(
psz_filters
);
/* */
region_fmt
=
p_region
->
fmt
;
p_region_picture
=
p_region
->
p_picture
;
vlc_mutex_unlock
(
&
p_sys
->
lock
);
}
void
spu_ChangeMargin
(
spu_t
*
p_spu
,
int
i_margin
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
/* Scale from rendered size to destination size */
if
(
p_sys
->
p_scale
&&
p_sys
->
p_scale
->
p_module
&&
(
!
b_using_palette
||
(
p_sys
->
p_scale_yuvp
&&
p_sys
->
p_scale_yuvp
->
p_module
)
)
&&
(
scale_size
.
w
!=
SCALE_UNIT
||
scale_size
.
h
!=
SCALE_UNIT
||
b_using_palette
)
)
{
const
unsigned
i_dst_width
=
spu_scale_w
(
p_region
->
fmt
.
i_width
,
scale_size
);
const
unsigned
i_dst_height
=
spu_scale_h
(
p_region
->
fmt
.
i_height
,
scale_size
);
vlc_mutex_lock
(
&
p_sys
->
lock
);
p_sys
->
i_margin
=
i_margin
;
vlc_mutex_unlock
(
&
p_sys
->
lock
);
}
/* Destroy the cache if unusable */
if
(
p_region
->
p_private
)
{
subpicture_region_private_t
*
p_private
=
p_region
->
p_private
;
bool
b_changed
=
false
;
/*
*/
static
void
SubpictureChain
(
subpicture_t
**
pp_head
,
subpicture_t
*
p_subpic
)
{
p_subpic
->
p_next
=
*
pp_head
;
/* Check resize changes
*/
if
(
i_dst_width
!=
p_private
->
fmt
.
i_width
||
i_dst_height
!=
p_private
->
fmt
.
i_height
)
b_changed
=
true
;
*
pp_head
=
p_subpic
;
}
/* Check forced palette changes */
if
(
b_changed_palette
)
b_changed
=
true
;
/*****************************************************************************
* heap managment
*****************************************************************************/
static
void
SpuHeapInit
(
spu_heap_t
*
p_heap
)
{
for
(
int
i
=
0
;
i
<
VOUT_MAX_SUBPICTURES
;
i
++
)
if
(
b_changed
)
{
spu_heap_entry_t
*
e
=
&
p_heap
->
p_entry
[
i
];
e
->
p_subpicture
=
NULL
;
e
->
b_reject
=
false
;
subpicture_region_private_Delete
(
p_private
);
p_region
->
p_private
=
NULL
;
}
}
}
static
int
SpuHeapPush
(
spu_heap_t
*
p_heap
,
subpicture_t
*
p_subpic
)
{
for
(
int
i
=
0
;
i
<
VOUT_MAX_SUBPICTURES
;
i
++
)
/* Scale if needed into cache */
if
(
!
p_region
->
p_private
&&
i_dst_width
>
0
&&
i_dst_height
>
0
)
{
spu_heap_entry_t
*
e
=
&
p_heap
->
p_entry
[
i
]
;
filter_t
*
p_scale
=
p_sys
->
p_scale
;
if
(
e
->
p_subpicture
)
continue
;
picture_t
*
p_picture
=
p_region
->
p_picture
;
picture_Hold
(
p_picture
)
;
e
->
p_subpicture
=
p_subpic
;
e
->
b_reject
=
false
;
return
VLC_SUCCESS
;
}
return
VLC_EGENERIC
;
}
/* Convert YUVP to YUVA/RGBA first for better scaling quality */
if
(
b_using_palette
)
{
filter_t
*
p_scale_yuvp
=
p_sys
->
p_scale_yuvp
;
static
void
SpuHeapDeleteAt
(
spu_heap_t
*
p_heap
,
int
i_index
)
{
spu_heap_entry_t
*
e
=
&
p_heap
->
p_entry
[
i_index
];
p_scale_yuvp
->
fmt_in
.
video
=
p_region
->
fmt
;
if
(
e
->
p_subpicture
)
subpicture_Delete
(
e
->
p_subpicture
);
/* TODO converting to RGBA for RGB video output is better */
p_scale_yuvp
->
fmt_out
.
video
=
p_region
->
fmt
;
p_scale_yuvp
->
fmt_out
.
video
.
i_chroma
=
VLC_CODEC_YUVA
;
e
->
p_subpicture
=
NULL
;
}
p_picture
=
p_scale_yuvp
->
pf_video_filter
(
p_scale_yuvp
,
p_picture
);
if
(
!
p_picture
)
{
/* Well we will try conversion+scaling */
msg_Warn
(
p_spu
,
"%4.4s to %4.4s conversion failed"
,
(
const
char
*
)
&
p_scale_yuvp
->
fmt_in
.
video
.
i_chroma
,
(
const
char
*
)
&
p_scale_yuvp
->
fmt_out
.
video
.
i_chroma
);
}
}
static
int
SpuHeapDeleteSubpicture
(
spu_heap_t
*
p_heap
,
subpicture_t
*
p_subpic
)
{
for
(
int
i
=
0
;
i
<
VOUT_MAX_SUBPICTURES
;
i
++
)
/* Conversion(except from YUVP)/Scaling */
if
(
p_picture
&&
(
p_picture
->
format
.
i_width
!=
i_dst_width
||
p_picture
->
format
.
i_height
!=
i_dst_height
)
)
{
spu_heap_entry_t
*
e
=
&
p_heap
->
p_entry
[
i
];
p_scale
->
fmt_in
.
video
=
p_picture
->
format
;
p_scale
->
fmt_out
.
video
=
p_picture
->
format
;
if
(
e
->
p_subpicture
!=
p_subpic
)
continue
;
p_scale
->
fmt_out
.
video
.
i_width
=
i_dst_width
;
p_scale
->
fmt_out
.
video
.
i_height
=
i_dst_height
;
SpuHeapDeleteAt
(
p_heap
,
i
);
return
VLC_SUCCESS
;
p_scale
->
fmt_out
.
video
.
i_visible_width
=
spu_scale_w
(
p_region
->
fmt
.
i_visible_width
,
scale_size
);
p_scale
->
fmt_out
.
video
.
i_visible_height
=
spu_scale_h
(
p_region
->
fmt
.
i_visible_height
,
scale_size
);
p_picture
=
p_scale
->
pf_video_filter
(
p_scale
,
p_picture
);
if
(
!
p_picture
)
msg_Err
(
p_spu
,
"scaling failed"
);
}
return
VLC_EGENERIC
;
}
static
void
SpuHeapClean
(
spu_heap_t
*
p_heap
)
{
for
(
int
i
=
0
;
i
<
VOUT_MAX_SUBPICTURES
;
i
++
)
/* */
if
(
p_picture
)
{
spu_heap_entry_t
*
e
=
&
p_heap
->
p_entry
[
i
];
if
(
e
->
p_subpicture
)
subpicture_Delete
(
e
->
p_subpicture
);
p_region
->
p_private
=
subpicture_region_private_New
(
&
p_picture
->
format
);
if
(
p_region
->
p_private
)
{
p_region
->
p_private
->
p_picture
=
p_picture
;
if
(
!
p_region
->
p_private
->
p_picture
)
{
subpicture_region_private_Delete
(
p_region
->
p_private
);
p_region
->
p_private
=
NULL
;
}
}
else
{
picture_Release
(
p_picture
);
}
}
}
}
static
void
FilterRelease
(
filter_t
*
p_filter
)
{
if
(
p_filter
->
p_module
)
module_unneed
(
p_filter
,
p_filter
->
p_module
);
if
(
p_filter
->
p_owner
)
free
(
p_filter
->
p_owner
);
vlc_object_release
(
p_filter
);
}
static
void
SpuRenderCreateAndLoadText
(
spu_t
*
p_spu
)
{
filter_t
*
p_text
;
assert
(
!
p_spu
->
p
->
p_text
);
p_spu
->
p
->
p_text
=
p_text
=
vlc_custom_create
(
p_spu
,
sizeof
(
filter_t
),
VLC_OBJECT_GENERIC
,
"spu text"
);
if
(
!
p_text
)
return
;
p_text
->
p_owner
=
xmalloc
(
sizeof
(
*
p_text
->
p_owner
)
);
p_text
->
p_owner
->
p_spu
=
p_spu
;
es_format_Init
(
&
p_text
->
fmt_in
,
VIDEO_ES
,
0
);
es_format_Init
(
&
p_text
->
fmt_out
,
VIDEO_ES
,
0
);
p_text
->
fmt_out
.
video
.
i_width
=
p_text
->
fmt_out
.
video
.
i_visible_width
=
32
;
p_text
->
fmt_out
.
video
.
i_height
=
p_text
->
fmt_out
.
video
.
i_visible_height
=
32
;
p_text
->
pf_get_attachments
=
spu_get_attachments
;
vlc_object_attach
(
p_text
,
p_spu
);
/* FIXME TOCHECK shouldn't module_need( , , psz_modulename, false ) do the
* same than these 2 calls ? */
char
*
psz_modulename
=
var_CreateGetString
(
p_spu
,
"text-renderer"
);
if
(
psz_modulename
&&
*
psz_modulename
)
/* And use the scaled picture */
if
(
p_region
->
p_private
)
{
p_text
->
p_module
=
module_need
(
p_text
,
"text renderer"
,
psz_modulename
,
true
);
region_fmt
=
p_region
->
p_private
->
fmt
;
p_region_picture
=
p_region
->
p_private
->
p_picture
;
}
}
free
(
psz_modulename
);
if
(
!
p_text
->
p_module
)
p_text
->
p_module
=
module_need
(
p_text
,
"text renderer"
,
NULL
,
false
);
/* Create a few variables used for enhanced text rendering */
var_Create
(
p_text
,
"spu-duration"
,
VLC_VAR_TIME
);
var_Create
(
p_text
,
"spu-elapsed"
,
VLC_VAR_TIME
);
var_Create
(
p_text
,
"text-rerender"
,
VLC_VAR_BOOL
);
var_Create
(
p_text
,
"scale"
,
VLC_VAR_INTEGER
);
}
static
filter_t
*
CreateAndLoadScale
(
vlc_object_t
*
p_obj
,
vlc_fourcc_t
i_src_chroma
,
vlc_fourcc_t
i_dst_chroma
,
bool
b_resize
)
{
filter_t
*
p_scale
;
p_scale
=
vlc_custom_create
(
p_obj
,
sizeof
(
filter_t
),
VLC_OBJECT_GENERIC
,
"scale"
);
if
(
!
p_scale
)
return
NULL
;
es_format_Init
(
&
p_scale
->
fmt_in
,
VIDEO_ES
,
0
);
p_scale
->
fmt_in
.
video
.
i_chroma
=
i_src_chroma
;
p_scale
->
fmt_in
.
video
.
i_width
=
p_scale
->
fmt_in
.
video
.
i_height
=
32
;
es_format_Init
(
&
p_scale
->
fmt_out
,
VIDEO_ES
,
0
);
p_scale
->
fmt_out
.
video
.
i_chroma
=
i_dst_chroma
;
p_scale
->
fmt_out
.
video
.
i_width
=
p_scale
->
fmt_out
.
video
.
i_height
=
b_resize
?
16
:
32
;
p_scale
->
pf_video_buffer_new
=
spu_new_video_buffer
;
p_scale
->
pf_video_buffer_del
=
spu_del_video_buffer
;
/* Force cropping if requested */
if
(
b_force_crop
)
{
int
i_crop_x
=
spu_scale_w
(
p_sys
->
i_crop_x
,
scale_size
);
int
i_crop_y
=
spu_scale_h
(
p_sys
->
i_crop_y
,
scale_size
);
int
i_crop_width
=
spu_scale_w
(
p_sys
->
i_crop_width
,
scale_size
);
int
i_crop_height
=
spu_scale_h
(
p_sys
->
i_crop_height
,
scale_size
);
vlc_object_attach
(
p_scale
,
p_obj
);
p_scale
->
p_module
=
module_need
(
p_scale
,
"video filter2"
,
NULL
,
false
);
/* Find the intersection */
if
(
i_crop_x
+
i_crop_width
<=
i_x_offset
||
i_x_offset
+
(
int
)
region_fmt
.
i_visible_width
<
i_crop_x
||
i_crop_y
+
i_crop_height
<=
i_y_offset
||
i_y_offset
+
(
int
)
region_fmt
.
i_visible_height
<
i_crop_y
)
{
/* No intersection */
region_fmt
.
i_visible_width
=
region_fmt
.
i_visible_height
=
0
;
}
else
{
int
i_x
,
i_y
,
i_x_end
,
i_y_end
;
i_x
=
__MAX
(
i_crop_x
,
i_x_offset
);
i_y
=
__MAX
(
i_crop_y
,
i_y_offset
);
i_x_end
=
__MIN
(
i_crop_x
+
i_crop_width
,
i_x_offset
+
(
int
)
region_fmt
.
i_visible_width
);
i_y_end
=
__MIN
(
i_crop_y
+
i_crop_height
,
i_y_offset
+
(
int
)
region_fmt
.
i_visible_height
);
return
p_scale
;
}
static
void
SpuRenderCreateAndLoadScale
(
spu_t
*
p_spu
)
{
assert
(
!
p_spu
->
p
->
p_scale
);
assert
(
!
p_spu
->
p
->
p_scale_yuvp
);
/* XXX p_spu->p_scale is used for all conversion/scaling except yuvp to
* yuva/rgba */
p_spu
->
p
->
p_scale
=
CreateAndLoadScale
(
VLC_OBJECT
(
p_spu
),
VLC_CODEC_YUVA
,
VLC_CODEC_YUVA
,
true
);
/* This one is used for YUVP to YUVA/RGBA without scaling
* FIXME rename it */
p_spu
->
p
->
p_scale_yuvp
=
CreateAndLoadScale
(
VLC_OBJECT
(
p_spu
),
VLC_CODEC_YUVP
,
VLC_CODEC_YUVA
,
false
);
}
region_fmt
.
i_x_offset
=
i_x
-
i_x_offset
;
region_fmt
.
i_y_offset
=
i_y
-
i_y_offset
;
region_fmt
.
i_visible_width
=
i_x_end
-
i_x
;
region_fmt
.
i_visible_height
=
i_y_end
-
i_y
;
static
void
SpuRenderText
(
spu_t
*
p_spu
,
bool
*
pb_rerender_text
,
subpicture_t
*
p_subpic
,
subpicture_region_t
*
p_region
,
int
i_min_scale_ratio
,
mtime_t
render_date
)
{
filter_t
*
p_text
=
p_spu
->
p
->
p_text
;
i_x_offset
=
__MAX
(
i_x
,
0
);
i_y_offset
=
__MAX
(
i_y
,
0
);
}
}
assert
(
p_region
->
fmt
.
i_chroma
==
VLC_CODEC_TEXT
);
subpicture_region_t
*
p_dst
=
*
pp_dst
=
subpicture_region_New
(
&
region_fmt
);
if
(
p_dst
)
{
p_dst
->
i_x
=
i_x_offset
;
p_dst
->
i_y
=
i_y_offset
;
p_dst
->
i_align
=
0
;
p_dst
->
p_picture
=
picture_Hold
(
p_region_picture
);
int
i_fade_alpha
=
255
;
if
(
p_subpic
->
b_fade
)
{
mtime_t
fade_start
=
(
p_subpic
->
i_stop
+
p_subpic
->
i_start
)
/
2
;
if
(
!
p_text
||
!
p_text
->
p_module
)
goto
exit
;
if
(
fade_start
<=
render_date
&&
fade_start
<
p_subpic
->
i_stop
)
i_fade_alpha
=
255
*
(
p_subpic
->
i_stop
-
render_date
)
/
(
p_subpic
->
i_stop
-
fade_start
);
}
p_dst
->
i_alpha
=
i_fade_alpha
*
p_subpic
->
i_alpha
*
p_region
->
i_alpha
/
65025
;
}
/* Setup 3 variables which can be used to render
* time-dependent text (and effects). The first indicates
* the total amount of time the text will be on screen,
* the second the amount of time it has already been on
* screen (can be a negative value as text is layed out
* before it is rendered) and the third is a feedback
* variable from the renderer - if the renderer sets it
* then this particular text is time-dependent, eg. the
* visual progress bar inside the text in karaoke and the
* text needs to be rendered multiple times in order for
* the effect to work - we therefore need to return the
* region to its original state at the end of the loop,
* instead of leaving it in YUVA or YUVP.
* Any renderer which is unaware of how to render
* time-dependent text can happily ignore the variables
* and render the text the same as usual - it should at
* least show up on screen, but the effect won't change
* the text over time.
exit:
if
(
b_rerender_text
)
{
/* Some forms of subtitles need to be re-rendered more than
* once, eg. karaoke. We therefore restore the region to its
* pre-rendered state, so the next time through everything is
* calculated again.
*/
var_SetTime
(
p_text
,
"spu-duration"
,
p_subpic
->
i_stop
-
p_subpic
->
i_start
);
var_SetTime
(
p_text
,
"spu-elapsed"
,
render_date
);
var_SetBool
(
p_text
,
"text-rerender"
,
false
);
var_SetInteger
(
p_text
,
"scale"
,
i_min_scale_ratio
);
if
(
p_text
->
pf_render_html
&&
p_region
->
psz_html
)
if
(
p_region
->
p_picture
)
{
p_text
->
pf_render_html
(
p_text
,
p_region
,
p_region
);
picture_Release
(
p_region
->
p_picture
);
p_region
->
p_picture
=
NULL
;
}
else
if
(
p_text
->
pf_render_text
)
if
(
p_region
->
p_private
)
{
p_text
->
pf_render_text
(
p_text
,
p_region
,
p_region
);
subpicture_region_private_Delete
(
p_region
->
p_private
);
p_region
->
p_private
=
NULL
;
}
*
pb_rerender_text
=
var_GetBool
(
p_text
,
"text-rerender"
);
exit:
p_region
->
i_align
|=
SUBPICTURE_RENDERED
;
}
/**
* A few scale functions helpers.
*/
static
spu_scale_t
spu_scale_create
(
int
w
,
int
h
)
{
spu_scale_t
s
=
{
.
w
=
w
,
.
h
=
h
};
if
(
s
.
w
<=
0
)
s
.
w
=
SCALE_UNIT
;
if
(
s
.
h
<=
0
)
s
.
h
=
SCALE_UNIT
;
return
s
;
}
static
spu_scale_t
spu_scale_unit
(
void
)
{
return
spu_scale_create
(
SCALE_UNIT
,
SCALE_UNIT
);
}
static
spu_scale_t
spu_scale_createq
(
int
wn
,
int
wd
,
int
hn
,
int
hd
)
{
return
spu_scale_create
(
wn
*
SCALE_UNIT
/
wd
,
hn
*
SCALE_UNIT
/
hd
);
}
static
int
spu_scale_w
(
int
v
,
const
spu_scale_t
s
)
{
return
v
*
s
.
w
/
SCALE_UNIT
;
}
static
int
spu_scale_h
(
int
v
,
const
spu_scale_t
s
)
{
return
v
*
s
.
h
/
SCALE_UNIT
;
}
static
int
spu_invscale_w
(
int
v
,
const
spu_scale_t
s
)
{
return
v
*
SCALE_UNIT
/
s
.
w
;
}
static
int
spu_invscale_h
(
int
v
,
const
spu_scale_t
s
)
{
return
v
*
SCALE_UNIT
/
s
.
h
;
p_region
->
i_align
&=
~
SUBPICTURE_RENDERED
;
}
if
(
b_restore_format
)
p_region
->
fmt
=
fmt_original
;
}
/**
*
A few area functions helpers
*
This function renders all sub picture units in the list.
*/
static
spu_area_t
spu_area_create
(
int
x
,
int
y
,
int
w
,
int
h
,
spu_scale_t
s
)
{
spu_area_t
a
=
{
.
i_x
=
x
,
.
i_y
=
y
,
.
i_width
=
w
,
.
i_height
=
h
,
.
scale
=
s
};
return
a
;
}
static
spu_area_t
spu_area_scaled
(
spu_area_t
a
)
{
if
(
a
.
scale
.
w
==
SCALE_UNIT
&&
a
.
scale
.
h
==
SCALE_UNIT
)
return
a
;
a
.
i_x
=
spu_scale_w
(
a
.
i_x
,
a
.
scale
);
a
.
i_y
=
spu_scale_h
(
a
.
i_y
,
a
.
scale
);
a
.
i_width
=
spu_scale_w
(
a
.
i_width
,
a
.
scale
);
a
.
i_height
=
spu_scale_h
(
a
.
i_height
,
a
.
scale
);
a
.
scale
=
spu_scale_unit
();
return
a
;
}
static
spu_area_t
spu_area_unscaled
(
spu_area_t
a
,
spu_scale_t
s
)
{
if
(
a
.
scale
.
w
==
s
.
w
&&
a
.
scale
.
h
==
s
.
h
)
return
a
;
a
=
spu_area_scaled
(
a
);
a
.
i_x
=
spu_invscale_w
(
a
.
i_x
,
s
);
a
.
i_y
=
spu_invscale_h
(
a
.
i_y
,
s
);
a
.
i_width
=
spu_invscale_w
(
a
.
i_width
,
s
);
a
.
i_height
=
spu_invscale_h
(
a
.
i_height
,
s
);
a
.
scale
=
s
;
return
a
;
}
static
bool
spu_area_overlap
(
spu_area_t
a
,
spu_area_t
b
)
static
subpicture_t
*
SpuRenderSubpictures
(
spu_t
*
p_spu
,
const
video_format_t
*
p_fmt_dst
,
subpicture_t
*
p_subpic_list
,
const
video_format_t
*
p_fmt_src
,
mtime_t
render_subtitle_date
,
mtime_t
render_osd_date
)
{
const
int
i_dx
=
0
;
const
int
i_dy
=
0
;
spu_private_t
*
p_sys
=
p_spu
->
p
;
a
=
spu_area_scaled
(
a
)
;
b
=
spu_area_scaled
(
b
)
;
const
int
i_source_video_width
=
p_fmt_src
->
i_width
;
const
int
i_source_video_height
=
p_fmt_src
->
i_height
;
return
__MAX
(
a
.
i_x
-
i_dx
,
b
.
i_x
)
<
__MIN
(
a
.
i_x
+
a
.
i_width
+
i_dx
,
b
.
i_x
+
b
.
i_width
)
&&
__MAX
(
a
.
i_y
-
i_dy
,
b
.
i_y
)
<
__MIN
(
a
.
i_y
+
a
.
i_height
+
i_dy
,
b
.
i_y
+
b
.
i_height
);
}
unsigned
int
i_subpicture
;
subpicture_t
*
pp_subpicture
[
VOUT_MAX_SUBPICTURES
];
/**
* Avoid area overlapping
*/
static
void
SpuAreaFixOverlap
(
spu_area_t
*
p_dst
,
const
spu_area_t
*
p_sub
,
int
i_sub
,
int
i_align
)
{
spu_area_t
a
=
spu_area_scaled
(
*
p_dst
);
bool
b_moved
=
false
;
bool
b_ok
;
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
;
/*
Check for overlap
* XXX It is not fast O(n^2) but we should not have a lot of region */
do
{
b_ok
=
true
;
for
(
int
i
=
0
;
i
<
i_sub
;
i
++
)
/*
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
)
{
spu_area_t
sub
=
spu_area_scaled
(
p_sub
[
i
]
);
if
(
!
spu_area_overlap
(
a
,
sub
)
)
continue
;
subpicture_Update
(
p_subpic
,
p_fmt_src
,
p_fmt_dst
,
p_subpic
->
b_subtitle
?
render_subtitle_date
:
render_osd_date
);
if
(
i_align
&
SUBPICTURE_ALIGN_TOP
)
{
/* We go down */
int
i_y
=
sub
.
i_y
+
sub
.
i_height
;
a
.
i_y
=
i_y
;
b_moved
=
true
;
}
else
if
(
i_align
&
SUBPICTURE_ALIGN_BOTTOM
)
{
/* We go up */
int
i_y
=
sub
.
i_y
-
a
.
i_height
;
a
.
i_y
=
i_y
;
b_moved
=
true
;
}
else
/* */
if
(
p_subpic
->
b_subtitle
)
{
/* TODO what to do in this case? */
//fprintf( stderr, "Overlap with unsupported alignment\n" );
break
;
for
(
subpicture_region_t
*
r
=
p_subpic
->
p_region
;
r
!=
NULL
;
r
=
r
->
p_next
)
i_subtitle_region_count
++
;
}
b_ok
=
false
;
break
;
/* */
pp_subpicture
[
i_subpicture
++
]
=
p_subpic
;
}
}
while
(
!
b_ok
);
if
(
b_moved
)
*
p_dst
=
spu_area_unscaled
(
a
,
p_dst
->
scale
);
}
/* Be sure we have at least 1 picture to process */
if
(
i_subpicture
<=
0
)
return
NULL
;
static
void
SpuAreaFitInside
(
spu_area_t
*
p_area
,
const
spu_area_t
*
p_boundary
)
{
spu_area_t
a
=
spu_area_scaled
(
*
p_area
);
subpicture_t
*
p_output
=
subpicture_New
(
NULL
);
const
int
i_error_x
=
(
a
.
i_x
+
a
.
i_width
)
-
p_boundary
->
i_width
;
if
(
i_error_x
>
0
)
a
.
i_x
-=
i_error_x
;
if
(
a
.
i_x
<
0
)
a
.
i_x
=
0
;
/* Now order subpicture array
* XXX The order is *really* important for overlap subtitles positionning */
qsort
(
pp_subpicture
,
i_subpicture
,
sizeof
(
*
pp_subpicture
),
SubpictureCmp
);
const
int
i_error_y
=
(
a
.
i_y
+
a
.
i_height
)
-
p_boundary
->
i_height
;
if
(
i_error_y
>
0
)
a
.
i_y
-=
i_error_y
;
if
(
a
.
i_y
<
0
)
a
.
i_y
=
0
;
/* 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
)
)
;
*
p_area
=
spu_area_unscaled
(
a
,
p_area
->
scale
);
}
/* 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
;
/**
* Place a region
*/
static
void
SpuRegionPlace
(
int
*
pi_x
,
int
*
pi_y
,
const
subpicture_t
*
p_subpic
,
const
subpicture_region_t
*
p_region
)
{
const
int
i_delta_x
=
p_region
->
i_x
;
const
int
i_delta_y
=
p_region
->
i_y
;
int
i_x
,
i_y
;
if
(
!
p_subpic
->
p_region
)
continue
;
assert
(
p_region
->
i_x
!=
INT_MAX
&&
p_region
->
i_y
!=
INT_MAX
);
if
(
p_region
->
i_align
&
SUBPICTURE_ALIGN_TOP
)
/* 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
)
{
i_y
=
i_delta_y
;
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
;
}
else
if
(
p_region
->
i_align
&
SUBPICTURE_ALIGN_BOTTOM
)
if
(
p_sys
->
p_text
)
{
i_y
=
p_subpic
->
i_original_picture_height
-
p_region
->
fmt
.
i_height
-
i_delta_y
;
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
;
}
else
/* 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
)
{
i_y
=
p_subpic
->
i_original_picture_height
/
2
-
p_region
->
fmt
.
i_height
/
2
;
p_region
->
fmt
.
i_sar_den
=
p_fmt_dst
->
i_sar_den
;
p_region
->
fmt
.
i_sar_num
=
p_fmt_dst
->
i_sar_num
;
}
if
(
p_region
->
i_align
&
SUBPICTURE_ALIGN_LEFT
)
/* 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
)
{
i_x
=
i_delta_x
;
/* 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
;
}
else
if
(
p_region
->
i_align
&
SUBPICTURE_ALIGN_RIGHT
)
/* 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
)
{
i_x
=
p_subpic
->
i_original_picture_width
-
p_region
->
fmt
.
i_width
-
i_delta_x
;
}
else
spu_area_t
area
;
/* Check scale validity */
if
(
scale
.
w
<=
0
||
scale
.
h
<=
0
)
continue
;
/* */
subpicture_region_t
*
p_render
;
SpuRenderRegion
(
p_spu
,
&
p_render
,
&
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_render
)
{
i_x
=
p_subpic
->
i_original_picture_width
/
2
-
p_region
->
fmt
.
i_width
/
2
;
subpicture_region_t
**
pp_last
=
&
p_output
->
p_region
;
while
(
*
pp_last
)
pp_last
=
&
(
*
pp_last
)
->
p_next
;
*
pp_last
=
p_render
;
}
if
(
p_subpic
->
b_absolut
e
)
if
(
p_subpic
->
b_subtitl
e
)
{
i_x
=
i_delta_x
;
i_y
=
i_delta_y
;
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
;
}
*
pi_x
=
i_x
;
*
pi_y
=
i_y
;
/* */
if
(
p_subtitle_area
!=
p_subtitle_area_buffer
)
free
(
p_subtitle_area
);
return
p_output
;
}
/**
*
It will transform the provided region into another region suitable for rendering.
*/
/**
***************************************************************************
*
Object variables callbacks
*
****************************************************************************
/
static
void
SpuRenderRegion
(
spu_t
*
p_spu
,
subpicture_region_t
**
pp_dst
,
spu_area_t
*
p_dst_area
,
subpicture_t
*
p_subpic
,
subpicture_region_t
*
p_region
,
const
spu_scale_t
scale_size
,
const
video_format_t
*
p_fmt
,
const
spu_area_t
*
p_subtitle_area
,
int
i_subtitle_area
,
mtime_t
render_date
)
/*****************************************************************************
* UpdateSPU: update subpicture settings
*****************************************************************************
* This function is called from CropCallback and at initialization time, to
* retrieve crop information from the input.
*****************************************************************************/
static
void
UpdateSPU
(
spu_t
*
p_spu
,
vlc_object_t
*
p_object
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
vlc_value_t
val
;
video_format_t
fmt_original
=
p_region
->
fmt
;
bool
b_rerender_text
=
false
;
bool
b_restore_format
=
false
;
int
i_x_offset
;
int
i_y_offset
;
video_format_t
region_fmt
;
picture_t
*
p_region_picture
;
vlc_mutex_lock
(
&
p_sys
->
lock
);
/* Invalidate area by default */
*
p_dst_area
=
spu_area_create
(
0
,
0
,
0
,
0
,
scale_size
);
*
pp_dst
=
NULL
;
p_sys
->
b_force_palette
=
false
;
p_sys
->
b_force_crop
=
false
;
/* Render text region */
if
(
p_region
->
fmt
.
i_chroma
==
VLC_CODEC_TEXT
)
if
(
var_Get
(
p_object
,
"highlight"
,
&
val
)
||
!
val
.
b_bool
)
{
const
int
i_min_scale_ratio
=
SCALE_UNIT
;
/* FIXME what is the right value? (scale_size is not) */
SpuRenderText
(
p_spu
,
&
b_rerender_text
,
p_subpic
,
p_region
,
i_min_scale_ratio
,
render_date
);
b_restore_format
=
b_rerender_text
;
/* Check if the rendering has failed ... */
if
(
p_region
->
fmt
.
i_chroma
==
VLC_CODEC_TEXT
)
goto
exit
;
vlc_mutex_unlock
(
&
p_sys
->
lock
);
return
;
}
/* Force palette if requested
* FIXME b_force_palette and b_force_crop are applied to all subpictures using palette
* instead of only the right one (being the dvd spu).
*/
const
bool
b_using_palette
=
p_region
->
fmt
.
i_chroma
==
VLC_CODEC_YUVP
;
const
bool
b_force_palette
=
b_using_palette
&&
p_sys
->
b_force_palette
;
const
bool
b_force_crop
=
b_force_palette
&&
p_sys
->
b_force_crop
;
bool
b_changed_palette
=
false
;
/* Compute the margin which is expressed in destination pixel unit
* The margin is applied only to subtitle and when no forced crop is
* requested (dvd menu) */
int
i_margin_y
=
0
;
if
(
!
b_force_crop
&&
p_subpic
->
b_subtitle
)
i_margin_y
=
spu_invscale_h
(
p_sys
->
i_margin
,
scale_size
);
/* Place the picture
* We compute the position in the rendered size */
SpuRegionPlace
(
&
i_x_offset
,
&
i_y_offset
,
p_subpic
,
p_region
);
/* Save this position for subtitle overlap support
* it is really important that there are given without scale_size applied */
*
p_dst_area
=
spu_area_create
(
i_x_offset
,
i_y_offset
,
p_region
->
fmt
.
i_width
,
p_region
->
fmt
.
i_height
,
scale_size
);
p_sys
->
b_force_crop
=
true
;
p_sys
->
i_crop_x
=
var_GetInteger
(
p_object
,
"x-start"
);
p_sys
->
i_crop_y
=
var_GetInteger
(
p_object
,
"y-start"
);
p_sys
->
i_crop_width
=
var_GetInteger
(
p_object
,
"x-end"
)
-
p_sys
->
i_crop_x
;
p_sys
->
i_crop_height
=
var_GetInteger
(
p_object
,
"y-end"
)
-
p_sys
->
i_crop_y
;
/* Handle overlapping subtitles when possible */
if
(
p_subpic
->
b_subtitle
&&
!
p_subpic
->
b_absolute
)
if
(
var_Get
(
p_object
,
"menu-palette"
,
&
val
)
==
VLC_SUCCESS
)
{
SpuAreaFixOverlap
(
p_dst_area
,
p_subtitle_area
,
i_subtitle_area
,
p_region
->
i_align
)
;
memcpy
(
p_sys
->
palette
,
val
.
p_address
,
16
);
p_sys
->
b_force_palette
=
true
;
}
vlc_mutex_unlock
(
&
p_sys
->
lock
);
/* we copy the area: for the subtitle overlap support we want
* to only save the area without margin applied */
spu_area_t
restrained
=
*
p_dst_area
;
msg_Dbg
(
p_object
,
"crop: %i,%i,%i,%i, palette forced: %i"
,
p_sys
->
i_crop_x
,
p_sys
->
i_crop_y
,
p_sys
->
i_crop_width
,
p_sys
->
i_crop_height
,
p_sys
->
b_force_palette
);
}
/* apply margin to subtitles and correct if they go over the picture edge */
if
(
p_subpic
->
b_subtitle
)
restrained
.
i_y
-=
i_margin_y
;
/*****************************************************************************
* CropCallback: called when the highlight properties are changed
*****************************************************************************
* This callback is called from the input thread when we need cropping
*****************************************************************************/
static
int
CropCallback
(
vlc_object_t
*
p_object
,
char
const
*
psz_var
,
vlc_value_t
oldval
,
vlc_value_t
newval
,
void
*
p_data
)
{
VLC_UNUSED
(
oldval
);
VLC_UNUSED
(
newval
);
VLC_UNUSED
(
psz_var
);
spu_area_t
display
=
spu_area_create
(
0
,
0
,
p_fmt
->
i_width
,
p_fmt
->
i_height
,
spu_scale_unit
()
)
;
SpuAreaFitInside
(
&
restrained
,
&
display
);
UpdateSPU
(
(
spu_t
*
)
p_data
,
p_object
);
return
VLC_SUCCESS
;
}
/* Fix the position for the current scale_size */
i_x_offset
=
spu_scale_w
(
restrained
.
i_x
,
restrained
.
scale
);
i_y_offset
=
spu_scale_h
(
restrained
.
i_y
,
restrained
.
scale
);
/*****************************************************************************
* Buffers allocation callbacks for the filters
*****************************************************************************/
/* */
if
(
b_force_palette
)
{
video_palette_t
*
p_palette
=
p_region
->
fmt
.
p_palette
;
video_palette_t
palette
;
static
subpicture_t
*
sub_new_buffer
(
filter_t
*
p_filter
)
{
filter_owner_sys_t
*
p_sys
=
p_filter
->
p_owner
;
/* We suppose DVD palette here */
palette
.
i_entries
=
4
;
for
(
int
i
=
0
;
i
<
4
;
i
++
)
for
(
int
j
=
0
;
j
<
4
;
j
++
)
palette
.
palette
[
i
][
j
]
=
p_sys
->
palette
[
i
][
j
];
subpicture_t
*
p_subpicture
=
subpicture_New
(
NULL
);
if
(
p_subpicture
)
p_subpicture
->
i_channel
=
p_sys
->
i_channel
;
return
p_subpicture
;
}
static
void
sub_del_buffer
(
filter_t
*
p_filter
,
subpicture_t
*
p_subpic
)
{
VLC_UNUSED
(
p_filter
);
subpicture_Delete
(
p_subpic
);
}
if
(
p_palette
->
i_entries
==
palette
.
i_entries
)
{
for
(
int
i
=
0
;
i
<
p_palette
->
i_entries
;
i
++
)
for
(
int
j
=
0
;
j
<
4
;
j
++
)
b_changed_palette
|=
p_palette
->
palette
[
i
][
j
]
!=
palette
.
palette
[
i
][
j
];
}
else
{
b_changed_palette
=
true
;
}
*
p_palette
=
palette
;
}
static
int
SubFilterAllocationInit
(
filter_t
*
p_filter
,
void
*
p_data
)
{
spu_t
*
p_spu
=
p_data
;
/* */
region_fmt
=
p_region
->
fmt
;
p_region_picture
=
p_region
->
p_picture
;
filter_owner_sys_t
*
p_sys
=
malloc
(
sizeof
(
filter_owner_sys_t
)
);
if
(
!
p_sys
)
return
VLC_EGENERIC
;
p_filter
->
pf_sub_buffer_new
=
sub_new_buffer
;
p_filter
->
pf_sub_buffer_del
=
sub_del_buffer
;
/* Scale from rendered size to destination size */
if
(
p_sys
->
p_scale
&&
p_sys
->
p_scale
->
p_module
&&
(
!
b_using_palette
||
(
p_sys
->
p_scale_yuvp
&&
p_sys
->
p_scale_yuvp
->
p_module
)
)
&&
(
scale_size
.
w
!=
SCALE_UNIT
||
scale_size
.
h
!=
SCALE_UNIT
||
b_using_palette
)
)
{
const
unsigned
i_dst_width
=
spu_scale_w
(
p_region
->
fmt
.
i_width
,
scale_size
);
const
unsigned
i_dst_height
=
spu_scale_h
(
p_region
->
fmt
.
i_height
,
scale_size
);
p_filter
->
p_owner
=
p_sys
;
p_sys
->
i_channel
=
spu_RegisterChannel
(
p_spu
);
p_sys
->
p_spu
=
p_spu
;
/* Destroy the cache if unusable */
if
(
p_region
->
p_private
)
{
subpicture_region_private_t
*
p_private
=
p_region
->
p_private
;
bool
b_changed
=
false
;
return
VLC_SUCCESS
;
}
/* Check resize changes */
if
(
i_dst_width
!=
p_private
->
fmt
.
i_width
||
i_dst_height
!=
p_private
->
fmt
.
i_height
)
b_changed
=
true
;
static
void
SubFilterAllocationClean
(
filter_t
*
p_filter
)
{
filter_owner_sys_t
*
p_sys
=
p_filter
->
p_owner
;
/* Check forced palette changes */
if
(
b_changed_palette
)
b_changed
=
true
;
spu_ClearChannel
(
p_sys
->
p_spu
,
p_sys
->
i_channel
);
free
(
p_filter
->
p_owner
);
}
if
(
b_changed
)
{
subpicture_region_private_Delete
(
p_private
);
p_region
->
p_private
=
NULL
;
}
}
/*****************************************************************************
* Public API
*****************************************************************************/
/* Scale if needed into cache */
if
(
!
p_region
->
p_private
&&
i_dst_width
>
0
&&
i_dst_height
>
0
)
{
filter_t
*
p_scale
=
p_sys
->
p_scale
;
#undef spu_Create
/**
* Creates the subpicture unit
*
* \param p_this the parent object which creates the subpicture unit
*/
spu_t
*
spu_Create
(
vlc_object_t
*
p_this
)
{
spu_t
*
p_spu
;
spu_private_t
*
p_sys
;
picture_t
*
p_picture
=
p_region
->
p_picture
;
picture_Hold
(
p_picture
);
p_spu
=
vlc_custom_create
(
p_this
,
sizeof
(
spu_t
)
+
sizeof
(
spu_private_t
),
VLC_OBJECT_GENERIC
,
"subpicture"
);
if
(
!
p_spu
)
return
NULL
;
vlc_object_attach
(
p_spu
,
p_this
);
/* Convert YUVP to YUVA/RGBA first for better scaling quality */
if
(
b_using_palette
)
{
filter_t
*
p_scale_yuvp
=
p_sys
->
p_scale_yuvp
;
/* Initialize spu fields */
p_spu
->
p
=
p_sys
=
(
spu_private_t
*
)
&
p_spu
[
1
];
p_scale_yuvp
->
fmt_in
.
video
=
p_region
->
fmt
;
/* Initialize private fields */
vlc_mutex_init
(
&
p_sys
->
lock
);
/* TODO converting to RGBA for RGB video output is better */
p_scale_yuvp
->
fmt_out
.
video
=
p_region
->
fmt
;
p_scale_yuvp
->
fmt_out
.
video
.
i_chroma
=
VLC_CODEC_YUVA
;
SpuHeapInit
(
&
p_sys
->
heap
);
p_picture
=
p_scale_yuvp
->
pf_video_filter
(
p_scale_yuvp
,
p_picture
);
if
(
!
p_picture
)
{
/* Well we will try conversion+scaling */
msg_Warn
(
p_spu
,
"%4.4s to %4.4s conversion failed"
,
(
const
char
*
)
&
p_scale_yuvp
->
fmt_in
.
video
.
i_chroma
,
(
const
char
*
)
&
p_scale_yuvp
->
fmt_out
.
video
.
i_chroma
);
}
}
p_sys
->
p_text
=
NULL
;
p_sys
->
p_scale
=
NULL
;
p_sys
->
p_scale_yuvp
=
NULL
;
/* Conversion(except from YUVP)/Scaling */
if
(
p_picture
&&
(
p_picture
->
format
.
i_width
!=
i_dst_width
||
p_picture
->
format
.
i_height
!=
i_dst_height
)
)
{
p_scale
->
fmt_in
.
video
=
p_picture
->
format
;
p_scale
->
fmt_out
.
video
=
p_picture
->
format
;
p_sys
->
i_margin
=
var_InheritInteger
(
p_spu
,
"sub-margin"
);
p_scale
->
fmt_out
.
video
.
i_width
=
i_dst_width
;
p_scale
->
fmt_out
.
video
.
i_height
=
i_dst_height
;
/* Register the default subpicture channel */
p_sys
->
i_channel
=
SPU_DEFAULT_CHANNEL
+
1
;
p_scale
->
fmt_out
.
video
.
i_visible_width
=
spu_scale_w
(
p_region
->
fmt
.
i_visible_width
,
scale_size
);
p_scale
->
fmt_out
.
video
.
i_visible_height
=
spu_scale_h
(
p_region
->
fmt
.
i_visible_height
,
scale_size
);
p_sys
->
psz_chain_update
=
NULL
;
vlc_mutex_init
(
&
p_sys
->
chain_lock
);
p_sys
->
p_chain
=
filter_chain_New
(
p_spu
,
"sub filter"
,
false
,
SubFilterAllocationInit
,
SubFilterAllocationClean
,
p_spu
);
p_picture
=
p_scale
->
pf_video_filter
(
p_scale
,
p_picture
);
if
(
!
p_picture
)
msg_Err
(
p_spu
,
"scaling failed"
);
}
/* Load text and scale module */
SpuRenderCreateAndLoadText
(
p_spu
);
SpuRenderCreateAndLoadScale
(
p_spu
);
/* */
if
(
p_picture
)
{
p_region
->
p_private
=
subpicture_region_private_New
(
&
p_picture
->
format
);
if
(
p_region
->
p_private
)
{
p_region
->
p_private
->
p_picture
=
p_picture
;
if
(
!
p_region
->
p_private
->
p_picture
)
{
subpicture_region_private_Delete
(
p_region
->
p_private
);
p_region
->
p_private
=
NULL
;
}
}
else
{
picture_Release
(
p_picture
);
}
}
}
p_sys
->
i_last_sort_date
=
-
1
;
/* And use the scaled picture */
if
(
p_region
->
p_private
)
{
region_fmt
=
p_region
->
p_private
->
fmt
;
p_region_picture
=
p_region
->
p_private
->
p_picture
;
}
}
return
p_spu
;
}
/* Force cropping if requested */
if
(
b_force_crop
)
{
int
i_crop_x
=
spu_scale_w
(
p_sys
->
i_crop_x
,
scale_size
);
int
i_crop_y
=
spu_scale_h
(
p_sys
->
i_crop_y
,
scale_size
);
int
i_crop_width
=
spu_scale_w
(
p_sys
->
i_crop_width
,
scale_size
);
int
i_crop_height
=
spu_scale_h
(
p_sys
->
i_crop_height
,
scale_size
);
/**
* Destroy the subpicture unit
*
* \param p_this the parent object which destroys the subpicture unit
*/
void
spu_Destroy
(
spu_t
*
p_spu
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
/* Find the intersection */
if
(
i_crop_x
+
i_crop_width
<=
i_x_offset
||
i_x_offset
+
(
int
)
region_fmt
.
i_visible_width
<
i_crop_x
||
i_crop_y
+
i_crop_height
<=
i_y_offset
||
i_y_offset
+
(
int
)
region_fmt
.
i_visible_height
<
i_crop_y
)
{
/* No intersection */
region_fmt
.
i_visible_width
=
region_fmt
.
i_visible_height
=
0
;
}
else
{
int
i_x
,
i_y
,
i_x_end
,
i_y_end
;
i_x
=
__MAX
(
i_crop_x
,
i_x_offset
);
i_y
=
__MAX
(
i_crop_y
,
i_y_offset
);
i_x_end
=
__MIN
(
i_crop_x
+
i_crop_width
,
i_x_offset
+
(
int
)
region_fmt
.
i_visible_width
);
i_y_end
=
__MIN
(
i_crop_y
+
i_crop_height
,
i_y_offset
+
(
int
)
region_fmt
.
i_visible_height
);
if
(
p_sys
->
p_text
)
FilterRelease
(
p_sys
->
p_text
);
region_fmt
.
i_x_offset
=
i_x
-
i_x_offset
;
region_fmt
.
i_y_offset
=
i_y
-
i_y_offset
;
region_fmt
.
i_visible_width
=
i_x_end
-
i_x
;
region_fmt
.
i_visible_height
=
i_y_end
-
i_y
;
if
(
p_sys
->
p_scale_yuvp
)
FilterRelease
(
p_sys
->
p_scale_yuvp
);
i_x_offset
=
__MAX
(
i_x
,
0
);
i_y_offset
=
__MAX
(
i_y
,
0
);
}
}
if
(
p_sys
->
p_scale
)
FilterRelease
(
p_sys
->
p_scale
);
subpicture_region_t
*
p_dst
=
*
pp_dst
=
subpicture_region_New
(
&
region_fmt
);
if
(
p_dst
)
{
p_dst
->
i_x
=
i_x_offset
;
p_dst
->
i_y
=
i_y_offset
;
p_dst
->
i_align
=
0
;
p_dst
->
p_picture
=
picture_Hold
(
p_region_picture
);
int
i_fade_alpha
=
255
;
if
(
p_subpic
->
b_fade
)
{
mtime_t
fade_start
=
(
p_subpic
->
i_stop
+
p_subpic
->
i_start
)
/
2
;
filter_chain_Delete
(
p_sys
->
p_chain
);
vlc_mutex_destroy
(
&
p_sys
->
chain_lock
);
free
(
p_sys
->
psz_chain_update
);
if
(
fade_start
<=
render_date
&&
fade_start
<
p_subpic
->
i_stop
)
i_fade_alpha
=
255
*
(
p_subpic
->
i_stop
-
render_date
)
/
(
p_subpic
->
i_stop
-
fade_start
);
}
p_dst
->
i_alpha
=
i_fade_alpha
*
p_subpic
->
i_alpha
*
p_region
->
i_alpha
/
65025
;
}
/* Destroy all remaining subpictures */
SpuHeapClean
(
&
p_sys
->
heap
);
exit:
if
(
b_rerender_text
)
{
/* Some forms of subtitles need to be re-rendered more than
* once, eg. karaoke. We therefore restore the region to its
* pre-rendered state, so the next time through everything is
* calculated again.
vlc_mutex_destroy
(
&
p_sys
->
lock
);
vlc_object_release
(
p_spu
);
}
/**
* Attach/Detach the SPU from any input
*
* \param p_this the object in which to destroy the subpicture unit
* \param b_attach to select attach or detach
*/
if
(
p_region
->
p_picture
)
{
picture_Release
(
p_region
->
p_picture
);
p_region
->
p_picture
=
NULL
;
}
if
(
p_region
->
p_private
)
void
spu_Attach
(
spu_t
*
p_spu
,
vlc_object_t
*
p_input
,
bool
b_attach
)
{
if
(
b_attach
)
{
subpicture_region_private_Delete
(
p_region
->
p_private
);
p_region
->
p_private
=
NULL
;
UpdateSPU
(
p_spu
,
p_input
);
var_Create
(
p_input
,
"highlight"
,
VLC_VAR_BOOL
);
var_AddCallback
(
p_input
,
"highlight"
,
CropCallback
,
p_spu
);
vlc_mutex_lock
(
&
p_spu
->
p
->
lock
);
p_spu
->
p
->
p_input
=
p_input
;
FilterRelease
(
p_spu
->
p
->
p_text
);
p_spu
->
p
->
p_text
=
NULL
;
SpuRenderCreateAndLoadText
(
p_spu
);
vlc_mutex_unlock
(
&
p_spu
->
p
->
lock
);
}
p_region
->
i_align
&=
~
SUBPICTURE_RENDERED
;
else
{
vlc_mutex_lock
(
&
p_spu
->
p
->
lock
);
p_spu
->
p
->
p_input
=
NULL
;
vlc_mutex_unlock
(
&
p_spu
->
p
->
lock
);
/* Delete callbacks */
var_DelCallback
(
p_input
,
"highlight"
,
CropCallback
,
p_spu
);
var_Destroy
(
p_input
,
"highlight"
);
}
if
(
b_restore_format
)
p_region
->
fmt
=
fmt_original
;
}
/**
* This function compares two 64 bits integers.
* It can be used by qsort.
* Inform the SPU filters of mouse event
*/
static
int
IntegerCmp
(
int64_t
i0
,
int64_t
i1
)
int
spu_ProcessMouse
(
spu_t
*
p_spu
,
const
vlc_mouse_t
*
p_mouse
,
const
video_format_t
*
p_fmt
)
{
return
i0
<
i1
?
-
1
:
i0
>
i1
?
1
:
0
;
spu_private_t
*
p_sys
=
p_spu
->
p
;
vlc_mutex_lock
(
&
p_sys
->
chain_lock
);
filter_chain_MouseEvent
(
p_sys
->
p_chain
,
p_mouse
,
p_fmt
);
vlc_mutex_unlock
(
&
p_sys
->
chain_lock
);
return
VLC_SUCCESS
;
}
/**
* This function compares 2 subpictures using the following properties
* (ordered by priority)
* 1. absolute positionning
* 2. start time
* 3. creation order (per channel)
*
* It can be used by qsort.
* Display a subpicture
*
* XXX spu_RenderSubpictures depends heavily on this order.
* 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
*/
static
int
SubpictureCmp
(
const
void
*
s0
,
const
void
*
s1
)
void
spu_PutSubpicture
(
spu_t
*
p_spu
,
subpicture_t
*
p_subpic
)
{
subpicture_t
*
p_subpic0
=
*
(
subpicture_t
**
)
s0
;
subpicture_t
*
p_subpic1
=
*
(
subpicture_t
**
)
s1
;
int
r
;
spu_private_t
*
p_sys
=
p_spu
->
p
;
r
=
IntegerCmp
(
!
p_subpic0
->
b_absolute
,
!
p_subpic1
->
b_absolute
);
if
(
!
r
)
r
=
IntegerCmp
(
p_subpic0
->
i_start
,
p_subpic1
->
i_start
);
if
(
!
r
)
r
=
IntegerCmp
(
p_subpic0
->
i_channel
,
p_subpic1
->
i_channel
);
if
(
!
r
)
r
=
IntegerCmp
(
p_subpic0
->
i_order
,
p_subpic1
->
i_order
);
return
r
;
}
/* SPU_DEFAULT_CHANNEL always reset itself */
if
(
p_subpic
->
i_channel
==
SPU_DEFAULT_CHANNEL
)
spu_ClearChannel
(
p_spu
,
SPU_DEFAULT_CHANNEL
);
/*****************************************************************************
* Object variables callbacks
*****************************************************************************/
/* 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
);
/*****************************************************************************
* UpdateSPU: update subpicture settings
*****************************************************************************
* This function is called from CropCallback and at initialization time, to
* retrieve crop information from the input.
*****************************************************************************/
static
void
UpdateSPU
(
spu_t
*
p_spu
,
vlc_object_t
*
p_object
)
/* */
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
);
}
subpicture_t
*
spu_Render
(
spu_t
*
p_spu
,
const
video_format_t
*
p_fmt_dst
,
const
video_format_t
*
p_fmt_src
,
mtime_t
render_subtitle_date
,
mtime_t
render_osd_date
,
bool
b_subtitle_only
)
{
spu_private_t
*
p_sys
=
p_spu
->
p
;
vlc_value_t
val
;
/* 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
);
p_sys
->
b_force_palette
=
false
;
p_sys
->
b_force_crop
=
false
;
if
(
var_Get
(
p_object
,
"highlight"
,
&
val
)
||
!
val
.
b_bool
)
vlc_mutex_lock
(
&
p_sys
->
chain_lock
);
if
(
psz_chain_update
)
{
vlc_mutex_unlock
(
&
p_sys
->
lock
);
return
;
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_unlock
(
&
p_sys
->
chain_lock
);
p_sys
->
b_force_crop
=
true
;
p_sys
->
i_crop_x
=
var_GetInteger
(
p_object
,
"x-start"
);
p_sys
->
i_crop_y
=
var_GetInteger
(
p_object
,
"y-start"
);
p_sys
->
i_crop_width
=
var_GetInteger
(
p_object
,
"x-end"
)
-
p_sys
->
i_crop_x
;
p_sys
->
i_crop_height
=
var_GetInteger
(
p_object
,
"y-end"
)
-
p_sys
->
i_crop_y
;
/* Get the sorted list of subpicture to render */
vlc_mutex_lock
(
&
p_sys
->
lock
);
if
(
var_Get
(
p_object
,
"menu-palette"
,
&
val
)
==
VLC_SUCCESS
)
subpicture_t
*
p_list
=
SpuSortSubpictures
(
p_spu
,
render_subtitle_date
,
render_osd_date
,
b_subtitle_only
);
if
(
!
p_list
)
{
memcpy
(
p_sys
->
palette
,
val
.
p_address
,
16
);
p_sys
->
b_force_palette
=
true
;
vlc_mutex_unlock
(
&
p_sys
->
lock
);
return
NULL
;
}
/* Render the current list of subpictures */
subpicture_t
*
p_render
=
SpuRenderSubpictures
(
p_spu
,
p_fmt_dst
,
p_list
,
p_fmt_src
,
render_subtitle_date
,
render_osd_date
);
vlc_mutex_unlock
(
&
p_sys
->
lock
);
msg_Dbg
(
p_object
,
"crop: %i,%i,%i,%i, palette forced: %i"
,
p_sys
->
i_crop_x
,
p_sys
->
i_crop_y
,
p_sys
->
i_crop_width
,
p_sys
->
i_crop_height
,
p_sys
->
b_force_palette
);
return
p_render
;
}
/*****************************************************************************
* CropCallback: called when the highlight properties are changed
*****************************************************************************
* This callback is called from the input thread when we need cropping
*****************************************************************************/
static
int
CropCallback
(
vlc_object_t
*
p_object
,
char
const
*
psz_var
,
vlc_value_t
oldval
,
vlc_value_t
newval
,
void
*
p_data
)
void
spu_OffsetSubtitleDate
(
spu_t
*
p_spu
,
mtime_t
i_duration
)
{
VLC_UNUSED
(
oldval
);
VLC_UNUSED
(
newval
);
VLC_UNUSED
(
psz_var
)
;
spu_private_t
*
p_sys
=
p_spu
->
p
;
UpdateSPU
(
(
spu_t
*
)
p_data
,
p_object
);
return
VLC_SUCCESS
;
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
);
}
/*****************************************************************************
* Buffers allocation callbacks for the filters
*****************************************************************************/
static
int
spu_get_attachments
(
filter_t
*
p_filter
,
input_attachment_t
***
ppp_attachment
,
int
*
pi_attachment
)
int
spu_RegisterChannel
(
spu_t
*
p_spu
)
{
spu_
t
*
p_spu
=
p_filter
->
p_owner
->
p_spu
;
spu_
private_t
*
p_sys
=
p_spu
->
p
;
int
i_ret
=
VLC_EGENERIC
;
if
(
p_spu
->
p
->
p_input
)
i_ret
=
input_Control
(
(
input_thread_t
*
)
p_spu
->
p
->
p_input
,
INPUT_GET_ATTACHMENTS
,
ppp_attachment
,
pi_attachment
);
return
i_ret
;
vlc_mutex_lock
(
&
p_sys
->
lock
);
int
i_channel
=
p_sys
->
i_channel
++
;
vlc_mutex_unlock
(
&
p_sys
->
lock
);
return
i_channel
;
}
static
subpicture_t
*
sub_new_buffer
(
filter_t
*
p_filter
)
void
spu_ClearChannel
(
spu_t
*
p_spu
,
int
i_channel
)
{
filter_owner_sys_t
*
p_sys
=
p_filter
->
p_owner
;
spu_private_t
*
p_sys
=
p_spu
->
p
;
subpicture_t
*
p_subpicture
=
subpicture_New
(
NULL
);
if
(
p_subpicture
)
p_subpicture
->
i_channel
=
p_sys
->
i_channel
;
return
p_subpicture
;
}
static
void
sub_del_buffer
(
filter_t
*
p_filter
,
subpicture_t
*
p_subpic
)
{
VLC_UNUSED
(
p_filter
);
subpicture_Delete
(
p_subpic
);
}
static
picture_t
*
spu_new_video_buffer
(
filter_t
*
p_filter
)
{
const
video_format_t
*
p_fmt
=
&
p_filter
->
fmt_out
.
video
;
vlc_mutex_lock
(
&
p_sys
->
lock
);
VLC_UNUSED
(
p_filter
);
return
picture_NewFromFormat
(
p_fmt
);
}
static
void
spu_del_video_buffer
(
filter_t
*
p_filter
,
picture_t
*
p_picture
)
{
VLC_UNUSED
(
p_filter
);
picture_Release
(
p_picture
);
for
(
int
i_subpic
=
0
;
i_subpic
<
VOUT_MAX_SUBPICTURES
;
i_subpic
++
)
{
spu_heap_entry_t
*
p_entry
=
&
p_sys
->
heap
.
p_entry
[
i_subpic
];
subpicture_t
*
p_subpic
=
p_entry
->
p_subpicture
;
if
(
!
p_subpic
)
continue
;
if
(
p_subpic
->
i_channel
!=
i_channel
&&
(
i_channel
!=
-
1
||
p_subpic
->
i_channel
==
SPU_DEFAULT_CHANNEL
)
)
continue
;
/* You cannot delete subpicture outside of spu_SortSubpictures */
p_entry
->
b_reject
=
true
;
}
vlc_mutex_unlock
(
&
p_sys
->
lock
);
}
static
int
SubFilterAllocationInit
(
filter_t
*
p_filter
,
void
*
p_data
)
void
spu_ChangeFilters
(
spu_t
*
p_spu
,
const
char
*
psz_filters
)
{
spu_t
*
p_spu
=
p_data
;
filter_owner_sys_t
*
p_sys
=
malloc
(
sizeof
(
filter_owner_sys_t
)
);
if
(
!
p_sys
)
return
VLC_EGENERIC
;
spu_private_t
*
p_sys
=
p_spu
->
p
;
p_filter
->
pf_sub_buffer_new
=
sub_new_buffer
;
p_filter
->
pf_sub_buffer_del
=
sub_del_buffer
;
vlc_mutex_lock
(
&
p_sys
->
lock
);
p_filter
->
p_owner
=
p_sys
;
p_sys
->
i_channel
=
spu_RegisterChannel
(
p_spu
);
p_sys
->
p_spu
=
p_spu
;
free
(
p_sys
->
psz_chain_update
);
p_sys
->
psz_chain_update
=
strdup
(
psz_filters
);
return
VLC_SUCCESS
;
vlc_mutex_unlock
(
&
p_sys
->
lock
)
;
}
static
void
SubFilterAllocationClean
(
filter_t
*
p_filter
)
void
spu_ChangeMargin
(
spu_t
*
p_spu
,
int
i_margin
)
{
filter_owner_sys_t
*
p_sys
=
p_filter
->
p_owner
;
spu_private_t
*
p_sys
=
p_spu
->
p
;
spu_ClearChannel
(
p_sys
->
p_spu
,
p_sys
->
i_channel
);
free
(
p_filter
->
p_owner
);
vlc_mutex_lock
(
&
p_sys
->
lock
);
p_sys
->
i_margin
=
i_margin
;
vlc_mutex_unlock
(
&
p_sys
->
lock
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment