@@ -46,6 +46,11 @@ cvar_t *cl_voipCaptureMult;
4646cvar_t * cl_voipShowMeter ;
4747cvar_t * cl_voipProtocol ;
4848cvar_t * cl_voip ;
49+ cvar_t * cl_voipBitrate ;
50+ cvar_t * cl_voipComplexity ;
51+ cvar_t * cl_voipFEC ;
52+ cvar_t * cl_voipPacketLossRate ;
53+ cvar_t * cl_voipAdaptive ;
4954#endif
5055
5156#ifdef USE_RENDERER_DLOPEN
@@ -198,7 +203,7 @@ static void CL_UpdateVoipIgnore(const char *idstr, qboolean ignore) {
198203 if ((* idstr >= '0' ) && (* idstr <= '9' )) {
199204 const int id = atoi (idstr );
200205 if ((id >= 0 ) && (id < MAX_CLIENTS )) {
201- clc .voipIgnore [id ] = ignore ;
206+ clc .voipSenders [id ]. ignore = ignore ;
202207 CL_AddReliableCommand (va ("voip %s %d" , ignore ? "ignore" : "unignore" , id ), qfalse );
203208 Com_Printf ("VoIP: %s ignoring player #%d\n" , ignore ? "Now" : "No longer" , id );
204209 return ;
@@ -213,7 +218,7 @@ static void CL_UpdateVoipGain(const char *idstr, float gain) {
213218 if (gain < 0.0f )
214219 gain = 0.0f ;
215220 if ((id >= 0 ) && (id < MAX_CLIENTS )) {
216- clc .voipGain [id ] = gain ;
221+ clc .voipSenders [id ]. gain = gain ;
217222 Com_Printf ("VoIP: player #%d gain now set to %f\n" , id , gain );
218223 }
219224 }
@@ -223,6 +228,25 @@ void CL_Voip_f(void) {
223228 const char * cmd = Cmd_Argv (1 );
224229 const char * reason = NULL ;
225230
231+ // Commands that only need the codec, not a server connection
232+ if (strcmp (cmd , "test" ) == 0 ) {
233+ CL_VoipToggleTest ();
234+ return ;
235+ } else if (strcmp (cmd , "test_stop" ) == 0 ) {
236+ if (clc .voipTestMode ) {
237+ CL_VoipToggleTest ();
238+ }
239+ return ;
240+ } else if (strcmp (cmd , "stats" ) == 0 ) {
241+ if (!clc .voipCodecInitialized ) {
242+ Com_Printf ("VoIP: codec not initialized.\n" );
243+ return ;
244+ }
245+ CL_VoipPrintStats ();
246+ return ;
247+ }
248+
249+ // All other commands require an active server connection
226250 if (clc .state != CA_ACTIVE )
227251 reason = "Not connected to a server" ;
228252 else if (!clc .voipCodecInitialized )
@@ -248,7 +272,7 @@ void CL_Voip_f(void) {
248272 } else if (Q_isanumber (Cmd_Argv (2 ))) {
249273 int id = atoi (Cmd_Argv (2 ));
250274 if (id >= 0 && id < MAX_CLIENTS ) {
251- Com_Printf ("VoIP: current gain for player #%d is %f\n" , id , clc .voipGain [id ]);
275+ Com_Printf ("VoIP: current gain for player #%d is %f\n" , id , clc .voipSenders [id ]. gain );
252276 } else {
253277 Com_Printf ("VoIP: invalid player ID#\n" );
254278 }
@@ -266,7 +290,9 @@ void CL_Voip_f(void) {
266290 } else {
267291 Com_Printf ("usage: voip [un]ignore <playerID#>\n"
268292 " voip [un]muteall\n"
269- " voip gain <playerID#> [value]\n" );
293+ " voip gain <playerID#> [value]\n"
294+ " voip stats\n"
295+ " voip test\n" );
270296 }
271297}
272298
@@ -354,8 +380,11 @@ static void CL_VoipParseTargets(void) {
354380===============
355381CL_CaptureVoip
356382
357- Record more audio from the hardware if required and encode it into Opus
358- data for later transmission.
383+ Record audio from the hardware, apply gain (with clipping protection),
384+ encode into Opus data, and queue for transmission. Handles push-to-talk,
385+ voice activity detection (VAD), and rate checking.
386+
387+ Called once per client frame from CL_Frame().
359388===============
360389*/
361390static void CL_CaptureVoip (void ) {
@@ -446,6 +475,7 @@ static void CL_CaptureVoip(void) {
446475 // audio capture is always MONO16.
447476 static int16_t sampbuffer [VOIP_MAX_PACKET_SAMPLES ];
448477 float voipPower = 0.0f ;
478+ float peakAmplitude = 0.0f ;
449479 int voipFrames ;
450480 int i , bytes ;
451481
@@ -470,7 +500,8 @@ static void CL_CaptureVoip(void) {
470500 const float flsamp = (float )sampbuffer [i ];
471501 const float s = fabs (flsamp );
472502 voipPower += s * s ;
473- sampbuffer [i ] = (int16_t )((flsamp )* audioMult );
503+ if (s > peakAmplitude ) peakAmplitude = s ;
504+ sampbuffer [i ] = CL_VoipClampSample (flsamp * audioMult );
474505 }
475506
476507 // encode raw audio samples into Opus data...
@@ -483,22 +514,16 @@ static void CL_CaptureVoip(void) {
483514
484515 clc .voipPower = (voipPower / (32768.0f * 32768.0f * ((float )samples ))) * 100.0f ;
485516
486- if ((useVad ) && (clc .voipPower < cl_voipVADThreshold -> value )) {
517+ // Update the mic level cvar for UI display (peak amplitude as % of full scale)
518+ Cvar_Set ("cl_voipMicLevel" , va ("%d" , (int )((peakAmplitude / 32768.0f ) * 100.0f )));
519+
520+ if ((useVad ) && !CL_VoipVADCheck (clc .voipPower )) {
487521 CL_VoipNewGeneration (); // no "talk" for at least 1/4 second.
488522 } else {
489523 clc .voipOutgoingDataSize = bytes ;
490524 clc .voipOutgoingDataFrames = voipFrames ;
491525
492526 Com_DPrintf ("VoIP: Send %d frames, %d bytes, %f power\n" , voipFrames , bytes , clc .voipPower );
493-
494- #if 0
495- static FILE * encio = NULL ;
496- if (encio == NULL ) encio = fopen ("voip-outgoing-encoded.bin" , "wb" );
497- if (encio != NULL ) { fwrite (clc .voipOutgoingData , bytes , 1 , encio ); fflush (encio ); }
498- static FILE * decio = NULL ;
499- if (decio == NULL ) decio = fopen ("voip-outgoing-decoded.bin" , "wb" );
500- if (decio != NULL ) { fwrite (sampbuffer , voipFrames * VOIP_MAX_FRAME_SAMPLES * 2 , 1 , decio ); fflush (decio ); }
501- #endif
502527 }
503528 }
504529 }
@@ -2773,7 +2798,13 @@ void CL_Frame(int msec) {
27732798 S_Update ();
27742799
27752800#ifdef USE_VOIP
2776- CL_CaptureVoip ();
2801+ if (clc .voipTestMode ) {
2802+ CL_VoipTestFrame ();
2803+ } else {
2804+ CL_CaptureVoip ();
2805+ }
2806+ CL_VoipProcessJitterBuffers ();
2807+ CL_VoipAdaptToNetwork ();
27772808#endif
27782809
27792810#ifdef USE_MUMBLE
@@ -2885,6 +2916,10 @@ void CL_StartHunkUsers(qboolean rendererOnly) {
28852916 if (!cls .soundStarted ) {
28862917 cls .soundStarted = qtrue ;
28872918 S_Init ();
2919+ #ifdef USE_VOIP
2920+ // Set capture availability immediately so the UI knows before codec init
2921+ Cvar_Set ("cl_voipNoMic" , S_CaptureAvailable () ? "0" : "1" );
2922+ #endif
28882923 }
28892924
28902925 if (!cls .soundRegistered ) {
@@ -3519,12 +3554,38 @@ void CL_Init(void) {
35193554 cl_voipGainDuringCapture = Cvar_Get ("cl_voipGainDuringCapture" , "0.2" , CVAR_ARCHIVE );
35203555 cl_voipCaptureMult = Cvar_Get ("cl_voipCaptureMult" , "2.0" , CVAR_ARCHIVE );
35213556 cl_voipUseVAD = Cvar_Get ("cl_voipUseVAD" , "0" , CVAR_ARCHIVE );
3522- cl_voipVADThreshold = Cvar_Get ("cl_voipVADThreshold" , "0.25 " , CVAR_ARCHIVE );
3557+ cl_voipVADThreshold = Cvar_Get ("cl_voipVADThreshold" , "0.05 " , CVAR_ARCHIVE );
35233558 cl_voipShowMeter = Cvar_Get ("cl_voipShowMeter" , "1" , CVAR_ARCHIVE );
35243559
3560+ // Opus encoder quality settings
3561+ cl_voipBitrate = Cvar_Get ("cl_voipBitrate" , "32000" , CVAR_ARCHIVE );
3562+ Cvar_CheckRange (cl_voipBitrate , 6000 , 128000 , qtrue );
3563+ cl_voipComplexity = Cvar_Get ("cl_voipComplexity" , "5" , CVAR_ARCHIVE );
3564+ Cvar_CheckRange (cl_voipComplexity , 0 , 10 , qtrue );
3565+ cl_voipFEC = Cvar_Get ("cl_voipFEC" , "1" , CVAR_ARCHIVE );
3566+ Cvar_CheckRange (cl_voipFEC , 0 , 1 , qtrue );
3567+ cl_voipPacketLossRate = Cvar_Get ("cl_voipPacketLossRate" , "5" , CVAR_ARCHIVE );
3568+ Cvar_CheckRange (cl_voipPacketLossRate , 0 , 100 , qtrue );
3569+
3570+ // Jitter buffer
3571+ cl_voipJitterDelay = Cvar_Get ("cl_voipJitterDelay" , "60" , CVAR_ARCHIVE );
3572+ Cvar_CheckRange (cl_voipJitterDelay , 0 , 200 , qtrue );
3573+
3574+ // Network adaptation
3575+ cl_voipAdaptive = Cvar_Get ("cl_voipAdaptive" , "1" , CVAR_ARCHIVE );
3576+ Cvar_CheckRange (cl_voipAdaptive , 0 , 1 , qtrue );
3577+
35253578 cl_voip = Cvar_Get ("cl_voip" , "1" , CVAR_ARCHIVE );
35263579 Cvar_CheckRange (cl_voip , 0 , 1 , qtrue );
35273580 cl_voipProtocol = Cvar_Get ("cl_voipProtocol" , cl_voip -> integer ? "opus" : "" , CVAR_USERINFO | CVAR_ROM );
3581+
3582+ // Mic level for UI display (updated each frame during capture)
3583+ Cvar_Get ("cl_voipMicLevel" , "0" , 0 );
3584+ Cvar_Get ("cl_voipTestMode" , "0" , 0 );
3585+ Cvar_Get ("cl_voipNoMic" , "0" , 0 );
3586+
3587+ // Register the voip command early so it's available from the menu
3588+ Cmd_AddCommand ("voip" , CL_Voip_f );
35283589#endif
35293590
35303591#ifdef USE_HTTP
0 commit comments