Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -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) ###

Expand Down
2 changes: 2 additions & 0 deletions src/channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
2 changes: 2 additions & 0 deletions src/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
Expand Down Expand Up @@ -272,6 +273,7 @@ public slots:
void ConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo );
void ChanInfoHasChanged();
void ClientIDReceived ( int iChanID );
void RawAudioSupported();
void MuteStateHasChanged ( int iChanID, bool bIsMuted );
void MuteStateHasChangedReceived ( int iChanID, bool bIsMuted );
void ReqChanInfo();
Expand Down
136 changes: 129 additions & 7 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ 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 )
{
int iOpusError;

Expand Down Expand Up @@ -131,6 +132,8 @@ 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 );
Expand Down Expand Up @@ -989,6 +992,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
Expand Down Expand Up @@ -1017,6 +1041,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 +1184,21 @@ void CClient::Init()
case AQ_HIGH:
iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE;
break;
case AQ_RAW:
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;
}
}
else
Expand All @@ -1175,6 +1218,21 @@ void CClient::Init()
case AQ_HIGH:
iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE;
break;
case AQ_RAW:
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;
}
}
}
Expand All @@ -1199,6 +1257,21 @@ void CClient::Init()
case AQ_HIGH:
iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY;
break;
case AQ_RAW:
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;
}
}
else
Expand All @@ -1218,6 +1291,21 @@ void CClient::Init()
case AQ_HIGH:
iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY;
break;
case AQ_RAW:
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;
}
}
}
Expand All @@ -1229,8 +1317,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 ( CurOpusEncoder != nullptr )
{
opus_custom_encoder_ctl ( CurOpusEncoder,
OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iOPUSFrameSizeSamples ) ) );
}

// inits for network and channel
vecbyNetwData.Init ( iCeltNumCodedBytes );
Expand Down Expand Up @@ -1391,9 +1483,10 @@ void CClient::ProcessAudioDataIntern ( CVector<int16_t>& vecsStereoSndCrd )

for ( i = 0, j = 0; i < iSndCrdFrameSizeFactor; i++, j += iNumAudioChannels * iOPUSFrameSizeSamples )
{
// OPUS encoding
// OPUS encoding or copying RAW audio?
if ( CurOpusEncoder != nullptr )
{
// OPUS encoding
Comment thread
pljones marked this conversation as resolved.
if ( bMuteOutStream )
{
iUnused = opus_custom_encode ( CurOpusEncoder, &vecZeros[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes );
Expand All @@ -1403,6 +1496,20 @@ void CClient::ProcessAudioDataIntern ( CVector<int16_t>& vecsStereoSndCrd )
iUnused = opus_custom_encode ( CurOpusEncoder, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes );
}
}
else if ( bRawAudioIsSupported )
{
// RAW audio
if ( bMuteOutStream )
{
// output muted - fill with silence
memset ( &vecCeltData[0], 0, iCeltNumCodedBytes );
}
else
{
// copy raw audio data
memcpy ( &vecCeltData[0], &vecsStereoSndCrd[j], iCeltNumCodedBytes );
}
}

// send coded audio through the network
Channel.PrepAndSendPacket ( &Socket, vecCeltData, iCeltNumCodedBytes );
Comment thread
dingodoppelt marked this conversation as resolved.
Expand Down Expand Up @@ -1437,11 +1544,26 @@ void CClient::ProcessAudioDataIntern ( CVector<int16_t>& vecsStereoSndCrd )
bJitterBufferOK = false;
}

// OPUS decoding
// 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
Expand Down Expand Up @@ -1488,7 +1610,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()"
Expand Down Expand Up @@ -1519,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 = fSystemBlockDurationMs / 2;
const float fAdditionalAudioCodecDelayMs = CurOpusDecoder != nullptr ? fSystemBlockDurationMs / 2 : 0.0f;

const float fTotalBufferDelayMs =
fDelayToFillNetworkPacketsMs + fTotalJitterBufferDelayMs + fTotalSoundCardDelayMs + fAdditionalAudioCodecDelayMs;
Expand Down
4 changes: 3 additions & 1 deletion src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,8 @@ class CClient : public QObject
QMutex MutexDriverReinit;

// server settings
int iServerSockBufNumFrames;
int iServerSockBufNumFrames;
bool bRawAudioIsSupported;

// for ping measurement
QElapsedTimer PreciseTime;
Expand Down Expand Up @@ -453,6 +454,7 @@ 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<uint16_t> vecLevelList );
void OnConClientListMesReceived ( 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
3 changes: 3 additions & 0 deletions src/global.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions src/protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

+--------------+
Expand Down Expand Up @@ -813,6 +818,10 @@ void CProtocol::ParseMessageBody ( const CVector<uint8_t>& vecbyMesBodyData, con
EvaluateSplitMessSupportedMes();
break;

case PROTMESSID_RAWAUDIO_SUPPORTED:
EvaluateRawAudioSupportedMes();
break;

case PROTMESSID_LICENCE_REQUIRED:
EvaluateLicenceRequiredMes ( vecbyMesBodyDataRef );
break;
Expand Down Expand Up @@ -1520,6 +1529,16 @@ bool CProtocol::EvaluateSplitMessSupportedMes()
return false; // no error
}

void CProtocol::CreateRawAudioSupportedMes() { CreateAndSendMessage ( PROTMESSID_RAWAUDIO_SUPPORTED, CVector<uint8_t> ( 0 ) ); }

bool CProtocol::EvaluateRawAudioSupportedMes()
{
// invoke message action
emit RawAudioSupported();

return false; // no error
}

void CProtocol::CreateLicenceRequiredMes ( const ELicenceType eLicenceType )
{
CVector<uint8_t> vecData ( 1 ); // 1 bytes of data
Expand Down
4 changes: 4 additions & 0 deletions src/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -124,6 +125,7 @@ class CProtocol : public QObject
void CreateReqNetwTranspPropsMes();
void CreateReqSplitMessSupportMes();
void CreateSplitMessSupportedMes();
void CreateRawAudioSupportedMes();
void CreateLicenceRequiredMes ( const ELicenceType eLicenceType );
void CreateOpusSupportedMes();

Expand Down Expand Up @@ -258,6 +260,7 @@ class CProtocol : public QObject
bool EvaluateReqNetwTranspPropsMes();
bool EvaluateReqSplitMessSupportMes();
bool EvaluateSplitMessSupportedMes();
bool EvaluateRawAudioSupportedMes();
bool EvaluateLicenceRequiredMes ( const CVector<uint8_t>& vecData );
bool EvaluateVersionAndOSMes ( const CVector<uint8_t>& vecData );
bool EvaluateRecorderStateMes ( const CVector<uint8_t>& vecData );
Expand Down Expand Up @@ -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 );
Expand Down
Loading
Loading