Skip to content

Commit 88f37d1

Browse files
committed
Add 16-bit PCM uncompressed audio transmission
1 parent e554c8c commit 88f37d1

8 files changed

Lines changed: 189 additions & 37 deletions

File tree

ChangeLog

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- Client: Fix occasional spurious version updated message display (#3691).
66
(contributed by @softins)
77

8+
- Add uncompressed audio transmission - dedicated to the memory of Hans Petter Selasky (1982 - 2023)
9+
(contributed by @dingodoppelt)
810

911
### 3.12.0 (2026-05-02) ###
1012

src/client.cpp

Lines changed: 121 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ CClient::CClient ( const quint16 iPortNumber,
7070
bJitterBufferOK ( true ),
7171
bEnableIPv6 ( bNEnableIPv6 ),
7272
bMuteMeInPersonalMix ( bNMuteMeInPersonalMix ),
73-
iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL )
73+
iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ),
74+
bRawAudioIsSupported ( false ),
75+
bUseRawAudio ( false )
7476
{
7577
int iOpusError;
7678

@@ -135,7 +137,7 @@ CClient::CClient ( const quint16 iPortNumber,
135137

136138
QObject::connect ( &Channel, &CChannel::LicenceRequired, this, &CClient::LicenceRequired );
137139

138-
QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::VersionAndOSReceived );
140+
QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::OnVersionAndOSReceived );
139141

140142
QObject::connect ( &Channel, &CChannel::RecorderStateReceived, this, &CClient::RecorderStateReceived );
141143

@@ -395,6 +397,32 @@ void CClient::OnConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo )
395397
emit ConClientListMesReceived ( vecChanInfo );
396398
}
397399

400+
void CClient::OnVersionAndOSReceived ( COSUtil::EOpSystemType eOSType, QString strVersion )
401+
{
402+
#if QT_VERSION >= QT_VERSION_CHECK( 5, 6, 0 )
403+
const bool bWasRunning = Sound.IsRunning();
404+
if ( bWasRunning )
405+
{
406+
Sound.Stop();
407+
}
408+
if ( QVersionNumber::compare ( QVersionNumber::fromString ( strVersion ), QVersionNumber ( 3, 11, 1 ) ) == 0 )
409+
{
410+
bRawAudioIsSupported = true;
411+
Init();
412+
}
413+
else
414+
{
415+
bRawAudioIsSupported = false;
416+
Init();
417+
}
418+
if ( bWasRunning )
419+
{
420+
Sound.Start();
421+
}
422+
#endif
423+
emit VersionAndOSReceived ( eOSType, strVersion );
424+
}
425+
398426
void CClient::CreateServerJitterBufferMessage()
399427
{
400428
// per definition in the client: if auto jitter buffer is enabled, both,
@@ -1017,6 +1045,11 @@ void CClient::Stop()
10171045
// disable channel
10181046
Channel.SetEnable ( false );
10191047

1048+
// Fall back to opus in case raw was used
1049+
bRawAudioIsSupported = false;
1050+
bUseRawAudio = false;
1051+
Init();
1052+
10201053
// wait for approx. 100 ms to make sure no audio packet is still in the
10211054
// network queue causing the channel to be reconnected right after having
10221055
// received the disconnect message (seems not to gain much, disconnect is
@@ -1156,6 +1189,16 @@ void CClient::Init()
11561189
case AQ_HIGH:
11571190
iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE;
11581191
break;
1192+
case AQ_RAW:
1193+
if ( bRawAudioIsSupported && Channel.IsEnabled() )
1194+
{
1195+
iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples;
1196+
}
1197+
else
1198+
{
1199+
iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE;
1200+
}
1201+
break;
11591202
}
11601203
}
11611204
else
@@ -1175,6 +1218,16 @@ void CClient::Init()
11751218
case AQ_HIGH:
11761219
iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE;
11771220
break;
1221+
case AQ_RAW:
1222+
if ( bRawAudioIsSupported && Channel.IsEnabled() )
1223+
{
1224+
iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples;
1225+
}
1226+
else
1227+
{
1228+
iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE;
1229+
}
1230+
break;
11781231
}
11791232
}
11801233
}
@@ -1199,6 +1252,16 @@ void CClient::Init()
11991252
case AQ_HIGH:
12001253
iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY;
12011254
break;
1255+
case AQ_RAW:
1256+
if ( bRawAudioIsSupported && Channel.IsEnabled() )
1257+
{
1258+
iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples;
1259+
}
1260+
else
1261+
{
1262+
iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY;
1263+
}
1264+
break;
12021265
}
12031266
}
12041267
else
@@ -1218,19 +1281,36 @@ void CClient::Init()
12181281
case AQ_HIGH:
12191282
iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY;
12201283
break;
1284+
case AQ_RAW:
1285+
if ( bRawAudioIsSupported && Channel.IsEnabled() )
1286+
{
1287+
iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples;
1288+
}
1289+
else
1290+
{
1291+
iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY;
1292+
}
1293+
break;
12211294
}
12221295
}
12231296
}
12241297

1298+
// determine whether to use raw audio
1299+
bUseRawAudio = bRawAudioIsSupported && eAudioQuality == AQ_RAW;
1300+
12251301
// calculate stereo (two channels) buffer size
12261302
iStereoBlockSizeSam = 2 * iMonoBlockSizeSam;
12271303

12281304
vecCeltData.Init ( iCeltNumCodedBytes );
12291305
vecZeros.Init ( iStereoBlockSizeSam, 0 );
12301306
vecsStereoSndCrdMuteStream.Init ( iStereoBlockSizeSam );
12311307

1232-
opus_custom_encoder_ctl ( CurOpusEncoder,
1233-
OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iOPUSFrameSizeSamples ) ) );
1308+
// In case we are connected to a non raw audio server or we don't use raw audio we need to initialze the codec
1309+
if ( !bUseRawAudio )
1310+
{
1311+
opus_custom_encoder_ctl ( CurOpusEncoder,
1312+
OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iOPUSFrameSizeSamples ) ) );
1313+
}
12341314

12351315
// inits for network and channel
12361316
vecbyNetwData.Init ( iCeltNumCodedBytes );
@@ -1391,19 +1471,33 @@ void CClient::ProcessAudioDataIntern ( CVector<int16_t>& vecsStereoSndCrd )
13911471

13921472
for ( i = 0, j = 0; i < iSndCrdFrameSizeFactor; i++, j += iNumAudioChannels * iOPUSFrameSizeSamples )
13931473
{
1394-
// OPUS encoding
1395-
if ( CurOpusEncoder != nullptr )
1474+
if ( !bUseRawAudio )
1475+
{
1476+
// OPUS encoding
1477+
if ( CurOpusEncoder != nullptr )
1478+
{
1479+
if ( bMuteOutStream )
1480+
{
1481+
iUnused = opus_custom_encode ( CurOpusEncoder, &vecZeros[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes );
1482+
}
1483+
else
1484+
{
1485+
iUnused = opus_custom_encode ( CurOpusEncoder, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes );
1486+
}
1487+
}
1488+
}
1489+
else
13961490
{
13971491
if ( bMuteOutStream )
13981492
{
1399-
iUnused = opus_custom_encode ( CurOpusEncoder, &vecZeros[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes );
1493+
memset ( &vecCeltData[0], 0, iCeltNumCodedBytes );
14001494
}
14011495
else
14021496
{
1403-
iUnused = opus_custom_encode ( CurOpusEncoder, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes );
1497+
// Send raw samples instead of OPUS
1498+
memcpy ( &vecCeltData[0], &vecsStereoSndCrd[j], iCeltNumCodedBytes );
14041499
}
14051500
}
1406-
14071501
// send coded audio through the network
14081502
Channel.PrepAndSendPacket ( &Socket, vecCeltData, iCeltNumCodedBytes );
14091503
}
@@ -1423,23 +1517,36 @@ void CClient::ProcessAudioDataIntern ( CVector<int16_t>& vecsStereoSndCrd )
14231517
// get pointer to coded data and manage the flags
14241518
if ( bReceiveDataOk )
14251519
{
1426-
pCurCodedData = &vecbyNetwData[0];
1520+
if ( eAudioQuality == AQ_RAW && bRawAudioIsSupported )
1521+
{
1522+
memcpy ( &vecsStereoSndCrd[j], &vecbyNetwData[0], iCeltNumCodedBytes );
1523+
pCurCodedData = nullptr;
1524+
}
1525+
else
1526+
{
1527+
pCurCodedData = &vecbyNetwData[0];
1528+
}
14271529

14281530
// on any valid received packet, we clear the initialization phase flag
14291531
bIsInitializationPhase = false;
14301532
}
14311533
else
14321534
{
1535+
if ( eAudioQuality == AQ_RAW && bRawAudioIsSupported )
1536+
{
1537+
memset ( &vecsStereoSndCrd[j], 0, iCeltNumCodedBytes );
1538+
}
1539+
14331540
// for lost packets use null pointer as coded input data
14341541
pCurCodedData = nullptr;
14351542

14361543
// invalidate the buffer OK status flag
14371544
bJitterBufferOK = false;
14381545
}
14391546

1440-
// OPUS decoding
1441-
if ( CurOpusDecoder != nullptr )
1547+
if ( !bUseRawAudio && CurOpusDecoder != nullptr )
14421548
{
1549+
// OPUS decoding
14431550
iUnused = opus_custom_decode ( CurOpusDecoder, pCurCodedData, iCeltNumCodedBytes, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples );
14441551
}
14451552
}
@@ -1488,7 +1595,7 @@ int CClient::EstimatedOverallDelay ( const int iPingTimeMs )
14881595
// length. Since that is usually not the case but the buffers are usually
14891596
// a bit larger than necessary, we introduce some factor for compensation.
14901597
// Consider the jitter buffer on the client and on the server side, too.
1491-
const float fTotalJitterBufferDelayMs = fSystemBlockDurationMs * ( GetSockBufNumFrames() + GetServerSockBufNumFrames() ) * 0.7f;
1598+
const float fTotalJitterBufferDelayMs = fSystemBlockDurationMs * ( GetSockBufNumFrames() + GetServerSockBufNumFrames() ) * JITTBUF_COMP_FACTOR;
14921599

14931600
// consider delay introduced by the sound card conversion buffer by using
14941601
// "GetSndCrdConvBufAdditionalDelayMonoBlSize()"
@@ -1519,7 +1626,7 @@ int CClient::EstimatedOverallDelay ( const int iPingTimeMs )
15191626
const float fDelayToFillNetworkPacketsMs = GetSystemMonoBlSize() * 1000.0f / SYSTEM_SAMPLE_RATE_HZ;
15201627

15211628
// OPUS additional delay at small frame sizes is half a frame size
1522-
const float fAdditionalAudioCodecDelayMs = fSystemBlockDurationMs / 2;
1629+
const float fAdditionalAudioCodecDelayMs = ( eAudioQuality == AQ_RAW && bRawAudioIsSupported ) ? 0.0f : fSystemBlockDurationMs / 2;
15231630

15241631
const float fTotalBufferDelayMs =
15251632
fDelayToFillNetworkPacketsMs + fTotalJitterBufferDelayMs + fTotalSoundCardDelayMs + fAdditionalAudioCodecDelayMs;

src/client.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ class CClient : public QObject
368368
bool bMuteOutStream;
369369
float fMuteOutStreamGain;
370370
CVector<unsigned char> vecCeltData;
371+
bool bUseRawAudio;
371372

372373
CHighPrioSocket Socket;
373374
CSound Sound;
@@ -410,7 +411,8 @@ class CClient : public QObject
410411
QMutex MutexDriverReinit;
411412

412413
// server settings
413-
int iServerSockBufNumFrames;
414+
int iServerSockBufNumFrames;
415+
bool bRawAudioIsSupported;
414416

415417
// for ping measurement
416418
QElapsedTimer PreciseTime;
@@ -456,6 +458,7 @@ protected slots:
456458
void OnMuteStateHasChangedReceived ( int iServerChanID, bool bIsMuted );
457459
void OnCLChannelLevelListReceived ( CHostAddress InetAddr, CVector<uint16_t> vecLevelList );
458460
void OnConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo );
461+
void OnVersionAndOSReceived ( COSUtil::EOpSystemType eOSType, QString strVersion );
459462

460463
signals:
461464
void ConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo );

src/clientsettingsdlg.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet
494494
cbxAudioQuality->addItem ( tr ( "Low" ) ); // AQ_LOW
495495
cbxAudioQuality->addItem ( tr ( "Normal" ) ); // AQ_NORMAL
496496
cbxAudioQuality->addItem ( tr ( "High" ) ); // AQ_HIGH
497+
cbxAudioQuality->addItem ( tr ( "Max" ) ); // AQ_RAW
497498
cbxAudioQuality->setCurrentIndex ( static_cast<int> ( pClient->GetAudioQuality() ) );
498499

499500
// GUI design (skin) combo box

src/global.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ LED bar: lbr
230230
// defines the time interval at which the ping time is updated in the GUI
231231
#define PING_UPDATE_TIME_MS 500 // ms
232232

233+
// defines a factor to compensate for larger than ideal jitter buffer sizes for estimated overall delay calculation
234+
#define JITTBUF_COMP_FACTOR 0.7f
235+
233236
// defines the time interval at which the ping time is updated for the server list
234237
#define PING_UPDATE_TIME_SERVER_LIST_MS 2500 // ms
235238

0 commit comments

Comments
 (0)