diff --git a/ChangeLog b/ChangeLog index 528b7700..efe64e62 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +3.2.1 + + +TODO working on bug fix: Windows: driver is changed during connection results + sometimes in lowed noise + + + 3.2.0 - new GUI style of the main window, added switch for selecting the GUI style diff --git a/linux/sound.h b/linux/sound.h index 37358772..09ffcaa3 100755 --- a/linux/sound.h +++ b/linux/sound.h @@ -59,18 +59,14 @@ class CSound : public CSoundBase { public: CSound ( void (*fpNewProcessCallback) ( CVector& psData, void* arg ), void* arg ) : - CSoundBase ( true, fpNewProcessCallback, arg ), iJACKBufferSizeMono ( 0 ), + CSoundBase ( "Jack", true, fpNewProcessCallback, arg ), iJACKBufferSizeMono ( 0 ), iJACKBufferSizeStero ( 0 ) { OpenJack(); } virtual ~CSound() { CloseJack(); } - virtual int Init ( const int iNewPrefMonoBufferSize ); + virtual int Init ( const int iNewPrefMonoBufferSize ); virtual void Start(); virtual void Stop(); - // device cannot be set under Linux - virtual int GetNumDev() { return 1; } - virtual QString GetDeviceName ( const int ) { return "Jack"; } - // these variables should be protected but cannot since we want // to access them from the callback function CVector vecsTmpAudioSndCrdStereo; diff --git a/mac/sound.h b/mac/sound.h index eda2096e..a46f9226 100755 --- a/mac/sound.h +++ b/mac/sound.h @@ -38,17 +38,13 @@ class CSound : public CSoundBase { public: CSound ( void (*fpNewProcessCallback) ( CVector& psData, void* arg ), void* arg ) : - CSoundBase ( true, fpNewProcessCallback, arg ) { OpenCoreAudio(); } + CSoundBase ( "CoreAudio", true, fpNewProcessCallback, arg ) { OpenCoreAudio(); } virtual ~CSound() { CloseCoreAudio(); } virtual int Init ( const int iNewPrefMonoBufferSize ); virtual void Start(); virtual void Stop(); - // device cannot be set under MacOS - virtual int GetNumDev() { return 1; } - virtual QString GetDeviceName ( const int ) { return "Core Audio"; } - // these variables should be protected but cannot since we want // to access them from the callback function CVector vecsTmpAudioSndCrdStereo; diff --git a/src/client.cpp b/src/client.cpp index 01c5100b..dcdb5138 100755 --- a/src/client.cpp +++ b/src/client.cpp @@ -1,999 +1,1011 @@ -/******************************************************************************\ - * Copyright (c) 2004-2011 - * - * 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" - - -/* Implementation *************************************************************/ -CClient::CClient ( const quint16 iPortNumber ) : - vstrIPAddress ( MAX_NUM_SERVER_ADDR_ITEMS, "" ), - strName ( "" ), - Channel ( false ), /* we need a client channel -> "false" */ - iCeltNumCodedBytes ( CELT_NUM_BYTES_MONO_NORMAL_QUALITY ), - bCeltDoHighQuality ( false ), - bUseStereo ( false ), - Socket ( &Channel, &ConnLessProtocol, iPortNumber ), - Sound ( AudioCallback, this ), - iAudioInFader ( AUD_FADER_IN_MIDDLE ), - bReverbOnLeftChan ( false ), - iReverbLevel ( 0 ), - iSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_PREFERRED ), - iSndCrdFrameSizeFactor ( FRAME_SIZE_FACTOR_PREFERRED ), - bSndCrdConversionBufferRequired ( false ), - iSndCardMonoBlockSizeSamConvBuff ( 0 ), - bFraSiFactPrefSupported ( false ), - bFraSiFactDefSupported ( false ), - bFraSiFactSafeSupported ( false ), - bOpenChatOnNewMessage ( true ), - eGUIDesign ( GD_ORIGINAL ), - strCentralServerAddress ( "" ), - bUseDefaultCentralServerAddress ( true ), - iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ) -{ - // init audio encoder/decoder (mono) - CeltModeMono = celt_mode_create ( - SYSTEM_SAMPLE_RATE_HZ, 1, SYSTEM_FRAME_SIZE_SAMPLES, NULL ); - - CeltEncoderMono = celt_encoder_create ( CeltModeMono ); - CeltDecoderMono = celt_decoder_create ( CeltModeMono ); - -#ifdef USE_LOW_COMPLEXITY_CELT_ENC - // set encoder low complexity - celt_encoder_ctl ( CeltEncoderMono, - CELT_SET_COMPLEXITY_REQUEST, celt_int32_t ( 1 ) ); -#endif - - // init audio encoder/decoder (stereo) - CeltModeStereo = celt_mode_create ( - SYSTEM_SAMPLE_RATE_HZ, 2, SYSTEM_FRAME_SIZE_SAMPLES, NULL ); - - CeltEncoderStereo = celt_encoder_create ( CeltModeStereo ); - CeltDecoderStereo = celt_decoder_create ( CeltModeStereo ); - -#ifdef USE_LOW_COMPLEXITY_CELT_ENC - // set encoder low complexity - celt_encoder_ctl ( CeltEncoderStereo, - CELT_SET_COMPLEXITY_REQUEST, celt_int32_t ( 1 ) ); -#endif - - - // Connections ------------------------------------------------------------- - // connection for protocol - QObject::connect ( &Channel, - SIGNAL ( MessReadyForSending ( CVector ) ), - this, SLOT ( OnSendProtMessage ( CVector ) ) ); - - QObject::connect ( &Channel, - SIGNAL ( DetectedCLMessage ( CVector, int ) ), - this, SLOT ( OnDetectedCLMessage ( CVector, int ) ) ); - - QObject::connect ( &Channel, SIGNAL ( ReqJittBufSize() ), - this, SLOT ( OnReqJittBufSize() ) ); - - QObject::connect ( &Channel, SIGNAL ( JittBufSizeChanged ( int ) ), - this, SLOT ( OnJittBufSizeChanged ( int ) ) ); - - QObject::connect ( &Channel, SIGNAL ( ReqChanName() ), - this, SLOT ( OnReqChanName() ) ); - - QObject::connect ( &Channel, - SIGNAL ( ConClientListMesReceived ( CVector ) ), - SIGNAL ( ConClientListMesReceived ( CVector ) ) ); - - QObject::connect ( &Channel, - SIGNAL ( Disconnected() ), - SIGNAL ( Disconnected() ) ); - - QObject::connect ( &Channel, SIGNAL ( NewConnection() ), - this, SLOT ( OnNewConnection() ) ); - - QObject::connect ( &Channel, - SIGNAL ( ChatTextReceived ( QString ) ), - SIGNAL ( ChatTextReceived ( QString ) ) ); - - QObject::connect ( &ConnLessProtocol, - SIGNAL ( CLMessReadyForSending ( CHostAddress, CVector ) ), - this, SLOT ( OnSendCLProtMessage ( CHostAddress, CVector ) ) ); - - QObject::connect ( &ConnLessProtocol, - SIGNAL ( CLServerListReceived ( CHostAddress, CVector ) ), - SIGNAL ( CLServerListReceived ( CHostAddress, CVector ) ) ); - - QObject::connect ( &ConnLessProtocol, - SIGNAL ( CLPingReceived ( CHostAddress, int ) ), - this, SLOT ( OnCLPingReceived ( CHostAddress, int ) ) ); - - QObject::connect ( &ConnLessProtocol, - SIGNAL ( CLPingWithNumClientsReceived ( CHostAddress, int, int ) ), - this, SLOT ( OnCLPingWithNumClientsReceived ( CHostAddress, int, int ) ) ); - - QObject::connect ( &Sound, SIGNAL ( ReinitRequest() ), - this, SLOT ( OnSndCrdReinitRequest() ) ); -} - -void CClient::OnSendProtMessage ( CVector vecMessage ) -{ - // the protocol queries me to call the function to send the message - // send it through the network - Socket.SendPacket ( vecMessage, Channel.GetAddress() ); -} - -void CClient::OnSendCLProtMessage ( CHostAddress InetAddr, - CVector vecMessage ) -{ - // the protocol queries me to call the function to send the message - // send it through the network - Socket.SendPacket ( vecMessage, InetAddr ); -} - -void CClient::OnDetectedCLMessage ( CVector vecbyData, - int iNumBytes ) -{ - // this is a special case: we received a connection less message but we are - // in a connection - ConnLessProtocol.ParseConnectionLessMessage ( vecbyData, - iNumBytes, - Channel.GetAddress() ); -} - -void CClient::OnJittBufSizeChanged ( int iNewJitBufSize ) -{ - // we received a jitter buffer size changed message from the server, - // only apply this value if auto jitter buffer size is enabled - if ( GetDoAutoSockBufSize() ) - { - // Note: Do not use the "SetServerSockBufNumFrames" function for setting - // the new server jitter buffer size since then a message would be sent - // to the server which is incorrect. - iServerSockBufNumFrames = iNewJitBufSize; - } -} - -void CClient::OnNewConnection() -{ - // a new connection was successfully initiated, send name and request - // connected clients list - Channel.SetRemoteName ( strName ); - - // We have to send a connected clients list request since it can happen - // that we just had connected to the server and then disconnected but - // the server still thinks that we are connected (the server is still - // waiting for the channel time-out). If we now connect again, we would - // not get the list because the server does not know about a new connection. - // Same problem is with the jitter buffer message. - Channel.CreateReqConnClientsList(); - CreateServerJitterBufferMessage(); -} - -void CClient::CreateServerJitterBufferMessage() -{ - // per definition in the client: if auto jitter buffer is enabled, both, - // the client and server shall use an auto jitter buffer - if ( GetDoAutoSockBufSize() ) - { - // in case auto jitter buffer size is enabled, we have to transmit a - // special value - Channel.CreateJitBufMes ( AUTO_NET_BUF_SIZE_FOR_PROTOCOL ); - } - else - { - Channel.CreateJitBufMes ( GetServerSockBufNumFrames() ); - } -} - -void CClient::OnCLPingReceived ( CHostAddress InetAddr, - int iMs ) -{ - // make sure we are running and the server address is correct - if ( IsRunning() && ( InetAddr == Channel.GetAddress() ) ) - { - // take care of wrap arounds (if wrapping, do not use result) - const int iCurDiff = EvaluatePingMessage ( iMs ); - if ( iCurDiff >= 0 ) - { - emit PingTimeReceived ( iCurDiff ); - } - } -} - -void CClient::OnCLPingWithNumClientsReceived ( CHostAddress InetAddr, - int iMs, - int iNumClients ) -{ - // take care of wrap arounds (if wrapping, do not use result) - const int iCurDiff = EvaluatePingMessage ( iMs ); - if ( iCurDiff >= 0 ) - { - emit CLPingTimeWithNumClientsReceived ( InetAddr, - iCurDiff, - iNumClients ); - } -} - -int CClient::PreparePingMessage() -{ - // transmit the current precise time (in ms) - return PreciseTime.elapsed(); -} - -int CClient::EvaluatePingMessage ( const int iMs ) -{ - // calculate difference between received time in ms and current time in ms - return PreciseTime.elapsed() - iMs; -} - -void CClient::SetDoAutoSockBufSize ( const bool bValue ) -{ - // first, set new value in the channel object - Channel.SetDoAutoSockBufSize ( bValue ); - - // inform the server about the change - CreateServerJitterBufferMessage(); -} - -bool CClient::SetServerAddr ( QString strNAddr ) -{ - CHostAddress HostAddress; - if ( LlconNetwUtil().ParseNetworkAddress ( strNAddr, - HostAddress ) ) - { - // apply address to the channel - Channel.SetAddress ( HostAddress ); - - return true; - } - else - { - return false; // invalid address - } -} - -void CClient::SetSndCrdPrefFrameSizeFactor ( const int iNewFactor ) -{ - // first check new input parameter - if ( ( iNewFactor == FRAME_SIZE_FACTOR_PREFERRED ) || - ( iNewFactor == FRAME_SIZE_FACTOR_DEFAULT ) || - ( iNewFactor == FRAME_SIZE_FACTOR_SAFE ) ) - { - // init with new parameter, if client was running then first - // stop it and restart again after new initialization - const bool bWasRunning = Sound.IsRunning(); - if ( bWasRunning ) - { - Sound.Stop(); - } - - // set new parameter - iSndCrdPrefFrameSizeFactor = iNewFactor; - - // init with new block size index parameter - Init(); - - if ( bWasRunning ) - { - // restart client - Sound.Start(); - } - } -} - -void CClient::SetCELTHighQuality ( const bool bNCeltHighQualityFlag ) -{ - // init with new parameter, if client was running then first - // stop it and restart again after new initialization - const bool bWasRunning = Sound.IsRunning(); - if ( bWasRunning ) - { - Sound.Stop(); - } - - // set new parameter - bCeltDoHighQuality = bNCeltHighQualityFlag; - Init(); - - if ( bWasRunning ) - { - Sound.Start(); - } -} - -void CClient::SetUseStereo ( const bool bNUseStereo ) -{ - // init with new parameter, if client was running then first - // stop it and restart again after new initialization - const bool bWasRunning = Sound.IsRunning(); - if ( bWasRunning ) - { - Sound.Stop(); - } - - // set new parameter - bUseStereo = bNUseStereo; - Init(); - - if ( bWasRunning ) - { - Sound.Start(); - } -} - -QString CClient::SetSndCrdDev ( const int iNewDev ) -{ - // if client was running then first - // stop it and restart again after new initialization - const bool bWasRunning = Sound.IsRunning(); - if ( bWasRunning ) - { - Sound.Stop(); - } - - const QString strReturn = Sound.SetDev ( iNewDev ); - - // init again because the sound card actual buffer size might - // be changed on new device - Init(); - - if ( bWasRunning ) - { - // restart client - Sound.Start(); - } - - return strReturn; -} - -void CClient::SetSndCrdLeftInputChannel ( const int iNewChan ) -{ - // if client was running then first - // stop it and restart again after new initialization - const bool bWasRunning = Sound.IsRunning(); - if ( bWasRunning ) - { - Sound.Stop(); - } - - Sound.SetLeftInputChannel ( iNewChan ); - Init(); - - if ( bWasRunning ) - { - // restart client - Sound.Start(); - } -} - -void CClient::SetSndCrdRightInputChannel ( const int iNewChan ) -{ - // if client was running then first - // stop it and restart again after new initialization - const bool bWasRunning = Sound.IsRunning(); - if ( bWasRunning ) - { - Sound.Stop(); - } - - Sound.SetRightInputChannel ( iNewChan ); - Init(); - - if ( bWasRunning ) - { - // restart client - Sound.Start(); - } -} - -void CClient::SetSndCrdLeftOutputChannel ( const int iNewChan ) -{ - // if client was running then first - // stop it and restart again after new initialization - const bool bWasRunning = Sound.IsRunning(); - if ( bWasRunning ) - { - Sound.Stop(); - } - - Sound.SetLeftOutputChannel ( iNewChan ); - Init(); - - if ( bWasRunning ) - { - // restart client - Sound.Start(); - } -} - -void CClient::SetSndCrdRightOutputChannel ( const int iNewChan ) -{ - // if client was running then first - // stop it and restart again after new initialization - const bool bWasRunning = Sound.IsRunning(); - if ( bWasRunning ) - { - Sound.Stop(); - } - - Sound.SetRightOutputChannel ( iNewChan ); - Init(); - - if ( bWasRunning ) - { - // restart client - Sound.Start(); - } -} - -void CClient::OnSndCrdReinitRequest() -{ - // if client was running then first - // stop it and restart again after new initialization - const bool bWasRunning = Sound.IsRunning(); - if ( bWasRunning ) - { - Sound.Stop(); - } - - // reinit the driver (we use the currently selected driver) and - // init client object, too - Sound.SetDev ( Sound.GetDev() ); - Init(); - - if ( bWasRunning ) - { - // restart client - Sound.Start(); - } -} - -void CClient::Start() -{ - // init object - Init(); - - // enable channel - Channel.SetEnable ( true ); - - // start audio interface - Sound.Start(); -} - -void CClient::Stop() -{ - // stop audio interface - Sound.Stop(); - - // disable channel - Channel.SetEnable ( false ); - - // wait for approx. 100 ms to make sure no audio packet is still in the - // network queue causing the channel to be reconnected right after having - // received the disconnect message (seems not to gain much, disconnect is - // still not working reliably) - QTime DieTime = QTime::currentTime().addMSecs ( 100 ); - while ( QTime::currentTime() < DieTime ) - { - // exclude user input events because if we use AllEvents, it happens - // that if the user initiates a connection and disconnection quickly - // (e.g. quickly pressing enter five times), the software can get into - // an unknown state - QCoreApplication::processEvents ( - QEventLoop::ExcludeUserInputEvents, 100 ); - } - - // Send disconnect message to server (Since we disable our protocol - // receive mechanism with the next command, we do not evaluate any - // respond from the server, therefore we just hope that the message - // gets its way to the server, if not, the old behaviour time-out - // disconnects the connection anyway). - ConnLessProtocol.CreateCLDisconnection ( Channel.GetAddress() ); - - // reset current signal level and LEDs - SignalLevelMeter.Reset(); - PostWinMessage ( MS_RESET_ALL, 0 ); -} - -void CClient::Init() -{ - // check if possible frame size factors are supported - const int iFraSizePreffered = - FRAME_SIZE_FACTOR_PREFERRED * SYSTEM_FRAME_SIZE_SAMPLES; - - bFraSiFactPrefSupported = - ( Sound.Init ( iFraSizePreffered ) == iFraSizePreffered ); - - const int iFraSizeDefault = - FRAME_SIZE_FACTOR_DEFAULT * SYSTEM_FRAME_SIZE_SAMPLES; - - bFraSiFactDefSupported = - ( Sound.Init ( iFraSizeDefault ) == iFraSizeDefault ); - - const int iFraSizeSafe = - FRAME_SIZE_FACTOR_SAFE * SYSTEM_FRAME_SIZE_SAMPLES; - - bFraSiFactSafeSupported = - ( Sound.Init ( iFraSizeSafe ) == iFraSizeSafe ); - - // translate block size index in actual block size - const int iPrefMonoFrameSize = - iSndCrdPrefFrameSizeFactor * SYSTEM_FRAME_SIZE_SAMPLES; - - // get actual sound card buffer size using preferred size - iMonoBlockSizeSam = Sound.Init ( iPrefMonoFrameSize ); - - // Calculate the current sound card frame size factor. In case - // the current mono block size is not a multiple of the system - // frame size, we have to use a sound card conversion buffer. - if ( ( iMonoBlockSizeSam == ( SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_PREFERRED ) ) || - ( iMonoBlockSizeSam == ( SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_DEFAULT ) ) || - ( iMonoBlockSizeSam == ( SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_SAFE ) ) ) - { - // regular case: one of our predefined buffer sizes is available - iSndCrdFrameSizeFactor = iMonoBlockSizeSam / SYSTEM_FRAME_SIZE_SAMPLES; - - // no sound card conversion buffer required - bSndCrdConversionBufferRequired = false; - } - else - { - // An unsupported sound card buffer size is currently used -> we have - // to use a conversion buffer. Per definition we use the smallest buffer - // size as llcon frame size - - // store actual sound card buffer size (stereo) - iSndCardMonoBlockSizeSamConvBuff = iMonoBlockSizeSam; - const int iSndCardStereoBlockSizeSamConvBuff = 2 * iMonoBlockSizeSam; - - // overwrite block size by smallest supported llcon buffer size - iSndCrdFrameSizeFactor = FRAME_SIZE_FACTOR_PREFERRED; - iMonoBlockSizeSam = - SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_PREFERRED; - - iStereoBlockSizeSam = 2 * iMonoBlockSizeSam; - - // inits for conversion buffer (the size of the conversion buffer must - // be the sum of input/output sizes which is the worst case fill level) - const int iConBufSize = - iStereoBlockSizeSam + iSndCardStereoBlockSizeSamConvBuff; - - SndCrdConversionBufferIn.Init ( iConBufSize ); - SndCrdConversionBufferOut.Init ( iConBufSize ); - vecDataConvBuf.Init ( iStereoBlockSizeSam ); - - // the output conversion buffer must be filled with the inner - // block size for initialization (this is the latency which is - // introduced by the conversion buffer) to avoid buffer underruns - const CVector vZeros ( iStereoBlockSizeSam, 0 ); - SndCrdConversionBufferOut.Put ( vZeros, vZeros.Size() ); - - bSndCrdConversionBufferRequired = true; - } - - // calculate stereo (two channels) buffer size - iStereoBlockSizeSam = 2 * iMonoBlockSizeSam; - - vecsAudioSndCrdMono.Init ( iMonoBlockSizeSam ); - vecdAudioStereo.Init ( iStereoBlockSizeSam ); - - // init reverberation - AudioReverbL.Init ( SYSTEM_SAMPLE_RATE_HZ ); - AudioReverbR.Init ( SYSTEM_SAMPLE_RATE_HZ ); - - // inits for CELT coding - if ( bCeltDoHighQuality ) - { - if ( bUseStereo ) - { - iCeltNumCodedBytes = CELT_NUM_BYTES_STEREO_HIGH_QUALITY; - } - else - { - iCeltNumCodedBytes = CELT_NUM_BYTES_MONO_HIGH_QUALITY; - } - } - else - { - if ( bUseStereo ) - { - iCeltNumCodedBytes = CELT_NUM_BYTES_STEREO_NORMAL_QUALITY; - } - else - { - iCeltNumCodedBytes = CELT_NUM_BYTES_MONO_NORMAL_QUALITY; - } - } - vecCeltData.Init ( iCeltNumCodedBytes ); - - // inits for network and channel - vecbyNetwData.Init ( iCeltNumCodedBytes ); - if ( bUseStereo ) - { - vecsNetwork.Init ( iStereoBlockSizeSam ); - - // set the channel network properties - Channel.SetAudioStreamProperties ( iCeltNumCodedBytes, - iSndCrdFrameSizeFactor, - 2 ); - } - else - { - vecsNetwork.Init ( iMonoBlockSizeSam ); - - // set the channel network properties - Channel.SetAudioStreamProperties ( iCeltNumCodedBytes, - iSndCrdFrameSizeFactor, - 1 ); - } -} - -void CClient::AudioCallback ( CVector& psData, void* arg ) -{ - // get the pointer to the object - CClient* pMyClientObj = reinterpret_cast ( arg ); - - // process audio data - pMyClientObj->ProcessSndCrdAudioData ( psData ); -} - -void CClient::ProcessSndCrdAudioData ( CVector& vecsStereoSndCrd ) -{ - // check if a conversion buffer is required or not - if ( bSndCrdConversionBufferRequired ) - { - // add new sound card block in conversion buffer - SndCrdConversionBufferIn.Put ( vecsStereoSndCrd, vecsStereoSndCrd.Size() ); - - // process all available blocks of data - while ( SndCrdConversionBufferIn.GetAvailData() >= iStereoBlockSizeSam ) - { - // get one block of data for processing - SndCrdConversionBufferIn.Get ( vecDataConvBuf ); - - // process audio data - ProcessAudioDataIntern ( vecDataConvBuf ); - - SndCrdConversionBufferOut.Put ( vecDataConvBuf, vecDataConvBuf.Size() ); - } - - // get processed sound card block out of the conversion buffer - SndCrdConversionBufferOut.Get ( vecsStereoSndCrd ); - } - else - { - // regular case: no conversion buffer required - // process audio data - ProcessAudioDataIntern ( vecsStereoSndCrd ); - } -} - -void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) -{ - int i, j; - - // Transmit signal --------------------------------------------------------- - // update stereo signal level meter - SignalLevelMeter.Update ( vecsStereoSndCrd ); - - // convert data from short to double - for ( i = 0; i < iStereoBlockSizeSam; i++ ) - { - vecdAudioStereo[i] = static_cast ( vecsStereoSndCrd[i] ); - } - - // add reverberation effect if activated - if ( iReverbLevel != 0 ) - { - // calculate attenuation amplification factor - const double dRevLev = - static_cast ( iReverbLevel ) / AUD_REVERB_MAX / 2; - - if ( bUseStereo ) - { - // for stereo always apply reverberation effect on both channels - for ( i = 0; i < iStereoBlockSizeSam; i += 2 ) - { - // left channel - vecdAudioStereo[i] += - dRevLev * AudioReverbL.ProcessSample ( vecdAudioStereo[i] ); - - // right channel - vecdAudioStereo[i + 1] += - dRevLev * AudioReverbR.ProcessSample ( vecdAudioStereo[i + 1] ); - } - } - else - { - if ( bReverbOnLeftChan ) - { - for ( i = 0; i < iStereoBlockSizeSam; i += 2 ) - { - // left channel - vecdAudioStereo[i] += - dRevLev * AudioReverbL.ProcessSample ( vecdAudioStereo[i] ); - } - } - else - { - for ( i = 1; i < iStereoBlockSizeSam; i += 2 ) - { - // right channel - vecdAudioStereo[i] += - dRevLev * AudioReverbR.ProcessSample ( vecdAudioStereo[i] ); - } - } - } - } - - // mix both signals depending on the fading setting, convert - // from double to short - if ( iAudioInFader == AUD_FADER_IN_MIDDLE ) - { - if ( bUseStereo ) - { - // perform type conversion - for ( i = 0; i < iStereoBlockSizeSam; i++ ) - { - vecsNetwork[i] = Double2Short ( vecdAudioStereo[i] ); - } - } - else - { - // mix channels together - for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) - { - vecsNetwork[i] = - Double2Short ( ( vecdAudioStereo[j] + - vecdAudioStereo[j + 1] ) / 2 ); - } - } - } - else - { - if ( bUseStereo ) - { - // stereo - const double dAttFactStereo = static_cast ( - AUD_FADER_IN_MIDDLE - abs ( AUD_FADER_IN_MIDDLE - iAudioInFader ) ) / - AUD_FADER_IN_MIDDLE; - - if ( iAudioInFader > AUD_FADER_IN_MIDDLE ) - { - for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) - { - // attenuation on right channel - vecsNetwork[j] = Double2Short ( - vecdAudioStereo[j] ); - - vecsNetwork[j + 1] = Double2Short ( - dAttFactStereo * vecdAudioStereo[j + 1] ); - } - } - else - { - for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) - { - // attenuation on left channel - vecsNetwork[j] = Double2Short ( - dAttFactStereo * vecdAudioStereo[j] ); - - vecsNetwork[j + 1] = Double2Short ( - vecdAudioStereo[j + 1] ); - } - } - } - else - { - // mono - // make sure that in the middle position the two channels are - // amplified by 1/2, if the pan is set to one channel, this - // channel should have an amplification of 1 - const double dAttFactMono = static_cast ( - AUD_FADER_IN_MIDDLE - abs ( AUD_FADER_IN_MIDDLE - iAudioInFader ) ) / - AUD_FADER_IN_MIDDLE / 2; - - const double dAmplFactMono = 0.5 + static_cast ( - abs ( AUD_FADER_IN_MIDDLE - iAudioInFader ) ) / - AUD_FADER_IN_MIDDLE / 2; - - if ( iAudioInFader > AUD_FADER_IN_MIDDLE ) - { - for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) - { - // attenuation on right channel - vecsNetwork[i] = Double2Short ( - dAmplFactMono * vecdAudioStereo[j] + - dAttFactMono * vecdAudioStereo[j + 1] ); - } - } - else - { - for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) - { - // attenuation on left channel - vecsNetwork[i] = Double2Short ( - dAmplFactMono * vecdAudioStereo[j + 1] + - dAttFactMono * vecdAudioStereo[j] ); - } - } - } - } - - for ( i = 0; i < iSndCrdFrameSizeFactor; i++ ) - { - if ( bUseStereo ) - { - // encode current audio frame with CELT encoder - celt_encode ( CeltEncoderStereo, - &vecsNetwork[i * 2 * SYSTEM_FRAME_SIZE_SAMPLES], - NULL, - &vecCeltData[0], - iCeltNumCodedBytes ); - } - else - { - // encode current audio frame with CELT encoder - celt_encode ( CeltEncoderMono, - &vecsNetwork[i * SYSTEM_FRAME_SIZE_SAMPLES], - NULL, - &vecCeltData[0], - iCeltNumCodedBytes ); - } - - // send coded audio through the network - Socket.SendPacket ( Channel.PrepSendPacket ( vecCeltData ), - Channel.GetAddress() ); - } - - - // Receive signal ---------------------------------------------------------- - for ( i = 0; i < iSndCrdFrameSizeFactor; i++ ) - { - // receive a new block - const bool bReceiveDataOk = - ( Channel.GetData ( vecbyNetwData ) == GS_BUFFER_OK ); - - if ( bReceiveDataOk ) - { - PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_GREEN ); - } - else - { - PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_RED ); - } - - // CELT decoding - if ( bReceiveDataOk ) - { - if ( bUseStereo ) - { - celt_decode ( CeltDecoderStereo, - &vecbyNetwData[0], - iCeltNumCodedBytes, - &vecsStereoSndCrd[i * 2 * SYSTEM_FRAME_SIZE_SAMPLES] ); - } - else - { - celt_decode ( CeltDecoderMono, - &vecbyNetwData[0], - iCeltNumCodedBytes, - &vecsAudioSndCrdMono[i * SYSTEM_FRAME_SIZE_SAMPLES] ); - } - } - else - { - // lost packet - if ( bUseStereo ) - { - celt_decode ( CeltDecoderStereo, - NULL, - 0, - &vecsStereoSndCrd[i * 2 * SYSTEM_FRAME_SIZE_SAMPLES] ); - } - else - { - celt_decode ( CeltDecoderMono, - NULL, - 0, - &vecsAudioSndCrdMono[i * SYSTEM_FRAME_SIZE_SAMPLES] ); - } - } - } - - -/* -// TEST -// fid=fopen('v.dat','r');x=fread(fid,'int16');fclose(fid); -static FILE* pFileDelay = fopen("c:\\temp\\test2.dat", "wb"); -short sData[2]; -for (i = 0; i < iMonoBlockSizeSam; i++) -{ - sData[0] = (short) vecsAudioSndCrdMono[i]; - fwrite(&sData, size_t(2), size_t(1), pFileDelay); -} -fflush(pFileDelay); -*/ - - - // check if channel is connected - if ( Channel.IsConnected() ) - { - if ( !bUseStereo ) - { - // copy mono data in stereo sound card buffer - for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) - { - vecsStereoSndCrd[j] = vecsStereoSndCrd[j + 1] = - vecsAudioSndCrdMono[i]; - } - } - } - else - { - // if not connected, clear data - vecsStereoSndCrd.Reset ( 0 ); - } - - // update socket buffer size - Channel.UpdateSocketBufferSize(); -} - -int CClient::EstimatedOverallDelay ( const int iPingTimeMs ) -{ -/* - For estimating the overall delay, use the following assumptions: - - the mean delay of a cyclic buffer is half the buffer size (since - for the average it is assumed that the buffer is half filled) - - consider the jitter buffer on the server side, too -*/ - // the buffer sizes at client and server divided by 2 (half the buffer - // for the delay) is the total socket buffer size - const double dTotalJitterBufferDelayMs = SYSTEM_BLOCK_DURATION_MS_FLOAT * - static_cast ( GetSockBufNumFrames() + - GetServerSockBufNumFrames() ) / 2; - - // we assume that we have two period sizes for the input and one for the - // output, therefore we have "3 *" instead of "2 *" (for input and output) - // the actual sound card buffer size, also consider delay introduced by - // sound card conversion buffer by using - // "GetSndCrdConvBufAdditionalDelayMonoBlSize" - const double dTotalSoundCardDelayMs = - ( 3 * GetSndCrdActualMonoBlSize() + - GetSndCrdConvBufAdditionalDelayMonoBlSize() ) * - 1000 / SYSTEM_SAMPLE_RATE_HZ; - - // network packets are of the same size as the audio packets per definition - // if no sound card conversion buffer is used - const double dDelayToFillNetworkPacketsMs = - GetSystemMonoBlSize() * 1000 / SYSTEM_SAMPLE_RATE_HZ; - - // CELT additional delay at small frame sizes is half a frame size - const double dAdditionalAudioCodecDelayMs = - SYSTEM_BLOCK_DURATION_MS_FLOAT / 2; - - const double dTotalBufferDelayMs = - dDelayToFillNetworkPacketsMs + - dTotalJitterBufferDelayMs + - dTotalSoundCardDelayMs + - dAdditionalAudioCodecDelayMs; - - return LlconMath::round ( dTotalBufferDelayMs + iPingTimeMs ); -} +/******************************************************************************\ + * Copyright (c) 2004-2011 + * + * 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" + + +/* Implementation *************************************************************/ +CClient::CClient ( const quint16 iPortNumber ) : + vstrIPAddress ( MAX_NUM_SERVER_ADDR_ITEMS, "" ), + strName ( "" ), + Channel ( false ), /* we need a client channel -> "false" */ + iCeltNumCodedBytes ( CELT_NUM_BYTES_MONO_NORMAL_QUALITY ), + bCeltDoHighQuality ( false ), + bUseStereo ( false ), + Socket ( &Channel, &ConnLessProtocol, iPortNumber ), + Sound ( AudioCallback, this ), + iAudioInFader ( AUD_FADER_IN_MIDDLE ), + bReverbOnLeftChan ( false ), + iReverbLevel ( 0 ), + iSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_PREFERRED ), + iSndCrdFrameSizeFactor ( FRAME_SIZE_FACTOR_PREFERRED ), + bSndCrdConversionBufferRequired ( false ), + iSndCardMonoBlockSizeSamConvBuff ( 0 ), + bFraSiFactPrefSupported ( false ), + bFraSiFactDefSupported ( false ), + bFraSiFactSafeSupported ( false ), + bOpenChatOnNewMessage ( true ), + eGUIDesign ( GD_ORIGINAL ), + strCentralServerAddress ( "" ), + bUseDefaultCentralServerAddress ( true ), + iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ) +{ + // init audio encoder/decoder (mono) + CeltModeMono = celt_mode_create ( + SYSTEM_SAMPLE_RATE_HZ, 1, SYSTEM_FRAME_SIZE_SAMPLES, NULL ); + + CeltEncoderMono = celt_encoder_create ( CeltModeMono ); + CeltDecoderMono = celt_decoder_create ( CeltModeMono ); + +#ifdef USE_LOW_COMPLEXITY_CELT_ENC + // set encoder low complexity + celt_encoder_ctl ( CeltEncoderMono, + CELT_SET_COMPLEXITY_REQUEST, celt_int32_t ( 1 ) ); +#endif + + // init audio encoder/decoder (stereo) + CeltModeStereo = celt_mode_create ( + SYSTEM_SAMPLE_RATE_HZ, 2, SYSTEM_FRAME_SIZE_SAMPLES, NULL ); + + CeltEncoderStereo = celt_encoder_create ( CeltModeStereo ); + CeltDecoderStereo = celt_decoder_create ( CeltModeStereo ); + +#ifdef USE_LOW_COMPLEXITY_CELT_ENC + // set encoder low complexity + celt_encoder_ctl ( CeltEncoderStereo, + CELT_SET_COMPLEXITY_REQUEST, celt_int32_t ( 1 ) ); +#endif + + + // Connections ------------------------------------------------------------- + // connection for protocol + QObject::connect ( &Channel, + SIGNAL ( MessReadyForSending ( CVector ) ), + this, SLOT ( OnSendProtMessage ( CVector ) ) ); + + QObject::connect ( &Channel, + SIGNAL ( DetectedCLMessage ( CVector, int ) ), + this, SLOT ( OnDetectedCLMessage ( CVector, int ) ) ); + + QObject::connect ( &Channel, SIGNAL ( ReqJittBufSize() ), + this, SLOT ( OnReqJittBufSize() ) ); + + QObject::connect ( &Channel, SIGNAL ( JittBufSizeChanged ( int ) ), + this, SLOT ( OnJittBufSizeChanged ( int ) ) ); + + QObject::connect ( &Channel, SIGNAL ( ReqChanName() ), + this, SLOT ( OnReqChanName() ) ); + + QObject::connect ( &Channel, + SIGNAL ( ConClientListMesReceived ( CVector ) ), + SIGNAL ( ConClientListMesReceived ( CVector ) ) ); + + QObject::connect ( &Channel, + SIGNAL ( Disconnected() ), + SIGNAL ( Disconnected() ) ); + + QObject::connect ( &Channel, SIGNAL ( NewConnection() ), + this, SLOT ( OnNewConnection() ) ); + + QObject::connect ( &Channel, + SIGNAL ( ChatTextReceived ( QString ) ), + SIGNAL ( ChatTextReceived ( QString ) ) ); + + QObject::connect ( &ConnLessProtocol, + SIGNAL ( CLMessReadyForSending ( CHostAddress, CVector ) ), + this, SLOT ( OnSendCLProtMessage ( CHostAddress, CVector ) ) ); + + QObject::connect ( &ConnLessProtocol, + SIGNAL ( CLServerListReceived ( CHostAddress, CVector ) ), + SIGNAL ( CLServerListReceived ( CHostAddress, CVector ) ) ); + + QObject::connect ( &ConnLessProtocol, + SIGNAL ( CLPingReceived ( CHostAddress, int ) ), + this, SLOT ( OnCLPingReceived ( CHostAddress, int ) ) ); + + QObject::connect ( &ConnLessProtocol, + SIGNAL ( CLPingWithNumClientsReceived ( CHostAddress, int, int ) ), + this, SLOT ( OnCLPingWithNumClientsReceived ( CHostAddress, int, int ) ) ); + + QObject::connect ( &Sound, SIGNAL ( ReinitRequest() ), + this, SLOT ( OnSndCrdReinitRequest() ) ); +} + +void CClient::OnSendProtMessage ( CVector vecMessage ) +{ + // the protocol queries me to call the function to send the message + // send it through the network + Socket.SendPacket ( vecMessage, Channel.GetAddress() ); +} + +void CClient::OnSendCLProtMessage ( CHostAddress InetAddr, + CVector vecMessage ) +{ + // the protocol queries me to call the function to send the message + // send it through the network + Socket.SendPacket ( vecMessage, InetAddr ); +} + +void CClient::OnDetectedCLMessage ( CVector vecbyData, + int iNumBytes ) +{ + // this is a special case: we received a connection less message but we are + // in a connection + ConnLessProtocol.ParseConnectionLessMessage ( vecbyData, + iNumBytes, + Channel.GetAddress() ); +} + +void CClient::OnJittBufSizeChanged ( int iNewJitBufSize ) +{ + // we received a jitter buffer size changed message from the server, + // only apply this value if auto jitter buffer size is enabled + if ( GetDoAutoSockBufSize() ) + { + // Note: Do not use the "SetServerSockBufNumFrames" function for setting + // the new server jitter buffer size since then a message would be sent + // to the server which is incorrect. + iServerSockBufNumFrames = iNewJitBufSize; + } +} + +void CClient::OnNewConnection() +{ + // a new connection was successfully initiated, send name and request + // connected clients list + Channel.SetRemoteName ( strName ); + + // We have to send a connected clients list request since it can happen + // that we just had connected to the server and then disconnected but + // the server still thinks that we are connected (the server is still + // waiting for the channel time-out). If we now connect again, we would + // not get the list because the server does not know about a new connection. + // Same problem is with the jitter buffer message. + Channel.CreateReqConnClientsList(); + CreateServerJitterBufferMessage(); +} + +void CClient::CreateServerJitterBufferMessage() +{ + // per definition in the client: if auto jitter buffer is enabled, both, + // the client and server shall use an auto jitter buffer + if ( GetDoAutoSockBufSize() ) + { + // in case auto jitter buffer size is enabled, we have to transmit a + // special value + Channel.CreateJitBufMes ( AUTO_NET_BUF_SIZE_FOR_PROTOCOL ); + } + else + { + Channel.CreateJitBufMes ( GetServerSockBufNumFrames() ); + } +} + +void CClient::OnCLPingReceived ( CHostAddress InetAddr, + int iMs ) +{ + // make sure we are running and the server address is correct + if ( IsRunning() && ( InetAddr == Channel.GetAddress() ) ) + { + // take care of wrap arounds (if wrapping, do not use result) + const int iCurDiff = EvaluatePingMessage ( iMs ); + if ( iCurDiff >= 0 ) + { + emit PingTimeReceived ( iCurDiff ); + } + } +} + +void CClient::OnCLPingWithNumClientsReceived ( CHostAddress InetAddr, + int iMs, + int iNumClients ) +{ + // take care of wrap arounds (if wrapping, do not use result) + const int iCurDiff = EvaluatePingMessage ( iMs ); + if ( iCurDiff >= 0 ) + { + emit CLPingTimeWithNumClientsReceived ( InetAddr, + iCurDiff, + iNumClients ); + } +} + +int CClient::PreparePingMessage() +{ + // transmit the current precise time (in ms) + return PreciseTime.elapsed(); +} + +int CClient::EvaluatePingMessage ( const int iMs ) +{ + // calculate difference between received time in ms and current time in ms + return PreciseTime.elapsed() - iMs; +} + +void CClient::SetDoAutoSockBufSize ( const bool bValue ) +{ + // first, set new value in the channel object + Channel.SetDoAutoSockBufSize ( bValue ); + + // inform the server about the change + CreateServerJitterBufferMessage(); +} + +bool CClient::SetServerAddr ( QString strNAddr ) +{ + CHostAddress HostAddress; + if ( LlconNetwUtil().ParseNetworkAddress ( strNAddr, + HostAddress ) ) + { + // apply address to the channel + Channel.SetAddress ( HostAddress ); + + return true; + } + else + { + return false; // invalid address + } +} + +void CClient::SetSndCrdPrefFrameSizeFactor ( const int iNewFactor ) +{ + // first check new input parameter + if ( ( iNewFactor == FRAME_SIZE_FACTOR_PREFERRED ) || + ( iNewFactor == FRAME_SIZE_FACTOR_DEFAULT ) || + ( iNewFactor == FRAME_SIZE_FACTOR_SAFE ) ) + { + // init with new parameter, if client was running then first + // stop it and restart again after new initialization + const bool bWasRunning = Sound.IsRunning(); + if ( bWasRunning ) + { + Sound.Stop(); + } + + // set new parameter + iSndCrdPrefFrameSizeFactor = iNewFactor; + + // init with new block size index parameter + Init(); + + if ( bWasRunning ) + { + // restart client + Sound.Start(); + } + } +} + +void CClient::SetCELTHighQuality ( const bool bNCeltHighQualityFlag ) +{ + // init with new parameter, if client was running then first + // stop it and restart again after new initialization + const bool bWasRunning = Sound.IsRunning(); + if ( bWasRunning ) + { + Sound.Stop(); + } + + // set new parameter + bCeltDoHighQuality = bNCeltHighQualityFlag; + Init(); + + if ( bWasRunning ) + { + Sound.Start(); + } +} + +void CClient::SetUseStereo ( const bool bNUseStereo ) +{ + // init with new parameter, if client was running then first + // stop it and restart again after new initialization + const bool bWasRunning = Sound.IsRunning(); + if ( bWasRunning ) + { + Sound.Stop(); + } + + // set new parameter + bUseStereo = bNUseStereo; + Init(); + + if ( bWasRunning ) + { + Sound.Start(); + } +} + +QString CClient::SetSndCrdDev ( const int iNewDev ) +{ + // if client was running then first + // stop it and restart again after new initialization + const bool bWasRunning = Sound.IsRunning(); + if ( bWasRunning ) + { + Sound.Stop(); + } + + const QString strReturn = Sound.SetDev ( iNewDev ); + + // init again because the sound card actual buffer size might + // be changed on new device + Init(); + + if ( bWasRunning ) + { + // restart client + Sound.Start(); + } + + return strReturn; +} + +void CClient::SetSndCrdLeftInputChannel ( const int iNewChan ) +{ + // if client was running then first + // stop it and restart again after new initialization + const bool bWasRunning = Sound.IsRunning(); + if ( bWasRunning ) + { + Sound.Stop(); + } + + Sound.SetLeftInputChannel ( iNewChan ); + Init(); + + if ( bWasRunning ) + { + // restart client + Sound.Start(); + } +} + +void CClient::SetSndCrdRightInputChannel ( const int iNewChan ) +{ + // if client was running then first + // stop it and restart again after new initialization + const bool bWasRunning = Sound.IsRunning(); + if ( bWasRunning ) + { + Sound.Stop(); + } + + Sound.SetRightInputChannel ( iNewChan ); + Init(); + + if ( bWasRunning ) + { + // restart client + Sound.Start(); + } +} + +void CClient::SetSndCrdLeftOutputChannel ( const int iNewChan ) +{ + // if client was running then first + // stop it and restart again after new initialization + const bool bWasRunning = Sound.IsRunning(); + if ( bWasRunning ) + { + Sound.Stop(); + } + + Sound.SetLeftOutputChannel ( iNewChan ); + Init(); + + if ( bWasRunning ) + { + // restart client + Sound.Start(); + } +} + +void CClient::SetSndCrdRightOutputChannel ( const int iNewChan ) +{ + // if client was running then first + // stop it and restart again after new initialization + const bool bWasRunning = Sound.IsRunning(); + if ( bWasRunning ) + { + Sound.Stop(); + } + + Sound.SetRightOutputChannel ( iNewChan ); + Init(); + + if ( bWasRunning ) + { + // restart client + Sound.Start(); + } +} + +void CClient::OnSndCrdReinitRequest() +{ + // if client was running then first + // stop it and restart again after new initialization + const bool bWasRunning = Sound.IsRunning(); + if ( bWasRunning ) + { + Sound.Stop(); + } + + // reinit the driver (we use the currently selected driver) and + // init client object, too + +// TODO possible bug: In ASIO driver if set dev is called, the driver is +// unloaded. See ASIO manual: "Note: A host application has to defer +// processing of these notification to a later "secure" time as the +// driver has finish its processing of the notification. Especially on +// the kAsioResetRequest it is a bad idea to unload the driver during +// the asioMessage callback since the callback has to return back into +// the driver, which is no longer present." + +// TODO write separate driver reset function in sound base instead +// of doing setdev with the old driver ID -> avoid unloading driver + + Sound.SetDev ( Sound.GetDev() ); + Init(); + + if ( bWasRunning ) + { + // restart client + Sound.Start(); + } +} + +void CClient::Start() +{ + // init object + Init(); + + // enable channel + Channel.SetEnable ( true ); + + // start audio interface + Sound.Start(); +} + +void CClient::Stop() +{ + // stop audio interface + Sound.Stop(); + + // disable channel + Channel.SetEnable ( false ); + + // wait for approx. 100 ms to make sure no audio packet is still in the + // network queue causing the channel to be reconnected right after having + // received the disconnect message (seems not to gain much, disconnect is + // still not working reliably) + QTime DieTime = QTime::currentTime().addMSecs ( 100 ); + while ( QTime::currentTime() < DieTime ) + { + // exclude user input events because if we use AllEvents, it happens + // that if the user initiates a connection and disconnection quickly + // (e.g. quickly pressing enter five times), the software can get into + // an unknown state + QCoreApplication::processEvents ( + QEventLoop::ExcludeUserInputEvents, 100 ); + } + + // Send disconnect message to server (Since we disable our protocol + // receive mechanism with the next command, we do not evaluate any + // respond from the server, therefore we just hope that the message + // gets its way to the server, if not, the old behaviour time-out + // disconnects the connection anyway). + ConnLessProtocol.CreateCLDisconnection ( Channel.GetAddress() ); + + // reset current signal level and LEDs + SignalLevelMeter.Reset(); + PostWinMessage ( MS_RESET_ALL, 0 ); +} + +void CClient::Init() +{ + // check if possible frame size factors are supported + const int iFraSizePreffered = + FRAME_SIZE_FACTOR_PREFERRED * SYSTEM_FRAME_SIZE_SAMPLES; + + bFraSiFactPrefSupported = + ( Sound.Init ( iFraSizePreffered ) == iFraSizePreffered ); + + const int iFraSizeDefault = + FRAME_SIZE_FACTOR_DEFAULT * SYSTEM_FRAME_SIZE_SAMPLES; + + bFraSiFactDefSupported = + ( Sound.Init ( iFraSizeDefault ) == iFraSizeDefault ); + + const int iFraSizeSafe = + FRAME_SIZE_FACTOR_SAFE * SYSTEM_FRAME_SIZE_SAMPLES; + + bFraSiFactSafeSupported = + ( Sound.Init ( iFraSizeSafe ) == iFraSizeSafe ); + + // translate block size index in actual block size + const int iPrefMonoFrameSize = + iSndCrdPrefFrameSizeFactor * SYSTEM_FRAME_SIZE_SAMPLES; + + // get actual sound card buffer size using preferred size + iMonoBlockSizeSam = Sound.Init ( iPrefMonoFrameSize ); + + // Calculate the current sound card frame size factor. In case + // the current mono block size is not a multiple of the system + // frame size, we have to use a sound card conversion buffer. + if ( ( iMonoBlockSizeSam == ( SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_PREFERRED ) ) || + ( iMonoBlockSizeSam == ( SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_DEFAULT ) ) || + ( iMonoBlockSizeSam == ( SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_SAFE ) ) ) + { + // regular case: one of our predefined buffer sizes is available + iSndCrdFrameSizeFactor = iMonoBlockSizeSam / SYSTEM_FRAME_SIZE_SAMPLES; + + // no sound card conversion buffer required + bSndCrdConversionBufferRequired = false; + } + else + { + // An unsupported sound card buffer size is currently used -> we have + // to use a conversion buffer. Per definition we use the smallest buffer + // size as llcon frame size + + // store actual sound card buffer size (stereo) + iSndCardMonoBlockSizeSamConvBuff = iMonoBlockSizeSam; + const int iSndCardStereoBlockSizeSamConvBuff = 2 * iMonoBlockSizeSam; + + // overwrite block size by smallest supported llcon buffer size + iSndCrdFrameSizeFactor = FRAME_SIZE_FACTOR_PREFERRED; + iMonoBlockSizeSam = + SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_PREFERRED; + + iStereoBlockSizeSam = 2 * iMonoBlockSizeSam; + + // inits for conversion buffer (the size of the conversion buffer must + // be the sum of input/output sizes which is the worst case fill level) + const int iConBufSize = + iStereoBlockSizeSam + iSndCardStereoBlockSizeSamConvBuff; + + SndCrdConversionBufferIn.Init ( iConBufSize ); + SndCrdConversionBufferOut.Init ( iConBufSize ); + vecDataConvBuf.Init ( iStereoBlockSizeSam ); + + // the output conversion buffer must be filled with the inner + // block size for initialization (this is the latency which is + // introduced by the conversion buffer) to avoid buffer underruns + const CVector vZeros ( iStereoBlockSizeSam, 0 ); + SndCrdConversionBufferOut.Put ( vZeros, vZeros.Size() ); + + bSndCrdConversionBufferRequired = true; + } + + // calculate stereo (two channels) buffer size + iStereoBlockSizeSam = 2 * iMonoBlockSizeSam; + + vecsAudioSndCrdMono.Init ( iMonoBlockSizeSam ); + vecdAudioStereo.Init ( iStereoBlockSizeSam ); + + // init reverberation + AudioReverbL.Init ( SYSTEM_SAMPLE_RATE_HZ ); + AudioReverbR.Init ( SYSTEM_SAMPLE_RATE_HZ ); + + // inits for CELT coding + if ( bCeltDoHighQuality ) + { + if ( bUseStereo ) + { + iCeltNumCodedBytes = CELT_NUM_BYTES_STEREO_HIGH_QUALITY; + } + else + { + iCeltNumCodedBytes = CELT_NUM_BYTES_MONO_HIGH_QUALITY; + } + } + else + { + if ( bUseStereo ) + { + iCeltNumCodedBytes = CELT_NUM_BYTES_STEREO_NORMAL_QUALITY; + } + else + { + iCeltNumCodedBytes = CELT_NUM_BYTES_MONO_NORMAL_QUALITY; + } + } + vecCeltData.Init ( iCeltNumCodedBytes ); + + // inits for network and channel + vecbyNetwData.Init ( iCeltNumCodedBytes ); + if ( bUseStereo ) + { + vecsNetwork.Init ( iStereoBlockSizeSam ); + + // set the channel network properties + Channel.SetAudioStreamProperties ( iCeltNumCodedBytes, + iSndCrdFrameSizeFactor, + 2 ); + } + else + { + vecsNetwork.Init ( iMonoBlockSizeSam ); + + // set the channel network properties + Channel.SetAudioStreamProperties ( iCeltNumCodedBytes, + iSndCrdFrameSizeFactor, + 1 ); + } +} + +void CClient::AudioCallback ( CVector& psData, void* arg ) +{ + // get the pointer to the object + CClient* pMyClientObj = reinterpret_cast ( arg ); + + // process audio data + pMyClientObj->ProcessSndCrdAudioData ( psData ); +} + +void CClient::ProcessSndCrdAudioData ( CVector& vecsStereoSndCrd ) +{ + // check if a conversion buffer is required or not + if ( bSndCrdConversionBufferRequired ) + { + // add new sound card block in conversion buffer + SndCrdConversionBufferIn.Put ( vecsStereoSndCrd, vecsStereoSndCrd.Size() ); + + // process all available blocks of data + while ( SndCrdConversionBufferIn.GetAvailData() >= iStereoBlockSizeSam ) + { + // get one block of data for processing + SndCrdConversionBufferIn.Get ( vecDataConvBuf ); + + // process audio data + ProcessAudioDataIntern ( vecDataConvBuf ); + + SndCrdConversionBufferOut.Put ( vecDataConvBuf, vecDataConvBuf.Size() ); + } + + // get processed sound card block out of the conversion buffer + SndCrdConversionBufferOut.Get ( vecsStereoSndCrd ); + } + else + { + // regular case: no conversion buffer required + // process audio data + ProcessAudioDataIntern ( vecsStereoSndCrd ); + } +} + +void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) +{ + int i, j; + + // Transmit signal --------------------------------------------------------- + // update stereo signal level meter + SignalLevelMeter.Update ( vecsStereoSndCrd ); + + // convert data from short to double + for ( i = 0; i < iStereoBlockSizeSam; i++ ) + { + vecdAudioStereo[i] = static_cast ( vecsStereoSndCrd[i] ); + } + + // add reverberation effect if activated + if ( iReverbLevel != 0 ) + { + // calculate attenuation amplification factor + const double dRevLev = + static_cast ( iReverbLevel ) / AUD_REVERB_MAX / 2; + + if ( bUseStereo ) + { + // for stereo always apply reverberation effect on both channels + for ( i = 0; i < iStereoBlockSizeSam; i += 2 ) + { + // left channel + vecdAudioStereo[i] += + dRevLev * AudioReverbL.ProcessSample ( vecdAudioStereo[i] ); + + // right channel + vecdAudioStereo[i + 1] += + dRevLev * AudioReverbR.ProcessSample ( vecdAudioStereo[i + 1] ); + } + } + else + { + if ( bReverbOnLeftChan ) + { + for ( i = 0; i < iStereoBlockSizeSam; i += 2 ) + { + // left channel + vecdAudioStereo[i] += + dRevLev * AudioReverbL.ProcessSample ( vecdAudioStereo[i] ); + } + } + else + { + for ( i = 1; i < iStereoBlockSizeSam; i += 2 ) + { + // right channel + vecdAudioStereo[i] += + dRevLev * AudioReverbR.ProcessSample ( vecdAudioStereo[i] ); + } + } + } + } + + // mix both signals depending on the fading setting, convert + // from double to short + if ( iAudioInFader == AUD_FADER_IN_MIDDLE ) + { + if ( bUseStereo ) + { + // perform type conversion + for ( i = 0; i < iStereoBlockSizeSam; i++ ) + { + vecsNetwork[i] = Double2Short ( vecdAudioStereo[i] ); + } + } + else + { + // mix channels together + for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) + { + vecsNetwork[i] = + Double2Short ( ( vecdAudioStereo[j] + + vecdAudioStereo[j + 1] ) / 2 ); + } + } + } + else + { + if ( bUseStereo ) + { + // stereo + const double dAttFactStereo = static_cast ( + AUD_FADER_IN_MIDDLE - abs ( AUD_FADER_IN_MIDDLE - iAudioInFader ) ) / + AUD_FADER_IN_MIDDLE; + + if ( iAudioInFader > AUD_FADER_IN_MIDDLE ) + { + for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) + { + // attenuation on right channel + vecsNetwork[j] = Double2Short ( + vecdAudioStereo[j] ); + + vecsNetwork[j + 1] = Double2Short ( + dAttFactStereo * vecdAudioStereo[j + 1] ); + } + } + else + { + for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) + { + // attenuation on left channel + vecsNetwork[j] = Double2Short ( + dAttFactStereo * vecdAudioStereo[j] ); + + vecsNetwork[j + 1] = Double2Short ( + vecdAudioStereo[j + 1] ); + } + } + } + else + { + // mono + // make sure that in the middle position the two channels are + // amplified by 1/2, if the pan is set to one channel, this + // channel should have an amplification of 1 + const double dAttFactMono = static_cast ( + AUD_FADER_IN_MIDDLE - abs ( AUD_FADER_IN_MIDDLE - iAudioInFader ) ) / + AUD_FADER_IN_MIDDLE / 2; + + const double dAmplFactMono = 0.5 + static_cast ( + abs ( AUD_FADER_IN_MIDDLE - iAudioInFader ) ) / + AUD_FADER_IN_MIDDLE / 2; + + if ( iAudioInFader > AUD_FADER_IN_MIDDLE ) + { + for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) + { + // attenuation on right channel + vecsNetwork[i] = Double2Short ( + dAmplFactMono * vecdAudioStereo[j] + + dAttFactMono * vecdAudioStereo[j + 1] ); + } + } + else + { + for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) + { + // attenuation on left channel + vecsNetwork[i] = Double2Short ( + dAmplFactMono * vecdAudioStereo[j + 1] + + dAttFactMono * vecdAudioStereo[j] ); + } + } + } + } + + for ( i = 0; i < iSndCrdFrameSizeFactor; i++ ) + { + if ( bUseStereo ) + { + // encode current audio frame with CELT encoder + celt_encode ( CeltEncoderStereo, + &vecsNetwork[i * 2 * SYSTEM_FRAME_SIZE_SAMPLES], + NULL, + &vecCeltData[0], + iCeltNumCodedBytes ); + } + else + { + // encode current audio frame with CELT encoder + celt_encode ( CeltEncoderMono, + &vecsNetwork[i * SYSTEM_FRAME_SIZE_SAMPLES], + NULL, + &vecCeltData[0], + iCeltNumCodedBytes ); + } + + // send coded audio through the network + Socket.SendPacket ( Channel.PrepSendPacket ( vecCeltData ), + Channel.GetAddress() ); + } + + + // Receive signal ---------------------------------------------------------- + for ( i = 0; i < iSndCrdFrameSizeFactor; i++ ) + { + // receive a new block + const bool bReceiveDataOk = + ( Channel.GetData ( vecbyNetwData ) == GS_BUFFER_OK ); + + if ( bReceiveDataOk ) + { + PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_GREEN ); + } + else + { + PostWinMessage ( MS_JIT_BUF_GET, MUL_COL_LED_RED ); + } + + // CELT decoding + if ( bReceiveDataOk ) + { + if ( bUseStereo ) + { + celt_decode ( CeltDecoderStereo, + &vecbyNetwData[0], + iCeltNumCodedBytes, + &vecsStereoSndCrd[i * 2 * SYSTEM_FRAME_SIZE_SAMPLES] ); + } + else + { + celt_decode ( CeltDecoderMono, + &vecbyNetwData[0], + iCeltNumCodedBytes, + &vecsAudioSndCrdMono[i * SYSTEM_FRAME_SIZE_SAMPLES] ); + } + } + else + { + // lost packet + if ( bUseStereo ) + { + celt_decode ( CeltDecoderStereo, + NULL, + 0, + &vecsStereoSndCrd[i * 2 * SYSTEM_FRAME_SIZE_SAMPLES] ); + } + else + { + celt_decode ( CeltDecoderMono, + NULL, + 0, + &vecsAudioSndCrdMono[i * SYSTEM_FRAME_SIZE_SAMPLES] ); + } + } + } + + +/* +// TEST +// fid=fopen('v.dat','r');x=fread(fid,'int16');fclose(fid); +static FILE* pFileDelay = fopen("c:\\temp\\test2.dat", "wb"); +short sData[2]; +for (i = 0; i < iMonoBlockSizeSam; i++) +{ + sData[0] = (short) vecsAudioSndCrdMono[i]; + fwrite(&sData, size_t(2), size_t(1), pFileDelay); +} +fflush(pFileDelay); +*/ + + + // check if channel is connected + if ( Channel.IsConnected() ) + { + if ( !bUseStereo ) + { + // copy mono data in stereo sound card buffer + for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) + { + vecsStereoSndCrd[j] = vecsStereoSndCrd[j + 1] = + vecsAudioSndCrdMono[i]; + } + } + } + else + { + // if not connected, clear data + vecsStereoSndCrd.Reset ( 0 ); + } + + // update socket buffer size + Channel.UpdateSocketBufferSize(); +} + +int CClient::EstimatedOverallDelay ( const int iPingTimeMs ) +{ +/* + For estimating the overall delay, use the following assumptions: + - the mean delay of a cyclic buffer is half the buffer size (since + for the average it is assumed that the buffer is half filled) + - consider the jitter buffer on the server side, too +*/ + // the buffer sizes at client and server divided by 2 (half the buffer + // for the delay) is the total socket buffer size + const double dTotalJitterBufferDelayMs = SYSTEM_BLOCK_DURATION_MS_FLOAT * + static_cast ( GetSockBufNumFrames() + + GetServerSockBufNumFrames() ) / 2; + + // we assume that we have two period sizes for the input and one for the + // output, therefore we have "3 *" instead of "2 *" (for input and output) + // the actual sound card buffer size, also consider delay introduced by + // sound card conversion buffer by using + // "GetSndCrdConvBufAdditionalDelayMonoBlSize" + const double dTotalSoundCardDelayMs = + ( 3 * GetSndCrdActualMonoBlSize() + + GetSndCrdConvBufAdditionalDelayMonoBlSize() ) * + 1000 / SYSTEM_SAMPLE_RATE_HZ; + + // network packets are of the same size as the audio packets per definition + // if no sound card conversion buffer is used + const double dDelayToFillNetworkPacketsMs = + GetSystemMonoBlSize() * 1000 / SYSTEM_SAMPLE_RATE_HZ; + + // CELT additional delay at small frame sizes is half a frame size + const double dAdditionalAudioCodecDelayMs = + SYSTEM_BLOCK_DURATION_MS_FLOAT / 2; + + const double dTotalBufferDelayMs = + dDelayToFillNetworkPacketsMs + + dTotalJitterBufferDelayMs + + dTotalSoundCardDelayMs + + dAdditionalAudioCodecDelayMs; + + return LlconMath::round ( dTotalBufferDelayMs + iPingTimeMs ); +} diff --git a/src/soundbase.cpp b/src/soundbase.cpp index 5a478539..52ba6b45 100755 --- a/src/soundbase.cpp +++ b/src/soundbase.cpp @@ -26,16 +26,18 @@ /* Implementation *************************************************************/ -CSoundBase::CSoundBase ( const bool bNewIsCallbackAudioInterface, +CSoundBase::CSoundBase ( const QString& strNewSystemDriverTechniqueName, + const bool bNewIsCallbackAudioInterface, void (*fpNewProcessCallback) ( CVector& psData, void* pParg ), void* pParg ) : fpProcessCallback ( fpNewProcessCallback ), pProcessCallbackArg ( pParg ), bRun ( false ), - bIsCallbackAudioInterface ( bNewIsCallbackAudioInterface ) + bIsCallbackAudioInterface ( bNewIsCallbackAudioInterface ), + strSystemDriverTechniqueName ( strNewSystemDriverTechniqueName ) { // initializations for the sound card names (default) lNumDevs = 1; - strDriverNames[0] = "Default"; + strDriverNames[0] = strSystemDriverTechniqueName; // set current device lCurDev = 0; // default device @@ -123,3 +125,139 @@ void CSoundBase::run() } } } + + +/******************************************************************************\ +* Device handling * +\******************************************************************************/ +QString CSoundBase::SetDev ( const int iNewDev ) +{ + QString strReturn = ""; // init with no error + bool bTryLoadAnyDriver = false; + + // check if an ASIO driver was already initialized + if ( lCurDev >= 0 ) + { + // a device was already been initialized and is used, first clean up + // driver + UnloadCurrentDriver(); + + const QString strErrorMessage = LoadAndInitializeDriver ( iNewDev ); + + if ( !strErrorMessage.isEmpty() ) + { + if ( iNewDev != lCurDev ) + { + // loading and initializing the new driver failed, go back to + // original driver and display error message + LoadAndInitializeDriver ( lCurDev ); + } + else + { + // the same driver is used but the driver properties seems to + // have changed so that they are not compatible to our + // software anymore + QMessageBox::critical ( + 0, APP_NAME, QString ( tr ( "The audio driver properties " + "have changed to a state which is incompatible to this " + "software. The selected audio device could not be used " + "because of the following error: " ) ) + + strErrorMessage + + QString ( tr ( "

Please restart the software." ) ), + "Close", 0 ); + + _exit ( 0 ); + } + + // store error return message + strReturn = strErrorMessage; + } + } + else + { + if ( iNewDev != INVALID_SNC_CARD_DEVICE ) + { + // This is the first time a driver is to be initialized, we first + // try to load the selected driver, if this fails, we try to load + // the first available driver in the system. If this fails, too, we + // throw an error that no driver is available -> it does not make + // sense to start the llcon software if no audio hardware is + // available. + if ( !LoadAndInitializeDriver ( iNewDev ).isEmpty() ) + { + // loading and initializing the new driver failed, try to find + // at least one usable driver + bTryLoadAnyDriver = true; + } + } + else + { + // try to find one usable driver (select the first valid driver) + bTryLoadAnyDriver = true; + } + } + + if ( bTryLoadAnyDriver ) + { + // try to load and initialize any valid driver + QVector vsErrorList = + LoadAndInitializeFirstValidDriver(); + + if ( !vsErrorList.isEmpty() ) + { + // create error message with all details + QString sErrorMessage = tr ( "No usable " ) + + strSystemDriverTechniqueName + tr ( " audio device " + "(driver) found.

" + "In the following there is a list of all available drivers " + "with the associated error message:
    " ); + + for ( int i = 0; i < lNumDevs; i++ ) + { + sErrorMessage += "
  • " + GetDeviceName ( i ) + ": " + + vsErrorList[i] + "
  • "; + } + sErrorMessage += "
"; + + throw CGenErr ( sErrorMessage ); + } + } + + return strReturn; +} + +QVector CSoundBase::LoadAndInitializeFirstValidDriver() +{ + QVector vsErrorList; + + // load and initialize first valid ASIO driver + bool bValidDriverDetected = false; + int iCurDriverIdx = 0; + + // try all available drivers in the system ("lNumDevs" devices) + while ( !bValidDriverDetected && ( iCurDriverIdx < lNumDevs ) ) + { + // try to load and initialize current driver, store error message + const QString strCurError = + LoadAndInitializeDriver ( iCurDriverIdx ); + + vsErrorList.append ( strCurError ); + + if ( strCurError.isEmpty() ) + { + // initialization was successful + bValidDriverDetected = true; + + // store ID of selected driver + lCurDev = iCurDriverIdx; + + // empty error list shows that init was successful + vsErrorList.clear(); + } + + // try next driver + iCurDriverIdx++; + } + + return vsErrorList; +} diff --git a/src/soundbase.h b/src/soundbase.h index 865c722f..ed7911b9 100755 --- a/src/soundbase.h +++ b/src/soundbase.h @@ -27,6 +27,7 @@ #include #include +#include #include "global.h" #include "util.h" @@ -37,7 +38,8 @@ class CSoundBase : public QThread Q_OBJECT public: - CSoundBase ( const bool bNewIsCallbackAudioInterface, + CSoundBase ( const QString& strNewSystemDriverTechniqueName, + const bool bNewIsCallbackAudioInterface, void (*fpNewProcessCallback) ( CVector& psData, void* pParg ), void* pParg ); @@ -48,10 +50,8 @@ public: // device selection virtual int GetNumDev() { return lNumDevs; } virtual QString GetDeviceName ( const int iDiD ) { return strDriverNames[iDiD]; } - - // dummy implementations in base class - virtual QString SetDev ( const int ) { return ""; } - virtual int GetDev() { return lCurDev; } + virtual QString SetDev ( const int ); + virtual int GetDev() { return lCurDev; } virtual int GetNumInputChannels() { return 2; } virtual QString GetInputChannelName ( const int ) { return "Default"; } @@ -76,6 +76,11 @@ public: void EmitReinitRequestSignal() { emit ReinitRequest(); } protected: + // driver handling + virtual QString LoadAndInitializeDriver ( int ) { return ""; }; + virtual void UnloadCurrentDriver() {} + QVector LoadAndInitializeFirstValidDriver(); + // function pointer to callback function void (*fpProcessCallback) ( CVector& psData, void* arg ); void* pProcessCallbackArg; @@ -93,13 +98,15 @@ protected: void run(); bool bRun; - bool bIsCallbackAudioInterface; + + bool bIsCallbackAudioInterface; + QString strSystemDriverTechniqueName; CVector vecsAudioSndCrdStereo; - long lNumDevs; - long lCurDev; - QString strDriverNames[MAX_NUMBER_SOUND_CARDS]; + long lNumDevs; + long lCurDev; + QString strDriverNames[MAX_NUMBER_SOUND_CARDS]; signals: void ReinitRequest(); diff --git a/windows/sound.cpp b/windows/sound.cpp index fd054ee1..0b791126 100755 --- a/windows/sound.cpp +++ b/windows/sound.cpp @@ -40,143 +40,6 @@ CSound* pSound; /******************************************************************************\ * Common * \******************************************************************************/ -QString CSound::SetDev ( const int iNewDev ) -{ - QString strReturn = ""; // init with no error - bool bTryLoadAnyDriver = false; - - // check if an ASIO driver was already initialized - if ( lCurDev >= 0 ) - { - // a device was already been initialized and is used, first clean up - // dispose ASIO buffers - ASIODisposeBuffers(); - - // remove old driver - ASIOExit(); - asioDrivers->removeCurrentDriver(); - - const QString strErrorMessage = LoadAndInitializeDriver ( iNewDev ); - - if ( !strErrorMessage.isEmpty() ) - { - if ( iNewDev != lCurDev ) - { - // loading and initializing the new driver failed, go back to - // original driver and display error message - LoadAndInitializeDriver ( lCurDev ); - } - else - { - // the same driver is used but the driver properties seems to - // have changed so that they are not compatible to our - // software anymore - QMessageBox::critical ( - 0, APP_NAME, QString ( tr ( "The audio driver properties " - "have changed to a state which is incompatible to this " - "software. The selected audio device could not be used " - "because of the following error: " ) ) + - strErrorMessage + - QString ( tr ( "

Please restart the software." ) ), - "Close", 0 ); - - _exit ( 0 ); - } - - // store error return message - strReturn = strErrorMessage; - } - - Init ( iASIOBufferSizeStereo ); - } - else - { - if ( iNewDev != INVALID_SNC_CARD_DEVICE ) - { - // This is the first time a driver is to be initialized, we first - // try to load the selected driver, if this fails, we try to load - // the first available driver in the system. If this fails, too, we - // throw an error that no driver is available -> it does not make - // sense to start the llcon software if no audio hardware is - // available. - if ( !LoadAndInitializeDriver ( iNewDev ).isEmpty() ) - { - // loading and initializing the new driver failed, try to find - // at least one usable driver - bTryLoadAnyDriver = true; - } - } - else - { - // try to find one usable driver (select the first valid driver) - bTryLoadAnyDriver = true; - } - } - - if ( bTryLoadAnyDriver ) - { - // try to load and initialize any valid driver - QVector vsErrorList = - LoadAndInitializeFirstValidDriver(); - - if ( !vsErrorList.isEmpty() ) - { - // create error message with all details - QString sErrorMessage = tr ( "No usable ASIO audio device " - "(driver) found.

" - "In the following there is a list of all available drivers " - "with the associated error message:
    " ); - - for ( int i = 0; i < lNumDevs; i++ ) - { - sErrorMessage += "
  • " + GetDeviceName ( i ) + ": " + - vsErrorList[i] + "
  • "; - } - sErrorMessage += "
"; - - throw CGenErr ( sErrorMessage ); - } - } - - return strReturn; -} - -QVector CSound::LoadAndInitializeFirstValidDriver() -{ - QVector vsErrorList; - - // load and initialize first valid ASIO driver - bool bValidDriverDetected = false; - int iCurDriverIdx = 0; - - // try all available drivers in the system ("lNumDevs" devices) - while ( !bValidDriverDetected && ( iCurDriverIdx < lNumDevs ) ) - { - // try to load and initialize current driver, store error message - const QString strCurError = - LoadAndInitializeDriver ( iCurDriverIdx ); - - vsErrorList.append ( strCurError ); - - if ( strCurError.isEmpty() ) - { - // initialization was successful - bValidDriverDetected = true; - - // store ID of selected driver - lCurDev = iCurDriverIdx; - - // empty error list shows that init was successful - vsErrorList.clear(); - } - - // try next driver - iCurDriverIdx++; - } - - return vsErrorList; -} - QString CSound::LoadAndInitializeDriver ( int iDriverIdx ) { // first check and correct input parameter @@ -212,6 +75,15 @@ QString CSound::LoadAndInitializeDriver ( int iDriverIdx ) return strStat; } +void CSound::UnloadCurrentDriver() +{ + // cleanup ASIO stuff + ASIOStop(); + ASIODisposeBuffers(); + ASIOExit(); + asioDrivers->removeCurrentDriver(); +} + QString CSound::CheckDeviceCapabilities() { // This function checks if our required input/output channel @@ -525,11 +397,11 @@ void CSound::Stop() } CSound::CSound ( void (*fpNewCallback) ( CVector& psData, void* arg ), void* arg ) : - CSoundBase ( true, fpNewCallback, arg ), - vSelectedInputChannels ( NUM_IN_OUT_CHANNELS ), + CSoundBase ( "ASIO", true, fpNewCallback, arg ), + vSelectedInputChannels ( NUM_IN_OUT_CHANNELS ), vSelectedOutputChannels ( NUM_IN_OUT_CHANNELS ), - lNumInChan ( 0 ), - lNumOutChan ( 0 ) + lNumInChan ( 0 ), + lNumOutChan ( 0 ) { int i; @@ -589,14 +461,6 @@ void CSound::ResetChannelMapping() vSelectedOutputChannels[1] = 1; } -CSound::~CSound() -{ - // cleanup ASIO stuff - ASIOStop(); - ASIODisposeBuffers(); - ASIOExit(); - asioDrivers->removeCurrentDriver(); -} // ASIO callbacks ------------------------------------------------------------- ASIOTime* CSound::bufferSwitchTimeInfo ( ASIOTime*, @@ -1104,6 +968,11 @@ long CSound::asioMessages ( long selector, // both messages might be send if the buffer size changes case kAsioBufferSizeChange: case kAsioResetRequest: + + +// TODO implement separate ASIO reset function in base class! + + pSound->EmitReinitRequestSignal(); ret = 1L; // 1L if request is accepted or 0 otherwise break; diff --git a/windows/sound.h b/windows/sound.h index 354b0518..3b3e1830 100755 --- a/windows/sound.h +++ b/windows/sound.h @@ -49,7 +49,7 @@ class CSound : public CSoundBase { public: CSound ( void (*fpNewCallback) ( CVector& psData, void* arg ), void* arg ); - virtual ~CSound(); + virtual ~CSound() { UnloadCurrentDriver(); } virtual int Init ( const int iNewPrefMonoBufferSize ); virtual void Start(); @@ -57,9 +57,6 @@ public: virtual void OpenDriverSetup() { ASIOControlPanel(); } - // device selection - virtual QString SetDev ( const int iNewDev ); - // channel selection virtual int GetNumInputChannels() { return static_cast ( lNumInChan ); } virtual QString GetInputChannelName ( const int iDiD ) { return channelInfosInput[iDiD].name; } @@ -76,8 +73,8 @@ public: virtual int GetRightOutputChannel() { return vSelectedOutputChannels[1]; } protected: - QVector LoadAndInitializeFirstValidDriver(); - QString LoadAndInitializeDriver ( int iIdx ); + virtual QString LoadAndInitializeDriver ( int iIdx ); + virtual void UnloadCurrentDriver(); int GetActualBufferSize ( const int iDesiredBufferSizeMono ); QString CheckDeviceCapabilities(); bool CheckSampleTypeSupported ( const ASIOSampleType SamType );