certain bug fixes and code cleanup
This commit is contained in:
parent
bfbda9eb73
commit
84f0a31a20
6 changed files with 136 additions and 130 deletions
25
src/buffer.h
25
src/buffer.h
|
@ -61,7 +61,7 @@ protected:
|
||||||
template<class TData> class CConvBuf
|
template<class TData> class CConvBuf
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CConvBuf() {}
|
CConvBuf() { Init ( 0 ); }
|
||||||
virtual ~CConvBuf() {}
|
virtual ~CConvBuf() {}
|
||||||
|
|
||||||
void Init ( const int iNewMemSize )
|
void Init ( const int iNewMemSize )
|
||||||
|
@ -84,13 +84,24 @@ public:
|
||||||
// copy new data in internal buffer
|
// copy new data in internal buffer
|
||||||
int iCurPos = 0;
|
int iCurPos = 0;
|
||||||
const int iEnd = iPutPos + iVecSize;
|
const int iEnd = iPutPos + iVecSize;
|
||||||
while ( iPutPos < iEnd )
|
|
||||||
{
|
|
||||||
vecsMemory[iPutPos++] = vecsData[iCurPos++];
|
|
||||||
}
|
|
||||||
|
|
||||||
// return "buffer is ready for readout" flag
|
// first check for buffer overrun
|
||||||
return ( iEnd == iMemSize );
|
if ( iEnd <= iMemSize )
|
||||||
|
{
|
||||||
|
// actual copy operation
|
||||||
|
while ( iPutPos < iEnd )
|
||||||
|
{
|
||||||
|
vecsMemory[iPutPos++] = vecsData[iCurPos++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// return "buffer is ready for readout" flag
|
||||||
|
return ( iEnd == iMemSize );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// buffer overrun or not initialized, return "not ready"
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CVector<TData> Get()
|
CVector<TData> Get()
|
||||||
|
|
|
@ -31,7 +31,7 @@ CChannel::CChannel ( const bool bNIsServer ) :
|
||||||
sName ( "" ),
|
sName ( "" ),
|
||||||
vecdGains ( USED_NUM_CHANNELS, (double) 1.0 ),
|
vecdGains ( USED_NUM_CHANNELS, (double) 1.0 ),
|
||||||
bIsEnabled ( false ),
|
bIsEnabled ( false ),
|
||||||
iNetwFrameSizeFact ( 0 ),
|
iNetwFrameSizeFact ( FRAME_SIZE_FACTOR_DEFAULT ),
|
||||||
iNetwFrameSize ( 0 )
|
iNetwFrameSize ( 0 )
|
||||||
{
|
{
|
||||||
// initial value for connection time out counter, we calculate the total
|
// initial value for connection time out counter, we calculate the total
|
||||||
|
@ -45,6 +45,10 @@ CChannel::CChannel ( const bool bNIsServer ) :
|
||||||
// init the socket buffer
|
// init the socket buffer
|
||||||
SetSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL );
|
SetSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL );
|
||||||
|
|
||||||
|
// initialize cycle time variance measurement with defaults
|
||||||
|
CycleTimeVariance.Init ( SYSTEM_BLOCK_FRAME_SAMPLES,
|
||||||
|
SYSTEM_SAMPLE_RATE, TIME_MOV_AV_RESPONSE );
|
||||||
|
|
||||||
|
|
||||||
// connections -------------------------------------------------------------
|
// connections -------------------------------------------------------------
|
||||||
QObject::connect ( &Protocol,
|
QObject::connect ( &Protocol,
|
||||||
|
@ -286,6 +290,9 @@ void CChannel::OnNetTranspPropsReceived ( CNetworkTransportProps NetworkTranspor
|
||||||
// update socket buffer (the network block size is a multiple of the
|
// update socket buffer (the network block size is a multiple of the
|
||||||
// minimum network frame size
|
// minimum network frame size
|
||||||
SockBuf.Init ( iNetwFrameSize, iCurSockBufNumFrames );
|
SockBuf.Init ( iNetwFrameSize, iCurSockBufNumFrames );
|
||||||
|
|
||||||
|
// init conversion buffer
|
||||||
|
ConvBuf.Init ( iNetwFrameSize * iNetwFrameSizeFact );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,13 +375,17 @@ EPutDataStat CChannel::PutData ( const CVector<uint8_t>& vecbyData,
|
||||||
eRet = PS_AUDIO_ERR;
|
eRet = PS_AUDIO_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update cycle time variance measurement
|
// update cycle time variance measurement (this is only
|
||||||
|
// used by the client so do not update for server channel)
|
||||||
|
if ( !bIsServer )
|
||||||
|
{
|
||||||
|
|
||||||
// TODO only update if time difference of received packets is below
|
// TODO only update if time difference of received packets is below
|
||||||
// a limit to avoid having short network troubles incorporated in the
|
// a limit to avoid having short network troubles incorporated in the
|
||||||
// statistic
|
// statistic
|
||||||
|
|
||||||
CycleTimeVariance.Update();
|
CycleTimeVariance.Update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -395,7 +395,10 @@ void CClient::ProcessAudioData ( CVector<int16_t>& vecsStereoSndCrd )
|
||||||
|
|
||||||
|
|
||||||
// receive a new block
|
// receive a new block
|
||||||
if ( Channel.GetData ( vecbyNetwData ) == GS_BUFFER_OK )
|
const bool bReceiveDataOk =
|
||||||
|
( Channel.GetData ( vecbyNetwData ) == GS_BUFFER_OK );
|
||||||
|
|
||||||
|
if ( bReceiveDataOk )
|
||||||
{
|
{
|
||||||
PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_GREEN );
|
PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_GREEN );
|
||||||
}
|
}
|
||||||
|
@ -404,48 +407,34 @@ void CClient::ProcessAudioData ( CVector<int16_t>& vecsStereoSndCrd )
|
||||||
PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_RED );
|
PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_RED );
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// TEST
|
|
||||||
// fid=fopen('v.dat','r');x=fread(fid,'int16');fclose(fid);
|
|
||||||
static FILE* pFileDelay = fopen("v.dat", "wb");
|
|
||||||
short sData[2];
|
|
||||||
for (i = 0; i < iMonoBlockSizeSam; i++)
|
|
||||||
{
|
|
||||||
sData[0] = (short) vecdNetwData[i];
|
|
||||||
fwrite(&sData, size_t(2), size_t(1), pFileDelay);
|
|
||||||
}
|
|
||||||
fflush(pFileDelay);
|
|
||||||
*/
|
|
||||||
|
|
||||||
// check if channel is connected
|
// check if channel is connected
|
||||||
if ( Channel.IsConnected() )
|
if ( Channel.IsConnected() )
|
||||||
{
|
{
|
||||||
/*
|
CVector<short> vecsAudioSndCrdMono ( iMonoBlockSizeSam );
|
||||||
// convert data from double to short type and copy mono
|
|
||||||
// received data in both sound card channels
|
// CELT decoding
|
||||||
for ( i = 0, j = 0; i < iSndCrdMonoBlockSizeSam; i++, j += 2 )
|
if ( bReceiveDataOk )
|
||||||
|
{
|
||||||
|
celt_decode ( CeltDecoder,
|
||||||
|
&vecbyNetwData[0],
|
||||||
|
iCeltNumCodedBytes,
|
||||||
|
&vecsAudioSndCrdMono[0] );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// lost packet
|
||||||
|
celt_decode ( CeltDecoder,
|
||||||
|
NULL,
|
||||||
|
iCeltNumCodedBytes,
|
||||||
|
&vecsAudioSndCrdMono[0] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy mono data in stereo sound card buffer
|
||||||
|
for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 )
|
||||||
{
|
{
|
||||||
vecsStereoSndCrd[j] = vecsStereoSndCrd[j + 1] =
|
vecsStereoSndCrd[j] = vecsStereoSndCrd[j + 1] =
|
||||||
Double2Short ( vecdNetwData[i] );
|
vecsAudioSndCrdMono[i];
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
// TEST CELT
|
|
||||||
CVector<short> vecsAudioSndCrdMono ( iMonoBlockSizeSam );
|
|
||||||
|
|
||||||
celt_decode ( CeltDecoder,
|
|
||||||
&vecbyNetwData[0],
|
|
||||||
iCeltNumCodedBytes,
|
|
||||||
&vecsAudioSndCrdMono[0] );
|
|
||||||
|
|
||||||
for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 )
|
|
||||||
{
|
|
||||||
vecsStereoSndCrd[j] = vecsStereoSndCrd[j + 1] =
|
|
||||||
vecsAudioSndCrdMono[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -75,11 +75,11 @@ CLlconServerDlg::CLlconServerDlg ( CServer* pNServP, QWidget* parent )
|
||||||
|
|
||||||
void CLlconServerDlg::OnTimer()
|
void CLlconServerDlg::OnTimer()
|
||||||
{
|
{
|
||||||
CVector<CHostAddress> vecHostAddresses;
|
CVector<CHostAddress> vecHostAddresses;
|
||||||
CVector<QString> vecsName;
|
CVector<QString> vecsName;
|
||||||
CVector<int> veciJitBufNumFrames;
|
CVector<int> veciJitBufNumFrames;
|
||||||
CVector<int> veciNetwFrameSizeFact;
|
CVector<int> veciNetwFrameSizeFact;
|
||||||
double dCurTiStdDev;
|
double dCurTiStdDev;
|
||||||
|
|
||||||
ListViewMutex.lock();
|
ListViewMutex.lock();
|
||||||
|
|
||||||
|
|
142
src/server.cpp
142
src/server.cpp
|
@ -187,11 +187,6 @@ CServer::CServer ( const QString& strLoggingFileName,
|
||||||
CeltDecoder[i] = celt_decoder_create ( CeltMode[i] );
|
CeltDecoder[i] = celt_decoder_create ( CeltMode[i] );
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO init these variables:
|
|
||||||
// iCeltNumCodedBytes[MAX_NUM_CHANNELS];
|
|
||||||
// vecCeltData[MAX_NUM_CHANNELS];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// connections -------------------------------------------------------------
|
// connections -------------------------------------------------------------
|
||||||
// connect timer timeout signal
|
// connect timer timeout signal
|
||||||
|
@ -277,7 +272,7 @@ void CServer::OnSendProtMessage ( int iChID, CVector<uint8_t> vecMessage )
|
||||||
{
|
{
|
||||||
// the protocol queries me to call the function to send the message
|
// the protocol queries me to call the function to send the message
|
||||||
// send it through the network
|
// send it through the network
|
||||||
Socket.SendPacket ( vecMessage, GetAddress ( iChID ) );
|
Socket.SendPacket ( vecMessage, vecChannels[iChID].GetAddress() );
|
||||||
}
|
}
|
||||||
|
|
||||||
void CServer::Start()
|
void CServer::Start()
|
||||||
|
@ -322,26 +317,24 @@ void CServer::OnTimer()
|
||||||
// actual processing of audio data -> mix
|
// actual processing of audio data -> mix
|
||||||
vecsSendData = ProcessData ( vecvecsData, vecvecdGains[i] );
|
vecsSendData = ProcessData ( vecvecsData, vecvecdGains[i] );
|
||||||
|
|
||||||
// send separate mix to current clients
|
|
||||||
// Socket.SendPacket (
|
|
||||||
// PrepSendPacket ( vecChanID[i], vecsSendData ),
|
|
||||||
// GetAddress ( vecChanID[i] ) );
|
|
||||||
|
|
||||||
/*
|
|
||||||
// get current number of CELT coded bytes
|
// get current number of CELT coded bytes
|
||||||
const int iCeltNumCodedBytes =
|
const int iCeltNumCodedBytes =
|
||||||
vecChannels[i].GetNetwFrameSize() /
|
vecChannels[vecChanID[i]].GetNetwFrameSize() /
|
||||||
vecChannels[i].GetNetwFrameSizeFact();
|
vecChannels[vecChanID[i]].GetNetwFrameSizeFact();
|
||||||
|
|
||||||
celt_encode ( CeltEncoder,
|
// CELT encoding
|
||||||
&vecsNetwork[0],
|
CVector<unsigned char> vecCeltData ( iCeltNumCodedBytes );
|
||||||
NULL,
|
|
||||||
&vecCeltData[0],
|
|
||||||
iCeltNumCodedBytes );
|
|
||||||
|
|
||||||
Socket.SendPacket ( vecCeltData, Channel.GetAddress() );
|
celt_encode ( CeltEncoder[vecChanID[i]],
|
||||||
*/
|
&vecsSendData[0],
|
||||||
|
NULL,
|
||||||
|
&vecCeltData[0],
|
||||||
|
iCeltNumCodedBytes );
|
||||||
|
|
||||||
|
// send separate mix to current clients
|
||||||
|
Socket.SendPacket (
|
||||||
|
vecChannels[vecChanID[i]].PrepSendPacket ( vecCeltData ),
|
||||||
|
vecChannels[vecChanID[i]].GetAddress() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -355,6 +348,42 @@ Socket.SendPacket ( vecCeltData, Channel.GetAddress() );
|
||||||
CycleTimeVariance.Update();
|
CycleTimeVariance.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CVector<int16_t> CServer::ProcessData ( CVector<CVector<int16_t> >& vecvecsData,
|
||||||
|
CVector<double>& vecdGains )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
// init return vector with zeros since we mix all channels on that vector
|
||||||
|
CVector<int16_t> vecsOutData ( SYSTEM_BLOCK_FRAME_SAMPLES, 0 );
|
||||||
|
|
||||||
|
const int iNumClients = vecvecsData.Size();
|
||||||
|
|
||||||
|
// mix all audio data from all clients together
|
||||||
|
for ( int j = 0; j < iNumClients; j++ )
|
||||||
|
{
|
||||||
|
// if channel gain is 1, avoid multiplication for speed optimization
|
||||||
|
if ( vecdGains[j] == static_cast<double> ( 1.0 ) )
|
||||||
|
{
|
||||||
|
for ( i = 0; i < SYSTEM_BLOCK_FRAME_SAMPLES; i++ )
|
||||||
|
{
|
||||||
|
vecsOutData[i] =
|
||||||
|
Double2Short ( vecsOutData[i] + vecvecsData[j][i] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for ( i = 0; i < SYSTEM_BLOCK_FRAME_SAMPLES; i++ )
|
||||||
|
{
|
||||||
|
vecsOutData[i] =
|
||||||
|
Double2Short ( vecsOutData[i] +
|
||||||
|
vecvecsData[j][i] * vecdGains[j] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vecsOutData;
|
||||||
|
}
|
||||||
|
|
||||||
void CServer::GetBlockAllConC ( CVector<int>& vecChanID,
|
void CServer::GetBlockAllConC ( CVector<int>& vecChanID,
|
||||||
CVector<CVector<int16_t> >& vecvecsData,
|
CVector<CVector<int16_t> >& vecvecsData,
|
||||||
CVector<CVector<double> >& vecvecdGains )
|
CVector<CVector<double> >& vecvecdGains )
|
||||||
|
@ -385,14 +414,6 @@ void CServer::GetBlockAllConC ( CVector<int>& vecChanID,
|
||||||
// disconnected channels
|
// disconnected channels
|
||||||
const EGetDataStat eGetStat = vecChannels[i].GetData ( vecbyData );
|
const EGetDataStat eGetStat = vecChannels[i].GetData ( vecbyData );
|
||||||
|
|
||||||
// CELT decode received data stream
|
|
||||||
CVector<int16_t> vecsAudioMono ( SYSTEM_BLOCK_FRAME_SAMPLES );
|
|
||||||
|
|
||||||
celt_decode ( CeltDecoder[i],
|
|
||||||
&vecbyData[0],
|
|
||||||
iCeltNumCodedBytes,
|
|
||||||
&vecsAudioMono[0] );
|
|
||||||
|
|
||||||
// if channel was just disconnected, set flag that connected
|
// if channel was just disconnected, set flag that connected
|
||||||
// client list is sent to all other clients
|
// client list is sent to all other clients
|
||||||
if ( eGetStat == GS_CHAN_NOW_DISCONNECTED )
|
if ( eGetStat == GS_CHAN_NOW_DISCONNECTED )
|
||||||
|
@ -402,6 +423,25 @@ void CServer::GetBlockAllConC ( CVector<int>& vecChanID,
|
||||||
|
|
||||||
if ( vecChannels[i].IsConnected() )
|
if ( vecChannels[i].IsConnected() )
|
||||||
{
|
{
|
||||||
|
// CELT decode received data stream
|
||||||
|
CVector<int16_t> vecsAudioMono ( SYSTEM_BLOCK_FRAME_SAMPLES );
|
||||||
|
|
||||||
|
if ( eGetStat == GS_BUFFER_OK )
|
||||||
|
{
|
||||||
|
celt_decode ( CeltDecoder[i],
|
||||||
|
&vecbyData[0],
|
||||||
|
iCeltNumCodedBytes,
|
||||||
|
&vecsAudioMono[0] );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// lost packet
|
||||||
|
celt_decode ( CeltDecoder[i],
|
||||||
|
NULL,
|
||||||
|
iCeltNumCodedBytes,
|
||||||
|
&vecsAudioMono[0] );
|
||||||
|
}
|
||||||
|
|
||||||
// add ID and data
|
// add ID and data
|
||||||
vecChanID.Add ( i );
|
vecChanID.Add ( i );
|
||||||
|
|
||||||
|
@ -698,10 +738,10 @@ bAudioOK = true;
|
||||||
return bAudioOK;
|
return bAudioOK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CServer::GetConCliParam ( CVector<CHostAddress>& vecHostAddresses,
|
void CServer::GetConCliParam ( CVector<CHostAddress>& vecHostAddresses,
|
||||||
CVector<QString>& vecsName,
|
CVector<QString>& vecsName,
|
||||||
CVector<int>& veciJitBufNumFrames,
|
CVector<int>& veciJitBufNumFrames,
|
||||||
CVector<int>& veciNetwFrameSizeFact )
|
CVector<int>& veciNetwFrameSizeFact )
|
||||||
{
|
{
|
||||||
CHostAddress InetAddr;
|
CHostAddress InetAddr;
|
||||||
|
|
||||||
|
@ -797,42 +837,6 @@ void CServer::WriteHTMLChannelList()
|
||||||
streamFileOut << "</ul>" << endl;
|
streamFileOut << "</ul>" << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
CVector<int16_t> CServer::ProcessData ( CVector<CVector<int16_t> >& vecvecsData,
|
|
||||||
CVector<double>& vecdGains )
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
// init return vector with zeros since we mix all channels on that vector
|
|
||||||
CVector<int16_t> vecsOutData ( SYSTEM_BLOCK_FRAME_SAMPLES, 0 );
|
|
||||||
|
|
||||||
const int iNumClients = vecvecsData.Size();
|
|
||||||
|
|
||||||
// mix all audio data from all clients together
|
|
||||||
for ( int j = 0; j < iNumClients; j++ )
|
|
||||||
{
|
|
||||||
// if channel gain is 1, avoid multiplication for speed optimization
|
|
||||||
if ( vecdGains[j] == static_cast<double> ( 1.0 ) )
|
|
||||||
{
|
|
||||||
for ( i = 0; i < SYSTEM_BLOCK_FRAME_SAMPLES; i++ )
|
|
||||||
{
|
|
||||||
vecsOutData[i] =
|
|
||||||
Double2Short ( vecsOutData[i] + vecvecsData[j][i] );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for ( i = 0; i < SYSTEM_BLOCK_FRAME_SAMPLES; i++ )
|
|
||||||
{
|
|
||||||
vecsOutData[i] =
|
|
||||||
Double2Short ( vecsOutData[i] +
|
|
||||||
vecvecsData[j][i] * vecdGains[j] );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return vecsOutData;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CServer::GetTimingStdDev ( double& dCurTiStdDev )
|
bool CServer::GetTimingStdDev ( double& dCurTiStdDev )
|
||||||
{
|
{
|
||||||
dCurTiStdDev = 0.0; // init return value
|
dCurTiStdDev = 0.0; // init return value
|
||||||
|
|
|
@ -102,13 +102,6 @@ protected:
|
||||||
bool IsConnected ( const int iChanNum )
|
bool IsConnected ( const int iChanNum )
|
||||||
{ return vecChannels[iChanNum].IsConnected(); }
|
{ return vecChannels[iChanNum].IsConnected(); }
|
||||||
|
|
||||||
CVector<uint8_t> PrepSendPacket ( const int iChanNum,
|
|
||||||
const CVector<uint8_t>& vecbyNPacket )
|
|
||||||
{ return vecChannels[iChanNum].PrepSendPacket ( vecbyNPacket ); }
|
|
||||||
|
|
||||||
CHostAddress GetAddress ( const int iChanNum )
|
|
||||||
{ return vecChannels[iChanNum].GetAddress(); }
|
|
||||||
|
|
||||||
void StartStatusHTMLFileWriting ( const QString& strNewFileName,
|
void StartStatusHTMLFileWriting ( const QString& strNewFileName,
|
||||||
const QString& strNewServerNameWithPort );
|
const QString& strNewServerNameWithPort );
|
||||||
|
|
||||||
|
@ -139,8 +132,6 @@ protected:
|
||||||
CELTMode* CeltMode[MAX_NUM_CHANNELS];
|
CELTMode* CeltMode[MAX_NUM_CHANNELS];
|
||||||
CELTEncoder* CeltEncoder[MAX_NUM_CHANNELS];
|
CELTEncoder* CeltEncoder[MAX_NUM_CHANNELS];
|
||||||
CELTDecoder* CeltDecoder[MAX_NUM_CHANNELS];
|
CELTDecoder* CeltDecoder[MAX_NUM_CHANNELS];
|
||||||
int iCeltNumCodedBytes[MAX_NUM_CHANNELS];
|
|
||||||
CVector<unsigned char> vecCeltData[MAX_NUM_CHANNELS];
|
|
||||||
|
|
||||||
CVector<QString> vstrChatColors;
|
CVector<QString> vstrChatColors;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue