diff --git a/ChangeLog b/ChangeLog index 3013fac972..718d4daf94 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +### 3.11.1rawaudio ### + +- Add uncompressed audio transmission, improves latency by 2ms + ### 3.11.0dev <- NOTE: the release version number will be 3.12.0 ### - Extended SRV record support (#3556). diff --git a/Jamulus.pro b/Jamulus.pro index 28f3bacd74..e20ecd3da6 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -1,4 +1,4 @@ -VERSION = 3.11.0dev +VERSION = 3.11.1rawaudio # Using lrelease and embed_translations only works for Qt 5.12 or later. # See https://github.com/jamulussoftware/jamulus/pull/3288 for these changes. diff --git a/src/client.cpp b/src/client.cpp index 417f08a404..50dd40c7d7 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -137,6 +137,8 @@ CClient::CClient ( const quint16 iPortNumber, QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::VersionAndOSReceived ); + QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::OnVersionAndOSReceived ); + QObject::connect ( &Channel, &CChannel::RecorderStateReceived, this, &CClient::RecorderStateReceived ); QObject::connect ( &ConnLessProtocol, &CProtocol::CLMessReadyForSending, this, &CClient::OnSendCLProtMessage ); @@ -395,6 +397,31 @@ void CClient::OnConClientListMesReceived ( CVector vecChanInfo ) emit ConClientListMesReceived ( vecChanInfo ); } +void CClient::OnVersionAndOSReceived ( COSUtil::EOpSystemType eOSType, QString strVersion ) +{ +#if QT_VERSION >= QT_VERSION_CHECK( 5, 6, 0 ) + const bool bWasRunning = Sound.IsRunning(); + if ( bWasRunning ) + { + Sound.Stop(); + } + if ( QVersionNumber::compare ( QVersionNumber::fromString ( strVersion ), QVersionNumber ( 3, 11, 1 ) ) == 0 ) + { + bRawAudioIsSupported = true; + Init(); + } + else + { + bRawAudioIsSupported = false; + Init(); + } + if ( bWasRunning ) + { + Sound.Start(); + } +#endif +} + void CClient::CreateServerJitterBufferMessage() { // per definition in the client: if auto jitter buffer is enabled, both, @@ -1017,6 +1044,10 @@ void CClient::Stop() // disable channel Channel.SetEnable ( false ); + // Fall back to opus in case raw was used + bRawAudioIsSupported = false; + Init(); + // 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 @@ -1156,6 +1187,16 @@ void CClient::Init() case AQ_HIGH: iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE; break; + case AQ_RAW: + if ( bRawAudioIsSupported && Channel.IsEnabled() ) + { + iCeltNumCodedBytes = iNumAudioChannels * iOPUSFrameSizeSamples * sizeof ( int16_t ); + } + else + { + iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE; + } + break; } } else @@ -1175,6 +1216,16 @@ void CClient::Init() case AQ_HIGH: iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE; break; + case AQ_RAW: + if ( bRawAudioIsSupported && Channel.IsEnabled() ) + { + iCeltNumCodedBytes = iNumAudioChannels * iOPUSFrameSizeSamples * sizeof ( int16_t ); + } + else + { + iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE; + } + break; } } } @@ -1199,6 +1250,16 @@ void CClient::Init() case AQ_HIGH: iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY; break; + case AQ_RAW: + if ( bRawAudioIsSupported && Channel.IsEnabled() ) + { + iCeltNumCodedBytes = iNumAudioChannels * iOPUSFrameSizeSamples * sizeof ( int16_t ); + } + else + { + iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY; + } + break; } } else @@ -1218,6 +1279,16 @@ void CClient::Init() case AQ_HIGH: iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY; break; + case AQ_RAW: + if ( bRawAudioIsSupported && Channel.IsEnabled() ) + { + iCeltNumCodedBytes = iNumAudioChannels * iOPUSFrameSizeSamples * sizeof ( int16_t ); + } + else + { + iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY; + } + break; } } } @@ -1389,22 +1460,38 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) } } - for ( i = 0, j = 0; i < iSndCrdFrameSizeFactor; i++, j += iNumAudioChannels * iOPUSFrameSizeSamples ) + if ( eAudioQuality != AQ_RAW || !bRawAudioIsSupported ) { - // OPUS encoding - if ( CurOpusEncoder != nullptr ) + for ( i = 0, j = 0; i < iSndCrdFrameSizeFactor; i++, j += iNumAudioChannels * iOPUSFrameSizeSamples ) { - if ( bMuteOutStream ) + // OPUS encoding + if ( CurOpusEncoder != nullptr ) { - iUnused = opus_custom_encode ( CurOpusEncoder, &vecZeros[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes ); - } - else - { - iUnused = opus_custom_encode ( CurOpusEncoder, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes ); + if ( bMuteOutStream ) + { + iUnused = opus_custom_encode ( CurOpusEncoder, &vecZeros[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes ); + } + else + { + iUnused = opus_custom_encode ( CurOpusEncoder, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes ); + } } - } - // send coded audio through the network + // send coded audio through the network + Channel.PrepAndSendPacket ( &Socket, vecCeltData, iCeltNumCodedBytes ); + } + } + else + { + if ( !bMuteOutStream ) + { + // Send raw samples instead of OPUS + memcpy ( &vecCeltData[0], &vecsStereoSndCrd[0], iCeltNumCodedBytes ); + } + else + { + memset ( &vecCeltData[0], 0, iCeltNumCodedBytes ); + } Channel.PrepAndSendPacket ( &Socket, vecCeltData, iCeltNumCodedBytes ); } @@ -1423,24 +1510,41 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) // get pointer to coded data and manage the flags if ( bReceiveDataOk ) { - pCurCodedData = &vecbyNetwData[0]; - + if ( eAudioQuality == AQ_RAW && bRawAudioIsSupported ) + { + memcpy ( &vecsStereoSndCrd[0], &vecbyNetwData[0], iCeltNumCodedBytes ); + pCurCodedData = nullptr; + } + else + { + pCurCodedData = &vecbyNetwData[0]; + } // on any valid received packet, we clear the initialization phase flag bIsInitializationPhase = false; } else { - // for lost packets use null pointer as coded input data - pCurCodedData = nullptr; - + if ( eAudioQuality == AQ_RAW && bRawAudioIsSupported ) + { + memset ( &vecsStereoSndCrd[0], 0, iCeltNumCodedBytes ); + pCurCodedData = nullptr; + } + else + { + // for lost packets use null pointer as coded input data + pCurCodedData = nullptr; + } // invalidate the buffer OK status flag bJitterBufferOK = false; } - // OPUS decoding - if ( CurOpusDecoder != nullptr ) + if ( eAudioQuality != AQ_RAW || !bRawAudioIsSupported ) { - iUnused = opus_custom_decode ( CurOpusDecoder, pCurCodedData, iCeltNumCodedBytes, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples ); + // OPUS decoding + if ( CurOpusDecoder != nullptr ) + { + iUnused = opus_custom_decode ( CurOpusDecoder, pCurCodedData, iCeltNumCodedBytes, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples ); + } } } diff --git a/src/client.h b/src/client.h index 6139ef599e..dce919c675 100644 --- a/src/client.h +++ b/src/client.h @@ -403,6 +403,7 @@ class CClient : public QObject EMeterStyle eMeterStyle; bool bEnableAudioAlerts; bool bEnableOPUS64; + bool bRawAudioIsSupported; bool bJitterBufferOK; bool bEnableIPv6; @@ -456,6 +457,7 @@ protected slots: void OnMuteStateHasChangedReceived ( int iServerChanID, bool bIsMuted ); void OnCLChannelLevelListReceived ( CHostAddress InetAddr, CVector vecLevelList ); void OnConClientListMesReceived ( CVector vecChanInfo ); + void OnVersionAndOSReceived ( COSUtil::EOpSystemType eOSType, QString strVersion ); signals: void ConClientListMesReceived ( CVector vecChanInfo ); diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index d9f2e6ad11..f1c6af5dbe 100644 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -494,6 +494,7 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet cbxAudioQuality->addItem ( tr ( "Low" ) ); // AQ_LOW cbxAudioQuality->addItem ( tr ( "Normal" ) ); // AQ_NORMAL cbxAudioQuality->addItem ( tr ( "High" ) ); // AQ_HIGH + cbxAudioQuality->addItem ( tr ( "Max" ) ); // AQ_RAW cbxAudioQuality->setCurrentIndex ( static_cast ( pClient->GetAudioQuality() ) ); // GUI design (skin) combo box diff --git a/src/server.cpp b/src/server.cpp index a477771fde..ff85cd472b 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -874,27 +874,46 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) return; } + const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]; + const bool bIsRawAudio = ( iCeltNumCodedBytes == iClientFrameSizeSamples * vecNumAudioChannels[iChanCnt] * sizeof ( int16_t ) ); + // get pointer to coded data if ( eGetStat == GS_BUFFER_OK ) { - pCurCodedData = &vecvecbyCodedData[iChanCnt][0]; + if ( bIsRawAudio ) + { + memcpy ( &vecvecsData[iChanCnt][iOffset], &vecvecbyCodedData[iChanCnt][0], iCeltNumCodedBytes ); + } + else + { + pCurCodedData = &vecvecbyCodedData[iChanCnt][0]; + } } else { - // for lost packets use null pointer as coded input data - pCurCodedData = nullptr; + if ( bIsRawAudio ) + { + memset ( &vecvecsData[iChanCnt][iOffset], 0, iCeltNumCodedBytes ); + } + else + { + // for lost packets use null pointer as coded input data + pCurCodedData = nullptr; + } } - // OPUS decode received data stream - if ( CurOpusDecoder != nullptr ) + if ( !bIsRawAudio ) { - const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]; + // OPUS decode received data stream + if ( CurOpusDecoder != nullptr ) + { - iUnused = opus_custom_decode ( CurOpusDecoder, - pCurCodedData, - iCeltNumCodedBytes, - &vecvecsData[iChanCnt][iOffset], - iClientFrameSizeSamples ); + iUnused = opus_custom_decode ( CurOpusDecoder, + pCurCodedData, + iCeltNumCodedBytes, + &vecvecsData[iChanCnt][iOffset], + iClientFrameSizeSamples ); + } } } @@ -1154,27 +1173,41 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients DoubleFrameSizeConvBufOut[iCurChanID].GetAll ( vecsSendData, DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt] ); } - // OPUS encoding - if ( pCurOpusEncoder != nullptr ) + if ( iCeltNumCodedBytes != iClientFrameSizeSamples * vecNumAudioChannels[iChanCnt] * sizeof ( int16_t ) ) { - //### TODO: BEGIN ###// - // find a better place than this: the setting does not change all the time so for speed - // optimization it would be better to set it only if the network frame size is changed - opus_custom_encoder_ctl ( pCurOpusEncoder, - OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iClientFrameSizeSamples ) ) ); - //### TODO: END ###// + // OPUS encoding + if ( pCurOpusEncoder != nullptr ) + { + //### TODO: BEGIN ###// + // find a better place than this: the setting does not change all the time so for speed + // optimization it would be better to set it only if the network frame size is changed + opus_custom_encoder_ctl ( pCurOpusEncoder, + OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iClientFrameSizeSamples ) ) ); + //### TODO: END ###// + + for ( int iB = 0; iB < vecNumFrameSizeConvBlocks[iChanCnt]; iB++ ) + { + const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]; + + iUnused = opus_custom_encode ( pCurOpusEncoder, + &vecsSendData[iOffset], + iClientFrameSizeSamples, + &vecvecbyCodedData[iChanCnt][0], + iCeltNumCodedBytes ); + // send separate mix to current clients + vecChannels[iCurChanID].PrepAndSendPacket ( &Socket, vecvecbyCodedData[iChanCnt], iCeltNumCodedBytes ); + } + } + } + else + { for ( int iB = 0; iB < vecNumFrameSizeConvBlocks[iChanCnt]; iB++ ) { const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]; - iUnused = opus_custom_encode ( pCurOpusEncoder, - &vecsSendData[iOffset], - iClientFrameSizeSamples, - &vecvecbyCodedData[iChanCnt][0], - iCeltNumCodedBytes ); + memcpy ( &vecvecbyCodedData[iChanCnt][0], &vecsSendData[iOffset], iCeltNumCodedBytes ); - // send separate mix to current clients vecChannels[iCurChanID].PrepAndSendPacket ( &Socket, vecvecbyCodedData[iChanCnt], iCeltNumCodedBytes ); } } diff --git a/src/settings.cpp b/src/settings.cpp index af594c8bbe..daa2fd5736 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -605,7 +605,7 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, } // audio quality - if ( GetNumericIniSet ( IniXMLDocument, "client", "audioquality", 0, 2 /* AQ_HIGH */, iValue ) ) + if ( GetNumericIniSet ( IniXMLDocument, "client", "audioquality", 0, 3 /* AQ_RAW */, iValue ) ) { pClient->SetAudioQuality ( static_cast ( iValue ) ); } diff --git a/src/util.h b/src/util.h index e83e1f764a..ac13d8c37d 100644 --- a/src/util.h +++ b/src/util.h @@ -505,7 +505,8 @@ enum EAudioQuality // used for settings and the comobo box index -> enum values must be fixed! AQ_LOW = 0, AQ_NORMAL = 1, - AQ_HIGH = 2 + AQ_HIGH = 2, + AQ_RAW = 3 }; // Get data status enum --------------------------------------------------------