Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a45aded
Introduce raw audio setting in UI. Opus encoding in client now depend…
dingodoppelt Apr 17, 2026
73d983f
addition to last commit
dingodoppelt Apr 17, 2026
03f7ab3
client still sends raw audio to stock servers and causes garbled soun…
dingodoppelt Apr 17, 2026
9a05dd1
make client opus decoding depend on audio quality (raw audio)
dingodoppelt Apr 17, 2026
0f22ee9
no crashes but bad noise on raw audio setting
dingodoppelt Apr 17, 2026
7f47dd8
first working version (needs testing)
dingodoppelt Apr 17, 2026
7734d75
fix style
dingodoppelt Apr 17, 2026
fa61b55
fix style
dingodoppelt Apr 17, 2026
68deea9
Merge branch 'jamulussoftware:main' into rawaudio-dev
dingodoppelt Apr 17, 2026
5a467e0
fix mute myself
dingodoppelt Apr 17, 2026
8f53817
remove redundant include
dingodoppelt Apr 19, 2026
c77029d
Change audio quality option from 'Raw' to 'Max'
dingodoppelt Apr 20, 2026
d36448a
Only send raw audio if the server supports it (checked by version num…
dingodoppelt Apr 20, 2026
8079eed
fix style
dingodoppelt Apr 20, 2026
5c73b6a
fix style
dingodoppelt Apr 20, 2026
a597843
change version
dingodoppelt Apr 20, 2026
7fa20b7
Update changelog
dingodoppelt Apr 20, 2026
b602efa
fix changelog
dingodoppelt Apr 20, 2026
42af9ab
Fix noise issue with rawaudio servers when Max audio quality is selected
dingodoppelt Apr 20, 2026
1cb8a22
Merge branch 'rawaudio-dev' into autobuild-rawaudio
dingodoppelt Apr 20, 2026
c3b47fd
fix style (I'll never learn)
dingodoppelt Apr 20, 2026
1544eb7
fix last commit
dingodoppelt Apr 20, 2026
6fd09bb
Merge branch 'rawaudio-dev' into autobuild-rawaudio
dingodoppelt Apr 20, 2026
23399c5
Disable bRawAudioIsSupported when leaving a server
dingodoppelt Apr 21, 2026
5b8a709
limit rawaudio to server version 3.11.1 only
dingodoppelt Apr 21, 2026
2aad9b5
opus fallback on disconnect
dingodoppelt Apr 21, 2026
754d7b0
disable raw setting when unconnected to join a new server with forced…
dingodoppelt Apr 21, 2026
14d83ed
stop sound before calling client init
dingodoppelt Apr 21, 2026
d4da62c
Merge branch 'jamulussoftware:main' into rawaudio-dev
dingodoppelt Apr 22, 2026
d630234
make raw audio quality setting persistent in config file
dingodoppelt Apr 22, 2026
32fca21
remove redundant setting of bRawAudioIsSupported
dingodoppelt Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
2 changes: 1 addition & 1 deletion Jamulus.pro
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
142 changes: 123 additions & 19 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -395,6 +397,31 @@ void CClient::OnConClientListMesReceived ( CVector<CChannelInfo> 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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
}
}
Expand All @@ -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
Expand All @@ -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;
}
}
}
Expand Down Expand Up @@ -1389,22 +1460,38 @@ void CClient::ProcessAudioDataIntern ( CVector<int16_t>& 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 );
}

Expand All @@ -1423,24 +1510,41 @@ void CClient::ProcessAudioDataIntern ( CVector<int16_t>& 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 );
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ class CClient : public QObject
EMeterStyle eMeterStyle;
bool bEnableAudioAlerts;
bool bEnableOPUS64;
bool bRawAudioIsSupported;

bool bJitterBufferOK;
bool bEnableIPv6;
Expand Down Expand Up @@ -456,6 +457,7 @@ protected slots:
void OnMuteStateHasChangedReceived ( int iServerChanID, bool bIsMuted );
void OnCLChannelLevelListReceived ( CHostAddress InetAddr, CVector<uint16_t> vecLevelList );
void OnConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo );
void OnVersionAndOSReceived ( COSUtil::EOpSystemType eOSType, QString strVersion );

signals:
void ConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo );
Expand Down
1 change: 1 addition & 0 deletions src/clientsettingsdlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> ( pClient->GetAudioQuality() ) );

// GUI design (skin) combo box
Expand Down
83 changes: 58 additions & 25 deletions src/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
}
}

Expand Down Expand Up @@ -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 );
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<EAudioQuality> ( iValue ) );
}
Expand Down
3 changes: 2 additions & 1 deletion src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
dingodoppelt marked this conversation as resolved.
};

// Get data status enum --------------------------------------------------------
Expand Down
Loading