Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
F
ffmpeg.wasm-core
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Linshizhi
ffmpeg.wasm-core
Commits
b33fd66f
Commit
b33fd66f
authored
Nov 02, 2011
by
Nicolas George
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
lavc: add ffwavesynth pseudo-codec.
parent
62b5197f
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
499 additions
and
1 deletion
+499
-1
decoders.texi
doc/decoders.texi
+13
-0
Makefile
libavcodec/Makefile
+1
-0
allcodecs.c
libavcodec/allcodecs.c
+1
-0
avcodec.h
libavcodec/avcodec.h
+1
-0
ffwavesynth.c
libavcodec/ffwavesynth.c
+482
-0
version.h
libavcodec/version.h
+1
-1
No files found.
doc/decoders.texi
View file @
b33fd66f
...
...
@@ -48,3 +48,16 @@ top-field-first is assumed
@end table
@c man end VIDEO DECODERS
@chapter Audio Decoders
@c man begin AUDIO DECODERS
@section ffwavesynth
Internal wave synthetizer.
This decoder generates wave patterns according to predefined sequences. Its
use is purely internal and the format of the data it accepts is not publicly
documented.
@c man end AUDIO DECODERS
libavcodec/Makefile
View file @
b33fd66f
...
...
@@ -156,6 +156,7 @@ OBJS-$(CONFIG_FFV1_DECODER) += ffv1.o rangecoder.o
OBJS-$(CONFIG_FFV1_ENCODER)
+=
ffv1.o
rangecoder.o
OBJS-$(CONFIG_FFVHUFF_DECODER)
+=
huffyuv.o
OBJS-$(CONFIG_FFVHUFF_ENCODER)
+=
huffyuv.o
OBJS-$(CONFIG_FFWAVESYNTH_DECODER)
+=
ffwavesynth.o
OBJS-$(CONFIG_FLAC_DECODER)
+=
flacdec.o
flacdata.o
flac.o
vorbis_data.o
OBJS-$(CONFIG_FLAC_ENCODER)
+=
flacenc.o
flacdata.o
flac.o
vorbis_data.o
OBJS-$(CONFIG_FLASHSV_DECODER)
+=
flashsv.o
...
...
libavcodec/allcodecs.c
View file @
b33fd66f
...
...
@@ -265,6 +265,7 @@ void avcodec_register_all(void)
REGISTER_ENCDEC
(
DCA
,
dca
);
REGISTER_DECODER
(
DSICINAUDIO
,
dsicinaudio
);
REGISTER_ENCDEC
(
EAC3
,
eac3
);
REGISTER_DECODER
(
FFWAVESYNTH
,
ffwavesynth
);
REGISTER_ENCDEC
(
FLAC
,
flac
);
REGISTER_ENCDEC
(
G723_1
,
g723_1
);
REGISTER_DECODER
(
G729
,
g729
);
...
...
libavcodec/avcodec.h
View file @
b33fd66f
...
...
@@ -403,6 +403,7 @@ enum CodecID {
CODEC_ID_BMV_AUDIO
,
CODEC_ID_G729
=
0x15800
,
CODEC_ID_G723_1
=
0x15801
,
CODEC_ID_FFWAVESYNTH
=
MKBETAG
(
'F'
,
'F'
,
'W'
,
'S'
),
CODEC_ID_8SVX_RAW
=
MKBETAG
(
'8'
,
'S'
,
'V'
,
'X'
),
/* subtitle codecs */
...
...
libavcodec/ffwavesynth.c
0 → 100644
View file @
b33fd66f
/*
* Wavesynth pseudo-codec
* Copyright (c) 2011 Nicolas George
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "libavutil/intreadwrite.h"
#include "libavutil/log.h"
#include "avcodec.h"
#define SIN_BITS 14
#define WS_MAX_CHANNELS 32
#define INF_TS 0x7FFFFFFFFFFFFFFF
#define PINK_UNIT 128
/*
Format of the extradata and packets
THIS INFORMATION IS NOT PART OF THE PUBLIC API OR ABI.
IT CAN CHANGE WITHOUT NOTIFICATION.
All numbers are in little endian.
The codec extradata define a set of intervals with uniform content.
Overlapping intervals are added together.
extradata:
uint32 number of intervals
... intervals
interval:
int64 start timestamp; time_base must be 1/sample_rate;
start timestamps must be in ascending order
int64 end timestamp
uint32 type
uint32 channels mask
... additional information, depends on type
sine interval (type fourcc "SINE"):
int32 start frequency, in 1/(1<<16) Hz
int32 end frequency
int32 start amplitude, 1<<16 is the full amplitude
int32 end amplitude
uint32 start phase, 0 is sin(0), 0x20000000 is sin(pi/2), etc.;
n | (1<<31) means to match the phase of previous channel #n
pink noise interval (type fourcc "NOIS"):
int32 start amplitude
int32 end amplitude
The input packets encode the time and duration of the requested segment.
packet:
int64 start timestamp
int32 duration
*/
enum
ws_interval_type
{
WS_SINE
=
MKTAG
(
'S'
,
'I'
,
'N'
,
'E'
),
WS_NOISE
=
MKTAG
(
'N'
,
'O'
,
'I'
,
'S'
),
};
struct
ws_interval
{
int64_t
ts_start
,
ts_end
;
uint64_t
phi0
,
dphi0
,
ddphi
;
uint64_t
amp0
,
damp
;
uint64_t
phi
,
dphi
,
amp
;
uint32_t
channels
;
enum
ws_interval_type
type
;
int
next
;
};
struct
wavesynth_context
{
int64_t
cur_ts
;
int64_t
next_ts
;
int32_t
*
sin
;
AVFrame
frame
;
struct
ws_interval
*
inter
;
uint32_t
dither_state
;
uint32_t
pink_state
;
int32_t
pink_pool
[
PINK_UNIT
];
unsigned
pink_need
,
pink_pos
;
int
nb_inter
;
int
cur_inter
;
int
next_inter
;
};
#define LCG_A 1284865837
#define LCG_C 4150755663
#define LCG_AI 849225893
/* A*AI = 1 [mod 1<<32] */
static
uint32_t
lcg_next
(
uint32_t
*
s
)
{
*
s
=
*
s
*
LCG_A
+
LCG_C
;
return
*
s
;
}
static
void
lcg_seek
(
uint32_t
*
s
,
int64_t
dt
)
{
uint32_t
a
,
c
,
t
=
*
s
;
if
(
dt
>=
0
)
{
a
=
LCG_A
;
c
=
LCG_C
;
}
else
{
/* coefficients for a step backward */
a
=
LCG_AI
;
c
=
(
uint32_t
)(
LCG_AI
*
LCG_C
);
dt
=
-
dt
;
}
while
(
dt
)
{
if
(
dt
&
1
)
t
=
a
*
t
+
c
;
c
*=
a
+
1
;
/* coefficients for a double step */
a
*=
a
;
dt
>>=
1
;
}
*
s
=
t
;
}
/* Emulate pink noise by summing white noise at the sampling frequency,
* white noise at half the sampling frequency (each value taken twice),
* etc., with a total of 8 octaves.
* This is known as the Voss-McCartney algorithm. */
static
void
pink_fill
(
struct
wavesynth_context
*
ws
)
{
int32_t
vt
[
7
]
=
{
0
},
v
=
0
;
int
i
,
j
;
ws
->
pink_pos
=
0
;
if
(
!
ws
->
pink_need
)
return
;
for
(
i
=
0
;
i
<
PINK_UNIT
;
i
++
)
{
for
(
j
=
0
;
j
<
7
;
j
++
)
{
if
((
i
>>
j
)
&
1
)
break
;
v
-=
vt
[
j
];
vt
[
j
]
=
(
int32_t
)
lcg_next
(
&
ws
->
pink_state
)
>>
3
;
v
+=
vt
[
j
];
}
ws
->
pink_pool
[
i
]
=
v
+
((
int32_t
)
lcg_next
(
&
ws
->
pink_state
)
>>
3
);
}
lcg_next
(
&
ws
->
pink_state
);
/* so we use exactly 256 steps */
}
/**
* @return (1<<64) * a / b, without overflow, if a < b
*/
static
uint64_t
frac64
(
uint64_t
a
,
uint64_t
b
)
{
uint64_t
r
=
0
;
int
i
;
if
(
b
<
(
uint64_t
)
1
<<
32
)
{
/* b small, use two 32-bits steps */
a
<<=
32
;
return
((
a
/
b
)
<<
32
)
|
((
a
%
b
)
<<
32
)
/
b
;
}
if
(
b
<
(
uint64_t
)
1
<<
48
)
{
/* b medium, use four 16-bits steps */
for
(
i
=
0
;
i
<
4
;
i
++
)
{
a
<<=
16
;
r
=
(
r
<<
16
)
|
(
a
/
b
);
a
%=
b
;
}
return
r
;
}
for
(
i
=
63
;
i
>=
0
;
i
--
)
{
if
(
a
>=
(
uint64_t
)
1
<<
63
||
a
<<
1
>=
b
)
{
r
|=
(
uint64_t
)
1
<<
i
;
a
=
(
a
<<
1
)
-
b
;
}
else
{
a
<<=
1
;
}
}
return
r
;
}
static
uint64_t
phi_at
(
struct
ws_interval
*
in
,
int64_t
ts
)
{
uint64_t
dt
=
ts
-
in
->
ts_start
;
uint64_t
dt2
=
dt
&
1
?
/* dt * (dt - 1) / 2 without overflow */
dt
*
((
dt
-
1
)
>>
1
)
:
(
dt
>>
1
)
*
(
dt
-
1
);
return
in
->
phi0
+
dt
*
in
->
dphi0
+
dt2
*
in
->
ddphi
;
}
static
void
wavesynth_seek
(
struct
wavesynth_context
*
ws
,
int64_t
ts
)
{
int
*
last
,
i
;
struct
ws_interval
*
in
;
last
=
&
ws
->
cur_inter
;
for
(
i
=
0
;
i
<
ws
->
nb_inter
;
i
++
)
{
in
=
&
ws
->
inter
[
i
];
if
(
ts
<
in
->
ts_start
)
break
;
if
(
ts
>=
in
->
ts_end
)
continue
;
*
last
=
i
;
last
=
&
in
->
next
;
in
->
phi
=
phi_at
(
in
,
ts
);
in
->
dphi
=
in
->
dphi0
+
(
ts
-
in
->
ts_start
)
*
in
->
ddphi
;
in
->
amp
=
in
->
amp0
+
(
ts
-
in
->
ts_start
)
*
in
->
damp
;
}
ws
->
next_inter
=
i
;
ws
->
next_ts
=
i
<
ws
->
nb_inter
?
ws
->
inter
[
i
].
ts_start
:
INF_TS
;
*
last
=
-
1
;
lcg_seek
(
&
ws
->
dither_state
,
ts
-
ws
->
cur_ts
);
if
(
ws
->
pink_need
)
{
int64_t
pink_ts_cur
=
(
ws
->
cur_ts
+
PINK_UNIT
-
1
)
&
~
(
PINK_UNIT
-
1
);
int64_t
pink_ts_next
=
ts
&
~
(
PINK_UNIT
-
1
);
int
pos
=
ts
&
(
PINK_UNIT
-
1
);
lcg_seek
(
&
ws
->
pink_state
,
(
pink_ts_next
-
pink_ts_cur
)
<<
1
);
if
(
pos
)
{
pink_fill
(
ws
);
ws
->
pink_pos
=
pos
;
}
else
{
ws
->
pink_pos
=
PINK_UNIT
;
}
}
ws
->
cur_ts
=
ts
;
}
static
int
wavesynth_parse_extradata
(
AVCodecContext
*
avc
)
{
struct
wavesynth_context
*
ws
=
avc
->
priv_data
;
struct
ws_interval
*
in
;
uint8_t
*
edata
,
*
edata_end
;
int32_t
f1
,
f2
,
a1
,
a2
;
uint32_t
phi
;
int64_t
dphi1
,
dphi2
,
dt
,
cur_ts
=
-
0x8000000000000000
;
int
i
;
if
(
avc
->
extradata_size
<
4
)
return
AVERROR
(
EINVAL
);
edata
=
avc
->
extradata
;
edata_end
=
edata
+
avc
->
extradata_size
;
ws
->
nb_inter
=
AV_RL32
(
edata
);
edata
+=
4
;
if
(
ws
->
nb_inter
<
0
)
return
AVERROR
(
EINVAL
);
ws
->
inter
=
av_calloc
(
ws
->
nb_inter
,
sizeof
(
*
ws
->
inter
));
if
(
!
ws
->
inter
)
return
AVERROR
(
ENOMEM
);
for
(
i
=
0
;
i
<
ws
->
nb_inter
;
i
++
)
{
in
=
&
ws
->
inter
[
i
];
if
(
edata_end
-
edata
<
24
)
return
AVERROR
(
EINVAL
);
in
->
ts_start
=
AV_RL64
(
edata
+
0
);
in
->
ts_end
=
AV_RL64
(
edata
+
8
);
in
->
type
=
AV_RL32
(
edata
+
16
);
in
->
channels
=
AV_RL32
(
edata
+
20
);
edata
+=
24
;
if
(
in
->
ts_start
<
cur_ts
||
in
->
ts_end
<=
in
->
ts_start
)
return
AVERROR
(
EINVAL
);
cur_ts
=
in
->
ts_start
;
dt
=
in
->
ts_end
-
in
->
ts_start
;
switch
(
in
->
type
)
{
case
WS_SINE
:
if
(
edata_end
-
edata
<
20
)
return
AVERROR
(
EINVAL
);
f1
=
AV_RL32
(
edata
+
0
);
f2
=
AV_RL32
(
edata
+
4
);
a1
=
AV_RL32
(
edata
+
8
);
a2
=
AV_RL32
(
edata
+
12
);
phi
=
AV_RL32
(
edata
+
16
);
edata
+=
20
;
dphi1
=
frac64
(
f1
,
(
int64_t
)
avc
->
sample_rate
<<
16
);
dphi2
=
frac64
(
f2
,
(
int64_t
)
avc
->
sample_rate
<<
16
);
in
->
dphi0
=
dphi1
;
in
->
ddphi
=
(
dphi2
-
dphi1
)
/
dt
;
if
(
phi
&
0x80000000
)
{
phi
&=
~
0x80000000
;
if
(
phi
>=
i
)
return
AVERROR
(
EINVAL
);
in
->
phi0
=
phi_at
(
&
ws
->
inter
[
phi
],
in
->
ts_start
);
}
else
{
in
->
phi0
=
(
uint64_t
)
phi
<<
33
;
}
break
;
case
WS_NOISE
:
if
(
edata_end
-
edata
<
8
)
return
AVERROR
(
EINVAL
);
a1
=
AV_RL32
(
edata
+
0
);
a2
=
AV_RL32
(
edata
+
4
);
edata
+=
8
;
break
;
default:
return
AVERROR
(
EINVAL
);
}
in
->
amp0
=
(
int64_t
)
a1
<<
32
;
in
->
damp
=
(((
int64_t
)
a2
<<
32
)
-
((
int64_t
)
a1
<<
32
))
/
dt
;
}
if
(
edata
!=
edata_end
)
return
AVERROR
(
EINVAL
);
return
0
;
}
static
av_cold
int
wavesynth_init
(
AVCodecContext
*
avc
)
{
struct
wavesynth_context
*
ws
=
avc
->
priv_data
;
int
i
,
r
;
if
(
avc
->
channels
>
WS_MAX_CHANNELS
)
{
av_log
(
avc
,
AV_LOG_ERROR
,
"This implementation is limited to %d channels.
\n
"
,
WS_MAX_CHANNELS
);
return
AVERROR
(
EINVAL
);
}
r
=
wavesynth_parse_extradata
(
avc
);
if
(
r
<
0
)
{
av_log
(
avc
,
AV_LOG_ERROR
,
"Invalid intervals definitions.
\n
"
);
goto
fail
;
}
ws
->
sin
=
av_malloc
(
sizeof
(
*
ws
->
sin
)
<<
SIN_BITS
);
if
(
!
ws
->
sin
)
{
r
=
AVERROR
(
ENOMEM
);
goto
fail
;
}
for
(
i
=
0
;
i
<
1
<<
SIN_BITS
;
i
++
)
ws
->
sin
[
i
]
=
floor
(
32767
*
sin
(
2
*
M_PI
*
i
/
(
1
<<
SIN_BITS
)));
ws
->
dither_state
=
MKTAG
(
'D'
,
'I'
,
'T'
,
'H'
);
for
(
i
=
0
;
i
<
ws
->
nb_inter
;
i
++
)
ws
->
pink_need
+=
ws
->
inter
[
i
].
type
==
WS_NOISE
;
ws
->
pink_state
=
MKTAG
(
'P'
,
'I'
,
'N'
,
'K'
);
ws
->
pink_pos
=
PINK_UNIT
;
avcodec_get_frame_defaults
(
&
ws
->
frame
);
avc
->
coded_frame
=
&
ws
->
frame
;
wavesynth_seek
(
ws
,
0
);
avc
->
sample_fmt
=
AV_SAMPLE_FMT_S16
;
return
0
;
fail:
av_free
(
ws
->
inter
);
av_free
(
ws
->
sin
);
return
r
;
}
static
void
wavesynth_synth_sample
(
struct
wavesynth_context
*
ws
,
int64_t
ts
,
int32_t
*
channels
)
{
int32_t
amp
,
val
,
*
cv
;
struct
ws_interval
*
in
;
int
i
,
*
last
,
pink
;
uint32_t
c
,
all_ch
=
0
;
i
=
ws
->
cur_inter
;
last
=
&
ws
->
cur_inter
;
if
(
ws
->
pink_pos
==
PINK_UNIT
)
pink_fill
(
ws
);
pink
=
ws
->
pink_pool
[
ws
->
pink_pos
++
]
>>
16
;
while
(
i
>=
0
)
{
in
=
&
ws
->
inter
[
i
];
i
=
in
->
next
;
if
(
ts
>=
in
->
ts_end
)
{
*
last
=
i
;
continue
;
}
last
=
&
in
->
next
;
amp
=
in
->
amp
>>
32
;
in
->
amp
+=
in
->
damp
;
switch
(
in
->
type
)
{
case
WS_SINE
:
val
=
amp
*
ws
->
sin
[
in
->
phi
>>
(
64
-
SIN_BITS
)];
in
->
phi
+=
in
->
dphi
;
in
->
dphi
+=
in
->
ddphi
;
break
;
case
WS_NOISE
:
val
=
amp
*
pink
;
break
;
default:
val
=
0
;
}
all_ch
|=
in
->
channels
;
for
(
c
=
in
->
channels
,
cv
=
channels
;
c
;
c
>>=
1
,
cv
++
)
if
(
c
&
1
)
*
cv
+=
val
;
}
val
=
(
int32_t
)
lcg_next
(
&
ws
->
dither_state
)
>>
16
;
for
(
c
=
all_ch
,
cv
=
channels
;
c
;
c
>>=
1
,
cv
++
)
if
(
c
&
1
)
*
cv
+=
val
;
}
static
void
wavesynth_enter_intervals
(
struct
wavesynth_context
*
ws
,
int64_t
ts
)
{
int
*
last
,
i
;
struct
ws_interval
*
in
;
last
=
&
ws
->
cur_inter
;
for
(
i
=
ws
->
cur_inter
;
i
>=
0
;
i
=
ws
->
inter
[
i
].
next
)
last
=
&
ws
->
inter
[
i
].
next
;
for
(
i
=
ws
->
next_inter
;
i
<
ws
->
nb_inter
;
i
++
)
{
in
=
&
ws
->
inter
[
i
];
if
(
ts
<
in
->
ts_start
)
break
;
if
(
ts
>=
in
->
ts_end
)
continue
;
*
last
=
i
;
last
=
&
in
->
next
;
in
->
phi
=
in
->
phi0
;
in
->
dphi
=
in
->
dphi0
;
in
->
amp
=
in
->
amp0
;
}
ws
->
next_inter
=
i
;
ws
->
next_ts
=
i
<
ws
->
nb_inter
?
ws
->
inter
[
i
].
ts_start
:
INF_TS
;
*
last
=
-
1
;
}
static
int
wavesynth_decode
(
AVCodecContext
*
avc
,
void
*
rframe
,
int
*
rgot_frame
,
AVPacket
*
packet
)
{
struct
wavesynth_context
*
ws
=
avc
->
priv_data
;
int64_t
ts
;
int
duration
;
int
s
,
c
,
r
;
int16_t
*
pcm
;
int32_t
channels
[
WS_MAX_CHANNELS
];
*
rgot_frame
=
0
;
if
(
packet
->
size
!=
12
)
return
AVERROR_INVALIDDATA
;
ts
=
AV_RL64
(
packet
->
data
);
if
(
ts
!=
ws
->
cur_ts
)
wavesynth_seek
(
ws
,
ts
);
duration
=
AV_RL32
(
packet
->
data
+
8
);
if
(
duration
<=
0
)
return
AVERROR
(
EINVAL
);
ws
->
frame
.
nb_samples
=
duration
;
r
=
avc
->
get_buffer
(
avc
,
&
ws
->
frame
);
if
(
r
<
0
)
return
r
;
pcm
=
(
int16_t
*
)
ws
->
frame
.
data
[
0
];
for
(
s
=
0
;
s
<
duration
;
s
++
,
ts
++
)
{
memset
(
channels
,
0
,
avc
->
channels
*
sizeof
(
*
channels
));
if
(
ts
>=
ws
->
next_ts
)
wavesynth_enter_intervals
(
ws
,
ts
);
wavesynth_synth_sample
(
ws
,
ts
,
channels
);
for
(
c
=
0
;
c
<
avc
->
channels
;
c
++
)
*
(
pcm
++
)
=
channels
[
c
]
>>
16
;
}
ws
->
cur_ts
+=
duration
;
*
rgot_frame
=
1
;
*
(
AVFrame
*
)
rframe
=
ws
->
frame
;
return
packet
->
size
;
}
static
av_cold
int
wavesynth_close
(
AVCodecContext
*
avc
)
{
struct
wavesynth_context
*
ws
=
avc
->
priv_data
;
av_free
(
ws
->
sin
);
av_free
(
ws
->
inter
);
return
0
;
}
AVCodec
ff_ffwavesynth_decoder
=
{
.
name
=
"wavesynth"
,
.
type
=
AVMEDIA_TYPE_AUDIO
,
.
id
=
CODEC_ID_FFWAVESYNTH
,
.
priv_data_size
=
sizeof
(
struct
wavesynth_context
),
.
init
=
wavesynth_init
,
.
close
=
wavesynth_close
,
.
decode
=
wavesynth_decode
,
.
capabilities
=
CODEC_CAP_DR1
,
.
long_name
=
NULL_IF_CONFIG_SMALL
(
"Wave synthesis pseudo-codec"
),
};
libavcodec/version.h
View file @
b33fd66f
...
...
@@ -21,7 +21,7 @@
#define AVCODEC_VERSION_H
#define LIBAVCODEC_VERSION_MAJOR 53
#define LIBAVCODEC_VERSION_MINOR 4
5
#define LIBAVCODEC_VERSION_MINOR 4
6
#define LIBAVCODEC_VERSION_MICRO 0
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
...
...
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