Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
savoirfairelinux
jami-daemon
Commits
854bac4a
Commit
854bac4a
authored
Sep 01, 2011
by
Rafaël Carré
Browse files
* #6629 : use number of samples as arguments for audio filters
remove unused functions
parent
f8206833
Changes
15
Hide whitespace changes
Inline
Side-by-side
daemon/src/audio/audiortp/AudioRtpRecordHandler.cpp
View file @
854bac4a
...
...
@@ -141,14 +141,16 @@ int AudioRtpRecordHandler::processDataEncode (void)
return
0
;
}
fadeIn
(
micData
,
bytesToGet
/
sizeof
(
SFLDataFormat
),
&
_audioRtpRecord
.
_micAmplFactor
);
int
samples
=
bytesToGet
/
sizeof
(
SFLDataFormat
);
fadeIn
(
micData
,
samples
,
&
_audioRtpRecord
.
_micAmplFactor
);
if
(
Manager
::
instance
().
getEchoCancelState
()
==
"enabled"
)
echoCanceller
.
getData
(
micData
);
_audioRtpRecord
.
audioProcessMutex
.
enter
();
if
(
Manager
::
instance
().
audioPreference
.
getNoiseReduce
())
_audioRtpRecord
.
_noiseSuppress
->
process
(
micData
,
bytesToGet
);
_audioRtpRecord
.
_noiseSuppress
->
process
(
micData
,
samples
);
_audioRtpRecord
.
audioProcessMutex
.
leave
();
#ifdef DUMP_PROCESS_DATA_ENCODE
...
...
@@ -203,7 +205,7 @@ void AudioRtpRecordHandler::processDataDecode (unsigned char *spkrData, unsigned
}
if
(
Manager
::
instance
().
getEchoCancelState
()
==
"enabled"
)
echoCanceller
.
putData
(
out
,
outSamples
*
sizeof
(
SFLDataFormat
)
);
echoCanceller
.
putData
(
out
,
outSamples
);
Manager
::
instance
().
getMainBuffer
()
->
putData
(
out
,
outSamples
*
sizeof
(
SFLDataFormat
),
id_
);
}
...
...
daemon/src/audio/delaydetection.cpp
View file @
854bac4a
...
...
@@ -113,34 +113,12 @@ DelayDetection::DelayDetection() : _internalState (WaitForSpeaker), _decimationF
}
void
DelayDetection
::
reset
(
)
void
DelayDetection
::
putData
(
SFLDataFormat
*
inputData
,
int
nbSamples
)
{
_nbMicSampleStored
=
0
;
_nbSpkrSampleStored
=
0
;
_decimationFilter
.
reset
();
_bandpassFilter
.
reset
();
memset
(
_spkrReference
,
0
,
sizeof
(
float
)
*
WINDOW_SIZE
*
2
);
memset
(
_capturedData
,
0
,
sizeof
(
float
)
*
DELAY_BUFF_SIZE
*
2
);
memset
(
_spkrReferenceDown
,
0
,
sizeof
(
float
)
*
WINDOW_SIZE
*
2
);
memset
(
_captureDataDown
,
0
,
sizeof
(
float
)
*
DELAY_BUFF_SIZE
*
2
);
memset
(
_spkrReferenceFilter
,
0
,
sizeof
(
float
)
*
WINDOW_SIZE
*
2
);
memset
(
_captureDataFilter
,
0
,
sizeof
(
float
)
*
DELAY_BUFF_SIZE
*
2
);
memset
(
_correlationResult
,
0
,
sizeof
(
float
)
*
DELAY_BUFF_SIZE
*
2
);
_internalState
=
WaitForSpeaker
;
}
void
DelayDetection
::
putData
(
SFLDataFormat
*
inputData
,
int
nbBytes
)
{
// Machine may already got a spkr and is waiting for mic or computing correlation
if
(
_nbSpkrSampleStored
==
WINDOW_SIZE
)
return
;
int
nbSamples
=
nbBytes
/
sizeof
(
SFLDataFormat
);
if
(
(
_nbSpkrSampleStored
+
nbSamples
)
>
WINDOW_SIZE
)
nbSamples
=
WINDOW_SIZE
-
_nbSpkrSampleStored
;
...
...
@@ -166,14 +144,12 @@ void DelayDetection::putData (SFLDataFormat *inputData, int nbBytes)
}
void
DelayDetection
::
process
(
SFLDataFormat
*
inputData
,
int
nb
Byt
es
)
void
DelayDetection
::
process
(
SFLDataFormat
*
inputData
,
int
nb
Sampl
es
)
{
if
(
_internalState
!=
WaitForMic
)
return
;
int
nbSamples
=
nbBytes
/
sizeof
(
SFLDataFormat
);
if
(
(
_nbMicSampleStored
+
nbSamples
)
>
DELAY_BUFF_SIZE
)
nbSamples
=
DELAY_BUFF_SIZE
-
_nbMicSampleStored
;
...
...
daemon/src/audio/delaydetection.h
View file @
854bac4a
...
...
@@ -105,11 +105,9 @@ class DelayDetection
~
DelayDetection
();
void
reset
(
void
);
void
putData
(
SFLDataFormat
*
inputData
,
int
nbBytes
);
void
putData
(
SFLDataFormat
*
inputData
,
int
samples
);
void
process
(
SFLDataFormat
*
inputData
,
int
nbByt
es
);
void
process
(
SFLDataFormat
*
inputData
,
int
sampl
es
);
private:
...
...
daemon/src/audio/echocancel.cpp
View file @
854bac4a
...
...
@@ -211,10 +211,10 @@ void EchoCancel::reset()
_processedByte
=
0
;
}
void
EchoCancel
::
putData
(
SFLDataFormat
*
inputData
,
int
nbByt
es
)
void
EchoCancel
::
putData
(
SFLDataFormat
*
inputData
,
int
sampl
es
)
{
_delayDetector
.
putData
(
inputData
,
nbBytes
);
_delayDetector
.
putData
(
inputData
,
samples
*
sizeof
(
SFLDataFormat
)
);
if
(
_spkrStoped
)
{
_debug
(
"EchoCancel: Flush data"
);
...
...
@@ -224,7 +224,7 @@ void EchoCancel::putData (SFLDataFormat *inputData, int nbBytes)
}
// Put data in speaker ring buffer
_spkrData
->
Put
(
inputData
,
nbBytes
);
_spkrData
->
Put
(
inputData
,
samples
*
sizeof
(
SFLDataFormat
)
);
}
...
...
@@ -241,25 +241,25 @@ int EchoCancel::getData (SFLDataFormat *outputData)
return
copied
;
}
int
EchoCancel
::
process
(
SFLDataFormat
*
inputData
,
SFLDataFormat
*
outputData
,
int
nbByt
es
)
int
EchoCancel
::
process
(
SFLDataFormat
*
inputData
,
SFLDataFormat
*
outputData
,
int
sampl
es
)
{
_delayDetector
.
process
(
inputData
,
nbByt
es
);
_delayDetector
.
process
(
inputData
,
sampl
es
);
if
(
_spkrStoped
)
{
memcpy
(
outputData
,
inputData
,
nbBytes
);
return
nbByt
es
;
memcpy
(
outputData
,
inputData
,
samples
*
sizeof
(
SFLDataFormat
)
);
return
sampl
es
;
}
int
byteSize
=
_smplPerFrame
*
sizeof
(
SFLDataFormat
);
// init temporary buffers
memset
(
_tmpSpkr
,
0
,
BUFF_SIZE
*
sizeof
(
SFLDataFormat
));
memset
(
_tmpMic
,
0
,
BUFF_SIZE
*
sizeof
(
SFLDataFormat
));
memset
(
_tmpOut
,
0
,
BUFF_SIZE
*
sizeof
(
SFLDataForma
t
));
memset
(
_tmpSpkr
,
0
,
sizeof
(
_tmpSpkr
));
memset
(
_tmpMic
,
0
,
sizeof
(
_tmpMic
));
memset
(
_tmpOut
,
0
,
sizeof
(
_tmpOu
t
));
// Put mic data in ringbuffer
_micData
->
Put
(
inputData
,
nbBytes
);
_micData
->
Put
(
inputData
,
samples
*
sizeof
(
SFLDataFormat
)
);
// Store data for synchronization
int
spkrAvail
=
_spkrData
->
AvailForGet
();
...
...
daemon/src/audio/echocancel.h
View file @
854bac4a
...
...
@@ -86,7 +86,7 @@ class EchoCancel
* Add speaker data into internal buffer
* \param inputData containing far-end voice data to be sent to speakers
*/
void
putData
(
SFLDataFormat
*
inputData
,
int
nbByt
es
);
void
putData
(
SFLDataFormat
*
inputData
,
int
sampl
es
);
/**
* Get data ready to be played by speakers
...
...
@@ -98,7 +98,7 @@ class EchoCancel
* \param inputData containing mixed echo and voice data
* \param outputData containing
*/
int
process
(
SFLDataFormat
*
inputData
,
SFLDataFormat
*
outputData
,
int
nbByt
es
);
int
process
(
SFLDataFormat
*
inputData
,
SFLDataFormat
*
outputData
,
int
sampl
es
);
/**
* Set echo canceller internal sampling rate, reset if sampling rate changed
...
...
daemon/src/audio/echosuppress.cpp
View file @
854bac4a
...
...
@@ -5,12 +5,15 @@
* Author: asavard
*/
#include
<cassert>
#include
<stdexcept>
#include
"echosuppress.h"
#include
"pj/pool.h"
#include
"pj/os.h"
#include
<stdexcept>
#define ECHO_CANCEL_MEM_SIZE 1000
#define SAMPLES_PER_FRAME 160
EchoSuppress
::
EchoSuppress
(
pj_pool_t
*
pool
)
{
...
...
@@ -23,7 +26,7 @@ EchoSuppress::EchoSuppress(pj_pool_t *pool)
if
(
!
pj_thread_is_registered
())
_warn
(
"EchoCancel: Thread not registered..."
);
if
(
pjmedia_echo_create
(
pool
,
8000
,
160
,
250
,
0
,
PJMEDIA_ECHO_SIMPLE
,
&
echoState
)
!=
PJ_SUCCESS
)
if
(
pjmedia_echo_create
(
pool
,
8000
,
SAMPLES_PER_FRAME
,
250
,
0
,
PJMEDIA_ECHO_SIMPLE
,
&
echoState
)
!=
PJ_SUCCESS
)
throw
std
::
runtime_error
(
"EchoCancel: Error: Could not create echo canceller"
);
}
...
...
@@ -31,16 +34,15 @@ EchoSuppress::~EchoSuppress()
{
}
void
EchoSuppress
::
putData
(
SFLDataFormat
*
inputData
,
int
nbByt
es
)
void
EchoSuppress
::
putData
(
SFLDataFormat
*
inputData
,
int
sampl
es
)
{
assert
(
samples
=
SAMPLES_PER_FRAME
);
if
(
pjmedia_echo_playback
(
echoState
,
reinterpret_cast
<
pj_int16_t
*>
(
inputData
))
!=
PJ_SUCCESS
)
_warn
(
"EchoCancel: Warning: Problem while putting input data"
);
}
int
EchoSuppress
::
getData
(
SFLDataFormat
*
outputData
)
void
EchoSuppress
::
getData
(
SFLDataFormat
*
outputData
)
{
if
(
pjmedia_echo_capture
(
echoState
,
reinterpret_cast
<
pj_int16_t
*>
(
outputData
),
0
)
!=
PJ_SUCCESS
)
_warn
(
"EchoCancel: Warning: Problem while getting output data"
);
return
0
;
}
daemon/src/audio/echosuppress.h
View file @
854bac4a
...
...
@@ -23,7 +23,7 @@ class EchoSuppress {
*/
void
putData
(
SFLDataFormat
*
,
int
);
int
getData
(
SFLDataFormat
*
);
void
getData
(
SFLDataFormat
*
);
private:
...
...
daemon/src/audio/gaincontrol.cpp
View file @
854bac4a
...
...
@@ -42,12 +42,12 @@ std::fstream tmpIn("gaintestin.raw", std::fstream::out);
std
::
fstream
tmpOut
(
"gaintestout.raw"
,
std
::
fstream
::
out
);
#endif
void
GainControl
::
process
(
SFLDataFormat
*
buf
,
int
bufLength
)
void
GainControl
::
process
(
SFLDataFormat
*
buf
,
int
samples
)
{
double
rms
,
rmsAvgLevel
,
in
,
out
,
diffRms
,
maxRms
;
maxRms
=
0.0
;
for
(
int
i
=
0
;
i
<
bufLength
;
i
++
)
{
for
(
int
i
=
0
;
i
<
samples
;
i
++
)
{
// linear conversion
in
=
(
double
)
buf
[
i
]
/
(
double
)
SHRT_MAX
;
...
...
daemon/src/audio/gaincontrol.h
View file @
854bac4a
...
...
@@ -23,9 +23,9 @@ public:
/**
* Apply addaptive gain factor on input signal
* /param Input audio buffer
* /param Input
buffer length
* /param Input
samples
*/
void
process
(
SFLDataFormat
*
,
int
);
void
process
(
SFLDataFormat
*
,
int
samples
);
private:
...
...
daemon/src/audio/noisesuppress.cpp
View file @
854bac4a
...
...
@@ -31,34 +31,10 @@
#include
<cassert>
#include
"noisesuppress.h"
NoiseSuppress
::
NoiseSuppress
(
int
smplPerFrame
,
int
samplingRate
)
:
_noiseState
(
NULL
)
,
_smplPerFrame
(
smplPerFrame
)
,
_samplingRate
(
samplingRate
)
NoiseSuppress
::
NoiseSuppress
(
int
smplPerFrame
,
int
samplingRate
)
:
_smplPerFrame
(
smplPerFrame
)
{
initNewNoiseSuppressor
(
_smplPerFrame
,
_samplingRate
);
}
NoiseSuppress
::~
NoiseSuppress
()
{
speex_preprocess_state_destroy
(
_noiseState
);
}
void
NoiseSuppress
::
reset
(
void
)
{
speex_preprocess_state_destroy
(
_noiseState
);
initNewNoiseSuppressor
(
_smplPerFrame
,
_samplingRate
);
}
void
NoiseSuppress
::
process
(
SFLDataFormat
*
data
,
int
nBytes
)
{
assert
(
_smplPerFrame
==
nBytes
/
sizeof
(
SFLDataFormat
));
speex_preprocess_run
(
_noiseState
,
data
);
}
void
NoiseSuppress
::
initNewNoiseSuppressor
(
int
smplPerFrame
,
int
samplingRate
)
{
_noiseState
=
speex_preprocess_state_init
(
smplPerFrame
,
samplingRate
);
_noiseState
=
speex_preprocess_state_init
(
_smplPerFrame
,
samplingRate
);
int
i
=
1
;
speex_preprocess_ctl
(
_noiseState
,
SPEEX_PREPROCESS_SET_DENOISE
,
&
i
);
i
=-
20
;
...
...
@@ -78,3 +54,15 @@ void NoiseSuppress::initNewNoiseSuppressor (int smplPerFrame, int samplingRate)
i
=
0
;
speex_preprocess_ctl
(
_noiseState
,
SPEEX_PREPROCESS_SET_VAD
,
&
i
);
}
NoiseSuppress
::~
NoiseSuppress
()
{
speex_preprocess_state_destroy
(
_noiseState
);
}
void
NoiseSuppress
::
process
(
SFLDataFormat
*
data
,
int
samples
)
{
assert
(
_smplPerFrame
==
samples
);
speex_preprocess_run
(
_noiseState
,
data
);
}
daemon/src/audio/noisesuppress.h
View file @
854bac4a
...
...
@@ -36,33 +36,19 @@
class
NoiseSuppress
{
public:
NoiseSuppress
(
int
smplPerFrame
,
int
samplingRate
);
~
NoiseSuppress
(
void
);
/**
* Reset noise suppressor internal state at runtime. Usefull when making a new call
*/
void
reset
(
void
);
void
process
(
SFLDataFormat
*
data
,
int
nbBytes
);
void
process
(
SFLDataFormat
*
data
,
int
samples
);
private:
void
initNewNoiseSuppressor
(
int
_smplPerFrame
,
int
samplingRate
);
/**
* Noise reduction processing state
*/
SpeexPreprocessState
*
_noiseState
;
int
_smplPerFrame
;
int
_samplingRate
;
};
#endif
daemon/src/audio/speexechocancel.cpp
View file @
854bac4a
...
...
@@ -84,7 +84,7 @@ SpeexEchoCancel::~SpeexEchoCancel()
}
void
SpeexEchoCancel
::
putData
(
SFLDataFormat
*
inputData
,
int
nbByt
es
)
void
SpeexEchoCancel
::
putData
(
SFLDataFormat
*
inputData
,
int
sampl
es
)
{
if
(
_spkrStopped
)
{
_micData
->
flushAll
();
...
...
@@ -93,13 +93,13 @@ void SpeexEchoCancel::putData (SFLDataFormat *inputData, int nbBytes)
}
#ifdef DUMP_ECHOCANCEL_INTERNAL_DATA
spkrFile
->
write
(
reinterpret_cast
<
char
*>
(
inputData
),
nbBytes
);
spkrFile
->
write
(
reinterpret_cast
<
char
*>
(
inputData
),
samples
*
sizeof
(
SFLDataFormat
)
);
#endif
_spkrData
->
Put
(
inputData
,
nbBytes
);
_spkrData
->
Put
(
inputData
,
samples
*
sizeof
(
SFLDataFormat
)
);
}
int
SpeexEchoCancel
::
process
(
SFLDataFormat
*
inputData
,
SFLDataFormat
*
outputData
,
int
nbByt
es
)
int
SpeexEchoCancel
::
process
(
SFLDataFormat
*
inputData
,
SFLDataFormat
*
outputData
,
int
sampl
es
)
{
if
(
_spkrStopped
)
return
0
;
...
...
@@ -116,66 +116,50 @@ int SpeexEchoCancel::process (SFLDataFormat *inputData, SFLDataFormat *outputDat
#endif
// Put mic data in ringbuffer
_micData
->
Put
(
inputData
,
nbBytes
);
_micData
->
Put
(
inputData
,
samples
*
sizeof
(
SFLDataFormat
)
);
// Store data for synchronization
int
spkrAvail
=
_spkrData
->
AvailForGet
();
int
micAvail
=
_micData
->
AvailForGet
();
// Init number of frame processed
int
nbFrame
=
0
;
// Get data from mic and speaker
// if ((spkrAvail >= (byteSize * 6)) && (micAvail >= byteSize)) {
if
((
spkrAvail
>=
(
_echoDelay
+
byteSize
))
&&
(
micAvail
>=
byteSize
))
{
int
nbSamples
=
byteSize
/
sizeof
(
SFLDataFormat
);
if
(
spkrAvail
<
(
_echoDelay
+
byteSize
)
||
micAvail
<
byteSize
)
{
_micData
->
Discard
(
byteSize
);
return
0
;
}
// get synchronized data
_spkrData
->
Get
(
_tmpSpkr
,
byteSize
);
_micData
->
Get
(
_tmpMic
,
byteSize
);
_spkrData
->
Get
(
_tmpSpkr
,
byteSize
);
_micData
->
Get
(
_tmpMic
,
byteSize
);
#ifdef DUMP_ECHOCANCEL_INTERNAL_DATA
micProcessFile
->
write
(
reinterpret_cast
<
char
*>
(
_tmpMic
),
byteSize
);
spkrProcessFile
->
write
(
reinterpret_cast
<
char
*>
(
_tmpSpkr
),
byteSize
);
micProcessFile
->
write
(
reinterpret_cast
<
char
*>
(
_tmpMic
),
byteSize
);
spkrProcessFile
->
write
(
reinterpret_cast
<
char
*>
(
_tmpSpkr
),
byteSize
);
#endif
int32_t
tmp
;
for
(
int
i
=
0
;
i
<
nbSamples
;
i
++
)
{
tmp
=
_tmpSpkr
[
i
]
*
3
;
if
(
tmp
>
SHRT_MAX
)
{
tmp
=
SHRT_MAX
;
}
_tmpSpkr
[
i
]
=
(
int16_t
)
tmp
;
for
(
int
i
=
0
;
i
<
EC_FRAME_SIZE
;
i
++
)
{
int32_t
tmp
=
_tmpSpkr
[
i
]
*
3
;
if
(
tmp
>
SHRT_MAX
)
tmp
=
SHRT_MAX
;
_tmpSpkr
[
i
]
=
(
int16_t
)
tmp
;
_tmpMic
[
i
]
/=
3
;
}
_tmpMic
[
i
]
/=
3
;
}
// Processed echo cancellation
speex_echo_cancellation
(
_echoState
,
_tmpMic
,
_tmpSpkr
,
_tmpOut
);
speex_preprocess_run
(
_preState
,
reinterpret_cast
<
short
*>
(
_tmpOut
));
speex_echo_cancellation
(
_echoState
,
_tmpMic
,
_tmpSpkr
,
_tmpOut
);
speex_preprocess_run
(
_preState
,
reinterpret_cast
<
short
*>
(
_tmpOut
));
#ifdef DUMP_ECHOCANCEL_INTERNAL_DATA
echoFile
->
write
(
reinterpret_cast
<
char
*>
(
_tmpOut
),
byteSize
);
echoFile
->
write
(
reinterpret_cast
<
char
*>
(
_tmpOut
),
byteSize
);
#endif
for
(
int
i
=
0
;
i
<
nbSamples
;
i
++
)
{
_tmpOut
[
i
]
*=
3
;
}
for
(
int
i
=
0
;
i
<
EC_FRAME_SIZE
;
i
++
)
{
_tmpOut
[
i
]
*=
3
;
}
memcpy
(
outputData
,
_tmpOut
,
byteSize
);
memcpy
(
outputData
,
_tmpOut
,
byteSize
);
spkrAvail
=
_spkrData
->
AvailForGet
();
micAvail
=
_micData
->
AvailForGet
();
// increment nb of frame processed
++
nbFrame
;
}
else
{
_debug
(
"discarding"
);
_micData
->
Discard
(
byteSize
);
}
spkrAvail
=
_spkrData
->
AvailForGet
();
micAvail
=
_micData
->
AvailForGet
();
return
nbFrame
*
EC_FRAME_SIZE
*
sizeof
(
SFLDataFormat
)
;
return
EC_FRAME_SIZE
;
}
daemon/src/audio/speexechocancel.h
View file @
854bac4a
...
...
@@ -39,14 +39,14 @@ class SpeexEchoCancel
* Add speaker data into internal buffer
* \param inputData containing far-end voice data to be sent to speakers
*/
void
putData
(
SFLDataFormat
*
,
int
);
void
putData
(
SFLDataFormat
*
,
int
samples
);
/**
* Perform echo cancellation using internal buffers
* \param inputData containing mixed echo and voice data
* \param outputData containing
*/
int
process
(
SFLDataFormat
*
,
SFLDataFormat
*
,
int
);
int
process
(
SFLDataFormat
*
,
SFLDataFormat
*
,
int
samples
);
private:
...
...
daemon/test/delaydetectiontest.cpp
View file @
854bac4a
...
...
@@ -216,7 +216,7 @@ void DelayDetectionTest::testDelayDetection()
mic
[
delay
+
3
]
=
32000
;
mic
[
delay
+
4
]
=
32000
;
_delaydetect
.
putData
(
spkr
,
WINDOW_SIZE
*
sizeof
(
SFLDataFormat
)
);
_delaydetect
.
process
(
mic
,
DELAY_BUFF_SIZE
*
sizeof
(
SFLDataFormat
)
);
_delaydetect
.
putData
(
spkr
,
WINDOW_SIZE
);
_delaydetect
.
process
(
mic
,
DELAY_BUFF_SIZE
);
}
daemon/test/echocanceltest.cpp
View file @
854bac4a
...
...
@@ -65,8 +65,8 @@ void EchoCancelTest::testEchoCancelProcessing()
micFile
.
read
(
reinterpret_cast
<
char
*>
(
micData
),
nbSamples
*
sizeof
(
SFLDataFormat
));
spkrFile
.
read
(
reinterpret_cast
<
char
*>
(
spkrData
),
nbSamples
*
sizeof
(
SFLDataFormat
));
echoCanceller
.
putData
(
spkrData
,
nbSamples
*
sizeof
(
SFLDataFormat
)
);
echoCanceller
.
process
(
micData
,
echoCancelData
,
nbSamples
*
sizeof
(
SFLDataFormat
)
);
echoCanceller
.
putData
(
spkrData
,
nbSamples
);
echoCanceller
.
process
(
micData
,
echoCancelData
,
nbSamples
);
echoCancelFile
.
write
(
reinterpret_cast
<
char
*>
(
echoCancelData
),
nbSamples
*
sizeof
(
SFLDataFormat
));
...
...
Write
Preview
Supports
Markdown
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