Commit f9641e8f authored by Rafaël Carré's avatar Rafaël Carré

* #6611 : clarify codecs prototypes

decoder takes the size of encoded buffer and returns number of samples
encoder takes the maximum size of output buffer and returns compressed size
(it assumes we will encode only one frame at a time)
parent 7d8de705
......@@ -159,7 +159,6 @@ int AudioRtpRecordHandler::processDataEncode (void)
int bytesToGet = samplesToGet * sizeof (SFLDataFormat);
if (Manager::instance().getMainBuffer()->availForGet (id_) < bytesToGet) {
_error("%s : not enough data available", __PRETTY_FUNCTION__);
return 0;
}
......@@ -190,14 +189,17 @@ int AudioRtpRecordHandler::processDataEncode (void)
}
_audioRtpRecord.audioCodecMutex.enter();
int compSize = _audioRtpRecord._audioCodec->encode (micDataEncoded, out, getCodecFrameSize());
int compSize = _audioRtpRecord._audioCodec->encode (micDataEncoded, out, DEC_BUFFER_SIZE);
_audioRtpRecord.audioCodecMutex.leave();
return compSize;
}
void AudioRtpRecordHandler::processDataDecode (unsigned char *spkrData, unsigned int size)
void AudioRtpRecordHandler::processDataDecode (unsigned char *spkrData, unsigned int size, int payloadType)
{
if (getCodecPayloadType() != payloadType)
return;
int codecSampleRate = getCodecSampleRate();
SFLDataFormat *spkrDataDecoded = _audioRtpRecord.decData;
......@@ -207,13 +209,11 @@ void AudioRtpRecordHandler::processDataDecode (unsigned char *spkrData, unsigned
_audioRtpRecord.audioCodecMutex.enter();
// Return the size of data in bytes
int expandedSize = _audioRtpRecord._audioCodec->decode (spkrDataDecoded , spkrData , size);
// Return the size of data in samples
int inSamples = _audioRtpRecord._audioCodec->decode (spkrDataDecoded , spkrData , size);
_audioRtpRecord.audioCodecMutex.leave();
int inSamples = expandedSize / sizeof (SFLDataFormat);
fadeIn (spkrDataDecoded, inSamples, &_audioRtpRecord._micAmplFactor);
// Normalize incomming signal
......
......@@ -144,7 +144,7 @@ class AudioRtpRecordHandler
return _audioRtpRecord._codecFrameSize;
}
int getHasDynamicPayload (void) const {
bool getHasDynamicPayload (void) const {
return _audioRtpRecord._hasDynamicPayloadType;
}
......@@ -172,7 +172,7 @@ class AudioRtpRecordHandler
/**
* Decode audio data received from peer
*/
void processDataDecode (unsigned char * spkrData, unsigned int size);
void processDataDecode (unsigned char * spkrData, unsigned int size, int payloadType);
/**
* Ramp In audio data to avoid audio click from peer
......
......@@ -69,12 +69,13 @@ void AudioRtpSession::updateSessionMedia (AudioCodec *audioCodec)
// Update internal codec for this session
updateRtpMedia (audioCodec);
// store codec info locally
int payloadType = getCodecPayloadType();
int frameSize = getCodecFrameSize();
int smplRate = getCodecSampleRate();
int dynamic = getHasDynamicPayload();
bool dynamic = getHasDynamicPayload();
// G722 requires timetamp to be incremented at 8khz
// G722 requires timestamp to be incremented at 8khz
if (payloadType == g722PayloadType)
_timestampIncrement = g722RtpTimeincrement;
else
......@@ -85,7 +86,6 @@ void AudioRtpSession::updateSessionMedia (AudioCodec *audioCodec)
_debug ("AudioSymmetricRtpSession: Codec frame size: %d", frameSize);
_debug ("AudioSymmetricRtpSession: RTP timestamp increment: %d", _timestampIncrement);
if (payloadType == g722PayloadType) {
_debug ("AudioSymmetricRtpSession: Setting G722 payload format");
_queue->setPayloadFormat (ost::DynamicPayloadFormat ( (ost::PayloadType) payloadType, g722RtpClockRate));
......@@ -118,7 +118,7 @@ void AudioRtpSession::setSessionMedia (AudioCodec *audioCodec)
int smplRate = getCodecSampleRate();
bool dynamic = getHasDynamicPayload();
// G722 requires timestamp to be incremented at 8 kHz
// G722 requires timestamp to be incremented at 8kHz
if (payloadType == g722PayloadType)
_timestampIncrement = g722RtpTimeincrement;
else
......@@ -203,7 +203,7 @@ void AudioRtpSession::receiveSpeakerData ()
// DTMF over RTP, size must be over 4 in order to process it as voice data
if (size > 4)
processDataDecode (spkrDataIn, size);
processDataDecode (spkrDataIn, size, adu->getType());
delete adu;
}
......
......@@ -29,8 +29,10 @@
* as that of the covered work.
*/
#include "global.h"
#include "../common.h"
#include "audiocodec.h"
#include <cassert>
class Alaw : public sfl::AudioCodec
{
......@@ -48,23 +50,24 @@ class Alaw : public sfl::AudioCodec
virtual ~Alaw() {}
virtual int decode (short *dst, unsigned char *src, unsigned int size) {
int16* end = dst+size;
virtual int decode (short *dst, unsigned char *src, size_t buf_size) {
assert(buf_size == _frameSize / 2 /* compression factor = 2:1 */ * sizeof(SFLDataFormat));
unsigned char* end = src+buf_size;
while (dst<end)
while (src<end)
*dst++ = ALawDecode (*src++);
return size<<1;
return _frameSize;
}
virtual int encode (unsigned char *dst, short *src, unsigned int size) {
size >>= 1;
uint8* end = dst+size;
virtual int encode (unsigned char *dst, short *src, size_t buf_size) {
assert(buf_size >= _frameSize / 2 /* compression factor = 2:1 */ * sizeof(SFLDataFormat));
uint8* end = dst+_frameSize;
while (dst<end)
*dst++ = ALawEncode (*src++);
return size;
return _frameSize / 2 /* compression factor = 2:1 */ * sizeof(SFLDataFormat);
}
......
......@@ -91,15 +91,17 @@ class AudioCodec : public Codec
/**
* Decode an input buffer and fill the output buffer with the decoded data
* @return the number of bytes decoded
* @param buffer_size : the size of the input buffer
* @return the number of samples decoded
*/
virtual int decode (short *, unsigned char *, unsigned int) = 0;
virtual int decode (short *dst, unsigned char *buf, size_t buffer_size) = 0;
/**
* Encode an input buffer and fill the output buffer with the encoded data
* @param buffer_size : the maximum size of encoded data buffer (dst)
* @return the number of bytes encoded
*/
virtual int encode (unsigned char *, short *, unsigned int) = 0;
virtual int encode (unsigned char *dst, short *src, size_t buffer_size) = 0;
/**
* @Override
......
......@@ -46,7 +46,6 @@ class Celt : public sfl::AudioCodec
_hasDynamicPayload = true;
initCelt();
}
Celt (const Celt&);
......@@ -124,27 +123,26 @@ class Celt : public sfl::AudioCodec
celt_mode_destroy (_mode);
}
virtual int decode (short *dst, unsigned char *src, unsigned int size) {
virtual int decode (short *dst, unsigned char *src, size_t buf_size) {
#ifdef BUILD_CELT_91 // == 91
//int err = 0;
/*err =*/ celt_decode (_dec, src, size, (celt_int16*) dst, size);
/*err =*/ celt_decode (_dec, src, buf_size, (celt_int16*) dst, _frameSize);
#endif
#ifdef BUILD_CELT_71
//int err = 0; // FIXME: check error code
/*err =*/ celt_decode (_dec, src, size, (celt_int16*) dst);
/*err =*/ celt_decode (_dec, src, buf_size, (celt_int16*) dst);
#endif
return _frameSize * sizeof (celt_int16);
return _frameSize;
}
virtual int encode (unsigned char *dst, short *src, unsigned int size) {
virtual int encode (unsigned char *dst, short *src, size_t buf_size) {
int len = 0;
#ifdef BUILD_CELT_91// == 91
len = celt_encode (_enc, (celt_int16*) src, size, dst, 40);
len = celt_encode (_enc, (celt_int16*) src, _frameSize, dst, buf_size);
#endif
#ifdef BUILD_CELT_71
len = celt_encode (_enc, (celt_int16*) src, (celt_int16 *) src, dst, 40);
len = celt_encode (_enc, (celt_int16*) src, (celt_int16 *) src, dst, buf_size);
#endif
// returns the number of bytes writen
return len;
}
......
......@@ -32,12 +32,14 @@
#include "global.h"
#include "../common.h"
#include "audiocodec.h"
#include "g722.h"
#include <stdlib.h>
#include <string.h>
#include <cassert>
#define TRUE 1
#define FALSE 0
......@@ -70,25 +72,15 @@ class G722 : public sfl::AudioCodec
g722_encode_release();
}
virtual int decode (short *dst, unsigned char *src, unsigned int size) {
int in_byte = size;
int out_samples;
out_samples = g722_decode ( (int16_t*) dst, (const uint8_t*) src, in_byte);
return out_samples * 2;
virtual int decode (short *dst, unsigned char *src, size_t buf_size) {
assert(buf_size == _frameSize / sizeof(SFLDataFormat) * encode_s->bits_per_sample / 8);
return g722_decode ( (int16_t*) dst, (const uint8_t*) src, buf_size);
}
virtual int encode (unsigned char *dst, short *src, unsigned int size) {
// 2 bytes per sample (int16)
int in_samples = size / 2;
int out_bytes;
out_bytes = g722_encode ( (uint8_t*) dst, (const int16_t*) src, in_samples);
return out_bytes;
virtual int encode (unsigned char *dst, short *src, size_t buf_size) {
int out = g722_encode ( (uint8_t*) dst, (const int16_t*) src, _frameSize);
assert((size_t)out <= buf_size);
return out;
}
......
......@@ -33,6 +33,8 @@
#include "audiocodec.h"
extern "C" {
#include <gsm/gsm.h>
#include <cassert>
}
/**
......@@ -52,10 +54,10 @@ class Gsm : public sfl::AudioCodec
_hasDynamicPayload = false;
if (! (_decode_gsmhandle = gsm_create()))
printf ("ERROR: decode_gsm_create");
printf ("ERROR: decode_gsm_create\n");
if (! (_encode_gsmhandle = gsm_create()))
printf ("AudioCodec: ERROR: encode_gsm_create");
printf ("AudioCodec: ERROR: encode_gsm_create\n");
}
Gsm (const Gsm&);
......@@ -67,20 +69,19 @@ class Gsm : public sfl::AudioCodec
gsm_destroy (_encode_gsmhandle);
}
virtual int decode (short * dst, unsigned char * src, unsigned int size) {
// _debug("Decoded by gsm ");
(void) size;
virtual int decode (short * dst, unsigned char * src, size_t buf_size) {
assert(buf_size == 33);
(void) buf_size;
if (gsm_decode (_decode_gsmhandle, (gsm_byte*) src, (gsm_signal*) dst) < 0)
printf ("ERROR: gsm_decode");
printf ("ERROR: gsm_decode\n");
return 320;
return _frameSize;
}
virtual int encode (unsigned char * dst, short * src, unsigned int size) {
// _debug("Encoded by gsm ");
(void) size;
virtual int encode (unsigned char * dst, short * src, size_t buf_size) {
(void) buf_size;
assert(buf_size >= 33);
gsm_encode (_encode_gsmhandle, (gsm_signal*) src, (gsm_byte*) dst);
return 33;
}
......
......@@ -94,23 +94,16 @@ class Speex : public sfl::AudioCodec
_speex_enc_state = 0;
}
virtual int decode (short *dst, unsigned char *src, unsigned int size) {
// int ratio = 320 / _speex_frame_size;
speex_bits_read_from (&_speex_dec_bits, (char*) src, size);
virtual int decode (short *dst, unsigned char *src, size_t buf_size) {
speex_bits_read_from (&_speex_dec_bits, (char*) src, buf_size);
speex_decode_int (_speex_dec_state, &_speex_dec_bits, dst);
// return size in bytes
return _frameSize * 2;
return _frameSize;
}
virtual int encode (unsigned char *dst, short *src, unsigned int size) {
virtual int encode (unsigned char *dst, short *src, size_t buf_size) {
speex_bits_reset (&_speex_enc_bits);
speex_encode_int (_speex_enc_state, src, &_speex_enc_bits);
int nbBytes = speex_bits_write (&_speex_enc_bits, (char*) dst, size);
return nbBytes;
return speex_bits_write (&_speex_enc_bits, (char*) dst, buf_size);
}
private:
......
......@@ -95,24 +95,17 @@ class Speex : public sfl::AudioCodec
}
virtual int decode (short *dst, unsigned char *src, unsigned int size) {
// int ratio = 320 / _speex_frame_size;
speex_bits_read_from (&_speex_dec_bits, (char*) src, size);
virtual int decode (short *dst, unsigned char *src, size_t buf_size) {
speex_bits_read_from (&_speex_dec_bits, (char*) src, buf_size);
speex_decode_int (_speex_dec_state, &_speex_dec_bits, dst);
// return the nuber of byte, not the number of sample
return _frameSize * 2;
return _frameSize;
}
virtual int encode (unsigned char *dst, short *src, unsigned int size) {
virtual int encode (unsigned char *dst, short *src, size_t buf_size) {
speex_bits_reset (&_speex_enc_bits);
speex_encode_int (_speex_enc_state, src, &_speex_enc_bits);
speex_bits_nbytes (&_speex_enc_bits);
int nbBytes = speex_bits_write (&_speex_enc_bits, (char*) dst, size);
return nbBytes;
return speex_bits_write (&_speex_enc_bits, (char*) dst, buf_size);
}
private:
......
......@@ -93,25 +93,16 @@ class Speex : public sfl::AudioCodec
}
virtual int decode (short *dst, unsigned char *src, unsigned int size) {
// int ratio = 320 / _speex_frame_size;
speex_bits_read_from (&_speex_dec_bits, (char*) src, size);
virtual int decode (short *dst, unsigned char *src, size_t buf_size) {
speex_bits_read_from (&_speex_dec_bits, (char*) src, buf_size);
speex_decode_int (_speex_dec_state, &_speex_dec_bits, dst);
// return size in bytes
return _frameSize * 2;
return _frameSize;
}
virtual int encode (unsigned char *dst, short *src, unsigned int size) {
virtual int encode (unsigned char *dst, short *src, size_t buf_size) {
speex_bits_reset (&_speex_enc_bits);
//printf ("Codec::codecEncode() size %i\n", size);
speex_encode_int (_speex_enc_state, src, &_speex_enc_bits);
int nbBytes = speex_bits_write (&_speex_enc_bits, (char*) dst, size);
//printf ("Codec::codecEncode() nbBytes %i\n", nbBytes);
return nbBytes;
return speex_bits_write (&_speex_enc_bits, (char*) dst, buf_size);
}
private:
......
......@@ -30,13 +30,13 @@
*/
#include "global.h"
#include "../common.h"
#include "audiocodec.h"
#include <cassert>
class Ulaw : public sfl::AudioCodec
{
public:
// 0 PCMU A 8000 1 [RFC3551]
Ulaw (int payload=0)
......@@ -48,23 +48,24 @@ class Ulaw : public sfl::AudioCodec
_hasDynamicPayload = false;
}
virtual int decode (short *dst, unsigned char *src, unsigned int size) {
int16* end = dst+size;
virtual int decode (short *dst, unsigned char *src, size_t buf_size) {
assert(buf_size == _frameSize / 2 /* compression factor = 2:1 */ * sizeof(SFLDataFormat));
unsigned char* end = src+buf_size;
while (dst<end)
while (src<end)
*dst++ = ULawDecode (*src++);
return size<<1;
return _frameSize;
}
virtual int encode (unsigned char *dst, short *src, unsigned int size) {
size >>= 1;
uint8* end = dst+size;
virtual int encode (unsigned char *dst, short *src, size_t buf_size) {
assert(buf_size >= _frameSize / 2 /* compression factor = 2:1 */ * sizeof(SFLDataFormat));
uint8* end = dst+_frameSize;
while (dst<end)
*dst++ = ULawEncode (*src++);
return size;
return _frameSize / 2 /* compression factor = 2:1 */ * sizeof(SFLDataFormat);;
}
int ULawDecode (uint8 ulaw) {
......
......@@ -64,16 +64,17 @@ SamplerateConverter::Short2FloatArray (const short *in, float *out, int len)
//TODO Add ifdef for int16 or float32 type
void SamplerateConverter::resample (SFLDataFormat* dataIn , SFLDataFormat* dataOut , int inputFreq , int outputFreq , int nbSamples)
{
assert(outputFreq <= _maxFreq);
double sampleFactor = (double) outputFreq / inputFreq;
if (sampleFactor == 1.0)
return;
int outSamples = nbSamples * sampleFactor;
if (outSamples > _samples) {
unsigned int outSamples = nbSamples * sampleFactor;
unsigned int maxSamples = outSamples;
if (maxSamples < (unsigned int)nbSamples)
maxSamples = nbSamples;
if (maxSamples > _samples) {
/* grow buffer if needed */
_samples = outSamples;
_samples = maxSamples;
delete [] _floatBufferIn;
delete [] _floatBufferOut;
_floatBufferIn = new float32[_samples];
......
......@@ -43,100 +43,81 @@
#include "manager.h"
// load file in mono format
RawFile::RawFile(const std::string& name, sfl::AudioCodec* codec, unsigned int sampleRate)
: audioCodec (codec)
{
filepath = name;
// no filename to load
if (filepath.empty())
throw AudioFileException("Unable to open audio file: filename is empty");
std::fstream file;
std::fstream file;
file.open (filepath.c_str(), std::fstream::in);
if (!file.is_open()) {
if (!file.is_open())
throw AudioFileException("Unable to open audio file");
}
// get length of file:
file.seekg (0, std::ios::end);
int length = file.tellg();
size_t length = file.tellg();
file.seekg (0, std::ios::beg);
// allocate memory:
char fileBuffer[length];
// read data as a block:
char *fileBuffer = new char[length];
file.read (fileBuffer,length);
file.close();
// Decode file.ul
// expandedsize is the number of bytes, not the number of int
// expandedsize should be exactly two time more, else failed
int16 monoBuffer[length];
unsigned int expandedsize = audioCodec->decode (monoBuffer, reinterpret_cast<unsigned char *>(fileBuffer), length);
if (expandedsize != length * sizeof(int16)) {
throw AudioFileException("Audio file error on loading audio file!");
unsigned int frameSize = audioCodec->getFrameSize();
unsigned int bitrate = audioCodec->getBitRate() * 1000 / 8;
unsigned int audioRate = audioCodec->getClockRate();
unsigned int encFrameSize = frameSize * bitrate / audioRate;
unsigned int decodedSize = length * (frameSize / encFrameSize);
SFLDataFormat *monoBuffer = new SFLDataFormat[decodedSize];
SFLDataFormat *bufpos = monoBuffer;
unsigned char *filepos = reinterpret_cast<unsigned char *>(fileBuffer);
_size = decodedSize;
while(length >= encFrameSize) {
bufpos += audioCodec->decode (bufpos, filepos, encFrameSize);
filepos += encFrameSize;
length -= encFrameSize;
}
delete[] fileBuffer;
unsigned int nbSampling = expandedsize / sizeof(int16);
// we need to change the sample rating here:
// case 1: we don't have to resample : only do splitting and convert
if (sampleRate == 8000) {
// just s
_size = nbSampling;
_buffer = new SFLDataFormat[_size];
if (sampleRate == audioRate) {
#ifdef DATAFORMAT_IS_FLOAT
// src to dest
src_short_to_float_array (monoBuffer, _buffer, nbSampling);
_buffer = new SFLDataFormat[_size];
src_short_to_float_array (monoBuffer, _buffer, _size);
delete[] monoBuffer;
#else
// dest to src
memcpy (_buffer, monoBuffer, _size*sizeof (SFLDataFormat));
_buffer = monoBuffer;
#endif
} else {
// case 2: we need to convert it and split it
// convert here
double factord = (double) sampleRate / 8000;
float* floatBufferIn = new float[nbSampling];
int sizeOut = (int) (ceil (factord*nbSampling));
src_short_to_float_array (monoBuffer, floatBufferIn, nbSampling);
SFLDataFormat* bufferTmp = new SFLDataFormat[sizeOut];
double factord = (double) sampleRate / audioRate;
float* floatBufferIn = new float[_size];
int sizeOut = ceil(factord*_size);
src_short_to_float_array (monoBuffer, floatBufferIn, _size);
delete[] monoBuffer;
SFLDataFormat* _buffer = new SFLDataFormat[sizeOut];
SRC_DATA src_data;
src_data.data_in = floatBufferIn;
src_data.input_frames = nbSampling;
src_data.input_frames = _size;
src_data.output_frames = sizeOut;
src_data.src_ratio = factord;
#ifdef DATAFORMAT_IS_FLOAT
// case number 2.1: the output is float32 : convert directly in _bufferTmp
src_data.data_out = bufferTmp;
src_data.data_out = _buffer;
src_simple (&src_data, SRC_SINC_BEST_QUALITY, 1);
#else
// case number 2.2: the output is int16 : convert and change to int16
float* floatBufferOut = new float[sizeOut];
src_data.data_out = floatBufferOut;
src_simple (&src_data, SRC_SINC_BEST_QUALITY, 1);
src_float_to_short_array (floatBufferOut, bufferTmp, src_data.output_frames_gen);
src_float_to_short_array (floatBufferOut, _buffer, src_data.output_frames_gen);
delete [] floatBufferOut;
#endif
delete [] floatBufferIn;
nbSampling = src_data.output_frames_gen;
// if we are in mono, we send the bufferTmp location and don't delete it
// else we split the audio in 2 and put it into buffer
_size = nbSampling;
_buffer = bufferTmp; // just send the buffer pointer;
bufferTmp = 0;
_size = src_data.output_frames_gen;
}
}
......@@ -211,7 +192,7 @@ WaveFile::WaveFile (const std::string& fileName, unsigned int audioSamplingRate)
fileStream.read (data, 4);
// Sample rate converter initialized with 88200 sample long
int converterSamples = (srate > audioSamplingRate) ? srate : audioSamplingRate;
int converterSamples = ((unsigned int)srate > audioSamplingRate) ? srate : audioSamplingRate;
SamplerateConverter _converter (converterSamples);
// Get length of data from the header.
......@@ -240,7 +221,7 @@ WaveFile::WaveFile (const std::string& fileName, unsigned int audioSamplingRate)
nbSamples /= 2;
}
if (srate != audioSamplingRate) {
if ((unsigned int)srate != audioSamplingRate) {
int outSamples = ((float) nbSamples * ( (float) audioSamplingRate / (float) srate));
_buffer = new SFLDataFormat[outSamples];
_converter.resample (tempBuffer, _buffer, srate, audioSamplingRate, nbSamples);
......
......@@ -289,14 +289,15 @@ IAXVoIPLink::sendAudioFromMic (void)
int compSize;
unsigned int audioRate = audioCodec->getClockRate();
int outSamples;
SFLDataFormat *in;
if (audioRate != mainBufferSampleRate) {
int outSamples = ((float) samples * ( (float) mainBufferSampleRate / (float) audioRate));
converter->resample (decData , resampledData , audioRate, mainBufferSampleRate, samples);
compSize = audioCodec->encode (encodedData, resampledData , outSamples * sizeof(SFLDataFormat));
in = resampledData;
} else {
outSamples = samples;
compSize = audioCodec->encode (encodedData, decData, bytes);
in = decData;
}
compSize = audioCodec->encode (encodedData, in, DEC_BUFFER_SIZE);
// Send it out!
_mutexIAX.enterMutex();
......@@ -915,15 +916,15 @@ IAXVoIPLink::iaxHandleVoiceEvent (iax_event* event, IAXCall* call)
size = max;
}
int expandedSize = audioCodec->decode (decData, data , size);
int samples = audioCodec->decode (decData, data , size);
int outSize = samples * sizeof(SFLDataFormat);
unsigned int audioRate = audioCodec->getClockRate();
int inSamples = expandedSize / sizeof(SFLDataFormat);
if (audioRate != mainBufferSampleRate) {
int outSize = expandedSize * (mainBufferSampleRate / audioRate);
converter->resample (decData, resampledData, mainBufferSampleRate, audioRate, inSamples);
outSize = (double)outSize * (mainBufferSampleRate / audioRate);
converter->resample (decData, resampledData, mainBufferSampleRate, audioRate, samples);
audiolayer->getMainBuffer()->putData (resampledData, outSize, call->getCallId());
} else {
audiolayer->getMainBuffer()->putData (decData, expandedSize, call->getCallId());
audiolayer->getMainBuffer()->putData (decData, outSize, call->getCallId());
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment