2006-01-28 12:29:22 +01:00
|
|
|
/******************************************************************************\
|
|
|
|
* Copyright (c) 2004-2006
|
|
|
|
*
|
|
|
|
* Author(s):
|
2006-11-25 15:46:57 +01:00
|
|
|
* Volker Fischer
|
2006-01-28 12:29:22 +01:00
|
|
|
*
|
|
|
|
******************************************************************************
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
|
|
* Foundation; either version 2 of the License, or (at your option) any later
|
|
|
|
* version.
|
|
|
|
*
|
|
|
|
* This program 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 General Public License for more
|
|
|
|
* details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along with
|
|
|
|
* this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*
|
|
|
|
\******************************************************************************/
|
|
|
|
|
|
|
|
#include "channel.h"
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************\
|
|
|
|
* CChannelSet *
|
2006-03-04 12:11:26 +01:00
|
|
|
\******************************************************************************/
|
|
|
|
CChannelSet::CChannelSet()
|
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
// make sure we have MAX_NUM_CHANNELS connections!!!
|
|
|
|
// send message
|
|
|
|
QObject::connect(&vecChannels[0],SIGNAL(MessReadyForSending(CVector<uint8_t>)),this,SLOT(OnSendProtMessCh0(CVector<uint8_t>)));
|
|
|
|
QObject::connect(&vecChannels[1],SIGNAL(MessReadyForSending(CVector<uint8_t>)),this,SLOT(OnSendProtMessCh1(CVector<uint8_t>)));
|
|
|
|
QObject::connect(&vecChannels[2],SIGNAL(MessReadyForSending(CVector<uint8_t>)),this,SLOT(OnSendProtMessCh2(CVector<uint8_t>)));
|
|
|
|
QObject::connect(&vecChannels[3],SIGNAL(MessReadyForSending(CVector<uint8_t>)),this,SLOT(OnSendProtMessCh3(CVector<uint8_t>)));
|
|
|
|
QObject::connect(&vecChannels[4],SIGNAL(MessReadyForSending(CVector<uint8_t>)),this,SLOT(OnSendProtMessCh4(CVector<uint8_t>)));
|
2006-12-06 21:22:41 +01:00
|
|
|
QObject::connect(&vecChannels[5],SIGNAL(MessReadyForSending(CVector<uint8_t>)),this,SLOT(OnSendProtMessCh5(CVector<uint8_t>)));
|
|
|
|
QObject::connect(&vecChannels[6],SIGNAL(MessReadyForSending(CVector<uint8_t>)),this,SLOT(OnSendProtMessCh6(CVector<uint8_t>)));
|
|
|
|
QObject::connect(&vecChannels[7],SIGNAL(MessReadyForSending(CVector<uint8_t>)),this,SLOT(OnSendProtMessCh7(CVector<uint8_t>)));
|
|
|
|
QObject::connect(&vecChannels[8],SIGNAL(MessReadyForSending(CVector<uint8_t>)),this,SLOT(OnSendProtMessCh8(CVector<uint8_t>)));
|
|
|
|
QObject::connect(&vecChannels[9],SIGNAL(MessReadyForSending(CVector<uint8_t>)),this,SLOT(OnSendProtMessCh9(CVector<uint8_t>)));
|
2006-11-25 15:46:57 +01:00
|
|
|
|
|
|
|
// request jitter buffer size
|
|
|
|
QObject::connect(&vecChannels[0],SIGNAL(NewConnection()),this,SLOT(OnNewConnectionCh0()));
|
|
|
|
QObject::connect(&vecChannels[1],SIGNAL(NewConnection()),this,SLOT(OnNewConnectionCh1()));
|
|
|
|
QObject::connect(&vecChannels[2],SIGNAL(NewConnection()),this,SLOT(OnNewConnectionCh2()));
|
|
|
|
QObject::connect(&vecChannels[3],SIGNAL(NewConnection()),this,SLOT(OnNewConnectionCh3()));
|
|
|
|
QObject::connect(&vecChannels[4],SIGNAL(NewConnection()),this,SLOT(OnNewConnectionCh4()));
|
2006-12-06 21:22:41 +01:00
|
|
|
QObject::connect(&vecChannels[5],SIGNAL(NewConnection()),this,SLOT(OnNewConnectionCh5()));
|
|
|
|
QObject::connect(&vecChannels[6],SIGNAL(NewConnection()),this,SLOT(OnNewConnectionCh6()));
|
|
|
|
QObject::connect(&vecChannels[7],SIGNAL(NewConnection()),this,SLOT(OnNewConnectionCh7()));
|
|
|
|
QObject::connect(&vecChannels[8],SIGNAL(NewConnection()),this,SLOT(OnNewConnectionCh8()));
|
|
|
|
QObject::connect(&vecChannels[9],SIGNAL(NewConnection()),this,SLOT(OnNewConnectionCh9()));
|
2006-12-08 19:19:16 +01:00
|
|
|
|
|
|
|
// request connected clients list
|
|
|
|
QObject::connect(&vecChannels[0],SIGNAL(ReqConnClientsList()),this,SLOT(OnNewConnectionCh0()));
|
|
|
|
QObject::connect(&vecChannels[1],SIGNAL(ReqConnClientsList()),this,SLOT(OnNewConnectionCh1()));
|
|
|
|
QObject::connect(&vecChannels[2],SIGNAL(ReqConnClientsList()),this,SLOT(OnNewConnectionCh2()));
|
|
|
|
QObject::connect(&vecChannels[3],SIGNAL(ReqConnClientsList()),this,SLOT(OnNewConnectionCh3()));
|
|
|
|
QObject::connect(&vecChannels[4],SIGNAL(ReqConnClientsList()),this,SLOT(OnNewConnectionCh4()));
|
|
|
|
QObject::connect(&vecChannels[5],SIGNAL(ReqConnClientsList()),this,SLOT(OnNewConnectionCh5()));
|
|
|
|
QObject::connect(&vecChannels[6],SIGNAL(ReqConnClientsList()),this,SLOT(OnNewConnectionCh6()));
|
|
|
|
QObject::connect(&vecChannels[7],SIGNAL(ReqConnClientsList()),this,SLOT(OnNewConnectionCh7()));
|
|
|
|
QObject::connect(&vecChannels[8],SIGNAL(ReqConnClientsList()),this,SLOT(OnNewConnectionCh8()));
|
|
|
|
QObject::connect(&vecChannels[9],SIGNAL(ReqConnClientsList()),this,SLOT(OnNewConnectionCh9()));
|
2006-03-04 12:12:47 +01:00
|
|
|
}
|
2006-11-04 14:09:42 +01:00
|
|
|
|
|
|
|
void CChannelSet::CreateAndSendChanListForAllConClients()
|
|
|
|
{
|
2006-11-27 23:35:22 +01:00
|
|
|
int i;
|
|
|
|
CVector<CChannelShortInfo> vecChanInfo ( 0 );
|
2006-11-25 15:46:57 +01:00
|
|
|
|
|
|
|
// look for free channels
|
|
|
|
for ( i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
|
|
|
if ( vecChannels[i].IsConnected() )
|
|
|
|
{
|
|
|
|
// append channel ID, IP address and channel name to storing vectors
|
2006-11-27 23:35:22 +01:00
|
|
|
vecChanInfo.Add ( CChannelShortInfo (
|
|
|
|
i, // ID
|
|
|
|
vecChannels[i].GetAddress().InetAddr.ip4Addr(), // IP address
|
|
|
|
vecChannels[i].GetName() /* name */ ) );
|
2006-11-25 15:46:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// now send connected channels list to all connected clients
|
|
|
|
for ( i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
|
|
|
if ( vecChannels[i].IsConnected() )
|
|
|
|
{
|
|
|
|
// send message
|
2006-11-26 22:25:56 +01:00
|
|
|
vecChannels[i].CreateConClientListMes ( vecChanInfo );
|
2006-11-25 15:46:57 +01:00
|
|
|
}
|
|
|
|
}
|
2006-11-04 14:09:42 +01:00
|
|
|
}
|
2006-03-04 12:11:26 +01:00
|
|
|
|
2006-01-28 12:29:22 +01:00
|
|
|
int CChannelSet::GetFreeChan()
|
|
|
|
{
|
2006-12-06 22:10:24 +01:00
|
|
|
// look for a free channel
|
2006-11-25 15:46:57 +01:00
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
|
|
|
if ( !vecChannels[i].IsConnected() )
|
|
|
|
{
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* no free channel found, return invalid ID */
|
|
|
|
return INVALID_CHANNEL_ID;
|
2006-01-28 12:29:22 +01:00
|
|
|
}
|
|
|
|
|
2006-02-17 22:08:05 +01:00
|
|
|
int CChannelSet::CheckAddr ( const CHostAddress& Addr )
|
2006-01-28 12:29:22 +01:00
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
CHostAddress InetAddr;
|
2006-01-28 12:29:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
/* Check for all possible channels if IP is already in use */
|
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
2006-01-28 12:29:22 +01:00
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
if ( vecChannels[i].GetAddress ( InetAddr ) )
|
|
|
|
{
|
|
|
|
/* IP found, return channel number */
|
|
|
|
if ( InetAddr == Addr )
|
|
|
|
{
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* IP not found, return invalid ID */
|
|
|
|
return INVALID_CHANNEL_ID;
|
2006-01-28 12:29:22 +01:00
|
|
|
}
|
|
|
|
|
2006-02-17 22:08:05 +01:00
|
|
|
bool CChannelSet::PutData ( const CVector<unsigned char>& vecbyRecBuf,
|
2006-12-06 22:10:24 +01:00
|
|
|
const int iNumBytesRead,
|
|
|
|
const CHostAddress& HostAdr )
|
2006-01-28 12:29:22 +01:00
|
|
|
{
|
2006-11-27 23:35:22 +01:00
|
|
|
bool bRet = false;
|
2006-11-25 15:46:57 +01:00
|
|
|
bool bCreateChanList = false;
|
|
|
|
|
|
|
|
Mutex.lock();
|
|
|
|
{
|
|
|
|
bool bChanOK = true;
|
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// get channel ID ------------------------------------------------------
|
|
|
|
// check address
|
2006-11-25 15:46:57 +01:00
|
|
|
int iCurChanID = CheckAddr ( HostAdr );
|
|
|
|
|
|
|
|
if ( iCurChanID == INVALID_CHANNEL_ID )
|
|
|
|
{
|
2006-12-06 22:10:24 +01:00
|
|
|
// a new client is calling, look for free channel
|
2006-11-25 15:46:57 +01:00
|
|
|
iCurChanID = GetFreeChan();
|
|
|
|
|
|
|
|
if ( iCurChanID != INVALID_CHANNEL_ID )
|
2006-12-06 21:22:41 +01:00
|
|
|
{
|
|
|
|
// initialize current channel by storing the calling host
|
|
|
|
// address
|
2006-11-25 15:46:57 +01:00
|
|
|
vecChannels[iCurChanID].SetAddress ( HostAdr );
|
|
|
|
|
2006-12-06 21:22:41 +01:00
|
|
|
// reset the channel gains of current channel, at the same
|
|
|
|
// time reset gains of this channel ID for all other channels
|
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
|
|
|
vecChannels[iCurChanID].SetGain ( i, (double) 1.0 );
|
|
|
|
|
|
|
|
// other channels (we do not distinguish the case if
|
|
|
|
// i == iCurChanID for simplicity)
|
|
|
|
vecChannels[i].SetGain ( iCurChanID, (double) 1.0 );
|
|
|
|
}
|
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
// a new client connected to the server, set flag to create and
|
|
|
|
// send all clients the updated channel list, we cannot create
|
|
|
|
// the message here since the received data has to be put to the
|
|
|
|
// channel first so that this channel is marked as connected
|
|
|
|
bCreateChanList = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2006-12-06 22:10:24 +01:00
|
|
|
// no free channel available
|
2006-11-25 15:46:57 +01:00
|
|
|
bChanOK = false;
|
2006-11-27 23:35:22 +01:00
|
|
|
|
|
|
|
// create and send "server full" message
|
2006-12-06 22:10:24 +01:00
|
|
|
|
|
|
|
// TODO problem: if channel is not officially connected, we cannot send messages
|
2006-11-27 23:35:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// put received data in jitter buffer ----------------------------------
|
2006-11-25 15:46:57 +01:00
|
|
|
if ( bChanOK )
|
|
|
|
{
|
2006-12-06 22:10:24 +01:00
|
|
|
// put packet in socket buffer
|
2006-11-25 15:46:57 +01:00
|
|
|
switch ( vecChannels[iCurChanID].PutData ( vecbyRecBuf, iNumBytesRead ) )
|
|
|
|
{
|
|
|
|
case PS_AUDIO_OK:
|
|
|
|
PostWinMessage ( MS_JIT_BUF_PUT, MUL_COL_LED_GREEN, iCurChanID );
|
|
|
|
bRet = true; // in case we have an audio packet, return true
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PS_AUDIO_ERR:
|
|
|
|
PostWinMessage ( MS_JIT_BUF_PUT, MUL_COL_LED_RED, iCurChanID );
|
|
|
|
bRet = true; // in case we have an audio packet, return true
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PS_PROT_ERR:
|
|
|
|
PostWinMessage ( MS_JIT_BUF_PUT, MUL_COL_LED_YELLOW, iCurChanID );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// after data is put in channel buffer, create channel list message if
|
|
|
|
// requested
|
|
|
|
if ( bCreateChanList )
|
|
|
|
{
|
2006-12-08 19:19:16 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO list is only send for new connected clients after request, only
|
|
|
|
// the already connected clients get the list automatically, because they
|
|
|
|
// don't know when new clients connect!
|
|
|
|
|
|
|
|
// TODO use "void OnReqConnClientsListChx() {}" for sending list to specific client
|
|
|
|
|
|
|
|
CreateAndSendChanListForAllConClients(); // <- replace this
|
|
|
|
|
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Mutex.unlock();
|
|
|
|
|
|
|
|
return bRet;
|
2006-01-28 12:29:22 +01:00
|
|
|
}
|
|
|
|
|
2006-12-06 21:22:41 +01:00
|
|
|
void CChannelSet::GetBlockAllConC ( CVector<int>& vecChanID,
|
2006-11-25 15:46:57 +01:00
|
|
|
CVector<CVector<double> >& vecvecdData,
|
2006-12-06 21:22:41 +01:00
|
|
|
CVector<CVector<double> >& vecvecdGains )
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
// init temporal data vector and clear input buffers
|
2006-11-25 15:46:57 +01:00
|
|
|
CVector<double> vecdData ( MIN_BLOCK_SIZE_SAMPLES );
|
|
|
|
|
2006-12-06 21:22:41 +01:00
|
|
|
vecChanID.Init ( 0 );
|
|
|
|
vecvecdData.Init ( 0 );
|
|
|
|
vecvecdGains.Init ( 0 );
|
2006-11-25 15:46:57 +01:00
|
|
|
|
2006-12-06 21:22:41 +01:00
|
|
|
// make put and get calls thread safe. Do not forget to unlock mutex
|
|
|
|
// afterwards!
|
2006-11-25 15:46:57 +01:00
|
|
|
Mutex.lock();
|
|
|
|
{
|
2006-12-06 21:22:41 +01:00
|
|
|
// check all possible channels
|
|
|
|
for ( i = 0; i < MAX_NUM_CHANNELS; i++ )
|
2006-11-25 15:46:57 +01:00
|
|
|
{
|
2006-12-06 21:22:41 +01:00
|
|
|
// read out all input buffers to decrease timeout counter on
|
|
|
|
// disconnected channels
|
2006-11-25 15:46:57 +01:00
|
|
|
const bool bGetOK = vecChannels[i].GetData ( vecdData );
|
|
|
|
|
|
|
|
if ( vecChannels[i].IsConnected() )
|
|
|
|
{
|
2006-12-06 21:22:41 +01:00
|
|
|
// add ID and data
|
2006-11-25 15:46:57 +01:00
|
|
|
vecChanID.Add ( i );
|
|
|
|
|
|
|
|
const int iOldSize = vecvecdData.Size();
|
|
|
|
vecvecdData.Enlarge ( 1 );
|
|
|
|
vecvecdData[iOldSize].Init ( vecdData.Size() );
|
|
|
|
vecvecdData[iOldSize] = vecdData;
|
|
|
|
|
2006-12-06 21:22:41 +01:00
|
|
|
// send message for get status (for GUI)
|
2006-11-25 15:46:57 +01:00
|
|
|
if ( bGetOK )
|
|
|
|
{
|
|
|
|
PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_GREEN, i );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_RED, i );
|
|
|
|
}
|
|
|
|
}
|
2006-12-06 21:22:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// now that we know the IDs of the connected clients, get gains
|
|
|
|
const int iNumCurConnChan = vecChanID.Size();
|
|
|
|
vecvecdGains.Init ( iNumCurConnChan );
|
|
|
|
|
|
|
|
for ( i = 0; i < iNumCurConnChan; i++ )
|
|
|
|
{
|
|
|
|
vecvecdGains[i].Init ( iNumCurConnChan );
|
|
|
|
|
|
|
|
for ( j = 0; j < iNumCurConnChan; j++ )
|
|
|
|
{
|
|
|
|
// The second index of "vecvecdGains" does not represent
|
|
|
|
// the channel ID! Therefore we have to use "vecChanID" to
|
|
|
|
// query the IDs of the currently connected channels
|
|
|
|
vecvecdGains[i][j] = vecChannels[i].GetGain( vecChanID[j] );
|
|
|
|
}
|
2006-11-25 15:46:57 +01:00
|
|
|
}
|
|
|
|
}
|
2006-12-06 21:22:41 +01:00
|
|
|
Mutex.unlock(); // release mutex
|
2006-01-28 12:29:22 +01:00
|
|
|
}
|
|
|
|
|
2006-02-17 22:08:05 +01:00
|
|
|
void CChannelSet::GetConCliParam ( CVector<CHostAddress>& vecHostAddresses,
|
2006-12-06 21:22:41 +01:00
|
|
|
CVector<int>& veciJitBufSize,
|
|
|
|
CVector<int>& veciNetwOutBlSiFact,
|
|
|
|
CVector<int>& veciNetwInBlSiFact )
|
2006-01-28 12:29:22 +01:00
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
CHostAddress InetAddr;
|
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// init return values
|
2006-12-06 21:22:41 +01:00
|
|
|
vecHostAddresses.Init ( MAX_NUM_CHANNELS );
|
|
|
|
veciJitBufSize.Init ( MAX_NUM_CHANNELS );
|
2006-11-25 15:46:57 +01:00
|
|
|
veciNetwOutBlSiFact.Init ( MAX_NUM_CHANNELS );
|
2006-12-06 21:22:41 +01:00
|
|
|
veciNetwInBlSiFact.Init ( MAX_NUM_CHANNELS );
|
2006-11-25 15:46:57 +01:00
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// check all possible channels
|
2006-11-25 15:46:57 +01:00
|
|
|
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
|
|
|
|
{
|
|
|
|
if ( vecChannels[i].GetAddress ( InetAddr ) )
|
|
|
|
{
|
2006-12-06 22:10:24 +01:00
|
|
|
// get requested data
|
2006-12-06 21:22:41 +01:00
|
|
|
vecHostAddresses[i] = InetAddr;
|
|
|
|
veciJitBufSize[i] = vecChannels[i].GetSockBufSize ();
|
2006-11-25 15:46:57 +01:00
|
|
|
veciNetwOutBlSiFact[i] = vecChannels[i].GetNetwBufSizeFactOut ();
|
2006-12-06 21:22:41 +01:00
|
|
|
veciNetwInBlSiFact[i] = vecChannels[i].GetNetwBufSizeFactIn ();
|
2006-11-25 15:46:57 +01:00
|
|
|
}
|
|
|
|
}
|
2006-01-28 12:29:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************\
|
|
|
|
* CChannel *
|
|
|
|
\******************************************************************************/
|
2006-11-26 22:25:56 +01:00
|
|
|
CChannel::CChannel() : sName ( "" ),
|
|
|
|
vecdGains ( MAX_NUM_CHANNELS, (double) 1.0 )
|
2006-03-13 20:32:23 +01:00
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
// query all possible network in buffer sizes for determining if an
|
|
|
|
// audio packet was received
|
|
|
|
for ( int i = 0; i < MAX_NET_BLOCK_SIZE_FACTOR; i++ )
|
|
|
|
{
|
|
|
|
// network block size factor must start from 1 -> ( i + 1 )
|
|
|
|
vecNetwInBufSizes[i] = AudioCompressionIn.Init (
|
|
|
|
( i + 1 ) * MIN_BLOCK_SIZE_SAMPLES,
|
|
|
|
CAudioCompression::CT_IMAADPCM );
|
|
|
|
}
|
2006-03-13 20:32:23 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
iCurNetwInBlSiFact = DEF_NET_BLOCK_SIZE_FACTOR;
|
2006-03-13 20:32:23 +01:00
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// init the socket buffer
|
2006-11-25 15:46:57 +01:00
|
|
|
SetSockBufSize ( DEF_NET_BUF_SIZE_NUM_BL );
|
2006-01-28 12:29:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
// set initial input and output block size factors
|
|
|
|
SetNetwBufSizeFactOut ( iCurNetwInBlSiFact );
|
|
|
|
SetNetwInBlSiFact ( iCurNetwInBlSiFact );
|
2006-03-13 20:32:23 +01:00
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// init time-out for the buffer with zero -> no connection
|
2006-11-25 15:46:57 +01:00
|
|
|
iConTimeOut = 0;
|
2006-02-26 11:50:47 +01:00
|
|
|
|
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// connections -------------------------------------------------------------
|
2006-11-25 15:46:57 +01:00
|
|
|
QObject::connect ( &Protocol,
|
|
|
|
SIGNAL ( MessReadyForSending ( CVector<uint8_t> ) ),
|
|
|
|
this, SLOT ( OnSendProtMessage ( CVector<uint8_t> ) ) );
|
2006-02-27 20:45:27 +01:00
|
|
|
|
2006-12-06 21:22:41 +01:00
|
|
|
QObject::connect ( &Protocol,
|
|
|
|
SIGNAL ( ChangeJittBufSize ( int ) ),
|
2006-11-25 15:46:57 +01:00
|
|
|
this, SLOT ( OnJittBufSizeChange ( int ) ) );
|
2006-03-07 22:26:40 +01:00
|
|
|
|
2006-12-06 21:22:41 +01:00
|
|
|
QObject::connect ( &Protocol,
|
|
|
|
SIGNAL ( ReqJittBufSize() ),
|
2006-11-25 15:46:57 +01:00
|
|
|
SIGNAL ( ReqJittBufSize() ) );
|
2006-03-12 12:50:35 +01:00
|
|
|
|
2006-12-06 21:22:41 +01:00
|
|
|
QObject::connect ( &Protocol,
|
2006-12-08 19:19:16 +01:00
|
|
|
SIGNAL ( ReqConnClientsList() ),
|
|
|
|
SIGNAL ( ReqConnClientsList() ) );
|
|
|
|
|
|
|
|
QObject::connect ( &Protocol,
|
2006-12-06 21:22:41 +01:00
|
|
|
SIGNAL ( ConClientListMesReceived ( CVector<CChannelShortInfo> ) ),
|
2006-11-26 22:25:56 +01:00
|
|
|
SIGNAL ( ConClientListMesReceived ( CVector<CChannelShortInfo> ) ) );
|
|
|
|
|
2006-12-06 21:22:41 +01:00
|
|
|
QObject::connect ( &Protocol,
|
|
|
|
SIGNAL ( ChangeNetwBlSiFact ( int ) ),
|
2006-11-25 15:46:57 +01:00
|
|
|
this, SLOT ( OnNetwBlSiFactChange ( int ) ) );
|
2006-02-26 11:50:47 +01:00
|
|
|
}
|
2006-03-04 11:24:40 +01:00
|
|
|
|
2006-03-11 21:35:38 +01:00
|
|
|
void CChannel::SetNetwInBlSiFact ( const int iNewBlockSizeFactor )
|
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
// store new value
|
|
|
|
iCurNetwInBlSiFact = iNewBlockSizeFactor;
|
2006-03-11 21:35:38 +01:00
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// init audio compression unit
|
2006-11-25 15:46:57 +01:00
|
|
|
iAudComprSizeIn = AudioCompressionIn.Init (
|
|
|
|
iNewBlockSizeFactor * MIN_BLOCK_SIZE_SAMPLES,
|
|
|
|
CAudioCompression::CT_IMAADPCM );
|
2006-03-11 21:35:38 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
// initial value for connection time out counter
|
|
|
|
iConTimeOutStartVal = ( CON_TIME_OUT_SEC_MAX * 1000 ) /
|
|
|
|
( iNewBlockSizeFactor * MIN_BLOCK_DURATION_MS );
|
2006-03-12 13:24:42 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
// socket buffer must be adjusted
|
|
|
|
SetSockBufSize ( GetSockBufSize() );
|
2006-03-11 21:35:38 +01:00
|
|
|
}
|
|
|
|
|
2006-03-13 21:23:05 +01:00
|
|
|
void CChannel::SetNetwBufSizeFactOut ( const int iNewNetwBlSiFactOut )
|
2006-03-11 21:35:38 +01:00
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
// store new value
|
|
|
|
iCurNetwOutBlSiFact = iNewNetwBlSiFactOut;
|
2006-03-13 21:23:05 +01:00
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// init audio compression unit
|
2006-11-25 15:46:57 +01:00
|
|
|
iAudComprSizeOut = AudioCompressionOut.Init (
|
|
|
|
iNewNetwBlSiFactOut * MIN_BLOCK_SIZE_SAMPLES,
|
|
|
|
CAudioCompression::CT_IMAADPCM );
|
2006-03-11 21:35:38 +01:00
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// init conversion buffer
|
2006-11-25 15:46:57 +01:00
|
|
|
ConvBuf.Init ( iNewNetwBlSiFactOut * MIN_BLOCK_SIZE_SAMPLES );
|
2006-03-13 21:23:05 +01:00
|
|
|
}
|
|
|
|
|
2006-03-04 11:24:40 +01:00
|
|
|
void CChannel::OnSendProtMessage ( CVector<uint8_t> vecMessage )
|
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
// only send messages if we are connected, otherwise delete complete queue
|
|
|
|
if ( IsConnected() )
|
|
|
|
{
|
|
|
|
// emit message to actually send the data
|
|
|
|
emit MessReadyForSending ( vecMessage );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// delete send message queue
|
|
|
|
Protocol.DeleteSendMessQueue();
|
|
|
|
}
|
2006-03-04 11:24:40 +01:00
|
|
|
}
|
2006-03-12 12:50:35 +01:00
|
|
|
|
2006-03-12 11:02:01 +01:00
|
|
|
void CChannel::SetSockBufSize ( const int iNumBlocks )
|
2006-01-28 12:29:22 +01:00
|
|
|
{
|
2006-12-06 22:10:24 +01:00
|
|
|
// this opperation must be done with mutex
|
2006-11-25 15:46:57 +01:00
|
|
|
Mutex.lock();
|
|
|
|
{
|
|
|
|
iCurSockBufSize = iNumBlocks;
|
|
|
|
|
|
|
|
// the idea of setting the jitter buffer is as follows:
|
|
|
|
// The network block size is a multiple of the internal minimal
|
|
|
|
// block size. Therefore, the minimum jitter buffer size must be
|
|
|
|
// so that it can take one network buffer -> NET_BLOCK_SIZE_FACTOR.
|
|
|
|
// The actual jitter compensation are then the additional blocks of
|
|
|
|
// the internal block size, which is set with SetSockBufSize
|
|
|
|
SockBuf.Init ( MIN_BLOCK_SIZE_SAMPLES,
|
|
|
|
iNumBlocks + iCurNetwInBlSiFact );
|
|
|
|
}
|
|
|
|
Mutex.unlock();
|
2006-01-28 12:29:22 +01:00
|
|
|
}
|
2006-02-27 20:45:27 +01:00
|
|
|
|
2006-12-06 21:22:41 +01:00
|
|
|
void CChannel::OnNetwBlSiFactChange ( int iNewNetwBlSiFact )
|
|
|
|
{
|
|
|
|
// TEST
|
|
|
|
//qDebug ( "new network block size factor: %d", iNewNetwBlSiFact );
|
|
|
|
|
|
|
|
SetNetwBufSizeFactOut ( iNewNetwBlSiFact );
|
|
|
|
}
|
|
|
|
|
2006-02-27 20:45:27 +01:00
|
|
|
void CChannel::OnJittBufSizeChange ( int iNewJitBufSize )
|
|
|
|
{
|
2006-03-01 20:46:44 +01:00
|
|
|
// TEST
|
2006-09-03 11:49:15 +02:00
|
|
|
//qDebug ( "new jitter buffer size: %d", iNewJitBufSize );
|
2006-03-01 20:46:44 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
SetSockBufSize ( iNewJitBufSize );
|
2006-02-27 20:45:27 +01:00
|
|
|
}
|
2006-03-12 12:50:35 +01:00
|
|
|
|
2006-12-06 21:22:41 +01:00
|
|
|
void CChannel::OnChangeChanGain ( int iChanID, double dNewGain )
|
|
|
|
{
|
|
|
|
ASSERT ( ( iChanID >= 0 ) && ( iChanID < MAX_NUM_CHANNELS ) );
|
|
|
|
|
|
|
|
// set value
|
|
|
|
vecdGains[iChanID] = dNewGain;
|
|
|
|
}
|
|
|
|
|
2006-01-28 12:29:22 +01:00
|
|
|
bool CChannel::GetAddress(CHostAddress& RetAddr)
|
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
if ( IsConnected() )
|
|
|
|
{
|
|
|
|
RetAddr = InetAddr;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
RetAddr = CHostAddress();
|
|
|
|
return false;
|
|
|
|
}
|
2006-01-28 12:29:22 +01:00
|
|
|
}
|
|
|
|
|
2006-03-06 18:04:07 +01:00
|
|
|
EPutDataStat CChannel::PutData ( const CVector<unsigned char>& vecbyData,
|
2006-11-25 15:46:57 +01:00
|
|
|
int iNumBytes )
|
2006-01-28 12:29:22 +01:00
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
EPutDataStat eRet = PS_GEN_ERROR;
|
|
|
|
bool bNewConnection = false;
|
|
|
|
bool bIsAudioPacket = false;
|
|
|
|
|
|
|
|
// check if this is an audio packet by checking all possible lengths
|
|
|
|
for ( int i = 0; i < MAX_NET_BLOCK_SIZE_FACTOR; i++ )
|
|
|
|
{
|
|
|
|
if ( iNumBytes == vecNetwInBufSizes[i] )
|
|
|
|
{
|
|
|
|
bIsAudioPacket = true;
|
|
|
|
|
|
|
|
// check if we are correctly initialized
|
|
|
|
const int iNewNetwInBlSiFact = i + 1;
|
|
|
|
if ( iNewNetwInBlSiFact != iCurNetwInBlSiFact )
|
|
|
|
{
|
|
|
|
// re-initialize to new value
|
|
|
|
SetNetwInBlSiFact ( iNewNetwInBlSiFact );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// only process if packet has correct size
|
2006-11-25 15:46:57 +01:00
|
|
|
if ( bIsAudioPacket )
|
|
|
|
{
|
|
|
|
Mutex.lock();
|
|
|
|
{
|
2006-12-06 22:10:24 +01:00
|
|
|
// decompress audio
|
2006-11-25 15:46:57 +01:00
|
|
|
CVector<short> vecsDecomprAudio ( AudioCompressionIn.Decode ( vecbyData ) );
|
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// do resampling to compensate for sample rate offsets in the
|
|
|
|
// different sound cards of the clients
|
2006-01-28 12:29:22 +01:00
|
|
|
/*
|
|
|
|
for (int i = 0; i < BLOCK_SIZE_SAMPLES; i++)
|
2006-11-25 15:46:57 +01:00
|
|
|
vecdResInData[i] = (double) vecsData[i];
|
2006-01-28 12:29:22 +01:00
|
|
|
|
|
|
|
const int iInSize = ResampleObj.Resample(vecdResInData, vecdResOutData,
|
2006-11-25 15:46:57 +01:00
|
|
|
(double) SAMPLE_RATE / (SAMPLE_RATE - dSamRateOffset));
|
2006-01-28 12:29:22 +01:00
|
|
|
*/
|
|
|
|
|
2006-03-11 21:35:38 +01:00
|
|
|
vecdResOutData.Init ( iCurNetwInBlSiFact * MIN_BLOCK_SIZE_SAMPLES );
|
|
|
|
for ( int i = 0; i < iCurNetwInBlSiFact * MIN_BLOCK_SIZE_SAMPLES; i++ ) {
|
2006-11-25 15:46:57 +01:00
|
|
|
vecdResOutData[i] = (double) vecsDecomprAudio[i];
|
2006-03-11 21:35:38 +01:00
|
|
|
}
|
2006-01-28 12:29:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
if ( SockBuf.Put ( vecdResOutData ) )
|
|
|
|
{
|
|
|
|
eRet = PS_AUDIO_OK;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
eRet = PS_AUDIO_ERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if channel was not connected, this is a new connection
|
|
|
|
bNewConnection = !IsConnected();
|
|
|
|
|
|
|
|
// reset time-out counter
|
|
|
|
iConTimeOut = iConTimeOutStartVal;
|
|
|
|
}
|
|
|
|
Mutex.unlock();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// only use protocol data if channel is connected
|
|
|
|
if ( IsConnected() )
|
|
|
|
{
|
|
|
|
// this seems not to be an audio block, parse the message
|
|
|
|
if ( Protocol.ParseMessage ( vecbyData, iNumBytes ) )
|
|
|
|
{
|
|
|
|
eRet = PS_PROT_OK;
|
|
|
|
|
|
|
|
// create message for protocol status
|
|
|
|
emit ProtocolStatus ( true );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
eRet = PS_PROT_ERR;
|
|
|
|
|
|
|
|
// create message for protocol status
|
|
|
|
emit ProtocolStatus ( false );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// inform other objects that new connection was established
|
|
|
|
if ( bNewConnection )
|
|
|
|
{
|
|
|
|
// log new connection
|
|
|
|
CHostAddress address ( GetAddress() );
|
|
|
|
qDebug ( CLogTimeDate::toString() + "Connected with IP %s",
|
|
|
|
address.InetAddr.toString().latin1() );
|
|
|
|
|
|
|
|
emit NewConnection();
|
|
|
|
}
|
|
|
|
|
|
|
|
return eRet;
|
2006-01-28 12:29:22 +01:00
|
|
|
}
|
|
|
|
|
2006-02-26 11:50:47 +01:00
|
|
|
bool CChannel::GetData ( CVector<double>& vecdData )
|
2006-03-06 18:04:07 +01:00
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
bool bGetOK = false;
|
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
Mutex.lock(); // get mutex lock
|
2006-11-25 15:46:57 +01:00
|
|
|
{
|
|
|
|
bGetOK = SockBuf.Get ( vecdData );
|
|
|
|
|
|
|
|
if ( !bGetOK )
|
|
|
|
{
|
2006-12-06 22:10:24 +01:00
|
|
|
// decrease time-out counter
|
2006-11-25 15:46:57 +01:00
|
|
|
if ( iConTimeOut > 0 )
|
|
|
|
{
|
|
|
|
iConTimeOut--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2006-12-06 22:10:24 +01:00
|
|
|
Mutex.unlock(); // get mutex unlock
|
2006-11-25 15:46:57 +01:00
|
|
|
|
|
|
|
return bGetOK;
|
2006-01-28 12:29:22 +01:00
|
|
|
}
|
|
|
|
|
2006-03-12 14:19:41 +01:00
|
|
|
CVector<unsigned char> CChannel::PrepSendPacket ( const CVector<short>& vecsNPacket )
|
2006-01-28 12:29:22 +01:00
|
|
|
{
|
2006-12-06 22:10:24 +01:00
|
|
|
// if the block is not ready we have to initialize with zero length to
|
|
|
|
// tell the following network send routine that nothing should be sent
|
2006-11-25 15:46:57 +01:00
|
|
|
CVector<unsigned char> vecbySendBuf ( 0 );
|
|
|
|
|
2006-12-06 22:10:24 +01:00
|
|
|
// use conversion buffer to convert sound card block size in network
|
|
|
|
// block size
|
2006-11-25 15:46:57 +01:00
|
|
|
if ( ConvBuf.Put ( vecsNPacket ) )
|
|
|
|
{
|
2006-12-06 22:10:24 +01:00
|
|
|
// a packet is ready, compress audio
|
2006-11-25 15:46:57 +01:00
|
|
|
vecbySendBuf.Init ( iAudComprSizeOut );
|
|
|
|
vecbySendBuf = AudioCompressionOut.Encode ( ConvBuf.Get() );
|
|
|
|
}
|
|
|
|
|
|
|
|
return vecbySendBuf;
|
2006-01-28 12:29:22 +01:00
|
|
|
}
|
2006-02-12 15:26:46 +01:00
|
|
|
|
2006-03-04 11:24:40 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2006-01-28 12:29:22 +01:00
|
|
|
|
|
|
|
/******************************************************************************\
|
|
|
|
* CSampleOffsetEst *
|
|
|
|
\******************************************************************************/
|
|
|
|
void CSampleOffsetEst::Init()
|
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
/* init sample rate estimation */
|
|
|
|
dSamRateEst = SAMPLE_RATE;
|
2006-01-28 12:29:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
/* init vectors storing the data */
|
|
|
|
veciTimeElapsed.Init(VEC_LEN_SAM_OFFS_EST);
|
|
|
|
veciTiStIdx.Init(VEC_LEN_SAM_OFFS_EST);
|
2006-01-28 12:29:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
/* start reference time (the counter wraps to zero 24 hours after the last
|
|
|
|
call to start() or restart, but this should not concern us since this
|
|
|
|
software will most probably not be used that long) */
|
|
|
|
RefTime.start();
|
2006-01-28 12:29:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
/* init accumulated time stamp variable */
|
|
|
|
iAccTiStVal = 0;
|
2006-01-28 12:29:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
/* init count (do not ship any result in init phase) */
|
|
|
|
iInitCnt = VEC_LEN_SAM_OFFS_EST + 1;
|
2006-01-28 12:29:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CSampleOffsetEst::AddTimeStampIdx(const int iTimeStampIdx)
|
|
|
|
{
|
2006-11-25 15:46:57 +01:00
|
|
|
int i;
|
2006-01-28 12:29:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
const int iLastIdx = VEC_LEN_SAM_OFFS_EST - 1;
|
2006-01-28 12:29:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
/* take care of wrap of the time stamp index (byte wrap) */
|
|
|
|
if (iTimeStampIdx < veciTiStIdx[iLastIdx] - iAccTiStVal)
|
|
|
|
iAccTiStVal += _MAXBYTE + 1;
|
2006-01-28 12:29:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
/* add new data pair to the FIFO */
|
|
|
|
for (i = 1; i < VEC_LEN_SAM_OFFS_EST; i++)
|
|
|
|
{
|
|
|
|
/* move old data */
|
|
|
|
veciTimeElapsed[i - 1] = veciTimeElapsed[i];
|
|
|
|
veciTiStIdx[i - 1] = veciTiStIdx[i];
|
|
|
|
}
|
2006-01-28 12:29:22 +01:00
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
/* add new data */
|
|
|
|
veciTimeElapsed[iLastIdx] = RefTime.elapsed();
|
|
|
|
veciTiStIdx[iLastIdx] = iAccTiStVal + iTimeStampIdx;
|
2006-01-28 12:29:22 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
static FILE* pFile = fopen("v.dat", "w");
|
|
|
|
for (i = 0; i < VEC_LEN_SAM_OFFS_EST; i++)
|
2006-11-25 15:46:57 +01:00
|
|
|
fprintf(pFile, "%d\n", veciTimeElapsed[i]);
|
2006-01-28 12:29:22 +01:00
|
|
|
fflush(pFile);
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2006-11-25 15:46:57 +01:00
|
|
|
/* calculate linear regression for sample rate estimation */
|
|
|
|
/* first, calculate averages */
|
|
|
|
double dTimeAv = 0;
|
|
|
|
double dTiStAv = 0;
|
|
|
|
for (i = 0; i < VEC_LEN_SAM_OFFS_EST; i++)
|
|
|
|
{
|
|
|
|
dTimeAv += veciTimeElapsed[i];
|
|
|
|
dTiStAv += veciTiStIdx[i];
|
|
|
|
}
|
|
|
|
dTimeAv /= VEC_LEN_SAM_OFFS_EST;
|
|
|
|
dTiStAv /= VEC_LEN_SAM_OFFS_EST;
|
|
|
|
|
|
|
|
/* calculate gradient */
|
|
|
|
double dNom = 0;
|
|
|
|
double dDenom = 0;
|
|
|
|
for (i = 0; i < VEC_LEN_SAM_OFFS_EST; i++)
|
|
|
|
{
|
|
|
|
const double dCurTimeNoAv = veciTimeElapsed[i] - dTimeAv;
|
|
|
|
dNom += dCurTimeNoAv * (veciTiStIdx[i] - dTiStAv);
|
|
|
|
dDenom += dCurTimeNoAv * dCurTimeNoAv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* final sample rate offset estimation calculation */
|
|
|
|
if (iInitCnt > 0)
|
|
|
|
iInitCnt--;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dSamRateEst = dNom / dDenom * NUM_BL_TIME_STAMPS * MIN_BLOCK_SIZE_SAMPLES * 1000;
|
2006-01-28 12:29:22 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
static FILE* pFile = fopen("v.dat", "w");
|
|
|
|
for (i = 0; i < VEC_LEN_SAM_OFFS_EST; i++)
|
2006-11-25 15:46:57 +01:00
|
|
|
fprintf(pFile, "%d %d\n", veciTimeElapsed[i], veciTiStIdx[i]);
|
2006-01-28 12:29:22 +01:00
|
|
|
fflush(pFile);
|
|
|
|
*/
|
2006-11-25 15:46:57 +01:00
|
|
|
}
|
2006-01-28 12:29:22 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
static FILE* pFile = fopen("v.dat", "w");
|
|
|
|
fprintf(pFile, "%e\n", dSamRateEst);
|
|
|
|
fflush(pFile);
|
|
|
|
*/
|
|
|
|
|
|
|
|
}
|