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
05a8e024
Commit
05a8e024
authored
Mar 28, 2011
by
Juha Jeronen
Committed by
Laurent Aimar
Mar 29, 2011
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Phosphor deinterlacer
Signed-off-by:
Laurent Aimar
<
fenrir@videolan.org
>
parent
7e8f1ce6
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
763 additions
and
27 deletions
+763
-27
modules/video_filter/deinterlace.c
modules/video_filter/deinterlace.c
+755
-21
src/control/video.c
src/control/video.c
+5
-4
src/libvlc-module.c
src/libvlc-module.c
+2
-2
src/video_output/interlacing.c
src/video_output/interlacing.c
+1
-0
No files found.
modules/video_filter/deinterlace.c
View file @
05a8e024
/*****************************************************************************
/*****************************************************************************
* deinterlace.c : deinterlacer plugin for vlc
* deinterlace.c : deinterlacer plugin for vlc
*****************************************************************************
*****************************************************************************
* Copyright (C) 2000-20
09
the VideoLAN team
* Copyright (C) 2000-20
11
the VideoLAN team
* $Id$
* $Id$
*
*
* Author: Sam Hocevar <sam@zoy.org>
* Author: Sam Hocevar <sam@zoy.org>
* Juha Jeronen <juha.jeronen@jyu.fi> (Phosphor mode)
*
*
* This program is free software; you can redistribute it and/or modify
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU General Public License as published by
...
@@ -44,14 +45,15 @@
...
@@ -44,14 +45,15 @@
# include "mmx.h"
# include "mmx.h"
#endif
#endif
#define DEINTERLACE_DISCARD 1
#define DEINTERLACE_DISCARD 1
#define DEINTERLACE_MEAN 2
#define DEINTERLACE_MEAN 2
#define DEINTERLACE_BLEND 3
#define DEINTERLACE_BLEND 3
#define DEINTERLACE_BOB 4
#define DEINTERLACE_BOB 4
#define DEINTERLACE_LINEAR 5
#define DEINTERLACE_LINEAR 5
#define DEINTERLACE_X 6
#define DEINTERLACE_X 6
#define DEINTERLACE_YADIF 7
#define DEINTERLACE_YADIF 7
#define DEINTERLACE_YADIF2X 8
#define DEINTERLACE_YADIF2X 8
#define DEINTERLACE_PHOSPHOR 9
/*****************************************************************************
/*****************************************************************************
* Module descriptor
* Module descriptor
...
@@ -68,9 +70,63 @@ static void Close( vlc_object_t * );
...
@@ -68,9 +70,63 @@ static void Close( vlc_object_t * );
#define FILTER_CFG_PREFIX "sout-deinterlace-"
#define FILTER_CFG_PREFIX "sout-deinterlace-"
static
const
char
*
const
mode_list
[]
=
{
static
const
char
*
const
mode_list
[]
=
{
"discard"
,
"blend"
,
"mean"
,
"bob"
,
"linear"
,
"x"
,
"yadif"
,
"yadif2x"
};
"discard"
,
"blend"
,
"mean"
,
"bob"
,
"linear"
,
"x"
,
"yadif"
,
"yadif2x"
,
"phosphor"
};
static
const
char
*
const
mode_list_text
[]
=
{
static
const
char
*
const
mode_list_text
[]
=
{
N_
(
"Discard"
),
N_
(
"Blend"
),
N_
(
"Mean"
),
N_
(
"Bob"
),
N_
(
"Linear"
),
"X"
,
"Yadif"
,
"Yadif (2x)"
};
N_
(
"Discard"
),
N_
(
"Blend"
),
N_
(
"Mean"
),
N_
(
"Bob"
),
N_
(
"Linear"
),
"X"
,
"Yadif"
,
"Yadif (2x)"
,
N_
(
"Phosphor"
)
};
/* Tooltips drop linefeeds (at least in the Qt GUI);
thus the space before each set of consecutive \n. */
#define PHOSPHOR_CHROMA_TEXT N_("Phosphor chroma mode for 4:2:0 input")
#define PHOSPHOR_CHROMA_LONGTEXT N_("Choose handling for colours in those "\
"output frames that fall across input "\
"frame boundaries. \n"\
"\n"\
"Latest: take chroma from new (bright) "\
"field only. Good for interlaced input, "\
"such as videos from a camcorder. \n"\
"\n"\
"AltLine: take chroma line 1 from top "\
"field, line 2 from bottom field, etc. \n"\
"Default, good for NTSC telecined input "\
"(anime DVDs, etc.). \n"\
"\n"\
"Blend: average input field chromas. "\
"May distort the colours of the new "\
"(bright) field, too. \n"\
"\n"\
"Upconvert: output in 4:2:2 format "\
"(independent chroma for each field). "\
"Best simulation, but requires more CPU "\
"and memory bandwidth.")
#define PHOSPHOR_DIMMER_TEXT N_("Phosphor old field dimmer strength")
#define PHOSPHOR_DIMMER_LONGTEXT N_("This controls the strength of the "\
"darkening filter that simulates CRT TV "\
"phosphor light decay for the old field "\
"in the Phosphor framerate doubler. "\
"Default: Low.")
/* These numbers, and phosphor_chroma_list[], should be in the same order
as phosphor_chroma_list_text[]. The value 0 is reserved, because
var_GetInteger() returns 0 in case of error. */
typedef
enum
{
PC_LATEST
=
1
,
PC_ALTLINE
=
2
,
PC_BLEND
=
3
,
PC_UPCONVERT
=
4
}
phosphor_chroma_t
;
static
const
int
phosphor_chroma_list
[]
=
{
PC_LATEST
,
PC_ALTLINE
,
PC_BLEND
,
PC_UPCONVERT
};
static
const
char
*
const
phosphor_chroma_list_text
[]
=
{
N_
(
"Latest"
),
N_
(
"AltLine"
),
N_
(
"Blend"
),
N_
(
"Upconvert"
)
};
/* Same here. Same order as in phosphor_dimmer_list_text[],
and the value 0 is reserved for config error. */
static
const
int
phosphor_dimmer_list
[]
=
{
1
,
2
,
3
,
4
};
static
const
char
*
const
phosphor_dimmer_list_text
[]
=
{
N_
(
"Off"
),
N_
(
"Low"
),
N_
(
"Medium"
),
N_
(
"High"
)
};
vlc_module_begin
()
vlc_module_begin
()
set_description
(
N_
(
"Deinterlacing video filter"
)
)
set_description
(
N_
(
"Deinterlacing video filter"
)
)
...
@@ -83,6 +139,14 @@ vlc_module_begin ()
...
@@ -83,6 +139,14 @@ vlc_module_begin ()
SOUT_MODE_LONGTEXT
,
false
)
SOUT_MODE_LONGTEXT
,
false
)
change_string_list
(
mode_list
,
mode_list_text
,
0
)
change_string_list
(
mode_list
,
mode_list_text
,
0
)
change_safe
()
change_safe
()
add_integer
(
FILTER_CFG_PREFIX
"phosphor-chroma"
,
2
,
PHOSPHOR_CHROMA_TEXT
,
PHOSPHOR_CHROMA_LONGTEXT
,
true
)
change_integer_list
(
phosphor_chroma_list
,
phosphor_chroma_list_text
)
change_safe
()
add_integer
(
FILTER_CFG_PREFIX
"phosphor-dimmer"
,
2
,
PHOSPHOR_DIMMER_TEXT
,
PHOSPHOR_DIMMER_LONGTEXT
,
true
)
change_integer_list
(
phosphor_dimmer_list
,
phosphor_dimmer_list_text
)
change_safe
()
add_shortcut
(
"deinterlace"
)
add_shortcut
(
"deinterlace"
)
set_callbacks
(
Open
,
Close
)
set_callbacks
(
Open
,
Close
)
vlc_module_end
()
vlc_module_end
()
...
@@ -91,13 +155,14 @@ vlc_module_end ()
...
@@ -91,13 +155,14 @@ vlc_module_end ()
/*****************************************************************************
/*****************************************************************************
* Local protypes
* Local protypes
*****************************************************************************/
*****************************************************************************/
static
void
RenderDiscard
(
filter_t
*
,
picture_t
*
,
picture_t
*
,
int
);
static
void
RenderDiscard
(
filter_t
*
,
picture_t
*
,
picture_t
*
,
int
);
static
void
RenderBob
(
filter_t
*
,
picture_t
*
,
picture_t
*
,
int
);
static
void
RenderBob
(
filter_t
*
,
picture_t
*
,
picture_t
*
,
int
);
static
void
RenderMean
(
filter_t
*
,
picture_t
*
,
picture_t
*
);
static
void
RenderMean
(
filter_t
*
,
picture_t
*
,
picture_t
*
);
static
void
RenderBlend
(
filter_t
*
,
picture_t
*
,
picture_t
*
);
static
void
RenderBlend
(
filter_t
*
,
picture_t
*
,
picture_t
*
);
static
void
RenderLinear
(
filter_t
*
,
picture_t
*
,
picture_t
*
,
int
);
static
void
RenderLinear
(
filter_t
*
,
picture_t
*
,
picture_t
*
,
int
);
static
void
RenderX
(
picture_t
*
,
picture_t
*
);
static
void
RenderX
(
picture_t
*
,
picture_t
*
);
static
int
RenderYadif
(
filter_t
*
,
picture_t
*
,
picture_t
*
,
int
,
int
);
static
int
RenderYadif
(
filter_t
*
,
picture_t
*
,
picture_t
*
,
int
,
int
);
static
int
RenderPhosphor
(
filter_t
*
,
picture_t
*
,
picture_t
*
,
int
,
int
);
static
void
MergeGeneric
(
void
*
,
const
void
*
,
const
void
*
,
size_t
);
static
void
MergeGeneric
(
void
*
,
const
void
*
,
const
void
*
,
size_t
);
#if defined(CAN_COMPILE_C_ALTIVEC)
#if defined(CAN_COMPILE_C_ALTIVEC)
...
@@ -122,8 +187,19 @@ static void End3DNow ( void );
...
@@ -122,8 +187,19 @@ static void End3DNow ( void );
static
void
MergeNEON
(
void
*
,
const
void
*
,
const
void
*
,
size_t
);
static
void
MergeNEON
(
void
*
,
const
void
*
,
const
void
*
,
size_t
);
#endif
#endif
/* Converts a full-frame plane_t to a field plane_t */
static
void
FieldFromPlane
(
plane_t
*
p_dst
,
const
plane_t
*
p_src
,
int
i_field
);
/* Composes a frame from the given field pair */
typedef
enum
{
CC_ALTLINE
,
CC_UPCONVERT
,
CC_SOURCE_TOP
,
CC_SOURCE_BOTTOM
,
CC_MERGE
}
compose_chroma_t
;
static
void
ComposeFrame
(
filter_t
*
,
picture_t
*
,
picture_t
*
,
picture_t
*
,
compose_chroma_t
);
static
const
char
*
const
ppsz_filter_options
[]
=
{
static
const
char
*
const
ppsz_filter_options
[]
=
{
"mode"
,
NULL
"mode"
,
"phosphor-chroma"
,
"phosphor-dimmer"
,
NULL
};
};
/* Used for framerate doublers */
/* Used for framerate doublers */
...
@@ -134,6 +210,14 @@ typedef struct {
...
@@ -134,6 +210,14 @@ typedef struct {
bool
pb_top_field_first
[
METADATA_SIZE
];
bool
pb_top_field_first
[
METADATA_SIZE
];
}
metadata_history_t
;
}
metadata_history_t
;
/* Algorithm-specific state */
typedef
struct
{
phosphor_chroma_t
i_chroma_for_420
;
int
i_dimmer_strength
;
}
phosphor_sys_t
;
/* Top-level subsystem state */
#define HISTORY_SIZE (3)
#define HISTORY_SIZE (3)
#define CUSTOM_PTS -1
#define CUSTOM_PTS -1
struct
filter_sys_t
struct
filter_sys_t
...
@@ -154,6 +238,9 @@ struct filter_sys_t
...
@@ -154,6 +238,9 @@ struct filter_sys_t
/* Input frame history buffer for algorithms that perform temporal filtering. */
/* Input frame history buffer for algorithms that perform temporal filtering. */
picture_t
*
pp_history
[
HISTORY_SIZE
];
picture_t
*
pp_history
[
HISTORY_SIZE
];
/* Algorithm-specific substructures */
phosphor_sys_t
phosphor
;
};
};
/* NOTE on i_frame_offset:
/* NOTE on i_frame_offset:
...
@@ -271,6 +358,13 @@ static void SetFilterMethod( filter_t *p_filter, const char *psz_method, vlc_fou
...
@@ -271,6 +358,13 @@ static void SetFilterMethod( filter_t *p_filter, const char *psz_method, vlc_fou
p_sys
->
b_half_height
=
false
;
p_sys
->
b_half_height
=
false
;
p_sys
->
b_use_frame_history
=
true
;
p_sys
->
b_use_frame_history
=
true
;
}
}
else
if
(
!
strcmp
(
psz_method
,
"phosphor"
)
)
{
p_sys
->
i_mode
=
DEINTERLACE_PHOSPHOR
;
p_sys
->
b_double_rate
=
true
;
p_sys
->
b_half_height
=
false
;
p_sys
->
b_use_frame_history
=
true
;
}
else
if
(
!
strcmp
(
psz_method
,
"discard"
)
)
else
if
(
!
strcmp
(
psz_method
,
"discard"
)
)
{
{
const
bool
b_i422
=
i_chroma
==
VLC_CODEC_I422
||
const
bool
b_i422
=
i_chroma
==
VLC_CODEC_I422
||
...
@@ -322,6 +416,7 @@ static void GetOutputFormat( filter_t *p_filter,
...
@@ -322,6 +416,7 @@ static void GetOutputFormat( filter_t *p_filter,
case
DEINTERLACE_X
:
case
DEINTERLACE_X
:
case
DEINTERLACE_YADIF
:
case
DEINTERLACE_YADIF
:
case
DEINTERLACE_YADIF2X
:
case
DEINTERLACE_YADIF2X
:
case
DEINTERLACE_PHOSPHOR
:
p_dst
->
i_chroma
=
p_src
->
i_chroma
;
p_dst
->
i_chroma
=
p_src
->
i_chroma
;
break
;
break
;
default:
default:
...
@@ -330,6 +425,12 @@ static void GetOutputFormat( filter_t *p_filter,
...
@@ -330,6 +425,12 @@ static void GetOutputFormat( filter_t *p_filter,
break
;
break
;
}
}
}
}
else
if
(
p_sys
->
i_mode
==
DEINTERLACE_PHOSPHOR
&&
p_sys
->
phosphor
.
i_chroma_for_420
==
PC_UPCONVERT
)
{
p_dst
->
i_chroma
=
p_src
->
i_chroma
==
VLC_CODEC_J420
?
VLC_CODEC_J422
:
VLC_CODEC_I422
;
}
}
}
static
bool
IsChromaSupported
(
vlc_fourcc_t
i_chroma
)
static
bool
IsChromaSupported
(
vlc_fourcc_t
i_chroma
)
...
@@ -675,8 +776,6 @@ static void RenderBlend( filter_t *p_filter,
...
@@ -675,8 +776,6 @@ static void RenderBlend( filter_t *p_filter,
EndMerge
();
EndMerge
();
}
}
#undef Merge
static
void
MergeGeneric
(
void
*
_p_dest
,
const
void
*
_p_s1
,
static
void
MergeGeneric
(
void
*
_p_dest
,
const
void
*
_p_s1
,
const
void
*
_p_s2
,
size_t
i_bytes
)
const
void
*
_p_s2
,
size_t
i_bytes
)
{
{
...
@@ -1644,6 +1743,593 @@ static int RenderYadif( filter_t *p_filter, picture_t *p_dst, picture_t *p_src,
...
@@ -1644,6 +1743,593 @@ static int RenderYadif( filter_t *p_filter, picture_t *p_dst, picture_t *p_src,
}
}
}
}
/*****************************************************************************
* Phosphor - a framerate doubler that simulates gradual light decay of a CRT.
*****************************************************************************/
/**
* This function converts a normal (full frame) plane_t into a field plane_t.
*
* Field plane_t's can be used e.g. for a weaving copy operation from two
* source frames into one destination frame.
*
* The pixels themselves will not be touched; only the metadata is generated.
* The same pixel data is shared by both the original plane_t and the field
* plane_t. Note, however, that the bottom field's data starts from the
* second line, so for the bottom field, the actual pixel pointer value
* does not exactly match the original plane pixel pointer value. (It points
* one line further down.)
*
* The caller must allocate p_dst (creating a local variable is fine).
*
* @param p_dst Field plane_t is written here. Must be non-NULL.
* @param p_src Original full-frame plane_t. Must be non-NULL.
* @param i_field Extract which field? 0 = top field, 1 = bottom field.
* @see plane_CopyPixels()
* @see ComposeFrame()
* @see RenderPhosphor()
*/
static
void
FieldFromPlane
(
plane_t
*
p_dst
,
const
plane_t
*
p_src
,
int
i_field
)
{
assert
(
p_dst
!=
NULL
);
assert
(
p_src
!=
NULL
);
assert
(
i_field
==
0
||
i_field
==
1
);
/* Start with a copy of the metadata, and then update it to refer
to one field only.
We utilize the fact that plane_CopyPixels() differentiates between
visible_pitch and pitch.
The other field will be defined as the "margin" by doubling the pitch.
The visible pitch will be left as in the original.
*/
(
*
p_dst
)
=
(
*
p_src
);
p_dst
->
i_lines
/=
2
;
p_dst
->
i_visible_lines
/=
2
;
p_dst
->
i_pitch
*=
2
;
/* For the bottom field, skip the first line in the pixel data. */
if
(
i_field
==
1
)
p_dst
->
p_pixels
+=
p_src
->
i_pitch
;
}
/**
* Helper function: composes a frame from the given field pair.
*
* Caller must manage allocation/deallocation of p_outpic.
*
* The inputs are full pictures (frames); only one field
* will be used from each.
*
* Chroma formats of the inputs must match. It is also desirable that the
* visible pitches of both inputs are the same, so that this will do something
* sensible. The pitch or visible pitch of the output does not need to match
* with the input; the compatible (smaller) part of the visible pitch will
* be filled.
*
* The i_output_chroma parameter must always be supplied, but it is only used
* when the chroma format of the input is detected as 4:2:0. Available modes:
* - CC_ALTLINE: Alternate line copy, like for luma. Chroma line 0
* comes from top field picture, chroma line 1 comes
* from bottom field picture, chroma line 2 from top
* field picture, and so on. This is usually the right
* choice for IVTCing NTSC DVD material, but rarely
* for any other use cases.
* - CC_UPCONVERT: The output will have 4:2:2 chroma. All 4:2:0 chroma
* data from both input fields will be used to generate
* the 4:2:2 chroma data of the output. Each output line
* will thus have independent chroma. This is a good
* choice for most purposes except IVTC, if the machine
* can handle the increased throughput. (Make sure to
* allocate a 4:2:2 output picture first!)
* This mode can also be used for converting a 4:2:0
* frame to 4:2:2 format (by passing the same input
* picture for both input fields).
* Conversions: I420, YV12 --> I422
* J420 --> J422
* - CC_SOURCE_TOP: Copy chroma of source top field picture.
* Ignore chroma of source bottom field picture.
* - CC_SOURCE_BOTTOM: Copy chroma of source bottom field picture.
* Ignore chroma of source top field picture.
* - CC_MERGE: Average the chroma of the input field pictures.
* (Note that this has no effect if the input fields
* come from the same frame.)
*
* @param p_outpic Composed picture is written here. Allocated by caller.
* @param p_inpic_top Picture to extract the top field from.
* @param p_inpic_bottom Picture to extract the bottom field from.
* @param i_output_chroma Chroma operation mode for 4:2:0 (see function doc)
* @see compose_chroma_t
* @see RenderPhosphor()
*/
static
void
ComposeFrame
(
filter_t
*
p_filter
,
picture_t
*
p_outpic
,
picture_t
*
p_inpic_top
,
picture_t
*
p_inpic_bottom
,
compose_chroma_t
i_output_chroma
)
{
assert
(
p_filter
!=
NULL
);
assert
(
p_outpic
!=
NULL
);
assert
(
p_inpic_top
!=
NULL
);
assert
(
p_inpic_bottom
!=
NULL
);
/* Valid 4:2:0 chroma handling modes. */
assert
(
i_output_chroma
==
CC_ALTLINE
||
i_output_chroma
==
CC_UPCONVERT
||
i_output_chroma
==
CC_SOURCE_TOP
||
i_output_chroma
==
CC_SOURCE_BOTTOM
||
i_output_chroma
==
CC_MERGE
);
const
int
i_chroma
=
p_filter
->
fmt_in
.
video
.
i_chroma
;
const
bool
b_i422
=
i_chroma
==
VLC_CODEC_I422
||
i_chroma
==
VLC_CODEC_J422
;
const
bool
b_upconvert_chroma
=
(
!
b_i422
&&
i_output_chroma
==
CC_UPCONVERT
);
for
(
int
i_plane
=
0
;
i_plane
<
p_inpic_top
->
i_planes
;
i_plane
++
)
{
bool
b_is_chroma_plane
=
(
i_plane
==
U_PLANE
||
i_plane
==
V_PLANE
);
/* YV12 is YVU, but I422 is YUV. For such input, swap chroma planes
in output when converting to 4:2:2. */
int
i_out_plane
;
if
(
b_is_chroma_plane
&&
b_upconvert_chroma
&&
i_chroma
==
VLC_CODEC_YV12
)
{
if
(
i_plane
==
U_PLANE
)
i_out_plane
=
V_PLANE
;
else
/* V_PLANE */
i_out_plane
=
U_PLANE
;
}
else
{
i_out_plane
=
i_plane
;
}
/* Copy luma or chroma, alternating between input fields. */
if
(
!
b_is_chroma_plane
||
b_i422
||
i_output_chroma
==
CC_ALTLINE
)
{
/* Do an alternating line copy. This is always done for luma,
and for 4:2:2 chroma. It can be requested for 4:2:0 chroma
using CC_ALTLINE (see function doc).
Note that when we get here, the number of lines matches
in input and output.
*/
plane_t
dst_top
;
plane_t
dst_bottom
;
plane_t
src_top
;
plane_t
src_bottom
;
FieldFromPlane
(
&
dst_top
,
&
p_outpic
->
p
[
i_out_plane
],
0
);
FieldFromPlane
(
&
dst_bottom
,
&
p_outpic
->
p
[
i_out_plane
],
1
);
FieldFromPlane
(
&
src_top
,
&
p_inpic_top
->
p
[
i_plane
],
0
);
FieldFromPlane
(
&
src_bottom
,
&
p_inpic_bottom
->
p
[
i_plane
],
1
);
/* Copy each field from the corresponding source. */
plane_CopyPixels
(
&
dst_top
,
&
src_top
);
plane_CopyPixels
(
&
dst_bottom
,
&
src_bottom
);
}
else
/* Input 4:2:0, on a chroma plane, and not in altline mode. */
{
if
(
i_output_chroma
==
CC_UPCONVERT
)
{
/* Upconverting copy - use all data from both input fields.
This produces an output picture with independent chroma
for each field. It can be used for general input when
the two input frames are different.
The output is 4:2:2, but the input is 4:2:0. Thus the output
has twice the lines of the input, and each full chroma plane
in the input corresponds to a field chroma plane in the
output.
*/
plane_t
dst_top
;
plane_t
dst_bottom
;
FieldFromPlane
(
&
dst_top
,
&
p_outpic
->
p
[
i_out_plane
],
0
);
FieldFromPlane
(
&
dst_bottom
,
&
p_outpic
->
p
[
i_out_plane
],
1
);
/* Copy each field from the corresponding source. */
plane_CopyPixels
(
&
dst_top
,
&
p_inpic_top
->
p
[
i_plane
]
);
plane_CopyPixels
(
&
dst_bottom
,
&
p_inpic_bottom
->
p
[
i_plane
]
);
}
else
if
(
i_output_chroma
==
CC_SOURCE_TOP
)
{
/* Copy chroma of input top field. Ignore chroma of input
bottom field. Input and output are both 4:2:0, so we just
copy the whole plane. */
plane_CopyPixels
(
&
p_outpic
->
p
[
i_out_plane
],
&
p_inpic_top
->
p
[
i_plane
]
);
}
else
if
(
i_output_chroma
==
CC_SOURCE_BOTTOM
)
{
/* Copy chroma of input bottom field. Ignore chroma of input
top field. Input and output are both 4:2:0, so we just
copy the whole plane. */
plane_CopyPixels
(
&
p_outpic
->
p
[
i_out_plane
],
&
p_inpic_bottom
->
p
[
i_plane
]
);
}
else
/* i_output_chroma == CC_MERGE */
{
/* Average the chroma of the input fields.
Input and output are both 4:2:0. */
uint8_t
*
p_in_top
,
*
p_in_bottom
,
*
p_out_end
,
*
p_out
;
p_in_top
=
p_inpic_top
->
p
[
i_plane
].
p_pixels
;
p_in_bottom
=
p_inpic_bottom
->
p
[
i_plane
].
p_pixels
;
p_out
=
p_outpic
->
p
[
i_out_plane
].
p_pixels
;
p_out_end
=
p_out
+
p_outpic
->
p
[
i_out_plane
].
i_pitch
*
p_outpic
->
p
[
i_out_plane
].
i_visible_lines
;
int
w
=
FFMIN3
(
p_inpic_top
->
p
[
i_plane
].
i_visible_pitch
,
p_inpic_bottom
->
p
[
i_plane
].
i_visible_pitch
,
p_outpic
->
p
[
i_plane
].
i_visible_pitch
);
for
(
;
p_out
<
p_out_end
;
)
{
Merge
(
p_out
,
p_in_top
,
p_in_bottom
,
w
);
p_out
+=
p_outpic
->
p
[
i_out_plane
].
i_pitch
;
p_in_top
+=
p_inpic_top
->
p
[
i_plane
].
i_pitch
;
p_in_bottom
+=
p_inpic_bottom
->
p
[
i_plane
].
i_pitch
;
}
EndMerge
();
}
}
}
}
#undef Merge
/**
* Helper function: dims (darkens) the given field of the given picture.
*
* This is used for simulating CRT light output decay in RenderPhosphor().
*
* The strength "1" is recommended. It's a matter of taste,
* so it's parametrized.
*
* Note on chroma formats:
* - If input is 4:2:2, all planes are processed.
* - If input is 4:2:0, only the luma plane is processed, because both fields
* have the same chroma. This will distort colours, especially for high
* filter strengths, especially for pixels whose U and/or V values are
* far away from the origin (which is at 128 in uint8 format).
*
* @param p_dst Input/output picture. Will be modified in-place.
* @param i_field Darken which field? 0 = top, 1 = bottom.
* @param i_strength Strength of effect: 1, 2 or 3 (division by 2, 4 or 8).
* @see RenderPhosphor()
* @see ComposeFrame()
*/
static
void
DarkenField
(
picture_t
*
p_dst
,
const
int
i_field
,
const
int
i_strength
)
{
assert
(
p_dst
!=
NULL
);
assert
(
i_field
==
0
||
i_field
==
1
);
assert
(
i_strength
>=
1
&&
i_strength
<=
3
);
unsigned
u_cpu
=
vlc_CPU
();
/* Bitwise ANDing with this clears the i_strength highest bits
of each byte */
#ifdef CAN_COMPILE_MMXEXT
uint64_t
i_strength_u64
=
i_strength
;
/* for MMX version (needs to know
number of bits) */
#endif
const
uint8_t
remove_high_u8
=
0xFF
>>
i_strength
;
const
uint64_t
remove_high_u64
=
remove_high_u8
*
INT64_C
(
0x0101010101010101
);
/* Process luma.
For luma, the operation is just a shift + bitwise AND, so we vectorize
even in the C version.
There is an MMX version, too, because it performs about twice faster.
*/
int
i_plane
=
Y_PLANE
;
uint8_t
*
p_out
,
*
p_out_end
;
int
w
=
p_dst
->
p
[
i_plane
].
i_visible_pitch
;
p_out
=
p_dst
->
p
[
i_plane
].
p_pixels
;
p_out_end
=
p_out
+
p_dst
->
p
[
i_plane
].
i_pitch
*
p_dst
->
p
[
i_plane
].
i_visible_lines
;
/* skip first line for bottom field */
if
(
i_field
==
1
)
p_out
+=
p_dst
->
p
[
i_plane
].
i_pitch
;
int
wm8
=
w
%
8
;
/* remainder */
int
w8
=
w
-
wm8
;
/* part of width that is divisible by 8 */
for
(
;
p_out
<
p_out_end
;
p_out
+=
2
*
p_dst
->
p
[
i_plane
].
i_pitch
)
{
uint64_t
*
po
=
(
uint64_t
*
)
p_out
;
#ifdef CAN_COMPILE_MMXEXT
if
(
u_cpu
&
CPU_CAPABILITY_MMXEXT
)
{
movq_m2r
(
i_strength_u64
,
mm1
);
movq_m2r
(
remove_high_u64
,
mm2
);
for
(
int
x
=
0
;
x
<
w8
;
x
+=
8
)
{
movq_m2r
(
(
*
po
),
mm0
);
psrlq_r2r
(
mm1
,
mm0
);
pand_r2r
(
mm2
,
mm0
);
movq_r2m
(
mm0
,
(
*
po
++
)
);
}
}
else
{
#endif
for
(
int
x
=
0
;
x
<
w8
;
x
+=
8
,
++
po
)
(
*
po
)
=
(
((
*
po
)
>>
i_strength
)
&
remove_high_u64
);
#ifdef CAN_COMPILE_MMXEXT
}
#endif
/* handle the width remainder */
if
(
wm8
)
{
uint8_t
*
po_temp
=
(
uint8_t
*
)
po
;
for
(
int
x
=
0
;
x
<
wm8
;
++
x
,
++
po_temp
)
(
*
po_temp
)
=
(
((
*
po_temp
)
>>
i_strength
)
&
remove_high_u8
);
}
}
/* Process chroma if the field chromas are independent.
The origin (black) is at YUV = (0, 128, 128) in the uint8 format.
The chroma processing is a bit more complicated than luma,
and needs MMX for vectorization.
*/
if
(
p_dst
->
format
.
i_chroma
==
VLC_CODEC_I422
||
p_dst
->
format
.
i_chroma
==
VLC_CODEC_J422
)
{
for
(
i_plane
=
0
;
i_plane
<
p_dst
->
i_planes
;
i_plane
++
)
{
if
(
i_plane
==
Y_PLANE
)
continue
;
/* luma already handled */
int
w
=
p_dst
->
p
[
i_plane
].
i_visible_pitch
;
#ifdef CAN_COMPILE_MMXEXT
int
wm8
=
w
%
8
;
/* remainder */
int
w8
=
w
-
wm8
;
/* part of width that is divisible by 8 */
#endif
p_out
=
p_dst
->
p
[
i_plane
].
p_pixels
;
p_out_end
=
p_out
+
p_dst
->
p
[
i_plane
].
i_pitch
*
p_dst
->
p
[
i_plane
].
i_visible_lines
;
/* skip first line for bottom field */
if
(
i_field
==
1
)
p_out
+=
p_dst
->
p
[
i_plane
].
i_pitch
;
for
(
;
p_out
<
p_out_end
;
p_out
+=
2
*
p_dst
->
p
[
i_plane
].
i_pitch
)
{
#ifdef CAN_COMPILE_MMXEXT
/* See also easy-to-read C version below. */
if
(
u_cpu
&
CPU_CAPABILITY_MMXEXT
)
{
static
const
mmx_t
b128
=
{
.
uq
=
0x8080808080808080ULL
};
movq_m2r
(
b128
,
mm5
);
movq_m2r
(
i_strength_u64
,
mm6
);
movq_m2r
(
remove_high_u64
,
mm7
);
uint64_t
*
po
=
(
uint64_t
*
)
p_out
;
for
(
int
x
=
0
;
x
<
w8
;
x
+=
8
)
{
movq_m2r
(
(
*
po
),
mm0
);
movq_r2r
(
mm5
,
mm2
);
/* 128 */
movq_r2r
(
mm0
,
mm1
);
/* copy of data */
psubusb_r2r
(
mm2
,
mm1
);
/* mm1 = max(data - 128, 0) */
psubusb_r2r
(
mm0
,
mm2
);
/* mm2 = max(128 - data, 0) */
/* >> i_strength */
psrlq_r2r
(
mm6
,
mm1
);
psrlq_r2r
(
mm6
,
mm2
);
pand_r2r
(
mm7
,
mm1
);
pand_r2r
(
mm7
,
mm2
);
/* collect results from pos./neg. parts */
psubb_r2r
(
mm2
,
mm1
);
paddb_r2r
(
mm5
,
mm1
);
movq_r2m
(
mm1
,
(
*
po
++
)
);
}
/* handle the width remainder */
if
(
wm8
)
{
/* The output is closer to 128 than the input;
the result always fits in uint8. */
uint8_t
*
po8
=
(
uint8_t
*
)
po
;
for
(
int
x
=
0
;
x
<
wm8
;
++
x
,
++
po8
)
(
*
po8
)
=
128
+
(
((
*
po8
)
-
128
)
/
(
1
<<
i_strength
)
);
}
}
else
{
#endif
/* 4:2:2 chroma handler, C version */
uint8_t
*
po
=
p_out
;
for
(
int
x
=
0
;
x
<
w
;
++
x
,
++
po
)
(
*
po
)
=
128
+
(
((
*
po
)
-
128
)
/
(
1
<<
i_strength
)
);
#ifdef CAN_COMPILE_MMXEXT
}
#endif
}
/* for p_out... */
}
/* for i_plane... */
}
/* if b_i422 */
#ifdef CAN_COMPILE_MMXEXT
if
(
u_cpu
&
CPU_CAPABILITY_MMXEXT
)
emms
();
#endif
}
/**
* Deinterlace filter. Simulates an interlaced CRT TV (to some extent).
*
* The main use case for this filter is anime for which IVTC is not applicable.
* This is the case, if 24fps telecined material has been mixed with 60fps
* interlaced effects, such as in Sol Bianca or Silent Mobius. It can also
* be used for true interlaced video, such as most camcorder recordings.
*
* The filter has several modes for handling 4:2:0 chroma for those output
* frames that fall across input frame temporal boundaries (i.e. fields come
* from different frames). Upconvert (to 4:2:2) provides the most accurate
* CRT simulation, but requires more CPU and memory bandwidth than the other
* modes. The other modes keep the chroma at 4:2:0.
*
* About these modes: telecined input (such as NTSC anime DVDs) works better
* with AltLine, while true interlaced input works better with Latest.
* Merge is a compromise, which may or may not look acceptable.
* The mode can be set in the VLC advanced configuration,
* All settings > Video > Filters > Deinterlace
*
* Technically speaking, this is an interlaced field renderer targeted for
* progressive displays. It works by framerate doubling, and simulating one
* step of light output decay of the "old" field during the "new" field,
* until the next new field comes in to replace the "old" one.
*
* While playback is running, the simulated light decay gives the picture an
* appearance of visible "scanlines", much like on a real TV. Only when the
* video is paused, it is clearly visible that one of the fields is actually
* brighter than the other.
*
* The main differences to the Bob algorithm are:
* - in addition to the current field, the previous one (fading out)
* is also rendered
* - some horizontal lines don't seem to flicker as much
* - scanline visual effect (adjustable; the dimmer strength can be set
* in the VLC advanced configuration)
* - the picture appears 25%, 38% or 44% darker on average (for dimmer
* strengths 1, 2 and 3)
* - if the input has 4:2:0 chroma, the colours may look messed up in some
* output frames. This is a limitation of the 4:2:0 chroma format, and due
* to the fact that both fields are present in each output picture. Usually
* this doesn't matter in practice, but see the 4:2:0 chroma mode setting
* in the configuration if needed (it may help a bit).
*
* In addition, when this filter is used on an LCD computer monitor,
* the main differences to a real CRT TV are:
* - Pixel shape and grid layout; CRT TVs were designed for interlaced
* field rendering, while LCD monitors weren't.
* - No scan flicker even though the display runs (usually) at 60Hz.
* (This at least is a good thing.)
*
* The output vertical resolution should be large enough for the scaling
* not to have a too adverse effect on the regular scanline pattern.
* In practice, NTSC video can be acceptably rendered already at 1024x600
* if fullscreen even on an LCD. PAL video requires more.
*
* Just like Bob, this filter works properly only if the input framerate
* is stable. Otherwise the scanline effect breaks down and the picture
* will flicker.
*
* Soft field repeat (repeat_pict) is supported. Note that the generated
* "repeated" output picture is unique because of the simulated light decay.
* Its "old" field comes from the same input frame as the "new" one, unlike
* the first output picture of the same frame.
*
* As many output frames should be requested for each input frame as is
* indicated by p_src->i_nb_fields. This is done by calling this function
* several times, first with i_order = 0, and then with all other parameters
* the same, but a new p_dst, increasing i_order (1 for second field,
* and then if i_nb_fields = 3, also i_order = 2 to get the repeated first
* field), and alternating i_field (starting, at i_order = 0, with the field
* according to p_src->b_top_field_first). See Deinterlace() for an example.
*
* @param p_filter The filter instance. Must be non-NULL.
* @param p_dst Output frame. Must be allocated by caller.
* @param p_src Input frame. Must exist.
* @param i_order Temporal field number: 0 = first, 1 = second, 2 = rep. first.
* @param i_field Render which field? 0 = top field, 1 = bottom field.
* @return VLC error code (int).
* @retval VLC_SUCCESS The requested field was rendered into p_dst.
* @retval VLC_EGENERIC No pictures in history buffer, cannot render.
* @see RenderBob()
* @see RenderLinear()
* @see Deinterlace()
*/
static
int
RenderPhosphor
(
filter_t
*
p_filter
,
picture_t
*
p_dst
,
picture_t
*
p_src
,
int
i_order
,
int
i_field
)
{
assert
(
p_filter
!=
NULL
);
assert
(
p_dst
!=
NULL
);
assert
(
p_src
!=
NULL
);
assert
(
i_order
>=
0
&&
i_order
<=
2
);
/* 2 = soft field repeat */
assert
(
i_field
==
0
||
i_field
==
1
);
filter_sys_t
*
p_sys
=
p_filter
->
p_sys
;
/* Last two input frames */
picture_t
*
p_in
=
p_sys
->
pp_history
[
HISTORY_SIZE
-
1
];
picture_t
*
p_old
=
p_sys
->
pp_history
[
HISTORY_SIZE
-
2
];
/* Use the same input picture as "old" at the first frame after startup */
if
(
!
p_old
)
p_old
=
p_in
;
/* If the history mechanism has failed, we can't do anything. */
if
(
!
p_in
)
return
VLC_EGENERIC
;
assert
(
p_old
!=
NULL
);
assert
(
p_in
!=
NULL
);
/* Decide sources for top & bottom fields of output. */
picture_t
*
p_in_top
=
p_in
;
picture_t
*
p_in_bottom
=
p_in
;
/* For the first output field this frame,
grab "old" field from previous frame. */
if
(
i_order
==
0
)
{
if
(
i_field
==
0
)
/* rendering top field */
p_in_bottom
=
p_old
;
else
/* i_field == 1, rendering bottom field */
p_in_top
=
p_old
;
}
compose_chroma_t
cc
;
switch
(
p_sys
->
phosphor
.
i_chroma_for_420
)
{
case
PC_BLEND
:
cc
=
CC_MERGE
;
break
;
case
PC_LATEST
:
if
(
i_field
==
0
)
cc
=
CC_SOURCE_TOP
;
else
/* i_field == 1 */
cc
=
CC_SOURCE_BOTTOM
;
break
;
case
PC_ALTLINE
:
cc
=
CC_ALTLINE
;
break
;
case
PC_UPCONVERT
:
cc
=
CC_UPCONVERT
;
break
;
default:
/* The above are the only possibilities, if there are no bugs. */
assert
(
0
);
break
;
}
ComposeFrame
(
p_filter
,
p_dst
,
p_in_top
,
p_in_bottom
,
cc
);
/* Simulate phosphor light output decay for the old field.
The dimmer can also be switched off in the configuration, but that is
more of a technical curiosity or an educational toy for advanced users
than a useful deinterlacer mode (although it does make telecined
material look slightly better than without any filtering).
In most use cases the dimmer is used.
*/
if
(
p_sys
->
phosphor
.
i_dimmer_strength
>
0
)
DarkenField
(
p_dst
,
!
i_field
,
p_sys
->
phosphor
.
i_dimmer_strength
);
return
VLC_SUCCESS
;
}
/*****************************************************************************
/*****************************************************************************
* video filter2 functions
* video filter2 functions
*****************************************************************************/
*****************************************************************************/
...
@@ -1837,6 +2523,18 @@ static picture_t *Deinterlace( filter_t *p_filter, picture_t *p_pic )
...
@@ -1837,6 +2523,18 @@ static picture_t *Deinterlace( filter_t *p_filter, picture_t *p_pic )
if
(
p_dst
[
2
]
)
if
(
p_dst
[
2
]
)
RenderYadif
(
p_filter
,
p_dst
[
2
],
p_pic
,
2
,
!
b_top_field_first
);
RenderYadif
(
p_filter
,
p_dst
[
2
],
p_pic
,
2
,
!
b_top_field_first
);
break
;
break
;
case
DEINTERLACE_PHOSPHOR
:
if
(
RenderPhosphor
(
p_filter
,
p_dst
[
0
],
p_pic
,
0
,
!
b_top_field_first
)
)
goto
drop
;
if
(
p_dst
[
1
]
)
RenderPhosphor
(
p_filter
,
p_dst
[
1
],
p_pic
,
1
,
b_top_field_first
);
if
(
p_dst
[
2
]
)
RenderPhosphor
(
p_filter
,
p_dst
[
2
],
p_pic
,
2
,
!
b_top_field_first
);
break
;
}
}
/* Set output timestamps, if the algorithm didn't request CUSTOM_PTS for this frame. */
/* Set output timestamps, if the algorithm didn't request CUSTOM_PTS for this frame. */
...
@@ -2002,6 +2700,42 @@ static int Open( vlc_object_t *p_this )
...
@@ -2002,6 +2700,42 @@ static int Open( vlc_object_t *p_this )
SetFilterMethod
(
p_filter
,
psz_mode
,
p_filter
->
fmt_in
.
video
.
i_chroma
);
SetFilterMethod
(
p_filter
,
psz_mode
,
p_filter
->
fmt_in
.
video
.
i_chroma
);
free
(
psz_mode
);
free
(
psz_mode
);
if
(
p_sys
->
i_mode
==
DEINTERLACE_PHOSPHOR
)
{
int
i_c420
=
var_GetInteger
(
p_filter
,
FILTER_CFG_PREFIX
"phosphor-chroma"
);
if
(
i_c420
!=
PC_LATEST
&&
i_c420
!=
PC_ALTLINE
&&
i_c420
!=
PC_BLEND
&&
i_c420
!=
PC_UPCONVERT
)
{
msg_Dbg
(
p_filter
,
"Phosphor 4:2:0 input chroma mode not set"
\
"or out of range (valid: 1, 2, 3 or 4), "
\
"using default"
);
i_c420
=
PC_ALTLINE
;
}
msg_Dbg
(
p_filter
,
"using Phosphor 4:2:0 input chroma mode %d"
,
i_c420
);
/* This maps directly to the phosphor_chroma_t enum. */
p_sys
->
phosphor
.
i_chroma_for_420
=
i_c420
;
int
i_dimmer
=
var_GetInteger
(
p_filter
,
FILTER_CFG_PREFIX
"phosphor-dimmer"
);
if
(
i_dimmer
<
1
||
i_dimmer
>
4
)
{
msg_Dbg
(
p_filter
,
"Phosphor dimmer strength not set "
\
"or out of range (valid: 1, 2, 3 or 4), "
\
"using default"
);
i_dimmer
=
2
;
/* low */
}
msg_Dbg
(
p_filter
,
"using Phosphor dimmer strength %d"
,
i_dimmer
);
/* The internal value ranges from 0 to 3. */
p_sys
->
phosphor
.
i_dimmer_strength
=
i_dimmer
-
1
;
}
else
{
p_sys
->
phosphor
.
i_chroma_for_420
=
PC_ALTLINE
;
p_sys
->
phosphor
.
i_dimmer_strength
=
1
;
}
/* */
/* */
video_format_t
fmt
;
video_format_t
fmt
;
GetOutputFormat
(
p_filter
,
&
fmt
,
&
p_filter
->
fmt_in
.
video
);
GetOutputFormat
(
p_filter
,
&
fmt
,
&
p_filter
->
fmt_in
.
video
);
...
...
src/control/video.c
View file @
05a8e024
...
@@ -563,10 +563,11 @@ void libvlc_video_set_deinterlace( libvlc_media_player_t *p_mi,
...
@@ -563,10 +563,11 @@ void libvlc_video_set_deinterlace( libvlc_media_player_t *p_mi,
if
(
psz_mode
==
NULL
)
if
(
psz_mode
==
NULL
)
psz_mode
=
""
;
psz_mode
=
""
;
if
(
*
psz_mode
if
(
*
psz_mode
&&
strcmp
(
psz_mode
,
"blend"
)
&&
strcmp
(
psz_mode
,
"bob"
)
&&
strcmp
(
psz_mode
,
"blend"
)
&&
strcmp
(
psz_mode
,
"bob"
)
&&
strcmp
(
psz_mode
,
"discard"
)
&&
strcmp
(
psz_mode
,
"linear"
)
&&
strcmp
(
psz_mode
,
"discard"
)
&&
strcmp
(
psz_mode
,
"linear"
)
&&
strcmp
(
psz_mode
,
"mean"
)
&&
strcmp
(
psz_mode
,
"x"
)
&&
strcmp
(
psz_mode
,
"mean"
)
&&
strcmp
(
psz_mode
,
"x"
)
&&
strcmp
(
psz_mode
,
"yadif"
)
&&
strcmp
(
psz_mode
,
"yadif2x"
))
&&
strcmp
(
psz_mode
,
"yadif"
)
&&
strcmp
(
psz_mode
,
"yadif2x"
)
&&
strcmp
(
psz_mode
,
"phosphor"
))
return
;
return
;
if
(
*
psz_mode
)
if
(
*
psz_mode
)
...
...
src/libvlc-module.c
View file @
05a8e024
...
@@ -482,11 +482,11 @@ static const char * const ppsz_deinterlace_text[] = {
...
@@ -482,11 +482,11 @@ static const char * const ppsz_deinterlace_text[] = {
"Deinterlace method to use for video processing.")
"Deinterlace method to use for video processing.")
static
const
char
*
const
ppsz_deinterlace_mode
[]
=
{
static
const
char
*
const
ppsz_deinterlace_mode
[]
=
{
"discard"
,
"blend"
,
"mean"
,
"bob"
,
"discard"
,
"blend"
,
"mean"
,
"bob"
,
"linear"
,
"x"
,
"yadif"
,
"yadif2x"
"linear"
,
"x"
,
"yadif"
,
"yadif2x"
,
"phosphor"
};
};
static
const
char
*
const
ppsz_deinterlace_mode_text
[]
=
{
static
const
char
*
const
ppsz_deinterlace_mode_text
[]
=
{
N_
(
"Discard"
),
N_
(
"Blend"
),
N_
(
"Mean"
),
N_
(
"Bob"
),
N_
(
"Discard"
),
N_
(
"Blend"
),
N_
(
"Mean"
),
N_
(
"Bob"
),
N_
(
"Linear"
),
"X"
,
"Yadif"
,
"Yadif (2x)"
N_
(
"Linear"
),
"X"
,
"Yadif"
,
"Yadif (2x)"
,
N_
(
"Phosphor"
)
};
};
static
const
int
pi_pos_values
[]
=
{
0
,
1
,
2
,
4
,
8
,
5
,
6
,
9
,
10
};
static
const
int
pi_pos_values
[]
=
{
0
,
1
,
2
,
4
,
8
,
5
,
6
,
9
,
10
};
...
...
src/video_output/interlacing.c
View file @
05a8e024
...
@@ -48,6 +48,7 @@ static const char *deinterlace_modes[] = {
...
@@ -48,6 +48,7 @@ static const char *deinterlace_modes[] = {
"x"
,
"x"
,
"yadif"
,
"yadif"
,
"yadif2x"
,
"yadif2x"
,
"phosphor"
,
NULL
NULL
};
};
static
bool
DeinterlaceIsModeValid
(
const
char
*
mode
)
static
bool
DeinterlaceIsModeValid
(
const
char
*
mode
)
...
...
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