diff --git a/src/client.cpp b/src/client.cpp index 6c7464b976..2388429825 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -49,6 +49,186 @@ #include "util.h" /* Implementation *************************************************************/ +class CAuxiliaryMonoSender +{ +public: + CAuxiliaryMonoSender ( const quint16 iQosNumber, const bool bDisableIPv6 ) : + Channel ( false ), + bIPv6Available ( false ), + Socket ( &Channel, 0, iQosNumber, "", bDisableIPv6, bIPv6Available ), + pOpusMode ( nullptr ), + pOpusEncoder ( nullptr ), + eAudioCompressionType ( CT_NONE ), + iCodedBytes ( 0 ), + iFrameSizeSamples ( 0 ), + iFrameSizeFactor ( 0 ), + iSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ), + iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ), + bDoAutoSockBufSize ( false ) + { + QObject::connect ( &Channel, &CChannel::MessReadyForSending, &Channel, [this] ( CVector vecMessage ) { + Socket.SendPacket ( vecMessage, Channel.GetAddress() ); + } ); + + QObject::connect ( &Channel, &CChannel::NewConnection, &Channel, [this] { + Channel.SetRemoteInfo ( ChannelInfo ); + CreateServerJitterBufferMessage(); + } ); + + QObject::connect ( &Channel, &CChannel::ReqChanInfo, &Channel, [this] { Channel.SetRemoteInfo ( ChannelInfo ); } ); + + QObject::connect ( &Channel, &CChannel::ReqJittBufSize, &Channel, [this] { CreateServerJitterBufferMessage(); } ); + + QObject::connect ( &ConnLessProtocol, + &CProtocol::CLMessReadyForSending, + &Channel, + [this] ( CHostAddress InetAddr, CVector vecMessage ) { Socket.SendPacket ( vecMessage, InetAddr ); } ); + } + + ~CAuxiliaryMonoSender() + { + Stop(); + if ( pOpusEncoder != nullptr ) + { + opus_custom_encoder_destroy ( pOpusEncoder ); + } + } + + void Configure ( const CHostAddress& ServerAddress, + const CChannelCoreInfo& AuxiliaryChannelInfo, + OpusCustomMode* pNewOpusMode, + const EAudComprType eNewAudioCompressionType, + const int iNewCodedBytes, + const int iNewFrameSizeSamples, + const int iNewFrameSizeFactor, + const int iNewSockBufNumFrames, + const int iNewServerSockBufNumFrames, + const bool bNewDoAutoSockBufSize ) + { + const bool bEncoderChanged = ( pOpusMode != pNewOpusMode ); + + ChannelInfo = AuxiliaryChannelInfo; + pOpusMode = pNewOpusMode; + eAudioCompressionType = eNewAudioCompressionType; + iCodedBytes = iNewCodedBytes; + iFrameSizeSamples = iNewFrameSizeSamples; + iFrameSizeFactor = iNewFrameSizeFactor; + iSockBufNumFrames = iNewSockBufNumFrames; + iServerSockBufNumFrames = iNewServerSockBufNumFrames; + bDoAutoSockBufSize = bNewDoAutoSockBufSize; + + Channel.SetAddress ( ServerAddress ); + Channel.SetSockBufNumFrames ( iSockBufNumFrames ); + Channel.SetDoAutoSockBufSize ( bDoAutoSockBufSize ); + Channel.SetAudioStreamProperties ( eAudioCompressionType, iCodedBytes, iFrameSizeFactor, 1 ); + + if ( bEncoderChanged ) + { + if ( pOpusEncoder != nullptr ) + { + opus_custom_encoder_destroy ( pOpusEncoder ); + } + + int iOpusError; + pOpusEncoder = opus_custom_encoder_create ( pOpusMode, 1, &iOpusError ); + Q_UNUSED ( iOpusError ) + + opus_custom_encoder_ctl ( pOpusEncoder, OPUS_SET_VBR ( 0 ) ); + opus_custom_encoder_ctl ( pOpusEncoder, OPUS_SET_APPLICATION ( OPUS_APPLICATION_RESTRICTED_LOWDELAY ) ); + if ( eAudioCompressionType == CT_OPUS64 ) + { + opus_custom_encoder_ctl ( pOpusEncoder, OPUS_SET_PACKET_LOSS_PERC ( 35 ) ); + } + else + { + opus_custom_encoder_ctl ( pOpusEncoder, OPUS_SET_COMPLEXITY ( 1 ) ); + } + } + + opus_custom_encoder_ctl ( pOpusEncoder, OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCodedBytes, iFrameSizeSamples ) ) ); + + vecCodedData.Init ( iCodedBytes ); + vecReceiveData.Init ( iCodedBytes ); + vecZeros.Init ( iFrameSizeFactor * iFrameSizeSamples, 0 ); + } + + void Start() + { + Channel.SetEnable ( true ); + Socket.Start(); + } + + void Stop() + { + if ( Channel.IsEnabled() ) + { + Channel.SetEnable ( false ); + ConnLessProtocol.CreateCLDisconnection ( Channel.GetAddress() ); + } + } + + void Process ( const CVector& vecMonoSamples, const bool bMute ) + { + int iUnused; + for ( int i = 0, iOffset = 0; i < iFrameSizeFactor; i++, iOffset += iFrameSizeSamples ) + { + iUnused = opus_custom_encode ( pOpusEncoder, + bMute ? &vecZeros[iOffset] : &vecMonoSamples[iOffset], + iFrameSizeSamples, + &vecCodedData[0], + iCodedBytes ); + Channel.PrepAndSendPacket ( &Socket, vecCodedData, iCodedBytes ); + Channel.GetData ( vecReceiveData, iCodedBytes ); + } + Channel.UpdateSocketBufferSize(); + Q_UNUSED ( iUnused ) + } + + void SetChannelInfo ( const CChannelCoreInfo& AuxiliaryChannelInfo ) + { + ChannelInfo = AuxiliaryChannelInfo; + if ( Channel.IsEnabled() ) + { + Channel.SetRemoteInfo ( ChannelInfo ); + } + } + + void SetSocketBufferSettings ( const int iNewSockBufNumFrames, const int iNewServerSockBufNumFrames, const bool bNewDoAutoSockBufSize ) + { + iSockBufNumFrames = iNewSockBufNumFrames; + iServerSockBufNumFrames = iNewServerSockBufNumFrames; + bDoAutoSockBufSize = bNewDoAutoSockBufSize; + Channel.SetSockBufNumFrames ( iSockBufNumFrames ); + Channel.SetDoAutoSockBufSize ( bDoAutoSockBufSize ); + CreateServerJitterBufferMessage(); + } + +private: + void CreateServerJitterBufferMessage() + { + Channel.CreateJitBufMes ( bDoAutoSockBufSize ? AUTO_NET_BUF_SIZE_FOR_PROTOCOL : iServerSockBufNumFrames ); + } + + CChannel Channel; + bool bIPv6Available; + CHighPrioSocket Socket; + CProtocol ConnLessProtocol; + + CChannelCoreInfo ChannelInfo; + OpusCustomMode* pOpusMode; + OpusCustomEncoder* pOpusEncoder; + EAudComprType eAudioCompressionType; + int iCodedBytes; + int iFrameSizeSamples; + int iFrameSizeFactor; + int iSockBufNumFrames; + int iServerSockBufNumFrames; + bool bDoAutoSockBufSize; + CVector vecCodedData; + CVector vecReceiveData; + CVector vecZeros; +}; + CClient::CClient ( const quint16 iPortNumber, const quint16 iQosNumber, const bool bNoAutoJackConnect, @@ -69,9 +249,12 @@ CClient::CClient ( const quint16 iPortNumber, eAudioChannelConf ( CC_MONO ), iNumAudioChannels ( 1 ), bIsInitializationPhase ( true ), + bAuxiliaryPrimaryOnLeft ( true ), bMuteOutStream ( false ), fMuteOutStreamGain ( 1.0f ), bIPv6Available ( false ), + iSocketQosNumber ( iQosNumber ), + bDisableIPv6 ( bNDisableIPv6 ), Socket ( &Channel, iPortNumber, iQosNumber, "", bNDisableIPv6, bIPv6Available ), Sound ( AudioCallback, this, bNoAutoJackConnect, strNClientName ), iAudioInFader ( AUD_FADER_IN_MIDDLE ), @@ -244,6 +427,8 @@ CClient::~CClient() Sound.Stop(); } + StopAuxiliaryMonoSender(); + // free audio encoders and decoders opus_custom_encoder_destroy ( OpusEncoderMono ); opus_custom_decoder_destroy ( OpusDecoderMono ); @@ -259,6 +444,86 @@ CClient::~CClient() opus_custom_mode_destroy ( Opus64Mode ); } +EAudChanConf CClient::GetEffectiveAudioChannels() const { return IsAuxiliaryMonoSenderEnabled() ? CC_MONO : eAudioChannelConf; } + +bool CClient::CanUseAuxiliaryMonoSender() +{ + return ( eAudioQuality != AQ_RAW ) && ( Sound.GetNumInputChannels() >= 2 ) && ( Sound.GetLeftInputChannel() >= 0 ) && + ( Sound.GetRightInputChannel() >= 0 ) && ( Sound.GetLeftInputChannel() != Sound.GetRightInputChannel() ); +} + +void CClient::SetAuxiliaryMonoSenderEnabled ( const bool bEnable ) { SetAudioChannels ( bEnable ? CC_TWO_IN_STEREO_OUT : CC_STEREO ); } + +CChannelCoreInfo CClient::GetAuxiliaryChannelInfo() const +{ + const QString strSuffix = " Mic"; + + CChannelCoreInfo AuxiliaryChannelInfo = ChannelInfo; + AuxiliaryChannelInfo.strName = ChannelInfo.strName.left ( MAX_LEN_FADER_TAG - strSuffix.size() ) + strSuffix; + AuxiliaryChannelInfo.iInstrument = CInstPictures::GetVocalInstrument(); + return AuxiliaryChannelInfo; +} + +void CClient::ConfigureAuxiliaryMonoSender() +{ + if ( pAuxiliaryMonoSender != nullptr ) + { + pAuxiliaryMonoSender->Configure ( Channel.GetAddress(), + GetAuxiliaryChannelInfo(), + eAudioCompressionType == CT_OPUS ? OpusMode : Opus64Mode, + eAudioCompressionType, + iCeltNumCodedBytes, + iOPUSFrameSizeSamples, + iSndCrdFrameSizeFactor, + Channel.GetSockBufNumFrames(), + iServerSockBufNumFrames, + GetDoAutoSockBufSize() ); + } +} + +void CClient::StartAuxiliaryMonoSender() +{ + pAuxiliaryMonoSender.reset ( new CAuxiliaryMonoSender ( iSocketQosNumber, bDisableIPv6 ) ); + ConfigureAuxiliaryMonoSender(); + pAuxiliaryMonoSender->Start(); +} + +void CClient::StopAuxiliaryMonoSender() { pAuxiliaryMonoSender.reset(); } + +void CClient::SetRemoteInfo() +{ + Channel.SetRemoteInfo ( ChannelInfo ); + if ( pAuxiliaryMonoSender != nullptr ) + { + pAuxiliaryMonoSender->SetChannelInfo ( GetAuxiliaryChannelInfo() ); + } +} + +void CClient::SetSockBufNumFrames ( const int iNumBlocks, const bool bPreserve ) +{ + Channel.SetSockBufNumFrames ( iNumBlocks, bPreserve ); + if ( pAuxiliaryMonoSender != nullptr ) + { + pAuxiliaryMonoSender->SetSocketBufferSettings ( Channel.GetSockBufNumFrames(), iServerSockBufNumFrames, GetDoAutoSockBufSize() ); + } +} + +void CClient::SetServerSockBufNumFrames ( const int iNumBlocks ) +{ + iServerSockBufNumFrames = iNumBlocks; + + // if auto setting is disabled, inform the server about the new size + if ( !GetDoAutoSockBufSize() ) + { + Channel.CreateJitBufMes ( iServerSockBufNumFrames ); + } + + if ( pAuxiliaryMonoSender != nullptr ) + { + pAuxiliaryMonoSender->SetSocketBufferSettings ( Channel.GetSockBufNumFrames(), iServerSockBufNumFrames, GetDoAutoSockBufSize() ); + } +} + void CClient::OnSendProtMessage ( CVector vecMessage ) { // the protocol queries me to call the function to send the message @@ -301,6 +566,10 @@ void CClient::OnJittBufSizeChanged ( int iNewJitBufSize ) // the new server jitter buffer size since then a message would be sent // to the server which is incorrect. iServerSockBufNumFrames = iNewJitBufSize; + if ( pAuxiliaryMonoSender != nullptr ) + { + pAuxiliaryMonoSender->SetSocketBufferSettings ( Channel.GetSockBufNumFrames(), iServerSockBufNumFrames, true ); + } } } @@ -473,6 +742,11 @@ void CClient::SetDoAutoSockBufSize ( const bool bValue ) // inform the server about the change CreateServerJitterBufferMessage(); + + if ( pAuxiliaryMonoSender != nullptr ) + { + pAuxiliaryMonoSender->SetSocketBufferSettings ( Channel.GetSockBufNumFrames(), iServerSockBufNumFrames, bValue ); + } } // In order not to flood the server with gain or pan change messages, particularly when using @@ -698,6 +972,12 @@ void CClient::SetEnableOPUS64 ( const bool eNEnableOPUS64 ) void CClient::SetAudioQuality ( const EAudioQuality eNAudioQuality ) { + if ( IsAuxiliaryMonoSenderEnabled() && ( eNAudioQuality == AQ_RAW ) ) + { + eAudioChannelConf = CC_STEREO; + StopAuxiliaryMonoSender(); + } + // init with new parameter, if client was running then first // stop it and restart again after new initialization const bool bWasRunning = Sound.IsRunning(); @@ -718,6 +998,17 @@ void CClient::SetAudioQuality ( const EAudioQuality eNAudioQuality ) void CClient::SetAudioChannels ( const EAudChanConf eNAudChanConf ) { + if ( IsRunning() ) + { + return; + } + + EAudChanConf eRequestedAudioChannels = eNAudChanConf; + if ( eRequestedAudioChannels == CC_TWO_IN_STEREO_OUT && !CanUseAuxiliaryMonoSender() ) + { + eRequestedAudioChannels = CC_STEREO; + } + // init with new parameter, if client was running then first // stop it and restart again after new initialization const bool bWasRunning = Sound.IsRunning(); @@ -727,7 +1018,7 @@ void CClient::SetAudioChannels ( const EAudChanConf eNAudChanConf ) } // set new parameter - eAudioChannelConf = eNAudChanConf; + eAudioChannelConf = eRequestedAudioChannels; Init(); if ( bWasRunning ) @@ -748,6 +1039,12 @@ QString CClient::SetSndCrdDev ( const QString strNewDev ) const QString strError = Sound.SetDev ( strNewDev ); + if ( IsAuxiliaryMonoSenderEnabled() && !CanUseAuxiliaryMonoSender() ) + { + StopAuxiliaryMonoSender(); + eAudioChannelConf = CC_STEREO; + } + // init again because the sound card actual buffer size might // be changed on new device Init(); @@ -769,6 +1066,11 @@ QString CClient::SetSndCrdDev ( const QString strNewDev ) void CClient::SetSndCrdLeftInputChannel ( const int iNewChan ) { + if ( IsAuxiliaryMonoSenderEnabled() && ( iNewChan == Sound.GetRightInputChannel() ) ) + { + return; + } + // if client was running then first // stop it and restart again after new initialization const bool bWasRunning = Sound.IsRunning(); @@ -789,6 +1091,11 @@ void CClient::SetSndCrdLeftInputChannel ( const int iNewChan ) void CClient::SetSndCrdRightInputChannel ( const int iNewChan ) { + if ( IsAuxiliaryMonoSenderEnabled() && ( iNewChan == Sound.GetLeftInputChannel() ) ) + { + return; + } + // if client was running then first // stop it and restart again after new initialization const bool bWasRunning = Sound.IsRunning(); @@ -877,6 +1184,13 @@ void CClient::OnSndCrdReinitRequest ( int iSndCrdResetType ) strError = Sound.SetDev ( Sound.GetDev() ); } + if ( IsAuxiliaryMonoSenderEnabled() && !CanUseAuxiliaryMonoSender() ) + { + StopAuxiliaryMonoSender(); + eAudioChannelConf = CC_STEREO; + strError = tr ( "Two-in/Stereo-out was disabled because the active device no longer has two distinct input channels." ); + } + // init client object (must always be performed if the driver // was changed) Init(); @@ -1008,6 +1322,11 @@ void CClient::OnClientIDReceived ( int iServerChanID ) void CClient::OnRawAudioSupported() { + if ( IsAuxiliaryMonoSenderEnabled() ) + { + return; + } + if ( !bRawAudioIsSupported ) { const bool bWasRunning = Sound.IsRunning(); @@ -1029,6 +1348,11 @@ void CClient::OnRawAudioSupported() void CClient::Start() { + if ( IsAuxiliaryMonoSenderEnabled() && !CanUseAuxiliaryMonoSender() ) + { + throw CGenErr ( tr ( "Two-in/Stereo-out requires two distinct input channels and non-Max audio quality." ), tr ( "Audio Settings" ) ); + } + // init object Init(); @@ -1038,6 +1362,11 @@ void CClient::Start() // enable channel Channel.SetEnable ( true ); + if ( IsAuxiliaryMonoSenderEnabled() ) + { + StartAuxiliaryMonoSender(); + } + // start audio interface Sound.Start(); @@ -1052,6 +1381,8 @@ void CClient::Stop() // stop audio interface Sound.Stop(); + StopAuxiliaryMonoSender(); + // disable channel Channel.SetEnable ( false ); @@ -1092,6 +1423,8 @@ void CClient::Stop() void CClient::Init() { + const EAudChanConf eEffectiveAudioChannelConf = GetEffectiveAudioChannels(); + // check if possible frame size factors are supported const int iFraSizePreffered = SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_PREFERRED; const int iFraSizeDefault = SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_DEFAULT; @@ -1181,7 +1514,7 @@ void CClient::Init() { iOPUSFrameSizeSamples = DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES; - if ( eAudioChannelConf == CC_MONO ) + if ( eEffectiveAudioChannelConf == CC_MONO ) { CurOpusEncoder = OpusEncoderMono; CurOpusDecoder = OpusDecoderMono; @@ -1254,7 +1587,7 @@ void CClient::Init() { iOPUSFrameSizeSamples = SYSTEM_FRAME_SIZE_SAMPLES; - if ( eAudioChannelConf == CC_MONO ) + if ( eEffectiveAudioChannelConf == CC_MONO ) { CurOpusEncoder = Opus64EncoderMono; CurOpusDecoder = Opus64DecoderMono; @@ -1330,6 +1663,10 @@ void CClient::Init() vecCeltData.Init ( iCeltNumCodedBytes ); vecZeros.Init ( iStereoBlockSizeSam, 0 ); vecsStereoSndCrdMuteStream.Init ( iStereoBlockSizeSam ); + if ( IsAuxiliaryMonoSenderEnabled() ) + { + vecAuxiliaryMonoInput.Init ( iMonoBlockSizeSam ); + } // In case we are connected to a non raw audio server or we don't use raw audio we need to initialze the codec if ( CurOpusEncoder != nullptr ) @@ -1345,7 +1682,7 @@ void CClient::Init() Channel.SetAudioStreamProperties ( eAudioCompressionType, iCeltNumCodedBytes, iSndCrdFrameSizeFactor, iNumAudioChannels ); // init reverberation - AudioReverb.Init ( eAudioChannelConf, iStereoBlockSizeSam, SYSTEM_SAMPLE_RATE_HZ ); + AudioReverb.Init ( eEffectiveAudioChannelConf, iStereoBlockSizeSam, SYSTEM_SAMPLE_RATE_HZ ); // init the sound card conversion buffers if ( bSndCrdConversionBufferRequired ) @@ -1365,6 +1702,11 @@ void CClient::Init() SndCrdConversionBufferOut.Put ( vecZeros, iStereoBlockSizeSam ); } + if ( pAuxiliaryMonoSender != nullptr ) + { + ConfigureAuxiliaryMonoSender(); + } + // reset initialization phase flag and mute flag bIsInitializationPhase = true; } @@ -1439,59 +1781,76 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) SignalLevelMeter.Update ( vecsStereoSndCrd, iMonoBlockSizeSam, true ); #endif - // add reverberation effect if activated - if ( iReverbLevel != 0 ) + if ( IsAuxiliaryMonoSenderEnabled() && ( pAuxiliaryMonoSender != nullptr ) ) { - AudioReverb.Process ( vecsStereoSndCrd, bReverbOnLeftChan, static_cast ( iReverbLevel ) / AUD_REVERB_MAX / 4 ); - } + const bool bPrimaryOnLeft = bAuxiliaryPrimaryOnLeft.load(); + for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) + { + const int16_t iLeftSample = vecsStereoSndCrd[j]; + const int16_t iRightSample = vecsStereoSndCrd[j + 1]; + + vecsStereoSndCrd[i] = bPrimaryOnLeft ? iLeftSample : iRightSample; + vecAuxiliaryMonoInput[i] = bPrimaryOnLeft ? iRightSample : iLeftSample; + } - // apply pan (audio fader) and mix mono signals - if ( !( ( iAudioInFader == AUD_FADER_IN_MIDDLE ) && ( eAudioChannelConf == CC_STEREO ) ) ) + pAuxiliaryMonoSender->Process ( vecAuxiliaryMonoInput, bMuteOutStream ); + } + else { - // calculate pan gain in the range 0 to 1, where 0.5 is the middle position - const float fPan = static_cast ( iAudioInFader ) / AUD_FADER_IN_MAX; + // add reverberation effect if activated + if ( iReverbLevel != 0 ) + { + AudioReverb.Process ( vecsStereoSndCrd, bReverbOnLeftChan, static_cast ( iReverbLevel ) / AUD_REVERB_MAX / 4 ); + } - if ( eAudioChannelConf == CC_STEREO ) + // apply pan (audio fader) and mix mono signals + if ( !( ( iAudioInFader == AUD_FADER_IN_MIDDLE ) && ( eAudioChannelConf == CC_STEREO ) ) ) { - // for stereo only apply pan attenuation on one channel (same as pan in the server) - const float fGainL = MathUtils::GetLeftPan ( fPan, false ); - const float fGainR = MathUtils::GetRightPan ( fPan, false ); + // calculate pan gain in the range 0 to 1, where 0.5 is the middle position + const float fPan = static_cast ( iAudioInFader ) / AUD_FADER_IN_MAX; - for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) + if ( eAudioChannelConf == CC_STEREO ) { - // note that the gain is always <= 1, therefore a simple cast is - // ok since we never can get an overload - vecsStereoSndCrd[j + 1] = static_cast ( fGainR * vecsStereoSndCrd[j + 1] ); - vecsStereoSndCrd[j] = static_cast ( fGainL * vecsStereoSndCrd[j] ); - } - } - else - { - // for mono implement a cross-fade between channels and mix them, for - // mono-in/stereo-out use no attenuation in pan center - const float fGainL = MathUtils::GetLeftPan ( fPan, eAudioChannelConf != CC_MONO_IN_STEREO_OUT ); - const float fGainR = MathUtils::GetRightPan ( fPan, eAudioChannelConf != CC_MONO_IN_STEREO_OUT ); + // for stereo only apply pan attenuation on one channel (same as pan in the server) + const float fGainL = MathUtils::GetLeftPan ( fPan, false ); + const float fGainR = MathUtils::GetRightPan ( fPan, false ); - for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) + for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) + { + // note that the gain is always <= 1, therefore a simple cast is + // ok since we never can get an overload + vecsStereoSndCrd[j + 1] = static_cast ( fGainR * vecsStereoSndCrd[j + 1] ); + vecsStereoSndCrd[j] = static_cast ( fGainL * vecsStereoSndCrd[j] ); + } + } + else { - // note that we need the Float2Short for stereo pan mode - vecsStereoSndCrd[i] = Float2Short ( fGainL * vecsStereoSndCrd[j] + fGainR * vecsStereoSndCrd[j + 1] ); + // for mono implement a cross-fade between channels and mix them, for + // mono-in/stereo-out use no attenuation in pan center + const float fGainL = MathUtils::GetLeftPan ( fPan, eAudioChannelConf != CC_MONO_IN_STEREO_OUT ); + const float fGainR = MathUtils::GetRightPan ( fPan, eAudioChannelConf != CC_MONO_IN_STEREO_OUT ); + + for ( i = 0, j = 0; i < iMonoBlockSizeSam; i++, j += 2 ) + { + // note that we need the Float2Short for stereo pan mode + vecsStereoSndCrd[i] = Float2Short ( fGainL * vecsStereoSndCrd[j] + fGainR * vecsStereoSndCrd[j + 1] ); + } } } - } - // Support for mono-in/stereo-out mode: Per definition this mode works in - // full stereo mode at the transmission level. The only thing which is done - // is to mix both sound card inputs together and then put this signal on - // both stereo channels to be transmitted to the server. - if ( eAudioChannelConf == CC_MONO_IN_STEREO_OUT ) - { - // copy mono data in stereo sound card buffer (note that since the input - // and output is the same buffer, we have to start from the end not to - // overwrite input values) - for ( i = iMonoBlockSizeSam - 1, j = iStereoBlockSizeSam - 2; i >= 0; i--, j -= 2 ) + // Support for mono-in/stereo-out mode: Per definition this mode works in + // full stereo mode at the transmission level. The only thing which is done + // is to mix both sound card inputs together and then put this signal on + // both stereo channels to be transmitted to the server. + if ( eAudioChannelConf == CC_MONO_IN_STEREO_OUT ) { - vecsStereoSndCrd[j] = vecsStereoSndCrd[j + 1] = vecsStereoSndCrd[i]; + // copy mono data in stereo sound card buffer (note that since the input + // and output is the same buffer, we have to start from the end not to + // overwrite input values) + for ( i = iMonoBlockSizeSam - 1, j = iStereoBlockSizeSam - 2; i >= 0; i--, j -= 2 ) + { + vecsStereoSndCrd[j] = vecsStereoSndCrd[j + 1] = vecsStereoSndCrd[i]; + } } } @@ -1592,7 +1951,7 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) // check if channel is connected and if we do not have the initialization phase if ( Channel.IsConnected() && ( !bIsInitializationPhase ) ) { - if ( eAudioChannelConf == CC_MONO ) + if ( GetEffectiveAudioChannels() == CC_MONO ) { // copy mono data in stereo sound card buffer (note that since the input // and output is the same buffer, we have to start from the end not to diff --git a/src/client.h b/src/client.h index 06847dc6d4..2e057a4942 100644 --- a/src/client.h +++ b/src/client.h @@ -51,6 +51,8 @@ #include #include #include +#include +#include #ifdef USE_OPUS_SHARED_LIB # include "opus/opus_custom.h" #else @@ -142,6 +144,7 @@ class CClientChannel }; class CClientSettings; +class CAuxiliaryMonoSender; class CClient : public QObject { @@ -185,6 +188,13 @@ class CClient : public QObject EAudChanConf GetAudioChannels() const { return eAudioChannelConf; } void SetAudioChannels ( const EAudChanConf eNAudChanConf ); + bool IsAuxiliaryMonoSenderEnabled() const { return eAudioChannelConf == CC_TWO_IN_STEREO_OUT; } + void SetAuxiliaryMonoSenderEnabled ( const bool bEnable ); + bool CanUseAuxiliaryMonoSender(); + + bool IsAuxiliaryPrimaryOnLeft() const { return bAuxiliaryPrimaryOnLeft.load(); } + void SetAuxiliaryPrimaryOnLeft ( const bool bPrimaryOnLeft ) { bAuxiliaryPrimaryOnLeft.store ( bPrimaryOnLeft ); } + int GetAudioInFader() const { return iAudioInFader; } void SetAudioInFader ( const int iNV ) { iAudioInFader = iNV; } @@ -201,20 +211,11 @@ class CClient : public QObject void SetDoAutoSockBufSize ( const bool bValue ); bool GetDoAutoSockBufSize() const { return Channel.GetDoAutoSockBufSize(); } - void SetSockBufNumFrames ( const int iNumBlocks, const bool bPreserve = false ) { Channel.SetSockBufNumFrames ( iNumBlocks, bPreserve ); } + void SetSockBufNumFrames ( const int iNumBlocks, const bool bPreserve = false ); int GetSockBufNumFrames() { return Channel.GetSockBufNumFrames(); } - void SetServerSockBufNumFrames ( const int iNumBlocks ) - { - iServerSockBufNumFrames = iNumBlocks; - - // if auto setting is disabled, inform the server about the new size - if ( !GetDoAutoSockBufSize() ) - { - Channel.CreateJitBufMes ( iServerSockBufNumFrames ); - } - } - int GetServerSockBufNumFrames() { return iServerSockBufNumFrames; } + void SetServerSockBufNumFrames ( const int iNumBlocks ); + int GetServerSockBufNumFrames() { return iServerSockBufNumFrames; } int GetUploadRateKbps() { return Channel.GetUploadRateKbps(); } @@ -289,7 +290,7 @@ class CClient : public QObject void SetInputBoost ( const int iNewBoost ) { iInputBoost = iNewBoost; } - void SetRemoteInfo() { Channel.SetRemoteInfo ( ChannelInfo ); } + void SetRemoteInfo(); void CreateChatTextMes ( const QString& strChatText ) { Channel.CreateChatTextMes ( strChatText ); } @@ -340,9 +341,14 @@ class CClient : public QObject // callback function must be static, otherwise it does not work static void AudioCallback ( CVector& psData, void* arg ); - void Init(); - void ProcessSndCrdAudioData ( CVector& vecsStereoSndCrd ); - void ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ); + void Init(); + EAudChanConf GetEffectiveAudioChannels() const; + void ConfigureAuxiliaryMonoSender(); + void StartAuxiliaryMonoSender(); + void StopAuxiliaryMonoSender(); + CChannelCoreInfo GetAuxiliaryChannelInfo() const; + void ProcessSndCrdAudioData ( CVector& vecsStereoSndCrd ); + void ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ); int PreparePingMessage(); int EvaluatePingMessage ( const int iMs ); @@ -389,13 +395,18 @@ class CClient : public QObject EAudChanConf eAudioChannelConf; int iNumAudioChannels; bool bIsInitializationPhase; + std::atomic bAuxiliaryPrimaryOnLeft; bool bMuteOutStream; float fMuteOutStreamGain; CVector vecCeltData; bool bIPv6Available; // must be before Socket - passed by reference to Socket + const quint16 iSocketQosNumber; + const bool bDisableIPv6; CHighPrioSocket Socket; + std::unique_ptr pAuxiliaryMonoSender; + CSound Sound; CStereoSignalLevelMeter SignalLevelMeter; @@ -416,6 +427,7 @@ class CClient : public QObject CBuffer SndCrdConversionBufferOut; CVector vecDataConvBuf; CVector vecsStereoSndCrdMuteStream; + CVector vecAuxiliaryMonoInput; CVector vecZeros; bool bFraSiFactPrefSupported; diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp index b102c34724..397dba0a93 100644 --- a/src/clientdlg.cpp +++ b/src/clientdlg.cpp @@ -693,6 +693,39 @@ void CClientDlg::ManageDragNDrop ( QDropEvent* Event, const bool bCheckAccept ) void CClientDlg::UpdateRevSelection() { + if ( pClient->IsAuxiliaryMonoSenderEnabled() ) + { + const QString strPrimaryInput = "" + tr ( "Primary Input" ) + ": " + + tr ( "Selects which mapped input is sent by the visible client. " + "The other input is sent by the auxiliary client." ); + + lblAudioReverb->setText ( tr ( "Primary Input" ) ); + lblAudioReverb->setWhatsThis ( strPrimaryInput ); + sldAudioReverb->setVisible ( false ); + sldAudioReverb->setEnabled ( false ); + rbtReverbSelL->setVisible ( true ); + rbtReverbSelR->setVisible ( true ); + rbtReverbSelL->setWhatsThis ( strPrimaryInput ); + rbtReverbSelR->setWhatsThis ( strPrimaryInput ); + rbtReverbSelL->setAccessibleName ( tr ( "Left primary input" ) ); + rbtReverbSelR->setAccessibleName ( tr ( "Right primary input" ) ); + rbtReverbSelL->setChecked ( pClient->IsAuxiliaryPrimaryOnLeft() ); + rbtReverbSelR->setChecked ( !pClient->IsAuxiliaryPrimaryOnLeft() ); + + MainMixerBoard->SetDisplayPans ( false ); + return; + } + + const QString strReverbChannelSelection = + "" + tr ( "Reverb Channel Selection" ) + ": " + tr ( "Selects the input channel to which reverb is applied." ); + lblAudioReverb->setText ( tr ( "Reverb" ) ); + sldAudioReverb->setVisible ( true ); + sldAudioReverb->setEnabled ( true ); + rbtReverbSelL->setWhatsThis ( strReverbChannelSelection ); + rbtReverbSelR->setWhatsThis ( strReverbChannelSelection ); + rbtReverbSelL->setAccessibleName ( tr ( "Left channel selection for reverb" ) ); + rbtReverbSelR->setAccessibleName ( tr ( "Right channel selection for reverb" ) ); + if ( pClient->GetAudioChannels() == CC_STEREO ) { // for stereo make channel selection invisible since @@ -721,6 +754,30 @@ void CClientDlg::UpdateRevSelection() MainMixerBoard->SetDisplayPans ( pClient->GetAudioChannels() != CC_MONO ); } +void CClientDlg::OnReverbSelLClicked() +{ + if ( pClient->IsAuxiliaryMonoSenderEnabled() ) + { + pClient->SetAuxiliaryPrimaryOnLeft ( true ); + } + else + { + pClient->SetReverbOnLeftChan ( true ); + } +} + +void CClientDlg::OnReverbSelRClicked() +{ + if ( pClient->IsAuxiliaryMonoSenderEnabled() ) + { + pClient->SetAuxiliaryPrimaryOnLeft ( false ); + } + else + { + pClient->SetReverbOnLeftChan ( false ); + } +} + void CClientDlg::OnConnectDlgAccepted() { // We had an issue that the accepted signal was emit twice if a list item was double diff --git a/src/clientdlg.h b/src/clientdlg.h index 6a34cc66f0..6cafd40246 100644 --- a/src/clientdlg.h +++ b/src/clientdlg.h @@ -204,9 +204,9 @@ public slots: void OnAudioReverbValueChanged ( int value ) { pClient->SetReverbLevel ( value ); } - void OnReverbSelLClicked() { pClient->SetReverbOnLeftChan ( true ); } + void OnReverbSelLClicked(); - void OnReverbSelRClicked() { pClient->SetReverbOnLeftChan ( false ); } + void OnReverbSelRClicked(); void OnFeedbackDetectionChanged ( int state ) { ClientSettingsDlg.SetEnableFeedbackDetection ( state == Qt::Checked ); } diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index ad92a0f8e1..936a047830 100644 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -509,6 +509,7 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet cbxAudioChannels->addItem ( tr ( "Mono" ) ); // CC_MONO cbxAudioChannels->addItem ( tr ( "Mono-in/Stereo-out" ) ); // CC_MONO_IN_STEREO_OUT cbxAudioChannels->addItem ( tr ( "Stereo" ) ); // CC_STEREO + cbxAudioChannels->addItem ( tr ( "Two-in/Stereo-out" ) ); // CC_TWO_IN_STEREO_OUT cbxAudioChannels->setCurrentIndex ( static_cast ( pClient->GetAudioChannels() ) ); // Audio Quality combo box @@ -1226,12 +1227,14 @@ void CClientSettingsDlg::OnLInChanActivated ( int iChanIdx ) { pClient->SetSndCrdLeftInputChannel ( iChanIdx ); UpdateSoundDeviceChannelSelectionFrame(); + UpdateAuxiliaryMonoSender(); } void CClientSettingsDlg::OnRInChanActivated ( int iChanIdx ) { pClient->SetSndCrdRightInputChannel ( iChanIdx ); UpdateSoundDeviceChannelSelectionFrame(); + UpdateAuxiliaryMonoSender(); } void CClientSettingsDlg::OnLOutChanActivated ( int iChanIdx ) @@ -1249,6 +1252,7 @@ void CClientSettingsDlg::OnROutChanActivated ( int iChanIdx ) void CClientSettingsDlg::OnAudioChannelsActivated ( int iChanIdx ) { pClient->SetAudioChannels ( static_cast ( iChanIdx ) ); + cbxAudioChannels->setCurrentIndex ( static_cast ( pClient->GetAudioChannels() ) ); emit AudioChannelsChanged(); UpdateDisplay(); // upload rate will be changed } @@ -1365,6 +1369,18 @@ void CClientSettingsDlg::UpdateDisplay() UpdateJitterBufferFrame(); UpdateSoundCardFrame(); UpdateUploadRate(); + UpdateAuxiliaryMonoSender(); +} + +void CClientSettingsDlg::UpdateAuxiliaryMonoSender() +{ + const bool bAuxiliaryMonoSenderEnabled = pClient->IsAuxiliaryMonoSenderEnabled(); + + cbxAudioChannels->setEnabled ( !pClient->IsRunning() ); + cbxAudioQuality->setEnabled ( !bAuxiliaryMonoSenderEnabled ); + sldAudioPan->setEnabled ( !bAuxiliaryMonoSenderEnabled ); + lblAudioPan->setEnabled ( !bAuxiliaryMonoSenderEnabled ); + lblAudioPanValue->setEnabled ( !bAuxiliaryMonoSenderEnabled ); } void CClientSettingsDlg::UpdateDirectoryComboBox() diff --git a/src/clientsettingsdlg.h b/src/clientsettingsdlg.h index 7844f64117..7b537c480b 100644 --- a/src/clientsettingsdlg.h +++ b/src/clientsettingsdlg.h @@ -89,6 +89,7 @@ class CClientSettingsDlg : public CBaseDlg, private Ui_CClientSettingsDlgBase void UpdateSoundCardFrame(); void UpdateDirectoryComboBox(); void UpdateAudioFaderSlider(); + void UpdateAuxiliaryMonoSender(); QString GenSndCrdBufferDelayString ( const int iFrameSize, const QString strAddText = "" ); virtual void showEvent ( QShowEvent* ) override; diff --git a/src/settings.cpp b/src/settings.cpp index bdc453913e..4685d7a3e1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -621,7 +621,7 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, } // audio channels - if ( GetNumericIniSet ( IniXMLDocument, "client", "audiochannels", 0, 2 /* CC_STEREO */, iValue ) ) + if ( GetNumericIniSet ( IniXMLDocument, "client", "audiochannels", 0, 3 /* CC_TWO_IN_STEREO_OUT */, iValue ) ) { pClient->SetAudioChannels ( static_cast ( iValue ) ); } @@ -632,6 +632,11 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, pClient->SetAudioQuality ( static_cast ( iValue ) ); } + if ( GetFlagIniSet ( IniXMLDocument, "client", "auxiliaryprimaryleft", bValue ) ) + { + pClient->SetAuxiliaryPrimaryOnLeft ( bValue ); + } + // MIDI settings: Always read from XML first to preserve values if ( GetNumericIniSet ( IniXMLDocument, "client", "midichannel", 0, 16, iValue ) ) iMidiChannel = iValue; @@ -993,6 +998,9 @@ void CClientSettings::WriteSettingsToXML ( QDomDocument& IniXMLDocument, bool is // audio quality SetNumericIniSet ( IniXMLDocument, "client", "audioquality", static_cast ( pClient->GetAudioQuality() ) ); + // primary input for Two-in/Stereo-out mode + SetFlagIniSet ( IniXMLDocument, "client", "auxiliaryprimaryleft", pClient->IsAuxiliaryPrimaryOnLeft() ); + // custom directories for ( iIdx = 0; iIdx < MAX_NUM_SERVER_ADDR_ITEMS; iIdx++ ) { diff --git a/src/util.h b/src/util.h index 1185c56936..f0b2f7722e 100644 --- a/src/util.h +++ b/src/util.h @@ -504,7 +504,8 @@ enum EAudChanConf // used for settings -> enum values should be fixed CC_MONO = 0, CC_MONO_IN_STEREO_OUT = 1, - CC_STEREO = 2 + CC_STEREO = 2, + CC_TWO_IN_STEREO_OUT = 3 }; // Audio compression type enum ------------------------------------------------- @@ -844,6 +845,7 @@ class CInstPictures // per definition: the very first instrument is the "not used" instrument static int GetNotUsedInstrument() { return 0; } + static int GetVocalInstrument() { return 10; } static bool IsNotUsedInstrument ( const int iInstrument ) { return iInstrument == 0; } static int GetNumAvailableInst() { return GetTable().Size(); }