Skip to content

Commit 682246d

Browse files
committed
CLIENT: improved voip quality
improved opus settings and add jitter handling adaptive bitrates added input volume rendering to the ui and allow to test your mic
1 parent a3e7ece commit 682246d

29 files changed

Lines changed: 1418 additions & 150 deletions

code/cgame/cg_draw.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -746,8 +746,20 @@ static float CG_DrawVoiceNames(float y) {
746746
durationFraction = (float)(cg.time - cg.lastVoiceTime[i]) / duration_max;
747747
alpha = alpha_max - (durationFraction * (alpha_max - alpha_min));
748748

749-
nameLeft = SCREEN_WIDTH - (Q_PrintStrlen(name) * BIGCHAR_WIDTH);
750-
CG_DrawBigString(nameLeft, y, name, alpha);
749+
// Color-code the name based on VoIP quality
750+
{
751+
const char *qualityPrefix;
752+
int quality = cg.lastVoiceQuality[i];
753+
if (quality >= 90) {
754+
qualityPrefix = S_COLOR_GREEN;
755+
} else if (quality >= 70) {
756+
qualityPrefix = S_COLOR_YELLOW;
757+
} else {
758+
qualityPrefix = S_COLOR_RED;
759+
}
760+
nameLeft = SCREEN_WIDTH - ((Q_PrintStrlen(name) + 1) * BIGCHAR_WIDTH);
761+
CG_DrawBigString(nameLeft, y, va("%s%s", qualityPrefix, name), alpha);
762+
}
751763

752764
iconLeft = nameLeft - icon2textSpacing - iconWidth;
753765
CG_DrawPic(iconLeft, y, iconWidth, iconHeight, cgs.media.voiceIcon);

code/cgame/cg_local.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ typedef struct {
692692
qboolean testGun;
693693

694694
int lastVoiceTime[MAX_CLIENTS];
695+
int lastVoiceQuality[MAX_CLIENTS]; // 0-100, quality percentage per sender
695696
// cammod
696697
vec3_t CamPos;
697698
vec3_t CamAngles;
@@ -1841,6 +1842,7 @@ qboolean trap_getCameraInfo(int time, vec3_t *origin, vec3_t *angles);
18411842

18421843
qboolean trap_GetEntityToken(char *buffer, int bufferSize);
18431844
void trap_GetVoipTimes(int *times);
1845+
void trap_GetVoipQuality(int *quality);
18441846

18451847
int trap_RealTime(qtime_t *qtime);
18461848

code/cgame/cg_public.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ typedef enum {
158158
// 1.32
159159
CG_FS_SEEK,
160160
CG_GET_VOIP_TIMES,
161+
CG_GET_VOIP_QUALITY,
161162
/*
162163
CG_LOADCAMERA,
163164
CG_STARTCAMERA,

code/cgame/cg_syscalls.asm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ equ trap_Argv -9
1212
equ trap_Args -10
1313
equ trap_FS_FOpenFile -11
1414
equ trap_FS_Read -12
15-
equ trap_FS_Write -13
15+
equ trap_FS_Write -13
1616
equ trap_FS_FCloseFile -14
1717
equ trap_SendConsoleCommand -15
1818
equ trap_AddCommand -16
@@ -91,6 +91,7 @@ equ trap_R_AddPolysToScene -88
9191
equ trap_R_inPVS -89
9292
equ trap_FS_Seek -90
9393
equ trap_GetVoipTimes -91
94+
equ trap_GetVoipQuality -92
9495

9596
equ memset -101
9697
equ memcpy -102

code/cgame/cg_syscalls.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,7 @@ qboolean trap_R_inPVS(const vec3_t p1, const vec3_t p2) {
438438
void trap_GetVoipTimes(int *times) {
439439
syscall(CG_GET_VOIP_TIMES, times);
440440
}
441+
442+
void trap_GetVoipQuality(int *quality) {
443+
syscall(CG_GET_VOIP_QUALITY, quality);
444+
}

code/cgame/cg_view.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,5 +1129,6 @@ void CG_DrawActiveFrame(int serverTime, stereoFrame_t stereoView, qboolean demoP
11291129
CG_UpdateVoipTeamIDs();
11301130
if (cg_drawVoiceNames.integer) {
11311131
trap_GetVoipTimes(cg.lastVoiceTime);
1132+
trap_GetVoipQuality(cg.lastVoiceQuality);
11321133
}
11331134
}

code/client/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ set(SRCS
88
cl_main.c
99
cl_net_chan.c
1010
cl_parse.c
11+
cl_voip.c
1112
cl_scrn.c
1213
cl_ui.c
1314
cl_avi.c

code/client/cl_cgame.c

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,31 @@ static void CL_CM_LoadMap(const char *mapname) {
350350
}
351351

352352
static void CL_GetVoipTimes(int *times) {
353-
memcpy(times, clc.voipLastPacket, sizeof(int) * MAX_CLIENTS);
353+
int i;
354+
for (i = 0; i < MAX_CLIENTS; i++) {
355+
times[i] = clc.voipSenders[i].lastPacketTime;
356+
}
357+
}
358+
359+
// Returns per-client VoIP quality as integer percentage (0=worst, 100=perfect)
360+
static void CL_GetVoipQuality(int *quality) {
361+
int i;
362+
for (i = 0; i < MAX_CLIENTS; i++) {
363+
voipNetStats_t *stats = &clc.voipSenders[i].netStats;
364+
if (stats->packetsReceived > 0) {
365+
// Quality = 100 - loss percentage, clamped to [0, 100]
366+
int q = 100 - (int)(stats->lossRate * 100.0f + 0.5f);
367+
if (q < 0) {
368+
q = 0;
369+
}
370+
if (q > 100) {
371+
q = 100;
372+
}
373+
quality[i] = q;
374+
} else {
375+
quality[i] = 100; // no data = assume perfect
376+
}
377+
}
354378
}
355379

356380
/*
@@ -671,6 +695,9 @@ static intptr_t CL_CgameSystemCalls(intptr_t *args) {
671695
case CG_GET_VOIP_TIMES:
672696
CL_GetVoipTimes(VMA(1));
673697
return 0;
698+
case CG_GET_VOIP_QUALITY:
699+
CL_GetVoipQuality(VMA(1));
700+
return 0;
674701

675702
default:
676703
assert(0);
@@ -876,31 +903,10 @@ static void CL_FirstSnapshot(void) {
876903

877904
#ifdef USE_VOIP
878905
if (!clc.voipCodecInitialized) {
879-
int i;
880-
int error;
881-
882-
clc.opusEncoder = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, &error);
883-
884-
if (error) {
885-
Com_DPrintf("VoIP: Error opus_encoder_create %d\n", error);
886-
return;
887-
}
888-
889-
for (i = 0; i < MAX_CLIENTS; i++) {
890-
clc.opusDecoder[i] = opus_decoder_create(48000, 1, &error);
891-
if (error) {
892-
Com_DPrintf("VoIP: Error opus_decoder_create(%d) %d\n", i, error);
893-
return;
894-
}
895-
clc.voipIgnore[i] = qfalse;
896-
clc.voipGain[i] = 1.0f;
897-
clc.voipLastPacket[i] = 0;
906+
CL_VoipCodecInit();
907+
if (clc.voipCodecInitialized) {
908+
Cvar_Set("cl_voipSendTarget", "spatial");
898909
}
899-
clc.voipCodecInitialized = qtrue;
900-
clc.voipMuteAll = qfalse;
901-
Cmd_AddCommand("voip", CL_Voip_f);
902-
Cvar_Set("cl_voipSendTarget", "spatial");
903-
Com_Memset(clc.voipTargets, ~0, sizeof(clc.voipTargets));
904910
}
905911
#endif
906912
}

code/client/cl_main.c

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ cvar_t *cl_voipCaptureMult;
4646
cvar_t *cl_voipShowMeter;
4747
cvar_t *cl_voipProtocol;
4848
cvar_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
===============
355381
CL_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
*/
361390
static 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

Comments
 (0)