Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
7
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Open sidebar
Xiph.Org
aom-rav1e
Commits
992500b8
Commit
992500b8
authored
Feb 10, 2014
by
Deb Mukherjee
Committed by
Gerrit Code Review
Feb 10, 2014
Browse files
Options
Browse Files
Download
Plain Diff
Merge "Further one-pass vbr rate control changes"
parents
66bfc69b
15fb5510
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
100 additions
and
66 deletions
+100
-66
vp9/encoder/vp9_firstpass.c
vp9/encoder/vp9_firstpass.c
+4
-4
vp9/encoder/vp9_firstpass.h
vp9/encoder/vp9_firstpass.h
+2
-0
vp9/encoder/vp9_onyx_if.c
vp9/encoder/vp9_onyx_if.c
+31
-31
vp9/encoder/vp9_onyx_int.h
vp9/encoder/vp9_onyx_int.h
+1
-1
vp9/encoder/vp9_ratectrl.c
vp9/encoder/vp9_ratectrl.c
+62
-30
No files found.
vp9/encoder/vp9_firstpass.c
View file @
992500b8
...
...
@@ -902,8 +902,8 @@ static double calc_correction_factor(double err_per_mb,
return
fclamp
(
pow
(
error_term
,
power_term
),
0
.
05
,
5
.
0
);
}
static
int
estimate_max_q
(
VP9_COMP
*
cpi
,
FIRSTPASS_STATS
*
fpstats
,
int
section_target_bandwitdh
)
{
int
vp9_twopass_worst_quality
(
VP9_COMP
*
cpi
,
FIRSTPASS_STATS
*
fpstats
,
int
section_target_bandwitdh
)
{
int
q
;
const
int
num_mbs
=
cpi
->
common
.
MBs
;
int
target_norm_bits_per_mb
;
...
...
@@ -2280,8 +2280,8 @@ void vp9_rc_get_second_pass_params(VP9_COMP *cpi) {
// Special case code for first frame.
const
int
section_target_bandwidth
=
(
int
)(
twopass
->
bits_left
/
frames_left
);
const
int
tmp_q
=
estimate_max_q
(
cpi
,
&
twopass
->
total_left_stats
,
section_target_bandwidth
);
const
int
tmp_q
=
vp9_twopass_worst_quality
(
cpi
,
&
twopass
->
total_left_stats
,
section_target_bandwidth
);
rc
->
active_worst_quality
=
tmp_q
;
rc
->
ni_av_qi
=
tmp_q
;
...
...
vp9/encoder/vp9_firstpass.h
View file @
992500b8
...
...
@@ -88,6 +88,8 @@ void vp9_end_first_pass(struct VP9_COMP *cpi);
void
vp9_init_second_pass
(
struct
VP9_COMP
*
cpi
);
void
vp9_rc_get_second_pass_params
(
struct
VP9_COMP
*
cpi
);
void
vp9_end_second_pass
(
struct
VP9_COMP
*
cpi
);
int
vp9_twopass_worst_quality
(
struct
VP9_COMP
*
cpi
,
FIRSTPASS_STATS
*
fpstats
,
int
section_target_bandwitdh
);
// Post encode update of the rate control parameters for 2-pass
void
vp9_twopass_postencode_update
(
struct
VP9_COMP
*
cpi
,
...
...
vp9/encoder/vp9_onyx_if.c
View file @
992500b8
...
...
@@ -2777,10 +2777,10 @@ static void output_frame_level_debug_stats(VP9_COMP *cpi) {
static
void
encode_without_recode_loop
(
VP9_COMP
*
cpi
,
size_t
*
size
,
uint8_t
*
dest
,
int
*
q
)
{
int
q
)
{
VP9_COMMON
*
const
cm
=
&
cpi
->
common
;
vp9_clear_system_state
();
// __asm emms;
vp9_set_quantizer
(
cpi
,
*
q
);
vp9_set_quantizer
(
cpi
,
q
);
// Set up entropy context depending on frame type. The decoder mandates
// the use of the default context, index 0, for keyframes and inter
...
...
@@ -2814,7 +2814,7 @@ static void encode_without_recode_loop(VP9_COMP *cpi,
static
void
encode_with_recode_loop
(
VP9_COMP
*
cpi
,
size_t
*
size
,
uint8_t
*
dest
,
int
*
q
,
int
q
,
int
bottom_index
,
int
top_index
)
{
VP9_COMMON
*
const
cm
=
&
cpi
->
common
;
...
...
@@ -2834,7 +2834,7 @@ static void encode_with_recode_loop(VP9_COMP *cpi,
do
{
vp9_clear_system_state
();
// __asm emms;
vp9_set_quantizer
(
cpi
,
*
q
);
vp9_set_quantizer
(
cpi
,
q
);
if
(
loop_count
==
0
)
{
// Set up entropy context depending on frame type. The decoder mandates
...
...
@@ -2891,7 +2891,7 @@ static void encode_with_recode_loop(VP9_COMP *cpi,
if
((
cm
->
frame_type
==
KEY_FRAME
)
&&
cpi
->
rc
.
this_key_frame_forced
&&
(
cpi
->
rc
.
projected_frame_size
<
cpi
->
rc
.
max_frame_bandwidth
))
{
int
last_q
=
*
q
;
int
last_q
=
q
;
int
kf_err
=
vp9_calc_ss_err
(
cpi
->
Source
,
get_frame_new_buffer
(
cm
));
int
high_err_target
=
cpi
->
ambient_err
;
...
...
@@ -2907,32 +2907,32 @@ static void encode_with_recode_loop(VP9_COMP *cpi,
(
kf_err
>
low_err_target
&&
cpi
->
rc
.
projected_frame_size
<=
frame_under_shoot_limit
))
{
// Lower q_high
q_high
=
*
q
>
q_low
?
*
q
-
1
:
q_low
;
q_high
=
q
>
q_low
?
q
-
1
:
q_low
;
// Adjust Q
*
q
=
(
(
*
q
)
*
high_err_target
)
/
kf_err
;
*
q
=
MIN
(
(
*
q
)
,
(
q_high
+
q_low
)
>>
1
);
q
=
(
q
*
high_err_target
)
/
kf_err
;
q
=
MIN
(
q
,
(
q_high
+
q_low
)
>>
1
);
}
else
if
(
kf_err
<
low_err_target
&&
cpi
->
rc
.
projected_frame_size
>=
frame_under_shoot_limit
)
{
// The key frame is much better than the previous frame
// Raise q_low
q_low
=
*
q
<
q_high
?
*
q
+
1
:
q_high
;
q_low
=
q
<
q_high
?
q
+
1
:
q_high
;
// Adjust Q
*
q
=
(
(
*
q
)
*
low_err_target
)
/
kf_err
;
*
q
=
MIN
(
(
*
q
)
,
(
q_high
+
q_low
+
1
)
>>
1
);
q
=
(
q
*
low_err_target
)
/
kf_err
;
q
=
MIN
(
q
,
(
q_high
+
q_low
+
1
)
>>
1
);
}
// Clamp Q to upper and lower limits:
*
q
=
clamp
(
*
q
,
q_low
,
q_high
);
q
=
clamp
(
q
,
q_low
,
q_high
);
loop
=
*
q
!=
last_q
;
loop
=
q
!=
last_q
;
}
else
if
(
recode_loop_test
(
cpi
,
frame_over_shoot_limit
,
frame_under_shoot_limit
,
*
q
,
MAX
(
q_high
,
top_index
),
bottom_index
))
{
q
,
MAX
(
q_high
,
top_index
),
bottom_index
))
{
// Is the projected frame size out of range and are we allowed
// to attempt to recode.
int
last_q
=
*
q
;
int
last_q
=
q
;
int
retries
=
0
;
// Frame size out of permitted range:
...
...
@@ -2945,23 +2945,23 @@ static void encode_with_recode_loop(VP9_COMP *cpi,
q_high
=
cpi
->
rc
.
worst_quality
;
// Raise Qlow as to at least the current value
q_low
=
*
q
<
q_high
?
*
q
+
1
:
q_high
;
q_low
=
q
<
q_high
?
q
+
1
:
q_high
;
if
(
undershoot_seen
||
loop_count
>
1
)
{
// Update rate_correction_factor unless
vp9_rc_update_rate_correction_factors
(
cpi
,
1
);
*
q
=
(
q_high
+
q_low
+
1
)
/
2
;
q
=
(
q_high
+
q_low
+
1
)
/
2
;
}
else
{
// Update rate_correction_factor unless
vp9_rc_update_rate_correction_factors
(
cpi
,
0
);
*
q
=
vp9_rc_regulate_q
(
cpi
,
cpi
->
rc
.
this_frame_target
,
q
=
vp9_rc_regulate_q
(
cpi
,
cpi
->
rc
.
this_frame_target
,
bottom_index
,
MAX
(
q_high
,
top_index
));
while
(
*
q
<
q_low
&&
retries
<
10
)
{
while
(
q
<
q_low
&&
retries
<
10
)
{
vp9_rc_update_rate_correction_factors
(
cpi
,
0
);
*
q
=
vp9_rc_regulate_q
(
cpi
,
cpi
->
rc
.
this_frame_target
,
q
=
vp9_rc_regulate_q
(
cpi
,
cpi
->
rc
.
this_frame_target
,
bottom_index
,
MAX
(
q_high
,
top_index
));
retries
++
;
}
...
...
@@ -2970,27 +2970,27 @@ static void encode_with_recode_loop(VP9_COMP *cpi,
overshoot_seen
=
1
;
}
else
{
// Frame is too small
q_high
=
*
q
>
q_low
?
*
q
-
1
:
q_low
;
q_high
=
q
>
q_low
?
q
-
1
:
q_low
;
if
(
overshoot_seen
||
loop_count
>
1
)
{
vp9_rc_update_rate_correction_factors
(
cpi
,
1
);
*
q
=
(
q_high
+
q_low
)
/
2
;
q
=
(
q_high
+
q_low
)
/
2
;
}
else
{
vp9_rc_update_rate_correction_factors
(
cpi
,
0
);
*
q
=
vp9_rc_regulate_q
(
cpi
,
cpi
->
rc
.
this_frame_target
,
q
=
vp9_rc_regulate_q
(
cpi
,
cpi
->
rc
.
this_frame_target
,
bottom_index
,
top_index
);
// Special case reset for qlow for constrained quality.
// This should only trigger where there is very substantial
// undershoot on a frame and the auto cq level is above
// the user passsed in value.
if
(
cpi
->
oxcf
.
end_usage
==
USAGE_CONSTRAINED_QUALITY
&&
*
q
<
q_low
)
{
q_low
=
*
q
;
q
<
q_low
)
{
q_low
=
q
;
}
while
(
*
q
>
q_high
&&
retries
<
10
)
{
while
(
q
>
q_high
&&
retries
<
10
)
{
vp9_rc_update_rate_correction_factors
(
cpi
,
0
);
*
q
=
vp9_rc_regulate_q
(
cpi
,
cpi
->
rc
.
this_frame_target
,
q
=
vp9_rc_regulate_q
(
cpi
,
cpi
->
rc
.
this_frame_target
,
bottom_index
,
top_index
);
retries
++
;
}
...
...
@@ -3000,9 +3000,9 @@ static void encode_with_recode_loop(VP9_COMP *cpi,
}
// Clamp Q to upper and lower limits:
*
q
=
clamp
(
*
q
,
q_low
,
q_high
);
q
=
clamp
(
q
,
q_low
,
q_high
);
loop
=
*
q
!=
last_q
;
loop
=
q
!=
last_q
;
}
else
{
loop
=
0
;
}
...
...
@@ -3220,9 +3220,9 @@ static void encode_frame_to_data_rate(VP9_COMP *cpi,
}
if
(
cpi
->
sf
.
recode_loop
==
DISALLOW_RECODE
)
{
encode_without_recode_loop
(
cpi
,
size
,
dest
,
&
q
);
encode_without_recode_loop
(
cpi
,
size
,
dest
,
q
);
}
else
{
encode_with_recode_loop
(
cpi
,
size
,
dest
,
&
q
,
bottom_index
,
top_index
);
encode_with_recode_loop
(
cpi
,
size
,
dest
,
q
,
bottom_index
,
top_index
);
}
// Special case code to reduce pulsing when key frames are forced at a
...
...
vp9/encoder/vp9_onyx_int.h
View file @
992500b8
...
...
@@ -45,7 +45,7 @@ extern "C" {
#else
#define MIN_GF_INTERVAL 4
#endif
#define DEFAULT_GF_INTERVAL 1
1
#define DEFAULT_GF_INTERVAL 1
0
#define DEFAULT_KF_BOOST 2000
#define DEFAULT_GF_BOOST 2000
...
...
vp9/encoder/vp9_ratectrl.c
View file @
992500b8
...
...
@@ -215,7 +215,7 @@ int vp9_rc_clamp_pframe_target_size(const VP9_COMP *const cpi, int target) {
rc
->
av_per_frame_bandwidth
>>
5
);
if
(
target
<
min_frame_target
)
target
=
min_frame_target
;
if
(
cpi
->
refresh_golden_frame
&&
rc
->
sourc
e_alt_ref
_active
)
{
if
(
cpi
->
refresh_golden_frame
&&
rc
->
is_src_fram
e_alt_ref
)
{
// If there is an active ARF at this location use the minimum
// bits on this frame even if it is a constructed arf.
// The active maximum quantizer insures that an appropriate
...
...
@@ -487,8 +487,7 @@ static int rc_pick_q_and_adjust_q_bounds_one_pass(const VP9_COMP *cpi,
double
q_adj_factor
=
1
.
0
;
double
q_val
;
// Baseline value derived from cpi->active_worst_quality and kf boost
active_best_quality
=
get_active_quality
(
active_worst_quality
,
active_best_quality
=
get_active_quality
(
rc
->
avg_frame_qindex
[
KEY_FRAME
],
rc
->
kf_boost
,
kf_low
,
kf_high
,
kf_low_motion_minq
,
...
...
@@ -521,7 +520,8 @@ static int rc_pick_q_and_adjust_q_bounds_one_pass(const VP9_COMP *cpi,
rc
->
avg_frame_qindex
[
INTER_FRAME
]
<
active_worst_quality
)
{
q
=
rc
->
avg_frame_qindex
[
INTER_FRAME
];
}
else
{
q
=
active_worst_quality
;
q
=
(
oxcf
->
end_usage
==
USAGE_STREAM_FROM_SERVER
)
?
active_worst_quality
:
rc
->
avg_frame_qindex
[
KEY_FRAME
];
}
// For constrained quality dont allow Q less than the cq level
if
(
oxcf
->
end_usage
==
USAGE_CONSTRAINED_QUALITY
)
{
...
...
@@ -565,10 +565,24 @@ static int rc_pick_q_and_adjust_q_bounds_one_pass(const VP9_COMP *cpi,
active_best_quality
=
cpi
->
cq_target_quality
;
}
else
{
// Use the lower of active_worst_quality and recent/average Q.
if
(
rc
->
avg_frame_qindex
[
INTER_FRAME
]
<
active_worst_quality
)
active_best_quality
=
inter_minq
[
rc
->
avg_frame_qindex
[
INTER_FRAME
]];
else
active_best_quality
=
inter_minq
[
active_worst_quality
];
if
(
oxcf
->
end_usage
==
USAGE_STREAM_FROM_SERVER
)
{
if
(
cm
->
current_video_frame
>
1
)
{
if
(
rc
->
avg_frame_qindex
[
INTER_FRAME
]
<
active_worst_quality
)
active_best_quality
=
inter_minq
[
rc
->
avg_frame_qindex
[
INTER_FRAME
]];
else
active_best_quality
=
inter_minq
[
active_worst_quality
];
}
else
{
if
(
rc
->
avg_frame_qindex
[
KEY_FRAME
]
<
active_worst_quality
)
active_best_quality
=
inter_minq
[
rc
->
avg_frame_qindex
[
KEY_FRAME
]];
else
active_best_quality
=
inter_minq
[
active_worst_quality
];
}
}
else
{
if
(
cm
->
current_video_frame
>
1
)
active_best_quality
=
inter_minq
[
rc
->
avg_frame_qindex
[
INTER_FRAME
]];
else
active_best_quality
=
inter_minq
[
rc
->
avg_frame_qindex
[
KEY_FRAME
]];
}
// For the constrained quality mode we don't want
// q to fall below the cq level.
if
((
oxcf
->
end_usage
==
USAGE_CONSTRAINED_QUALITY
)
&&
...
...
@@ -1057,7 +1071,7 @@ static int test_for_kf_one_pass(VP9_COMP *cpi) {
#define USE_ALTREF_FOR_ONE_PASS 1
static
int
calc_pframe_target_size_one_pass_vbr
(
const
VP9_COMP
*
const
cpi
)
{
static
const
int
af_ratio
=
5
;
static
const
int
af_ratio
=
10
;
const
RATE_CONTROL
*
rc
=
&
cpi
->
rc
;
int
target
;
#if USE_ALTREF_FOR_ONE_PASS
...
...
@@ -1074,12 +1088,42 @@ static int calc_pframe_target_size_one_pass_vbr(const VP9_COMP *const cpi) {
}
static
int
calc_iframe_target_size_one_pass_vbr
(
const
VP9_COMP
*
const
cpi
)
{
static
const
int
kf_ratio
=
1
2
;
static
const
int
kf_ratio
=
2
5
;
const
RATE_CONTROL
*
rc
=
&
cpi
->
rc
;
int
target
=
rc
->
av_per_frame_bandwidth
*
kf_ratio
;
return
vp9_rc_clamp_iframe_target_size
(
cpi
,
target
);
}
static
int
calc_active_worst_quality_one_pass_vbr
(
const
VP9_COMP
*
cpi
)
{
int
active_worst_quality
;
if
(
cpi
->
common
.
frame_type
==
KEY_FRAME
)
{
if
(
cpi
->
common
.
current_video_frame
==
0
)
{
active_worst_quality
=
cpi
->
rc
.
worst_quality
;
}
else
{
// Choose active worst quality twice as large as the last q.
active_worst_quality
=
cpi
->
rc
.
last_q
[
KEY_FRAME
]
*
2
;
}
}
else
if
(
!
cpi
->
rc
.
is_src_frame_alt_ref
&&
(
cpi
->
refresh_golden_frame
||
cpi
->
refresh_alt_ref_frame
))
{
if
(
cpi
->
common
.
current_video_frame
==
1
)
{
active_worst_quality
=
cpi
->
rc
.
last_q
[
KEY_FRAME
]
*
5
/
4
;
}
else
{
// Choose active worst quality twice as large as the last q.
active_worst_quality
=
cpi
->
rc
.
last_q
[
INTER_FRAME
];
}
}
else
{
if
(
cpi
->
common
.
current_video_frame
==
1
)
{
active_worst_quality
=
cpi
->
rc
.
last_q
[
KEY_FRAME
]
*
2
;
}
else
{
// Choose active worst quality twice as large as the last q.
active_worst_quality
=
cpi
->
rc
.
last_q
[
INTER_FRAME
]
*
2
;
}
}
if
(
active_worst_quality
>
cpi
->
rc
.
worst_quality
)
active_worst_quality
=
cpi
->
rc
.
worst_quality
;
return
active_worst_quality
;
}
void
vp9_rc_get_one_pass_vbr_params
(
VP9_COMP
*
cpi
)
{
VP9_COMMON
*
const
cm
=
&
cpi
->
common
;
RATE_CONTROL
*
const
rc
=
&
cpi
->
rc
;
...
...
@@ -1095,22 +1139,8 @@ void vp9_rc_get_one_pass_vbr_params(VP9_COMP *cpi) {
rc
->
frames_to_key
=
cpi
->
key_frame_frequency
;
rc
->
kf_boost
=
DEFAULT_KF_BOOST
;
rc
->
source_alt_ref_active
=
0
;
if
(
cm
->
current_video_frame
==
0
)
{
rc
->
active_worst_quality
=
rc
->
worst_quality
;
}
else
{
// Choose active worst quality twice as large as the last q.
rc
->
active_worst_quality
=
MIN
(
rc
->
worst_quality
,
rc
->
last_q
[
KEY_FRAME
]
*
2
);
}
}
else
{
cm
->
frame_type
=
INTER_FRAME
;
if
(
cm
->
current_video_frame
==
1
)
{
rc
->
active_worst_quality
=
rc
->
worst_quality
;
}
else
{
// Choose active worst quality twice as large as the last q.
rc
->
active_worst_quality
=
MIN
(
rc
->
worst_quality
,
rc
->
last_q
[
INTER_FRAME
]
*
2
);
}
}
if
(
rc
->
frames_till_gf_update_due
==
0
)
{
rc
->
baseline_gf_interval
=
DEFAULT_GF_INTERVAL
;
...
...
@@ -1122,6 +1152,7 @@ void vp9_rc_get_one_pass_vbr_params(VP9_COMP *cpi) {
rc
->
source_alt_ref_pending
=
USE_ALTREF_FOR_ONE_PASS
;
rc
->
gfu_boost
=
DEFAULT_GF_BOOST
;
}
cpi
->
rc
.
active_worst_quality
=
calc_active_worst_quality_one_pass_vbr
(
cpi
);
if
(
cm
->
frame_type
==
KEY_FRAME
)
target
=
calc_iframe_target_size_one_pass_vbr
(
cpi
);
else
...
...
@@ -1140,13 +1171,15 @@ static int calc_active_worst_quality_one_pass_cbr(const VP9_COMP *cpi) {
const
RATE_CONTROL
*
rc
=
&
cpi
->
rc
;
int
active_worst_quality
=
rc
->
active_worst_quality
;
// Maximum limit for down adjustment, ~20%.
int
max_adjustment_down
=
active_worst_quality
/
5
;
// Buffer level below which we push active_worst to worst_quality.
int
critical_level
=
oxcf
->
optimal_buffer_level
>>
2
;
int
adjustment
=
0
;
int
buff_lvl_step
=
0
;
if
(
cpi
->
common
.
frame_type
==
KEY_FRAME
)
return
rc
->
worst_quality
;
if
(
rc
->
buffer_level
>
oxcf
->
optimal_buffer_level
)
{
// Adjust down.
int
max_adjustment_down
=
active_worst_quality
/
5
;
if
(
max_adjustment_down
)
{
buff_lvl_step
=
(
int
)((
oxcf
->
maximum_buffer_size
-
oxcf
->
optimal_buffer_level
)
/
max_adjustment_down
);
...
...
@@ -1229,16 +1262,15 @@ void vp9_rc_get_svc_params(VP9_COMP *cpi) {
cpi
->
rc
.
source_alt_ref_active
=
0
;
if
(
cpi
->
pass
==
0
&&
cpi
->
oxcf
.
end_usage
==
USAGE_STREAM_FROM_SERVER
)
{
target
=
calc_iframe_target_size_one_pass_cbr
(
cpi
);
cpi
->
rc
.
active_worst_quality
=
cpi
->
rc
.
worst_quality
;
}
}
else
{
cm
->
frame_type
=
INTER_FRAME
;
if
(
cpi
->
pass
==
0
&&
cpi
->
oxcf
.
end_usage
==
USAGE_STREAM_FROM_SERVER
)
{
target
=
calc_pframe_target_size_one_pass_cbr
(
cpi
);
cpi
->
rc
.
active_worst_quality
=
calc_active_worst_quality_one_pass_cbr
(
cpi
);
}
}
cpi
->
rc
.
active_worst_quality
=
calc_active_worst_quality_one_pass_cbr
(
cpi
);
vp9_rc_set_frame_target
(
cpi
,
target
);
cpi
->
rc
.
frames_till_gf_update_due
=
INT_MAX
;
cpi
->
rc
.
baseline_gf_interval
=
INT_MAX
;
...
...
@@ -1259,12 +1291,12 @@ void vp9_rc_get_one_pass_cbr_params(VP9_COMP *cpi) {
rc
->
kf_boost
=
DEFAULT_KF_BOOST
;
rc
->
source_alt_ref_active
=
0
;
target
=
calc_iframe_target_size_one_pass_cbr
(
cpi
);
rc
->
active_worst_quality
=
rc
->
worst_quality
;
}
else
{
cm
->
frame_type
=
INTER_FRAME
;
target
=
calc_pframe_target_size_one_pass_cbr
(
cpi
);
rc
->
active_worst_quality
=
calc_active_worst_quality_one_pass_cbr
(
cpi
);
}
cpi
->
rc
.
active_worst_quality
=
calc_active_worst_quality_one_pass_cbr
(
cpi
);
vp9_rc_set_frame_target
(
cpi
,
target
);
// Don't use gf_update by default in CBR mode.
rc
->
frames_till_gf_update_due
=
INT_MAX
;
...
...
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