From 8c67ed405b5f1df95b53d71d593de9e3386cb42e Mon Sep 17 00:00:00 2001 From: Nils Brederlow <62596379+dingodoppelt@users.noreply.github.com> Date: Fri, 17 Apr 2026 19:59:27 +0200 Subject: [PATCH 1/8] Add 16-bit PCM uncompressed audio transmission --- ChangeLog | 2 + src/client.cpp | 135 ++++++++++++++++++++++++++++++++++---- src/client.h | 5 +- src/clientsettingsdlg.cpp | 1 + src/global.h | 3 + src/server.cpp | 75 +++++++++++++++------ src/settings.cpp | 2 +- src/util.h | 3 +- 8 files changed, 189 insertions(+), 37 deletions(-) diff --git a/ChangeLog b/ChangeLog index 01760a61b7..63c512f7a5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,7 @@ ### 3.12.0dev <- NOTE: the release version number will be 4.0.0 ### +- Add uncompressed audio transmission - dedicated to the memory of Hans Petter Selasky (1982 - 2023) + (contributed by @dingodoppelt) ### 3.12.0 (2026-05-02) ### diff --git a/src/client.cpp b/src/client.cpp index 417f08a404..a25acf18ca 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -70,7 +70,9 @@ CClient::CClient ( const quint16 iPortNumber, bJitterBufferOK ( true ), bEnableIPv6 ( bNEnableIPv6 ), bMuteMeInPersonalMix ( bNMuteMeInPersonalMix ), - iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ) + iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ), + bRawAudioIsSupported ( false ), + bUseRawAudio ( false ) { int iOpusError; @@ -135,7 +137,7 @@ CClient::CClient ( const quint16 iPortNumber, QObject::connect ( &Channel, &CChannel::LicenceRequired, this, &CClient::LicenceRequired ); - QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::VersionAndOSReceived ); + QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::OnVersionAndOSReceived ); QObject::connect ( &Channel, &CChannel::RecorderStateReceived, this, &CClient::RecorderStateReceived ); @@ -395,6 +397,32 @@ 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 + emit VersionAndOSReceived ( eOSType, strVersion ); +} + void CClient::CreateServerJitterBufferMessage() { // per definition in the client: if auto jitter buffer is enabled, both, @@ -1017,6 +1045,11 @@ void CClient::Stop() // disable channel Channel.SetEnable ( false ); + // Fall back to opus in case raw was used + bRawAudioIsSupported = false; + bUseRawAudio = 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 +1189,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 = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; + } + else + { + iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE; + } + break; } } else @@ -1175,6 +1218,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 = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; + } + else + { + iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE; + } + break; } } } @@ -1199,6 +1252,16 @@ void CClient::Init() case AQ_HIGH: iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY; break; + case AQ_RAW: + if ( bRawAudioIsSupported && Channel.IsEnabled() ) + { + iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; + } + else + { + iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY; + } + break; } } else @@ -1218,10 +1281,23 @@ void CClient::Init() case AQ_HIGH: iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY; break; + case AQ_RAW: + if ( bRawAudioIsSupported && Channel.IsEnabled() ) + { + iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; + } + else + { + iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY; + } + break; } } } + // determine whether to use raw audio + bUseRawAudio = bRawAudioIsSupported && eAudioQuality == AQ_RAW; + // calculate stereo (two channels) buffer size iStereoBlockSizeSam = 2 * iMonoBlockSizeSam; @@ -1229,8 +1305,12 @@ void CClient::Init() vecZeros.Init ( iStereoBlockSizeSam, 0 ); vecsStereoSndCrdMuteStream.Init ( iStereoBlockSizeSam ); - opus_custom_encoder_ctl ( CurOpusEncoder, - OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iOPUSFrameSizeSamples ) ) ); + // 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 ( !bUseRawAudio ) + { + opus_custom_encoder_ctl ( CurOpusEncoder, + OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iOPUSFrameSizeSamples ) ) ); + } // inits for network and channel vecbyNetwData.Init ( iCeltNumCodedBytes ); @@ -1391,19 +1471,33 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) for ( i = 0, j = 0; i < iSndCrdFrameSizeFactor; i++, j += iNumAudioChannels * iOPUSFrameSizeSamples ) { - // OPUS encoding - if ( CurOpusEncoder != nullptr ) + if ( !bUseRawAudio ) + { + // OPUS encoding + if ( CurOpusEncoder != nullptr ) + { + 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 ); + } + } + } + else { if ( bMuteOutStream ) { - iUnused = opus_custom_encode ( CurOpusEncoder, &vecZeros[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes ); + memset ( &vecCeltData[0], 0, iCeltNumCodedBytes ); } else { - iUnused = opus_custom_encode ( CurOpusEncoder, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes ); + // Send raw samples instead of OPUS + memcpy ( &vecCeltData[0], &vecsStereoSndCrd[j], iCeltNumCodedBytes ); } } - // send coded audio through the network Channel.PrepAndSendPacket ( &Socket, vecCeltData, iCeltNumCodedBytes ); } @@ -1423,13 +1517,26 @@ 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[j], &vecbyNetwData[0], iCeltNumCodedBytes ); + pCurCodedData = nullptr; + } + else + { + pCurCodedData = &vecbyNetwData[0]; + } // on any valid received packet, we clear the initialization phase flag bIsInitializationPhase = false; } else { + if ( eAudioQuality == AQ_RAW && bRawAudioIsSupported ) + { + memset ( &vecsStereoSndCrd[j], 0, iCeltNumCodedBytes ); + } + // for lost packets use null pointer as coded input data pCurCodedData = nullptr; @@ -1437,9 +1544,9 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) bJitterBufferOK = false; } - // OPUS decoding - if ( CurOpusDecoder != nullptr ) + if ( !bUseRawAudio && CurOpusDecoder != nullptr ) { + // OPUS decoding iUnused = opus_custom_decode ( CurOpusDecoder, pCurCodedData, iCeltNumCodedBytes, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples ); } } @@ -1488,7 +1595,7 @@ int CClient::EstimatedOverallDelay ( const int iPingTimeMs ) // length. Since that is usually not the case but the buffers are usually // a bit larger than necessary, we introduce some factor for compensation. // Consider the jitter buffer on the client and on the server side, too. - const float fTotalJitterBufferDelayMs = fSystemBlockDurationMs * ( GetSockBufNumFrames() + GetServerSockBufNumFrames() ) * 0.7f; + const float fTotalJitterBufferDelayMs = fSystemBlockDurationMs * ( GetSockBufNumFrames() + GetServerSockBufNumFrames() ) * JITTBUF_COMP_FACTOR; // consider delay introduced by the sound card conversion buffer by using // "GetSndCrdConvBufAdditionalDelayMonoBlSize()" @@ -1519,7 +1626,7 @@ int CClient::EstimatedOverallDelay ( const int iPingTimeMs ) const float fDelayToFillNetworkPacketsMs = GetSystemMonoBlSize() * 1000.0f / SYSTEM_SAMPLE_RATE_HZ; // OPUS additional delay at small frame sizes is half a frame size - const float fAdditionalAudioCodecDelayMs = fSystemBlockDurationMs / 2; + const float fAdditionalAudioCodecDelayMs = ( eAudioQuality == AQ_RAW && bRawAudioIsSupported ) ? 0.0f : fSystemBlockDurationMs / 2; const float fTotalBufferDelayMs = fDelayToFillNetworkPacketsMs + fTotalJitterBufferDelayMs + fTotalSoundCardDelayMs + fAdditionalAudioCodecDelayMs; diff --git a/src/client.h b/src/client.h index 6139ef599e..d332d166e9 100644 --- a/src/client.h +++ b/src/client.h @@ -368,6 +368,7 @@ class CClient : public QObject bool bMuteOutStream; float fMuteOutStreamGain; CVector vecCeltData; + bool bUseRawAudio; CHighPrioSocket Socket; CSound Sound; @@ -410,7 +411,8 @@ class CClient : public QObject QMutex MutexDriverReinit; // server settings - int iServerSockBufNumFrames; + int iServerSockBufNumFrames; + bool bRawAudioIsSupported; // for ping measurement QElapsedTimer PreciseTime; @@ -456,6 +458,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/global.h b/src/global.h index 100b4039de..e93837c831 100644 --- a/src/global.h +++ b/src/global.h @@ -230,6 +230,9 @@ LED bar: lbr // defines the time interval at which the ping time is updated in the GUI #define PING_UPDATE_TIME_MS 500 // ms +// defines a factor to compensate for larger than ideal jitter buffer sizes for estimated overall delay calculation +#define JITTBUF_COMP_FACTOR 0.7f + // defines the time interval at which the ping time is updated for the server list #define PING_UPDATE_TIME_SERVER_LIST_MS 2500 // ms diff --git a/src/server.cpp b/src/server.cpp index a477771fde..0cfe719937 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -754,7 +754,7 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) int iUnused; int iClientFrameSizeSamples = 0; // initialize to avoid a compiler warning OpusCustomDecoder* CurOpusDecoder; - unsigned char* pCurCodedData; + unsigned char* pCurCodedData = nullptr; // Only used with opus coding, nullptr in case of raw or packet loss // get actual ID of current channel const int iCurChanID = vecChanIDsCurConChan[iChanCnt]; @@ -874,22 +874,42 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) return; } + const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]; + // Recognise a raw audio packet by its size: + // The client doesn't pass a value for the selected audio quality implicitly. + // Rather the server is passed the length of the data sent by the client in iClientFrameSizeSamples. + // We know the exact size to expect from a client sending raw audio packets. + // The length is calculated in the client by: iNumAudioChannels * iOPUSFrameSizeSamples * sizeof ( int16_t ) + // iOPUSFrameSizeSamples can be either 64 or 128 (small network buffers enabled|disabled) + // iNumAudioChannels is either 1 for mono or 2 for stereo and mono-in/stereo-out + // sizeof ( int16_t ) is the size in bytes for the raw pcm audio data = 2 + // Sizes other than that are considered OPUS coded because those depend on hardcoded sizes in client.h + const bool bIsRawAudio = + ( iCeltNumCodedBytes == static_cast ( sizeof ( int16_t ) * iClientFrameSizeSamples * vecNumAudioChannels[iChanCnt] ) ); + // 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 ); + } } // OPUS decode received data stream - if ( CurOpusDecoder != nullptr ) + if ( !bIsRawAudio && CurOpusDecoder != nullptr ) { - const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]; - iUnused = opus_custom_decode ( CurOpusDecoder, pCurCodedData, iCeltNumCodedBytes, @@ -1154,25 +1174,40 @@ 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 != static_cast ( sizeof ( int16_t ) * iClientFrameSizeSamples * vecNumAudioChannels[iChanCnt] ) ) { - //### 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 -------------------------------------------------------- From 3ee311f627c9d02a4695b01bf2fc00f73b4f4802 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Sat, 9 May 2026 17:15:44 +0100 Subject: [PATCH 2/8] Add message to indicate raw audio support --- src/protocol.cpp | 19 +++++++++++++++++++ src/protocol.h | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/src/protocol.cpp b/src/protocol.cpp index 47be8bc265..a30534bc72 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -235,6 +235,11 @@ MESSAGES (with connection) note: does not have any data -> n = 0 +- PROTMESSID_RAWAUDIO_SUPPORTED: informs client that server supports raw (uncompressed) audio + + note: does not have any data -> n = 0 + + - PROTMESSID_RECORDER_STATE: notifies of changes in the server jam recorder state +--------------+ @@ -813,6 +818,10 @@ void CProtocol::ParseMessageBody ( const CVector& vecbyMesBodyData, con EvaluateSplitMessSupportedMes(); break; + case PROTMESSID_RAWAUDIO_SUPPORTED: + EvaluateRawAudioSupportedMes(); + break; + case PROTMESSID_LICENCE_REQUIRED: EvaluateLicenceRequiredMes ( vecbyMesBodyDataRef ); break; @@ -1520,6 +1529,16 @@ bool CProtocol::EvaluateSplitMessSupportedMes() return false; // no error } +void CProtocol::CreateRawAudioSupportedMes() { CreateAndSendMessage ( PROTMESSID_RAWAUDIO_SUPPORTED, CVector ( 0 ) ); } + +bool CProtocol::EvaluateRawAudioSupportedMes() +{ + // invoke message action + emit RawAudioSupported(); + + return false; // no error +} + void CProtocol::CreateLicenceRequiredMes ( const ELicenceType eLicenceType ) { CVector vecData ( 1 ); // 1 bytes of data diff --git a/src/protocol.h b/src/protocol.h index b76e66607b..03ac9f328f 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -62,6 +62,7 @@ #define PROTMESSID_RECORDER_STATE 33 // contains the state of the jam recorder (ERecorderState) #define PROTMESSID_REQ_SPLIT_MESS_SUPPORT 34 // request support for split messages #define PROTMESSID_SPLIT_MESS_SUPPORTED 35 // split messages are supported +#define PROTMESSID_RAWAUDIO_SUPPORTED 36 // raw (uncompressed) audio is supported // message IDs of connection less messages (CLM) // DEFINITION -> start at 1000, end at 1999, see IsConnectionLessMessageID @@ -124,6 +125,7 @@ class CProtocol : public QObject void CreateReqNetwTranspPropsMes(); void CreateReqSplitMessSupportMes(); void CreateSplitMessSupportedMes(); + void CreateRawAudioSupportedMes(); void CreateLicenceRequiredMes ( const ELicenceType eLicenceType ); void CreateOpusSupportedMes(); @@ -258,6 +260,7 @@ class CProtocol : public QObject bool EvaluateReqNetwTranspPropsMes(); bool EvaluateReqSplitMessSupportMes(); bool EvaluateSplitMessSupportedMes(); + bool EvaluateRawAudioSupportedMes(); bool EvaluateLicenceRequiredMes ( const CVector& vecData ); bool EvaluateVersionAndOSMes ( const CVector& vecData ); bool EvaluateRecorderStateMes ( const CVector& vecData ); @@ -321,6 +324,7 @@ public slots: void ReqNetTranspProps(); void ReqSplitMessSupport(); void SplitMessSupported(); + void RawAudioSupported(); void LicenceRequired ( ELicenceType eLicenceType ); void VersionAndOSReceived ( COSUtil::EOpSystemType eOSType, QString strVersion ); void RecorderStateReceived ( ERecorderState eRecorderState ); From 9ee6b060241ab9a9e59fcbdb2b576b654e056cc8 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Sat, 9 May 2026 17:26:43 +0100 Subject: [PATCH 3/8] Server informs new client that raw audio is supported --- src/channel.h | 1 + src/server.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/channel.h b/src/channel.h index 1567268307..9f2fa8fd23 100644 --- a/src/channel.h +++ b/src/channel.h @@ -149,6 +149,7 @@ class CChannel : public QObject } } void CreateClientIDMes ( const int iChanID ) { Protocol.CreateClientIDMes ( iChanID ); } + void CreateRawAudioSupportedMes() { Protocol.CreateRawAudioSupportedMes(); } void CreateReqNetwTranspPropsMes() { Protocol.CreateReqNetwTranspPropsMes(); } void CreateReqSplitMessSupportMes() { Protocol.CreateReqSplitMessSupportMes(); } void CreateReqJitBufMes() { Protocol.CreateReqJitBufMes(); } diff --git a/src/server.cpp b/src/server.cpp index 0cfe719937..36657fb1f6 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -383,6 +383,9 @@ void CServer::OnNewConnection ( int iChID, int iTotChans, CHostAddress RecHostAd // must be the first message to be sent for a new connection) vecChannels[iChID].CreateClientIDMes ( iChID ); + // inform the client that the server supports raw (uncompressed) audio + vecChannels[iChID].CreateRawAudioSupportedMes(); + // Send an empty channel list in order to force clients to reset their // audio mixer state. This is required to trigger clients to re-send their // gain levels upon reconnecting after server restarts. From e84627296af4b5c60c238edc20ab65a3b49acf3b Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Sat, 9 May 2026 17:59:17 +0100 Subject: [PATCH 4/8] Use RAW_SUPPORTED message instead of version number --- src/channel.cpp | 2 ++ src/channel.h | 1 + src/client.cpp | 51 +++++++++++++++++++++++-------------------------- src/client.h | 2 +- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/channel.cpp b/src/channel.cpp index 9bd409889a..47a492dcb9 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -83,6 +83,8 @@ CChannel::CChannel ( const bool bNIsServer ) : QObject::connect ( &Protocol, &CProtocol::ClientIDReceived, this, &CChannel::ClientIDReceived ); + QObject::connect ( &Protocol, &CProtocol::RawAudioSupported, this, &CChannel::RawAudioSupported ); + QObject::connect ( &Protocol, &CProtocol::MuteStateHasChangedReceived, this, &CChannel::MuteStateHasChangedReceived ); QObject::connect ( &Protocol, &CProtocol::ChangeChanInfo, this, &CChannel::OnChangeChanInfo ); diff --git a/src/channel.h b/src/channel.h index 9f2fa8fd23..096a88c9e9 100644 --- a/src/channel.h +++ b/src/channel.h @@ -273,6 +273,7 @@ public slots: void ConClientListMesReceived ( CVector vecChanInfo ); void ChanInfoHasChanged(); void ClientIDReceived ( int iChanID ); + void RawAudioSupported(); void MuteStateHasChanged ( int iChanID, bool bIsMuted ); void MuteStateHasChangedReceived ( int iChanID, bool bIsMuted ); void ReqChanInfo(); diff --git a/src/client.cpp b/src/client.cpp index a25acf18ca..6ce5e22dd2 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -133,11 +133,13 @@ CClient::CClient ( const quint16 iPortNumber, QObject::connect ( &Channel, &CChannel::ClientIDReceived, this, &CClient::OnClientIDReceived ); + QObject::connect ( &Channel, &CChannel::RawAudioSupported, this, &CClient::OnRawAudioSupported ); + QObject::connect ( &Channel, &CChannel::MuteStateHasChangedReceived, this, &CClient::OnMuteStateHasChangedReceived ); QObject::connect ( &Channel, &CChannel::LicenceRequired, this, &CClient::LicenceRequired ); - QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::OnVersionAndOSReceived ); + QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::VersionAndOSReceived ); QObject::connect ( &Channel, &CChannel::RecorderStateReceived, this, &CClient::RecorderStateReceived ); @@ -397,32 +399,6 @@ 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 - emit VersionAndOSReceived ( eOSType, strVersion ); -} - void CClient::CreateServerJitterBufferMessage() { // per definition in the client: if auto jitter buffer is enabled, both, @@ -1017,6 +993,27 @@ void CClient::OnClientIDReceived ( int iServerChanID ) emit ClientIDReceived ( iChanID ); } +void CClient::OnRawAudioSupported() +{ + if ( !bRawAudioIsSupported ) + { + const bool bWasRunning = Sound.IsRunning(); + + if ( bWasRunning ) + { + Sound.Stop(); + } + + bRawAudioIsSupported = true; + Init(); + + if ( bWasRunning ) + { + Sound.Start(); + } + } +} + void CClient::Start() { // init object diff --git a/src/client.h b/src/client.h index d332d166e9..6840017db5 100644 --- a/src/client.h +++ b/src/client.h @@ -455,10 +455,10 @@ protected slots: void OnControllerInFaderIsMute ( int iChannelIdx, bool bIsMute ); void OnControllerInMuteMyself ( bool bMute ); void OnClientIDReceived ( int iServerChanID ); + void OnRawAudioSupported(); 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 ); From e9ca54de5133fd8add8adbc7572db8a0d055dca4 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Sat, 9 May 2026 18:37:11 +0100 Subject: [PATCH 5/8] Correct order of initialisation --- src/client.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 6ce5e22dd2..6ae668c920 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -50,6 +50,7 @@ CClient::CClient ( const quint16 iPortNumber, bIsInitializationPhase ( true ), bMuteOutStream ( false ), fMuteOutStreamGain ( 1.0f ), + bUseRawAudio ( false ), Socket ( &Channel, iPortNumber, iQosNumber, "", bNEnableIPv6 ), Sound ( AudioCallback, this, bNoAutoJackConnect, strNClientName ), iAudioInFader ( AUD_FADER_IN_MIDDLE ), @@ -71,8 +72,7 @@ CClient::CClient ( const quint16 iPortNumber, bEnableIPv6 ( bNEnableIPv6 ), bMuteMeInPersonalMix ( bNMuteMeInPersonalMix ), iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ), - bRawAudioIsSupported ( false ), - bUseRawAudio ( false ) + bRawAudioIsSupported ( false ) { int iOpusError; From e4df31849194a60f4af2b5275be27c4358b57960 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Mon, 11 May 2026 11:55:17 +0100 Subject: [PATCH 6/8] Make naming of current OPUS encoder consistent --- src/server.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index 36657fb1f6..596608428f 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1130,7 +1130,7 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients } int iClientFrameSizeSamples = 0; // initialize to avoid a compiler warning - OpusCustomEncoder* pCurOpusEncoder = nullptr; + OpusCustomEncoder* CurOpusEncoder = nullptr; // get current number of CELT coded bytes const int iCeltNumCodedBytes = vecChannels[iCurChanID].GetCeltNumCodedBytes(); @@ -1142,11 +1142,11 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients if ( vecNumAudioChannels[iChanCnt] == 1 ) { - pCurOpusEncoder = OpusEncoderMono[iCurChanID]; + CurOpusEncoder = OpusEncoderMono[iCurChanID]; } else { - pCurOpusEncoder = OpusEncoderStereo[iCurChanID]; + CurOpusEncoder = OpusEncoderStereo[iCurChanID]; } } else if ( vecAudioComprType[iChanCnt] == CT_OPUS64 ) @@ -1155,11 +1155,11 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients if ( vecNumAudioChannels[iChanCnt] == 1 ) { - pCurOpusEncoder = Opus64EncoderMono[iCurChanID]; + CurOpusEncoder = Opus64EncoderMono[iCurChanID]; } else { - pCurOpusEncoder = Opus64EncoderStereo[iCurChanID]; + CurOpusEncoder = Opus64EncoderStereo[iCurChanID]; } } @@ -1180,12 +1180,12 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients if ( iCeltNumCodedBytes != static_cast ( sizeof ( int16_t ) * iClientFrameSizeSamples * vecNumAudioChannels[iChanCnt] ) ) { // OPUS encoding - if ( pCurOpusEncoder != nullptr ) + if ( CurOpusEncoder != 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_custom_encoder_ctl ( CurOpusEncoder, OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iClientFrameSizeSamples ) ) ); //### TODO: END ###// @@ -1193,7 +1193,7 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients { const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]; - iUnused = opus_custom_encode ( pCurOpusEncoder, + iUnused = opus_custom_encode ( CurOpusEncoder, &vecsSendData[iOffset], iClientFrameSizeSamples, &vecvecbyCodedData[iChanCnt][0], From 39034225c578fd2e6e78cc31dca8247882b275e9 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Mon, 11 May 2026 14:53:43 +0100 Subject: [PATCH 7/8] Refactor client code --- src/client.cpp | 94 ++++++++++++++++++++++++++++++-------------------- src/client.h | 1 - 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 6ae668c920..13eac289b6 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -50,7 +50,6 @@ CClient::CClient ( const quint16 iPortNumber, bIsInitializationPhase ( true ), bMuteOutStream ( false ), fMuteOutStreamGain ( 1.0f ), - bUseRawAudio ( false ), Socket ( &Channel, iPortNumber, iQosNumber, "", bNEnableIPv6 ), Sound ( AudioCallback, this, bNoAutoJackConnect, strNClientName ), iAudioInFader ( AUD_FADER_IN_MIDDLE ), @@ -1044,7 +1043,6 @@ void CClient::Stop() // Fall back to opus in case raw was used bRawAudioIsSupported = false; - bUseRawAudio = false; Init(); // wait for approx. 100 ms to make sure no audio packet is still in the @@ -1187,12 +1185,17 @@ void CClient::Init() iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE; break; case AQ_RAW: - if ( bRawAudioIsSupported && Channel.IsEnabled() ) + if ( bRawAudioIsSupported ) { + // no OPUS encoding or decoding + CurOpusEncoder = nullptr; + CurOpusDecoder = nullptr; + iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; } else { + // fall back to highest OPUS quality iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE; } break; @@ -1216,12 +1219,17 @@ void CClient::Init() iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE; break; case AQ_RAW: - if ( bRawAudioIsSupported && Channel.IsEnabled() ) + if ( bRawAudioIsSupported ) { + // no OPUS encoding or decoding + CurOpusEncoder = nullptr; + CurOpusDecoder = nullptr; + iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; } else { + // fall back to highest OPUS quality iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE; } break; @@ -1250,12 +1258,17 @@ void CClient::Init() iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY; break; case AQ_RAW: - if ( bRawAudioIsSupported && Channel.IsEnabled() ) + if ( bRawAudioIsSupported ) { + // no OPUS encoding or decoding + CurOpusEncoder = nullptr; + CurOpusDecoder = nullptr; + iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; } else { + // fall back to highest OPUS quality iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY; } break; @@ -1279,12 +1292,17 @@ void CClient::Init() iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY; break; case AQ_RAW: - if ( bRawAudioIsSupported && Channel.IsEnabled() ) + if ( bRawAudioIsSupported ) { + // no OPUS encoding or decoding + CurOpusEncoder = nullptr; + CurOpusDecoder = nullptr; + iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; } else { + // fall back to highest OPUS quality iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY; } break; @@ -1292,9 +1310,6 @@ void CClient::Init() } } - // determine whether to use raw audio - bUseRawAudio = bRawAudioIsSupported && eAudioQuality == AQ_RAW; - // calculate stereo (two channels) buffer size iStereoBlockSizeSam = 2 * iMonoBlockSizeSam; @@ -1303,7 +1318,7 @@ void CClient::Init() vecsStereoSndCrdMuteStream.Init ( iStereoBlockSizeSam ); // 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 ( !bUseRawAudio ) + if ( CurOpusEncoder != nullptr ) { opus_custom_encoder_ctl ( CurOpusEncoder, OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iOPUSFrameSizeSamples ) ) ); @@ -1468,33 +1483,34 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) for ( i = 0, j = 0; i < iSndCrdFrameSizeFactor; i++, j += iNumAudioChannels * iOPUSFrameSizeSamples ) { - if ( !bUseRawAudio ) + // OPUS encoding or copying RAW audio? + if ( CurOpusEncoder != nullptr ) { // OPUS encoding - if ( CurOpusEncoder != nullptr ) + if ( bMuteOutStream ) { - 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 ); - } + iUnused = opus_custom_encode ( CurOpusEncoder, &vecZeros[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes ); + } + else + { + iUnused = opus_custom_encode ( CurOpusEncoder, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes ); } } - else + else if ( bRawAudioIsSupported ) { + // RAW audio if ( bMuteOutStream ) { + // output muted - fill with silence memset ( &vecCeltData[0], 0, iCeltNumCodedBytes ); } else { - // Send raw samples instead of OPUS + // copy raw audio data memcpy ( &vecCeltData[0], &vecsStereoSndCrd[j], iCeltNumCodedBytes ); } } + // send coded audio through the network Channel.PrepAndSendPacket ( &Socket, vecCeltData, iCeltNumCodedBytes ); } @@ -1514,26 +1530,13 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) // get pointer to coded data and manage the flags if ( bReceiveDataOk ) { - if ( eAudioQuality == AQ_RAW && bRawAudioIsSupported ) - { - memcpy ( &vecsStereoSndCrd[j], &vecbyNetwData[0], iCeltNumCodedBytes ); - pCurCodedData = nullptr; - } - else - { - pCurCodedData = &vecbyNetwData[0]; - } + pCurCodedData = &vecbyNetwData[0]; // on any valid received packet, we clear the initialization phase flag bIsInitializationPhase = false; } else { - if ( eAudioQuality == AQ_RAW && bRawAudioIsSupported ) - { - memset ( &vecsStereoSndCrd[j], 0, iCeltNumCodedBytes ); - } - // for lost packets use null pointer as coded input data pCurCodedData = nullptr; @@ -1541,11 +1544,26 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) bJitterBufferOK = false; } - if ( !bUseRawAudio && CurOpusDecoder != nullptr ) + // OPUS decoding or copying RAW audio? + if ( CurOpusDecoder != nullptr ) { // OPUS decoding iUnused = opus_custom_decode ( CurOpusDecoder, pCurCodedData, iCeltNumCodedBytes, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples ); } + else if ( bRawAudioIsSupported ) + { + // RAW audio + if ( pCurCodedData != nullptr ) + { + // copy raw audio data + memcpy ( &vecsStereoSndCrd[j], pCurCodedData, iCeltNumCodedBytes ); + } + else + { + // missing audio - fill with silence + memset ( &vecsStereoSndCrd[j], 0, iCeltNumCodedBytes ); + } + } } // for muted stream we have to add our local data here @@ -1623,7 +1641,7 @@ int CClient::EstimatedOverallDelay ( const int iPingTimeMs ) const float fDelayToFillNetworkPacketsMs = GetSystemMonoBlSize() * 1000.0f / SYSTEM_SAMPLE_RATE_HZ; // OPUS additional delay at small frame sizes is half a frame size - const float fAdditionalAudioCodecDelayMs = ( eAudioQuality == AQ_RAW && bRawAudioIsSupported ) ? 0.0f : fSystemBlockDurationMs / 2; + const float fAdditionalAudioCodecDelayMs = CurOpusDecoder != nullptr ? fSystemBlockDurationMs / 2 : 0.0f; const float fTotalBufferDelayMs = fDelayToFillNetworkPacketsMs + fTotalJitterBufferDelayMs + fTotalSoundCardDelayMs + fAdditionalAudioCodecDelayMs; diff --git a/src/client.h b/src/client.h index 6840017db5..c71fc414ab 100644 --- a/src/client.h +++ b/src/client.h @@ -368,7 +368,6 @@ class CClient : public QObject bool bMuteOutStream; float fMuteOutStreamGain; CVector vecCeltData; - bool bUseRawAudio; CHighPrioSocket Socket; CSound Sound; From 583c61b4b22f8263173723eed56cb5dd21834230 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Mon, 11 May 2026 21:15:00 +0100 Subject: [PATCH 8/8] Refactor server DecodeReceiveData --- src/server.cpp | 51 +++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index 596608428f..b26ae5d401 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -757,7 +757,7 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) int iUnused; int iClientFrameSizeSamples = 0; // initialize to avoid a compiler warning OpusCustomDecoder* CurOpusDecoder; - unsigned char* pCurCodedData = nullptr; // Only used with opus coding, nullptr in case of raw or packet loss + unsigned char* pCurCodedData; // get actual ID of current channel const int iCurChanID = vecChanIDsCurConChan[iChanCnt]; @@ -877,7 +877,17 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) return; } - const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]; + // get pointer to coded data + if ( eGetStat == GS_BUFFER_OK ) + { + pCurCodedData = &vecvecbyCodedData[iChanCnt][0]; + } + else + { + // for lost packets use null pointer as coded input data + pCurCodedData = nullptr; + } + // Recognise a raw audio packet by its size: // The client doesn't pass a value for the selected audio quality implicitly. // Rather the server is passed the length of the data sent by the client in iClientFrameSizeSamples. @@ -890,34 +900,29 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) const bool bIsRawAudio = ( iCeltNumCodedBytes == static_cast ( sizeof ( int16_t ) * iClientFrameSizeSamples * vecNumAudioChannels[iChanCnt] ) ); - // get pointer to coded data - if ( eGetStat == GS_BUFFER_OK ) + const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]; + + if ( !bIsRawAudio ) { - if ( bIsRawAudio ) - { - memcpy ( &vecvecsData[iChanCnt][iOffset], &vecvecbyCodedData[iChanCnt][0], iCeltNumCodedBytes ); - } - else + // OPUS decode received data stream + if ( CurOpusDecoder != nullptr ) { - pCurCodedData = &vecvecbyCodedData[iChanCnt][0]; + iUnused = opus_custom_decode ( CurOpusDecoder, + pCurCodedData, + iCeltNumCodedBytes, + &vecvecsData[iChanCnt][iOffset], + iClientFrameSizeSamples ); } } - else + else if ( pCurCodedData != nullptr ) { - if ( bIsRawAudio ) - { - memset ( &vecvecsData[iChanCnt][iOffset], 0, iCeltNumCodedBytes ); - } + // copy received raw data stream + memcpy ( &vecvecsData[iChanCnt][iOffset], pCurCodedData, iCeltNumCodedBytes ); } - - // OPUS decode received data stream - if ( !bIsRawAudio && CurOpusDecoder != nullptr ) + else { - iUnused = opus_custom_decode ( CurOpusDecoder, - pCurCodedData, - iCeltNumCodedBytes, - &vecvecsData[iChanCnt][iOffset], - iClientFrameSizeSamples ); + // lost packet - fill with silence + memset ( &vecvecsData[iChanCnt][iOffset], 0, iCeltNumCodedBytes ); } }