Add Voip code from ioq3

This commit is contained in:
smallmodel 2024-09-22 16:50:27 +02:00
parent dbd10a281b
commit 8f355fe188
No known key found for this signature in database
GPG key ID: 9F2D623CEDF08512
9 changed files with 358 additions and 21 deletions

View file

@ -1111,6 +1111,42 @@ void CL_FirstSnapshot( void ) {
Cbuf_AddText( cl_activeAction->string );
Cvar_Set( "activeAction", "" );
}
#ifdef USE_MUMBLE
if ((cl_useMumble->integer) && !mumble_islinked()) {
int ret = mumble_link(CLIENT_WINDOW_TITLE);
Com_Printf("Mumble: Linking to Mumble application %s\n", ret==0?"ok":"failed");
}
#endif
#ifdef USE_VOIP
if (!clc.voipCodecInitialized) {
int i;
int error;
clc.opusEncoder = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, &error);
if ( error ) {
Com_DPrintf("VoIP: Error opus_encoder_create %d\n", error);
return;
}
for (i = 0; i < MAX_CLIENTS; i++) {
clc.opusDecoder[i] = opus_decoder_create(48000, 1, &error);
if ( error ) {
Com_DPrintf("VoIP: Error opus_decoder_create(%d) %d\n", i, error);
return;
}
clc.voipIgnore[i] = qfalse;
clc.voipGain[i] = 1.0f;
}
clc.voipCodecInitialized = qtrue;
clc.voipMuteAll = qfalse;
Cmd_AddCommand ("voip", CL_Voip_f);
Cvar_Set("cl_voipSendTarget", "spatial");
Com_Memset(clc.voipTargets, ~0, sizeof(clc.voipTargets));
}
#endif
}
static int lastSnapFlags;

View file

@ -704,7 +704,7 @@ void CL_PlayDemo_f( void ) {
clc.state = CA_CONNECTED;
clc.demoplaying = qtrue;
Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) );
Q_strncpyz( clc.servername, Cmd_Argv(1), sizeof( clc.servername ) );
// read demo messages until connected
while ( clc.state >= CA_CONNECTED && clc.state < CA_PRIMED ) {
@ -830,7 +830,7 @@ void CL_MapLoading( qboolean flush, const char *pszMapName ) {
if (pszMapName) {
// if we are already connected to the local host, stay connected
if (clc.state >= CA_CONNECTED && !Q_stricmp(cls.servername, "localhost")) {
if (clc.state >= CA_CONNECTED && !Q_stricmp(clc.servername, "localhost")) {
clc.state = CA_CONNECTED; // so the connect screen is drawn
Com_Memset(cls.updateInfoString, 0, sizeof(cls.updateInfoString));
Com_Memset(clc.serverMessage, 0, sizeof(clc.serverMessage));
@ -841,11 +841,11 @@ void CL_MapLoading( qboolean flush, const char *pszMapName ) {
// clear nextmap so the cinematic shutdown doesn't execute it
Cvar_Set("nextmap", "");
CL_Disconnect();
Q_strncpyz(cls.servername, "localhost", sizeof(cls.servername));
Q_strncpyz(clc.servername, "localhost", sizeof(clc.servername));
clc.state = CA_CHALLENGING; // so the connect screen is drawn
clc.connectStartTime = cls.realtime;
clc.connectTime = -RETRANSMIT_TIMEOUT;
NET_StringToAdr(cls.servername, &clc.serverAddress, NA_UNSPEC);
NET_StringToAdr(clc.servername, &clc.serverAddress, NA_UNSPEC);
// we don't need a challenge on the localhost
CL_CheckForResend();
@ -1232,12 +1232,12 @@ CL_Reconnect_f
================
*/
void CL_Reconnect_f( void ) {
if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) {
if ( !strlen( clc.servername ) || !strcmp( clc.servername, "localhost" ) ) {
Com_Printf( "Can't reconnect to localhost.\n" );
return;
}
Cvar_Set("ui_singlePlayerActive", "0");
Cbuf_AddText( va("connect %s\n", cls.servername ) );
Cbuf_AddText( va("connect %s\n", clc.servername ) );
}
/*
@ -1273,9 +1273,9 @@ void CL_Connect( const char *server, netadrtype_t family ) {
CL_FlushMemory( );
*/
Q_strncpyz( cls.servername, server, sizeof( cls.servername ) );
Q_strncpyz( clc.servername, server, sizeof( clc.servername ) );
if( !NET_StringToAdr( cls.servername, &clc.serverAddress, family ) ) {
if( !NET_StringToAdr( clc.servername, &clc.serverAddress, family ) ) {
Com_Printf( "Bad server address\n" );
clc.state = CA_DISCONNECTED;
UI_PushMenu("badserveraddy");
@ -1286,7 +1286,7 @@ void CL_Connect( const char *server, netadrtype_t family ) {
}
serverString = NET_AdrToStringwPort(clc.serverAddress);
Com_Printf( "%s resolved to %s\n", cls.servername, serverString );
Com_Printf( "%s resolved to %s\n", clc.servername, serverString );
if (cl_guidServerUniq->integer)
CL_UpdateGUID(serverString, strlen(serverString));
@ -1705,7 +1705,7 @@ CL_Clientinfo_f
void CL_Clientinfo_f( void ) {
Com_Printf( "--------- Client Information ---------\n" );
Com_Printf( "state: %i\n", clc.state );
Com_Printf( "Server: %s\n", cls.servername );
Com_Printf( "Server: %s\n", clc.servername );
Com_Printf ("User info settings:\n");
Info_Print( Cvar_InfoString( CVAR_USERINFO ) );
Com_Printf( "--------------------------------------\n" );
@ -2618,7 +2618,7 @@ void CL_Frame ( int msec ) {
now.tm_min,
now.tm_sec );
Q_strncpyz( serverName, cls.servername, MAX_OSPATH );
Q_strncpyz( serverName, clc.servername, MAX_OSPATH );
// Replace the ":" in the address as it is not a valid
// file name character
p = strstr( serverName, ":" );

View file

@ -429,6 +429,18 @@ void CL_SystemInfoChanged( void ) {
// in some cases, outdated cp commands might get sent with this news serverId
cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) );
#ifdef USE_VOIP
#ifdef LEGACY_PROTOCOL
if(clc.compat)
clc.voipEnabled = qfalse;
else
#endif
{
s = Info_ValueForKey( systemInfo, "sv_voipProtocol" );
clc.voipEnabled = !Q_stricmp(s, "opus");
}
#endif
// don't set any vars when playing a demo
if ( clc.demoplaying ) {
return;
@ -784,6 +796,178 @@ void CL_ParseDownload ( msg_t *msg ) {
}
}
#ifdef USE_VOIP
static
qboolean CL_ShouldIgnoreVoipSender(int sender)
{
if (!cl_voip->integer)
return qtrue; // VoIP is disabled.
else if ((sender == clc.clientNum) && (!clc.demoplaying))
return qtrue; // ignore own voice (unless playing back a demo).
else if (clc.voipMuteAll)
return qtrue; // all channels are muted with extreme prejudice.
else if (clc.voipIgnore[sender])
return qtrue; // just ignoring this guy.
else if (clc.voipGain[sender] == 0.0f)
return qtrue; // too quiet to play.
return qfalse;
}
/*
=====================
CL_PlayVoip
Play raw data
=====================
*/
static void CL_PlayVoip(int sender, int samplecnt, const byte *data, int flags)
{
if(flags & VOIP_DIRECT)
{
S_RawSamples(sender + 1, samplecnt, 48000, 2, 1,
data, clc.voipGain[sender], -1);
}
if(flags & VOIP_SPATIAL)
{
S_RawSamples(sender + MAX_CLIENTS + 1, samplecnt, 48000, 2, 1,
data, 1.0f, sender);
}
}
/*
=====================
CL_ParseVoip
A VoIP message has been received from the server
=====================
*/
static
void CL_ParseVoip ( msg_t *msg, qboolean ignoreData ) {
static short decoded[VOIP_MAX_PACKET_SAMPLES*4]; // !!! FIXME: don't hard code
const int sender = MSG_ReadShort(msg);
const int generation = MSG_ReadByte(msg);
const int sequence = MSG_ReadLong(msg);
const int frames = MSG_ReadByte(msg);
const int packetsize = MSG_ReadShort(msg);
const int flags = MSG_ReadBits(msg, VOIP_FLAGCNT);
unsigned char encoded[4000];
int numSamples;
int seqdiff;
int written = 0;
int i;
Com_DPrintf("VoIP: %d-byte packet from client %d\n", packetsize, sender);
if (sender < 0)
return; // short/invalid packet, bail.
else if (generation < 0)
return; // short/invalid packet, bail.
else if (sequence < 0)
return; // short/invalid packet, bail.
else if (frames < 0)
return; // short/invalid packet, bail.
else if (packetsize < 0)
return; // short/invalid packet, bail.
if (packetsize > sizeof (encoded)) { // overlarge packet?
int bytesleft = packetsize;
while (bytesleft) {
int br = bytesleft;
if (br > sizeof (encoded))
br = sizeof (encoded);
MSG_ReadData(msg, encoded, br);
bytesleft -= br;
}
return; // overlarge packet, bail.
}
MSG_ReadData(msg, encoded, packetsize);
if (ignoreData) {
return; // just ignore legacy speex voip data
} else if (!clc.voipCodecInitialized) {
return; // can't handle VoIP without libopus!
} else if (sender >= MAX_CLIENTS) {
return; // bogus sender.
} else if (CL_ShouldIgnoreVoipSender(sender)) {
return; // Channel is muted, bail.
}
// !!! FIXME: make sure data is narrowband? Does decoder handle this?
Com_DPrintf("VoIP: packet accepted!\n");
seqdiff = sequence - clc.voipIncomingSequence[sender];
// This is a new "generation" ... a new recording started, reset the bits.
if (generation != clc.voipIncomingGeneration[sender]) {
Com_DPrintf("VoIP: new generation %d!\n", generation);
opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE);
clc.voipIncomingGeneration[sender] = generation;
seqdiff = 0;
} else if (seqdiff < 0) { // we're ahead of the sequence?!
// This shouldn't happen unless the packet is corrupted or something.
Com_DPrintf("VoIP: misordered sequence! %d < %d!\n",
sequence, clc.voipIncomingSequence[sender]);
// reset the decoder just in case.
opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE);
seqdiff = 0;
} else if (seqdiff * VOIP_MAX_PACKET_SAMPLES*2 >= sizeof (decoded)) { // dropped more than we can handle?
// just start over.
Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n",
seqdiff, sender);
opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE);
seqdiff = 0;
}
if (seqdiff != 0) {
Com_DPrintf("VoIP: Dropped %d frames from client #%d\n",
seqdiff, sender);
// tell opus that we're missing frames...
for (i = 0; i < seqdiff; i++) {
assert((written + VOIP_MAX_PACKET_SAMPLES) * 2 < sizeof (decoded));
numSamples = opus_decode(clc.opusDecoder[sender], NULL, 0, decoded + written, VOIP_MAX_PACKET_SAMPLES, 0);
if ( numSamples <= 0 ) {
Com_DPrintf("VoIP: Error decoding frame %d from client #%d\n", i, sender);
continue;
}
written += numSamples;
}
}
numSamples = opus_decode(clc.opusDecoder[sender], encoded, packetsize, decoded + written, ARRAY_LEN(decoded) - written, 0);
if ( numSamples <= 0 ) {
Com_DPrintf("VoIP: Error decoding voip data from client #%d\n", sender);
numSamples = 0;
}
#if 0
static FILE *encio = NULL;
if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb");
if (encio != NULL) { fwrite(encoded, packetsize, 1, encio); fflush(encio); }
static FILE *decio = NULL;
if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb");
if (decio != NULL) { fwrite(decoded+written, numSamples*2, 1, decio); fflush(decio); }
#endif
written += numSamples;
Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
written * 2, written, frames);
if(written > 0)
CL_PlayVoip(sender, written, (const byte *) decoded, flags);
clc.voipIncomingSequence[sender] = sequence + frames;
}
#endif
/*
=====================
CL_ParseCommandString
@ -932,8 +1116,16 @@ void CL_ParseServerMessage( msg_t *msg ) {
}
CL_ParseCGMessage( msg );
break;
case svc_voipSpeex:
#ifdef USE_VOIP
CL_ParseVoip( msg, qtrue );
#endif
break;
case svc_voipOpus:
#ifdef USE_VOIP
CL_ParseVoip( msg, !clc.voipEnabled );
#endif
break;
}
}
}

View file

@ -265,7 +265,7 @@ static void GetClientState(uiClientState_t *state)
{
state->connectPacketCount = clc.connectPacketCount;
state->connState = clc.state;
Q_strncpyz(state->servername, cls.servername, sizeof(state->servername));
Q_strncpyz(state->servername, clc.servername, sizeof(state->servername));
Q_strncpyz(state->updateInfoString, cls.updateInfoString, sizeof(state->updateInfoString));
Q_strncpyz(state->messageString, clc.serverMessage, sizeof(state->messageString));
state->clientNum = cl.snap.ps.clientNum;

View file

@ -36,6 +36,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "cl_curl.h"
#endif /* USE_CURL */
#ifdef USE_VOIP
#include <opus.h>
#endif
// file full of random crap that gets used to create cl_guid
#define QKEY_FILE "qkey"
#define QKEY_SIZE 2048
@ -174,6 +178,7 @@ typedef struct {
int lastPacketSentTime; // for retransmits during connection
int lastPacketTime; // for timeouts
char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect)
netadr_t serverAddress;
int connectTime; // for connection retransmits
int connectStartTime;
@ -239,15 +244,42 @@ typedef struct {
int timeDemoMaxDuration; // maximum frame duration
unsigned char timeDemoDurations[ MAX_TIMEDEMO_DURATIONS ]; // log of frame durations
float aviVideoFrameRemainder;
float aviSoundFrameRemainder;
#ifdef USE_VOIP
qboolean voipEnabled;
qboolean voipCodecInitialized;
// incoming data...
// !!! FIXME: convert from parallel arrays to array of a struct.
OpusDecoder *opusDecoder[MAX_CLIENTS];
byte voipIncomingGeneration[MAX_CLIENTS];
int voipIncomingSequence[MAX_CLIENTS];
float voipGain[MAX_CLIENTS];
qboolean voipIgnore[MAX_CLIENTS];
qboolean voipMuteAll;
// outgoing data...
// if voipTargets[i / 8] & (1 << (i % 8)),
// then we are sending to clientnum i.
uint8_t voipTargets[(MAX_CLIENTS + 7) / 8];
uint8_t voipFlags;
OpusEncoder *opusEncoder;
int voipOutgoingDataSize;
int voipOutgoingDataFrames;
int voipOutgoingSequence;
byte voipOutgoingGeneration;
byte voipOutgoingData[1024];
float voipPower;
#endif
#ifdef LEGACY_PROTOCOL
qboolean compat;
#endif
// big stuff at end of structure so most offsets are 15 bits or less
netchan_t netchan;
float aviVideoFrameRemainder;
float aviSoundFrameRemainder;
} clientConnection_t;
extern clientConnection_t clc;
@ -297,8 +329,6 @@ typedef struct {
qboolean cddialog; // bring up the cd needed dialog next frame
qboolean no_menus;
char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect)
// when the server clears the hunk, all of these must be restarted
qboolean rendererRegistered;
qboolean cgameStarted;
@ -445,6 +475,32 @@ extern cvar_t *cl_r_fullscreen;
extern cvar_t *cl_consoleKeys;
#ifdef USE_MUMBLE
extern cvar_t *cl_useMumble;
extern cvar_t *cl_mumbleScale;
#endif
#ifdef USE_VOIP
// cl_voipSendTarget is a string: "all" to broadcast to everyone, "none" to
// send to no one, or a comma-separated list of client numbers:
// "0,7,2,23" ... an empty string is treated like "all".
extern cvar_t *cl_voipUseVAD;
extern cvar_t *cl_voipVADThreshold;
extern cvar_t *cl_voipSend;
extern cvar_t *cl_voipSendTarget;
extern cvar_t *cl_voipGainDuringCapture;
extern cvar_t *cl_voipCaptureMult;
extern cvar_t *cl_voipShowMeter;
extern cvar_t *cl_voip;
// 20ms at 48k
#define VOIP_MAX_FRAME_SAMPLES ( 20 * 48 )
// 3 frame is 60ms of audio, the max opus will encode at once
#define VOIP_MAX_PACKET_FRAMES 3
#define VOIP_MAX_PACKET_SAMPLES ( VOIP_MAX_FRAME_SAMPLES * VOIP_MAX_PACKET_FRAMES )
#endif
extern cvar_t *cg_gametype;
extern cvar_t* j_pitch;
@ -546,6 +602,10 @@ extern int cl_connectedToPureServer;
extern int cl_connectedToCheatServer;
extern msg_t *cl_currentMSG;
#ifdef USE_VOIP
void CL_Voip_f( void );
#endif
void CL_SystemInfoChanged( void );
void CL_ParseServerMessage( msg_t *msg );

View file

@ -166,3 +166,20 @@ unsigned int S_GetMusicOffset();
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifdef USE_VOIP
void S_StartCapture( void );
int S_AvailableCaptureSamples( void );
void S_Capture( int samples, byte *data );
void S_StopCapture( void );
void S_MasterGain( float gain );
#endif
#ifdef __cplusplus
}
#endif

View file

@ -47,6 +47,29 @@ void SNDDMA_Submit(void)
{
}
#ifdef USE_VOIP
void SNDDMA_StartCapture(void)
{
}
int SNDDMA_AvailableCaptureSamples(void)
{
return 0;
}
void SNDDMA_Capture(int samples, byte *data)
{
}
void SNDDMA_StopCapture(void)
{
}
void SNDDMA_MasterGain( float val )
{
}
#endif
void SNDDMA_Activate(void) {
}

View file

@ -362,7 +362,11 @@ enum svc_ops_e {
svc_centerprint,
svc_locprint,
svc_cgameMessage,
svc_EOF
svc_EOF,
// new commands, supported only by ioquake3 protocol but not legacy
svc_voipSpeex, // not wrapped in USE_VOIP, so this value is reserved.
svc_voipOpus, //
};
//

View file

@ -24,7 +24,11 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "../gamespy/sv_gamespy.h"
#include "../gamespy/sv_gqueryreporting.h"
cvar_t *sv_mapname;
#ifdef USE_VOIP
cvar_t *sv_voip;
cvar_t *sv_voipProtocol;
#endif
serverStatic_t svs; // persistant server info
server_t sv; // local server
//vm_t *gvm = NULL; // game virtual machine
@ -45,6 +49,7 @@ cvar_t *sv_reconnectlimit; // minimum seconds between connect messages
cvar_t *sv_showloss; // report when usercmds are lost
cvar_t *sv_padPackets; // add nop bytes to messages
cvar_t *sv_killserver; // menu system can set to 1 to shut server down
cvar_t *sv_mapname;
cvar_t *sv_mapChecksum;
cvar_t *sv_serverid;
cvar_t *sv_minRate;