jamulus/src/client.cpp

438 lines
14 KiB
C++
Raw Normal View History

/******************************************************************************\
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer
*
******************************************************************************
*
* 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 "client.h"
2006-02-26 11:50:47 +01:00
/* Implementation *************************************************************/
CClient::CClient() : bRun ( false ), Socket ( &Channel ),
2008-03-29 08:14:35 +01:00
iAudioInFader ( AUD_FADER_IN_MAX / 2 ),
iReverbLevel ( AUD_REVERB_MAX / 6 ),
bReverbOnLeftChan ( false ),
iNetwBufSizeFactIn ( DEF_NET_BLOCK_SIZE_FACTOR ),
strIPAddress ( "" ), strName ( "" )
2006-02-26 11:50:47 +01:00
{
// connection for protocol
QObject::connect ( &Channel,
SIGNAL ( MessReadyForSending ( CVector<uint8_t> ) ),
this, SLOT ( OnSendProtMessage ( CVector<uint8_t> ) ) );
QObject::connect ( &Channel, SIGNAL ( ReqJittBufSize() ),
this, SLOT ( OnReqJittBufSize() ) );
QObject::connect ( &Channel, SIGNAL ( ProtocolStatus ( bool ) ),
this, SLOT ( OnProtocolStatus ( bool ) ) );
2006-11-26 22:25:56 +01:00
QObject::connect ( &Channel,
SIGNAL ( ConClientListMesReceived ( CVector<CChannelShortInfo> ) ),
2006-11-26 22:25:56 +01:00
SIGNAL ( ConClientListMesReceived ( CVector<CChannelShortInfo> ) ) );
QObject::connect ( &Channel, SIGNAL ( NewConnection() ),
this, SLOT ( OnNewConnection() ) );
2006-02-26 11:50:47 +01:00
}
2006-03-01 20:46:44 +01:00
void CClient::OnSendProtMessage ( CVector<uint8_t> vecMessage )
2006-02-26 11:50:47 +01:00
{
2006-03-01 20:46:44 +01:00
// convert unsigned uint8_t in char, TODO convert all buffers in uint8_t
CVector<unsigned char> vecbyDataConv ( vecMessage.Size() );
for ( int i = 0; i < vecMessage.Size(); i++ ) {
vecbyDataConv[i] = static_cast<unsigned char> ( vecMessage[i] );
2006-03-01 20:46:44 +01:00
}
// the protocol queries me to call the function to send the message
// send it through the network
Socket.SendPacket ( vecbyDataConv, Channel.GetAddress() );
2006-02-26 11:50:47 +01:00
}
void CClient::OnReqJittBufSize()
{
Channel.CreateJitBufMes ( Channel.GetSockBufSize() );
// FIXME: we set the network buffer size factor here, too -> in the
// future a separate request function for this parameter should be created
Channel.CreateNetwBlSiFactMes ( iNetwBufSizeFactIn );
}
2006-12-10 12:06:14 +01:00
void CClient::OnNewConnection()
{
// a new connection was successfully initiated, send name and request
// connected clients list
Channel.SetRemoteName ( strName );
Channel.CreateReqConnClientsList();
}
bool CClient::SetServerAddr ( QString strNAddr )
{
QHostAddress InetAddr;
// first try if this is an IP number an can directly applied to QHostAddress
if ( !InetAddr.setAddress ( strNAddr ) )
{
// it was no vaild IP address, try to get host by name, assuming
// that the string contains a valid host name string
QHostInfo HostInfo = QHostInfo::fromName ( strNAddr );
if ( HostInfo.error() == QHostInfo::NoError )
{
// apply IP address to QT object
if ( !HostInfo.addresses().isEmpty() )
{
// use the first IP address
InetAddr = HostInfo.addresses().first();
}
}
else
{
return false; // invalid address
}
}
// apply address (the server port is fixed and always the same)
Channel.SetAddress ( CHostAddress ( InetAddr, LLCON_PORT_NUMBER ) );
return true;
}
void CClient::OnProtocolStatus ( bool bOk )
{
// show protocol status in GUI
if ( bOk )
{
PostWinMessage ( MS_PROTOCOL, MUL_COL_LED_RED );
}
else
{
PostWinMessage ( MS_PROTOCOL, MUL_COL_LED_GREEN );
}
}
2006-01-28 12:29:22 +01:00
void CClient::Init()
{
// set block sizes (in samples)
iBlockSizeSam = MIN_BLOCK_SIZE_SAMPLES;
iSndCrdBlockSizeSam = MIN_SND_CRD_BLOCK_SIZE_SAMPLES;
2006-01-28 12:29:22 +01:00
vecsAudioSndCrd.Init ( iSndCrdBlockSizeSam * 2 ); // stereo
vecdAudioSndCrdL.Init ( iSndCrdBlockSizeSam );
vecdAudioSndCrdR.Init ( iSndCrdBlockSizeSam );
2006-01-28 12:29:22 +01:00
vecdAudioL.Init ( iBlockSizeSam );
vecdAudioR.Init ( iBlockSizeSam );
2006-01-28 12:29:22 +01:00
Sound.InitRecording ( iSndCrdBlockSizeSam * 2 ); // stereo
Sound.InitPlayback ( iSndCrdBlockSizeSam * 2 ); // stereo
2006-01-28 12:29:22 +01:00
// resample objects are always initialized with the input block size
// record
ResampleObjDownL.Init ( iSndCrdBlockSizeSam, SND_CRD_SAMPLE_RATE, SAMPLE_RATE );
ResampleObjDownR.Init ( iSndCrdBlockSizeSam, SND_CRD_SAMPLE_RATE, SAMPLE_RATE );
2006-01-28 12:29:22 +01:00
// playback
ResampleObjUpL.Init ( iBlockSizeSam, SAMPLE_RATE, SND_CRD_SAMPLE_RATE );
ResampleObjUpR.Init ( iBlockSizeSam, SAMPLE_RATE, SND_CRD_SAMPLE_RATE );
2006-01-28 12:29:22 +01:00
// init network buffers
vecsNetwork.Init ( iBlockSizeSam );
vecdNetwData.Init ( iBlockSizeSam );
2006-01-28 12:29:22 +01:00
// init moving average buffer for response time evaluation
RespTimeMoAvBuf.Init ( LEN_MOV_AV_RESPONSE );
2006-01-28 12:29:22 +01:00
// init time for response time evaluation
TimeLastBlock = QTime::currentTime();
2006-01-28 12:29:22 +01:00
AudioReverb.Clear();
2006-01-28 12:29:22 +01:00
}
void CClient::run()
{
int i, iInCnt;
// Set thread priority (The working thread should have a higher
// priority than the GUI)
#ifdef _WIN32
SetThreadPriority ( GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL );
2006-01-28 12:29:22 +01:00
#else
2008-02-02 10:35:58 +01:00
/*
// set the process to realtime privs, taken from
// "http://www.gardena.net/benno/linux/audio" but does not seem to work,
// maybe a problem with user rights
struct sched_param schp;
memset ( &schp, 0, sizeof ( schp ) );
schp.sched_priority = sched_get_priority_max ( SCHED_FIFO );
sched_setscheduler ( 0, SCHED_FIFO, &schp );
2008-02-02 10:35:58 +01:00
*/
#endif
// init object
Init();
// runtime phase ------------------------------------------------------------
// enable channel
Channel.SetEnable ( true );
bRun = true;
// main loop of working thread
while ( bRun )
{
// get audio from sound card (blocking function)
if ( Sound.Read ( vecsAudioSndCrd ) )
{
PostWinMessage ( MS_SOUND_IN, MUL_COL_LED_RED );
}
else
{
PostWinMessage ( MS_SOUND_IN, MUL_COL_LED_GREEN );
}
// TEST
//Sleep(300);
2008-01-27 11:05:15 +01:00
//wait(3000);
// copy data from one stereo buffer in two separate buffers
iInCnt = 0;
for ( i = 0; i < iSndCrdBlockSizeSam; i++ )
{
vecdAudioSndCrdL[i] = (double) vecsAudioSndCrd[iInCnt++];
vecdAudioSndCrdR[i] = (double) vecsAudioSndCrd[iInCnt++];
}
// resample data for each channel seaparately
ResampleObjDownL.Resample ( vecdAudioSndCrdL, vecdAudioL );
ResampleObjDownR.Resample ( vecdAudioSndCrdR, vecdAudioR );
// update signal level meters
SignalLevelMeterL.Update ( vecdAudioL );
SignalLevelMeterR.Update ( vecdAudioR );
// add reverberation effect if activated
if ( iReverbLevel != 0 )
{
// first attenuation amplification factor
const double dRevLev = (double) iReverbLevel / AUD_REVERB_MAX / 2;
if ( bReverbOnLeftChan )
{
for (i = 0; i < iBlockSizeSam; i++)
{
// left channel
vecdAudioL[i] +=
dRevLev * AudioReverb.ProcessSample ( vecdAudioL[i] );
}
}
else
{
for ( i = 0; i < iBlockSizeSam; i++ )
{
// right channel
vecdAudioR[i] +=
dRevLev * AudioReverb.ProcessSample ( vecdAudioR[i] );
}
}
}
// mix both signals depending on the fading setting
const int iMiddleOfFader = AUD_FADER_IN_MAX / 2;
const double dAttFact =
(double) ( iMiddleOfFader - abs ( iMiddleOfFader - iAudioInFader ) ) /
iMiddleOfFader;
for ( i = 0; i < iBlockSizeSam; i++ )
{
double dMixedSignal;
if ( iAudioInFader > iMiddleOfFader )
{
dMixedSignal = vecdAudioL[i] + dAttFact * vecdAudioR[i];
}
else
{
dMixedSignal = vecdAudioR[i] + dAttFact * vecdAudioL[i];
}
vecsNetwork[i] = Double2Short ( dMixedSignal );
}
// send it through the network
Socket.SendPacket ( Channel.PrepSendPacket ( vecsNetwork ),
Channel.GetAddress () );
2006-01-28 12:29:22 +01:00
// receive a new block
if ( Channel.GetData ( vecdNetwData ) == GS_BUFFER_OK )
{
PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_GREEN );
}
else
{
PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_RED );
}
#ifdef _DEBUG_
#if 0
#if 0
/* Determine network delay. We can do this very simple if only this client is
connected to the server. In this case, exactly the same audio material is
coming back and we can simply compare the samples */
/* store send data instatic buffer (may delay is 100 ms) */
const int iMaxDelaySamples = (int) ((float) 0.3 /*0.1*/ * SAMPLE_RATE);
static CVector<short> vecsOutBuf(iMaxDelaySamples);
/* update buffer */
const int iBufDiff = iMaxDelaySamples - iBlockSizeSam;
for (i = 0; i < iBufDiff; i++)
vecsOutBuf[i + iBlockSizeSam] = vecsOutBuf[i];
for (i = 0; i < iBlockSizeSam; i++)
vecsOutBuf[i] = vecsNetwork[i];
/* now search for equal samples */
int iDelaySamples = 0;
for (i = 0; i < iMaxDelaySamples - 1; i++)
{
/* compare two successive samples */
if ((vecsOutBuf[i] == (short) vecdNetwData[0]) &&
(vecsOutBuf[i + 1] == (short) vecdNetwData[1]))
{
iDelaySamples = i;
}
}
static FILE* pFileDelay = fopen("delay.dat", "w");
fprintf(pFileDelay, "%d\n", iDelaySamples);
fflush(pFileDelay);
#else
2006-01-28 12:29:22 +01:00
/* just store both, input and output, streams */
// 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 < iBlockSizeSam; i++)
{
sData[0] = vecsNetwork[i];
sData[1] = (short) vecdNetwData[i];
fwrite(&sData, size_t(2), size_t(2), pFileDelay);
}
fflush(pFileDelay);
#endif
#endif
2006-01-28 12:29:22 +01:00
#endif
/*
// 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 < iBlockSizeSam; i++)
{
sData[0] = (short) vecdNetwData[i];
fwrite(&sData, size_t(2), size_t(1), pFileDelay);
2006-01-28 12:29:22 +01:00
}
fflush(pFileDelay);
*/
// check if channel is connected
if ( Channel.IsConnected() )
{
// write mono input signal in both sound-card channels
for ( i = 0; i < iBlockSizeSam; i++ )
{
vecdAudioL[i] = vecdAudioR[i] = vecdNetwData[i];
}
}
else
{
// if not connected, clear data
for ( i = 0; i < iBlockSizeSam; i++ )
{
vecdAudioL[i] = vecdAudioR[i] = 0.0;
}
}
// resample data for each channel separately
ResampleObjUpL.Resample ( vecdAudioL, vecdAudioSndCrdL );
ResampleObjUpR.Resample ( vecdAudioR, vecdAudioSndCrdR );
// copy data from one stereo buffer in two separate buffers
iInCnt = 0;
for ( i = 0; i < iSndCrdBlockSizeSam; i++ )
{
vecsAudioSndCrd[iInCnt++] = Double2Short ( vecdAudioSndCrdL[i] );
vecsAudioSndCrd[iInCnt++] = Double2Short ( vecdAudioSndCrdR[i] );
}
// play the new block
if ( Sound.Write ( vecsAudioSndCrd ) )
{
PostWinMessage ( MS_SOUND_OUT, MUL_COL_LED_RED );
}
else
{
PostWinMessage ( MS_SOUND_OUT, MUL_COL_LED_GREEN );
}
// update response time measurement ------------------------------------
// add time difference
const QTime CurTime = QTime::currentTime();
// we want to calculate the standard deviation (we assume that the mean
// is correct at the block period time)
const double dCurAddVal =
( (double) TimeLastBlock.msecsTo ( CurTime ) - MIN_BLOCK_DURATION_MS );
2006-01-28 12:29:22 +01:00
/*
// TEST
static FILE* pFileTest = fopen("sti.dat", "w");
fprintf(pFileTest, "%e\n", dCurAddVal);
fflush(pFileTest);
*/
RespTimeMoAvBuf.Add ( dCurAddVal * dCurAddVal ); // add squared value
2006-01-28 12:29:22 +01:00
// store old time value
TimeLastBlock = CurTime;
}
// disable channel
Channel.SetEnable ( false );
// reset current signal level and LEDs
SignalLevelMeterL.Reset();
SignalLevelMeterR.Reset();
PostWinMessage ( MS_RESET_ALL, 0 );
}
bool CClient::Stop()
{
// set flag so that thread can leave the main loop
bRun = false;
// give thread some time to terminate, return status
return wait ( 5000 );
2006-02-26 11:50:47 +01:00
}