aboutsummaryrefslogtreecommitdiffstats
path: root/code
diff options
context:
space:
mode:
Diffstat (limited to 'code')
-rw-r--r--code/client/cl_cgame.c20
-rw-r--r--code/client/cl_cin.c10
-rw-r--r--code/client/cl_input.c40
-rw-r--r--code/client/cl_main.c235
-rw-r--r--code/client/cl_parse.c177
-rw-r--r--code/client/client.h43
-rw-r--r--code/client/snd_dma.c126
-rw-r--r--code/client/snd_local.h15
-rw-r--r--code/client/snd_main.c76
-rw-r--r--code/client/snd_mix.c38
-rw-r--r--code/client/snd_openal.c220
-rw-r--r--code/client/snd_public.h12
-rw-r--r--code/qcommon/qcommon.h12
-rw-r--r--code/server/server.h30
-rw-r--r--code/server/sv_client.c202
-rw-r--r--code/server/sv_init.c3
-rw-r--r--code/server/sv_main.c8
-rw-r--r--code/server/sv_snapshot.c4
18 files changed, 1144 insertions, 127 deletions
diff --git a/code/client/cl_cgame.c b/code/client/cl_cgame.c
index 334294e..7ea1b1d 100644
--- a/code/client/cl_cgame.c
+++ b/code/client/cl_cgame.c
@@ -916,6 +916,26 @@ void CL_FirstSnapshot( void ) {
Com_Printf("Mumble: Linking to Mumble application %s\n", ret==0?"ok":"failed");
}
#endif
+
+#if USE_VOIP
+ if (!clc.speexInitialized) {
+ int i;
+ speex_bits_init(&clc.speexEncoderBits);
+ speex_bits_reset(&clc.speexEncoderBits);
+ clc.speexEncoder = speex_encoder_init(&speex_nb_mode);
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ speex_bits_init(&clc.speexDecoderBits[i]);
+ speex_bits_reset(&clc.speexDecoderBits[i]);
+ clc.speexDecoder[i] = speex_decoder_init(&speex_nb_mode);
+ clc.voipIgnore[i] = qfalse;
+ }
+ speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_FRAME_SIZE,
+ &clc.speexFrameSize);
+ clc.speexInitialized = qtrue;
+ clc.voipMuteAll = qfalse;
+ Cmd_AddCommand ("voip", CL_Voip_f);
+ }
+#endif
}
/*
diff --git a/code/client/cl_cin.c b/code/client/cl_cin.c
index 0e44eee..bb5be7e 100644
--- a/code/client/cl_cin.c
+++ b/code/client/cl_cin.c
@@ -53,8 +53,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define MAX_VIDEO_HANDLES 16
extern glconfig_t glConfig;
-extern int s_paintedtime;
-extern int s_rawend;
static void RoQ_init( void );
@@ -1141,17 +1139,17 @@ redump:
case ZA_SOUND_MONO:
if (!cinTable[currentHandle].silent) {
ssize = RllDecodeMonoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags);
- S_RawSamples( ssize, 22050, 2, 1, (byte *)sbuf, 1.0f );
+ S_RawSamples( 0, ssize, 22050, 2, 1, (byte *)sbuf, 1.0f );
}
break;
case ZA_SOUND_STEREO:
if (!cinTable[currentHandle].silent) {
if (cinTable[currentHandle].numQuads == -1) {
S_Update();
- s_rawend = s_soundtime;
+ s_rawend[0] = s_soundtime;
}
ssize = RllDecodeStereoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags);
- S_RawSamples( ssize, 22050, 2, 2, (byte *)sbuf, 1.0f );
+ S_RawSamples( 0, ssize, 22050, 2, 2, (byte *)sbuf, 1.0f );
}
break;
case ROQ_QUAD_INFO:
@@ -1478,7 +1476,7 @@ int CIN_PlayCinematic( const char *arg, int x, int y, int w, int h, int systemBi
Con_Close();
- s_rawend = s_soundtime;
+ s_rawend[0] = s_soundtime;
return currentHandle;
}
diff --git a/code/client/cl_input.c b/code/client/cl_input.c
index f7e3920..a972536 100644
--- a/code/client/cl_input.c
+++ b/code/client/cl_input.c
@@ -52,6 +52,10 @@ kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright;
kbutton_t in_strafe, in_speed;
kbutton_t in_up, in_down;
+#if USE_VOIP
+kbutton_t in_voiprecord;
+#endif
+
kbutton_t in_buttons[16];
@@ -216,6 +220,11 @@ void IN_SpeedUp(void) {IN_KeyUp(&in_speed);}
void IN_StrafeDown(void) {IN_KeyDown(&in_strafe);}
void IN_StrafeUp(void) {IN_KeyUp(&in_strafe);}
+#if USE_VOIP
+void IN_VoipRecordDown(void) {IN_KeyDown(&in_voiprecord);}
+void IN_VoipRecordUp(void) {IN_KeyUp(&in_voiprecord);}
+#endif
+
void IN_Button0Down(void) {IN_KeyDown(&in_buttons[0]);}
void IN_Button0Up(void) {IN_KeyUp(&in_buttons[0]);}
void IN_Button1Down(void) {IN_KeyDown(&in_buttons[1]);}
@@ -547,6 +556,14 @@ usercmd_t CL_CreateCmd( void ) {
// get basic movement from joystick
CL_JoystickMove( &cmd );
+#if USE_VOIP
+ if ( ( in_voiprecord.active ) && ( !cl_voipSend->integer ) ) {
+ Cvar_Set("cl_voipSend", "1");
+ } else if ( ( !in_voiprecord.active ) && ( cl_voipSend->integer ) ) {
+ Cvar_Set("cl_voipSend", "0");
+ }
+#endif
+
// check to make sure the angles haven't wrapped
if ( cl.viewangles[PITCH] - oldAngles[PITCH] > 90 ) {
cl.viewangles[PITCH] = oldAngles[PITCH] + 90;
@@ -740,6 +757,24 @@ void CL_WritePacket( void ) {
count = MAX_PACKET_USERCMDS;
Com_Printf("MAX_PACKET_USERCMDS\n");
}
+
+ #if USE_VOIP
+ if (clc.voipOutgoingDataSize > 0) { // only send if data.
+ MSG_WriteByte (&buf, clc_voip);
+ MSG_WriteByte (&buf, clc.voipOutgoingGeneration);
+ MSG_WriteLong (&buf, clc.voipOutgoingSequence);
+ MSG_WriteByte (&buf, clc.voipOutgoingDataFrames);
+ MSG_WriteLong (&buf, 0x7FFFFFFF); // !!! FIXME: send to specific people.
+ MSG_WriteLong (&buf, 0x7FFFFFFF); // !!! FIXME: send to specific people.
+ MSG_WriteLong (&buf, 0x7FFFFFFF); // !!! FIXME: send to specific people.
+ MSG_WriteShort (&buf, clc.voipOutgoingDataSize);
+ MSG_WriteData (&buf, clc.voipOutgoingData, clc.voipOutgoingDataSize);
+ clc.voipOutgoingSequence += clc.voipOutgoingDataFrames;
+ clc.voipOutgoingDataSize = 0;
+ clc.voipOutgoingDataFrames = 0;
+ } else
+ #endif
+
if ( count >= 1 ) {
if ( cl_showSend->integer ) {
Com_Printf( "(%i)", count );
@@ -897,6 +932,11 @@ void CL_InitInput( void ) {
Cmd_AddCommand ("+mlook", IN_MLookDown);
Cmd_AddCommand ("-mlook", IN_MLookUp);
+#if USE_VOIP
+ Cmd_AddCommand ("+voiprecord", IN_VoipRecordDown);
+ Cmd_AddCommand ("-voiprecord", IN_VoipRecordUp);
+#endif
+
cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0);
cl_debugMove = Cvar_Get ("cl_debugMove", "0", 0);
}
diff --git a/code/client/cl_main.c b/code/client/cl_main.c
index b9243d5..22c89c6 100644
--- a/code/client/cl_main.c
+++ b/code/client/cl_main.c
@@ -33,6 +33,12 @@ cvar_t *cl_useMumble;
cvar_t *cl_mumbleScale;
#endif
+#if USE_VOIP
+cvar_t *cl_voipSend;
+cvar_t *cl_voipGainDuringCapture;
+cvar_t *voip;
+#endif
+
cvar_t *cl_nodelta;
cvar_t *cl_debugMove;
@@ -168,6 +174,200 @@ void CL_UpdateMumble(void)
#endif
+#if USE_VOIP
+static
+void CL_UpdateVoipIgnore(const char *idstr, qboolean ignore)
+{
+ if ((*idstr >= '0') && (*idstr <= '9')) {
+ const int id = atoi(idstr);
+ if ((id >= 0) && (id < MAX_CLIENTS)) {
+ clc.voipIgnore[id] = ignore;
+ CL_AddReliableCommand(va("voip %s %d",
+ ignore ? "ignore" : "unignore", id));
+ Com_Printf("VoIP: %s ignoring player #%d\n",
+ ignore ? "Now" : "No longer", id);
+ }
+ }
+}
+
+void CL_Voip_f( void )
+{
+ const char *cmd = Cmd_Argv(1);
+ const char *reason = NULL;
+
+ if (cls.state != CA_ACTIVE)
+ reason = "Not connected to a server";
+ else if (!clc.speexInitialized)
+ reason = "Speex not initialized";
+ else if (!cl_connectedToVoipServer)
+ reason = "Server doesn't support VoIP";
+ else if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive"))
+ reason = "running in single-player mode";
+
+ if (reason != NULL) {
+ Com_Printf("VoIP: command ignored: %s\n", reason);
+ return;
+ }
+
+ if (strcmp(cmd, "ignore") == 0) {
+ CL_UpdateVoipIgnore(Cmd_Argv(2), qtrue);
+ } else if (strcmp(cmd, "unignore") == 0) {
+ CL_UpdateVoipIgnore(Cmd_Argv(2), qfalse);
+ } else if (strcmp(cmd, "muteall") == 0) {
+ Com_Printf("VoIP: muting incoming voice\n");
+ CL_AddReliableCommand("voip muteall");
+ clc.voipMuteAll = qtrue;
+ } else if (strcmp(cmd, "unmuteall") == 0) {
+ Com_Printf("VoIP: unmuting incoming voice\n");
+ CL_AddReliableCommand("voip unmuteall");
+ clc.voipMuteAll = qfalse;
+ }
+}
+
+
+/*
+===============
+CL_CaptureVoip
+
+Record more audio from the hardware if required and encode it into Speex
+ data for later transmission.
+===============
+*/
+static
+void CL_CaptureVoip(void)
+{
+ qboolean initialFrame = qfalse;
+ qboolean finalFrame = qfalse;
+
+#if USE_MUMBLE
+ // if we're using Mumble, don't try to handle VoIP transmission ourselves.
+ if (cl_useMumble->integer)
+ return;
+#endif
+
+ if (!clc.speexInitialized)
+ return; // just in case this gets called at a bad time.
+
+ if (clc.voipOutgoingDataSize > 0)
+ return; // packet is pending transmission, don't record more yet.
+
+ if (cl_voipSend->modified) {
+ qboolean dontCapture = qfalse;
+ if (cls.state != CA_ACTIVE)
+ dontCapture = qtrue; // not connected to a server.
+ else if (!cl_connectedToVoipServer)
+ dontCapture = qtrue; // server doesn't support VoIP.
+ else if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive"))
+ dontCapture = qtrue; // single player game.
+
+ cl_voipSend->modified = qfalse;
+
+ if (dontCapture) {
+ cl_voipSend->integer = 0;
+ return;
+ }
+
+ if (cl_voipSend->integer) {
+ initialFrame = qtrue;
+ } else {
+ finalFrame = qtrue;
+ }
+ }
+
+ // try to get more audio data from the sound card...
+
+ if (initialFrame) {
+ float gain = cl_voipGainDuringCapture->value;
+ if (gain < 0.0f) gain = 0.0f; else if (gain >= 1.0f) gain = 1.0f;
+ S_MasterGain(cl_voipGainDuringCapture->value);
+ S_StartCapture();
+ clc.voipPower = 0.0f;
+ clc.voipOutgoingSequence = 0;
+ clc.voipOutgoingGeneration++;
+ if (clc.voipOutgoingGeneration == 0) // don't have a zero generation...
+ clc.voipOutgoingGeneration = 1; // ...so new clients won't match.
+ }
+
+ if ((cl_voipSend->integer) || (finalFrame)) { // user wants to capture audio?
+ // !!! FIXME: 8000, MONO16, 4096 samples are hardcoded in snd_openal.c
+ int samples = S_AvailableCaptureSamples();
+ const int mult = (finalFrame) ? 1 : 12; // 12 == 240ms of audio.
+
+ // enough data buffered in audio hardware to process yet?
+ if (samples >= (clc.speexFrameSize * mult)) {
+ // audio capture is always MONO16 (and that's what speex wants!).
+ static int16_t sampbuffer[4096]; // !!! FIXME: don't hardcode.
+ int16_t voipPower = 0;
+ int speexFrames = 0;
+ int wpos = 0;
+ int pos = 0;
+
+ if (samples > (clc.speexFrameSize * 12))
+ samples = (clc.speexFrameSize * 12);
+
+ // !!! FIXME: maybe separate recording from encoding, so voipPower
+ // !!! FIXME: updates faster than 4Hz?
+
+ samples -= samples % clc.speexFrameSize;
+ S_Capture(samples, (byte *) sampbuffer); // grab from audio card.
+
+ // this will probably generate multiple speex packets each time.
+ while (samples > 0) {
+ int i, bytes;
+
+ // Check the "power" of this packet...
+ for (i = 0; i < clc.speexFrameSize; i++) {
+ int16_t s = sampbuffer[i+pos];
+ if (s < 0)
+ s = -s;
+ if (s > voipPower)
+ voipPower = s; // !!! FIXME: this isn't very clever.
+ }
+
+ // Encode raw audio samples into Speex data...
+ speex_bits_reset(&clc.speexEncoderBits);
+ speex_encode_int(clc.speexEncoder, &sampbuffer[pos],
+ &clc.speexEncoderBits);
+ bytes = speex_bits_write(&clc.speexEncoderBits,
+ (char *) &clc.voipOutgoingData[wpos+1],
+ sizeof (clc.voipOutgoingData) - (wpos+1));
+ assert((bytes > 0) && (bytes < 256));
+ clc.voipOutgoingData[wpos] = (byte) bytes;
+ wpos += bytes + 1;
+
+ // look at the data for the next packet...
+ pos += clc.speexFrameSize;
+ samples -= clc.speexFrameSize;
+ speexFrames++;
+ }
+ clc.voipPower = ((float) voipPower) / 32767.0f;
+ clc.voipOutgoingDataSize = wpos;
+ clc.voipOutgoingDataFrames = speexFrames;
+
+ Com_DPrintf("Outgoing VoIP data: %d frames, %d bytes, %f power\n",
+ speexFrames, wpos, clc.voipPower);
+
+ #if 0
+ static FILE *encio = NULL;
+ if (encio == NULL) encio = fopen("outgoing-encoded.bin", "wb");
+ if (encio != NULL) { fwrite(clc.voipOutgoingData, wpos, 1, encio); fflush(encio); }
+ static FILE *decio = NULL;
+ if (decio == NULL) decio = fopen("outgoing-decoded.bin", "wb");
+ if (decio != NULL) { fwrite(sampbuffer, speexFrames * clc.speexFrameSize * 2, 1, decio); fflush(decio); }
+ #endif
+ }
+ }
+
+ // User requested we stop recording, and we've now processed the last of
+ // any previously-buffered data. Pause the capture device, etc.
+ if (finalFrame) {
+ S_StopCapture();
+ S_MasterGain(1.0f);
+ clc.voipPower = 0.0f; // force this value so it doesn't linger.
+ }
+}
+#endif
+
/*
=======================================================================
@@ -905,6 +1105,25 @@ void CL_Disconnect( qboolean showMainMenu ) {
}
#endif
+#if USE_VOIP
+ if (cl_voipSend->integer) {
+ Cvar_Set("cl_voipSend", "0");
+ CL_CaptureVoip(); // clean up any state...
+ }
+
+ if (clc.speexInitialized) {
+ int i;
+ speex_bits_destroy(&clc.speexEncoderBits);
+ speex_encoder_destroy(clc.speexEncoder);
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ speex_bits_destroy(&clc.speexDecoderBits[i]);
+ speex_decoder_destroy(clc.speexDecoder[i]);
+ }
+ }
+
+ Cmd_RemoveCommand ("voip");
+#endif
+
if ( clc.demofile ) {
FS_FCloseFile( clc.demofile );
clc.demofile = 0;
@@ -939,6 +1158,11 @@ void CL_Disconnect( qboolean showMainMenu ) {
// not connected to a pure server anymore
cl_connectedToPureServer = qfalse;
+#if USE_VOIP
+ // not connected to voip server anymore.
+ cl_connectedToVoipServer = qfalse;
+#endif
+
// Stop recording any video
if( CL_VideoRecording( ) ) {
// Finish rendering current frame
@@ -2359,9 +2583,14 @@ void CL_Frame ( int msec ) {
// update audio
S_Update();
+#if USE_VOIP
+ CL_CaptureVoip();
+#endif
+
#ifdef USE_MUMBLE
CL_UpdateMumble();
#endif
+
// advance local effects for next frame
SCR_RunCinematic();
@@ -2781,6 +3010,12 @@ void CL_Init( void ) {
cl_mumbleScale = Cvar_Get ("cl_mumbleScale", "0.0254", CVAR_ARCHIVE);
#endif
+#if USE_VOIP
+ cl_voipSend = Cvar_Get ("cl_voipSend", "0", 0);
+ cl_voipGainDuringCapture = Cvar_Get ("cl_voipGainDuringCapture", "0.2", CVAR_ARCHIVE);
+ voip = Cvar_Get ("voip", "0", CVAR_USERINFO | CVAR_ARCHIVE);
+#endif
+
// userinfo
Cvar_Get ("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("rate", "3000", CVAR_USERINFO | CVAR_ARCHIVE );
diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c
index 72ae6ed..167b035 100644
--- a/code/client/cl_parse.c
+++ b/code/client/cl_parse.c
@@ -32,7 +32,12 @@ char *svc_strings[256] = {
"svc_baseline",
"svc_serverCommand",
"svc_download",
- "svc_snapshot"
+ "svc_snapshot",
+ "svc_EOF",
+
+#if USE_VOIP
+ "svc_voip"
+#endif
};
void SHOWNET( msg_t *msg, char *s) {
@@ -327,6 +332,10 @@ void CL_ParseSnapshot( msg_t *msg ) {
int cl_connectedToPureServer;
int cl_connectedToCheatServer;
+#if USE_VOIP
+int cl_connectedToVoipServer;
+#endif
+
/*
==================
CL_SystemInfoChanged
@@ -355,6 +364,11 @@ void CL_SystemInfoChanged( void ) {
return;
}
+#if USE_VOIP
+ s = Info_ValueForKey( systemInfo, "sv_voip" );
+ cl_connectedToVoipServer = atoi( s );
+#endif
+
s = Info_ValueForKey( systemInfo, "sv_cheats" );
cl_connectedToCheatServer = atoi( s );
if ( !cl_connectedToCheatServer ) {
@@ -621,6 +635,162 @@ void CL_ParseDownload ( msg_t *msg ) {
}
}
+#if USE_VOIP
+static
+qboolean CL_ShouldIgnoreVoipSender(int sender)
+{
+ if (!voip->integer)
+ return qtrue; // VoIP is disabled.
+ else if (sender == clc.clientNum)
+ return qtrue; // this is us, don't output our own voice.
+ else if (clc.voipMuteAll)
+ return qtrue; // all channels are muted with extreme prejudice.
+ else if (clc.voipIgnore[sender])
+ return qtrue; // just ignoring this guy.
+
+ return qfalse; // !!! FIXME: implement per-channel muting.
+}
+
+/*
+=====================
+CL_ParseVoip
+
+A VoIP message has been received from the server
+=====================
+*/
+static
+void CL_ParseVoip ( msg_t *msg ) {
+ static short decoded[4096]; // !!! FIXME: don't hardcode.
+
+ 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);
+ char encoded[1024];
+ int seqdiff = sequence - clc.voipIncomingSequence[sender];
+ 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.
+ }
+
+ if (!clc.speexInitialized) {
+ MSG_ReadData(msg, encoded, packetsize); // skip payload.
+ return; // can't handle VoIP without libspeex!
+ } else if (sender >= MAX_CLIENTS) {
+ MSG_ReadData(msg, encoded, packetsize); // skip payload.
+ return; // bogus sender.
+ } else if (CL_ShouldIgnoreVoipSender(sender)) {
+ MSG_ReadData(msg, encoded, packetsize); // skip payload.
+ return; // Channel is muted, bail.
+ }
+
+ // !!! FIXME: make sure data is narrowband? Does decoder handle this?
+
+ Com_DPrintf("VoIP: packet accepted!\n");
+
+ // 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);
+ speex_bits_reset(&clc.speexDecoderBits[sender]);
+ 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 bits just in case.
+ speex_bits_reset(&clc.speexDecoderBits[sender]);
+ seqdiff = 0;
+ } else if (seqdiff > 100) { // more than 2 seconds of audio dropped?
+ // just start over.
+ Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n",
+ seqdiff, sender);
+ speex_bits_reset(&clc.speexDecoderBits[sender]);
+ seqdiff = 0;
+ }
+
+ if (seqdiff != 0) {
+ Com_DPrintf("VoIP: Dropped %d frames from client #%d\n",
+ seqdiff, sender);
+ // tell speex that we're missing frames...
+ for (i = 0; i < seqdiff; i++) {
+ assert((written + clc.speexFrameSize) * 2 < sizeof (decoded));
+ speex_decode_int(clc.speexDecoder[sender], NULL, decoded + written);
+ written += clc.speexFrameSize;
+ }
+ }
+
+ for (i = 0; i < frames; i++) {
+ char encoded[256];
+ const int len = MSG_ReadByte(msg);
+ if (len < 0) {
+ Com_DPrintf("VoIP: Short packet!\n");
+ break;
+ }
+ MSG_ReadData(msg, encoded, len);
+
+ // shouldn't happen, but just in case...
+ if ((written + clc.speexFrameSize) * 2 > sizeof (decoded)) {
+ Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
+ written * 2, written, i);
+ S_RawSamples(sender + 1, written, 8000, 2, 1,
+ (const byte *) decoded, 1.0f); // !!! FIXME: hardcoding!
+ written = 0;
+ }
+
+ speex_bits_read_from(&clc.speexDecoderBits[sender], encoded, len);
+ speex_decode_int(clc.speexDecoder[sender],
+ &clc.speexDecoderBits[sender], decoded + written);
+
+ #if 0
+ static FILE *encio = NULL;
+ if (encio == NULL) encio = fopen("incoming-encoded.bin", "wb");
+ if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); }
+ static FILE *decio = NULL;
+ if (decio == NULL) decio = fopen("incoming-decoded.bin", "wb");
+ if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); }
+ #endif
+
+ written += clc.speexFrameSize;
+ }
+
+ Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
+ written * 2, written, i);
+
+ if (written > 0) {
+ S_RawSamples(sender + 1, written, 8000, 2, 1,
+ (const byte *) decoded, 1.0f); // !!! FIXME: hardcoding!
+ }
+
+ clc.voipIncomingSequence[sender] = sequence + frames;
+}
+#endif
+
+
/*
=====================
CL_ParseCommandString
@@ -714,6 +884,11 @@ void CL_ParseServerMessage( msg_t *msg ) {
case svc_download:
CL_ParseDownload( msg );
break;
+#if USE_VOIP
+ case svc_voip:
+ CL_ParseVoip( msg );
+ break;
+#endif
}
}
}
diff --git a/code/client/client.h b/code/client/client.h
index b064404..802e99f 100644
--- a/code/client/client.h
+++ b/code/client/client.h
@@ -34,6 +34,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "cl_curl.h"
#endif /* USE_CURL */
+#if USE_VOIP
+#include "speex/speex.h"
+#endif
+
// file full of random crap that gets used to create cl_guid
#define QKEY_FILE "qkey"
#define QKEY_SIZE 2048
@@ -225,6 +229,30 @@ typedef struct {
int timeDemoMaxDuration; // maximum frame duration
unsigned char timeDemoDurations[ MAX_TIMEDEMO_DURATIONS ]; // log of frame durations
+#if USE_VOIP
+ qboolean speexInitialized;
+ int speexFrameSize;
+
+ // incoming data...
+ // !!! FIXME: convert from parallel arrays to array of a struct.
+ SpeexBits speexDecoderBits[MAX_CLIENTS];
+ void *speexDecoder[MAX_CLIENTS];
+ byte voipIncomingGeneration[MAX_CLIENTS];
+ int voipIncomingSequence[MAX_CLIENTS];
+ qboolean voipIgnore[MAX_CLIENTS];
+ qboolean voipMuteAll;
+
+ // outgoing data...
+ SpeexBits speexEncoderBits;
+ void *speexEncoder;
+ int voipOutgoingDataSize;
+ int voipOutgoingDataFrames;
+ int voipOutgoingSequence;
+ byte voipOutgoingGeneration;
+ byte voipOutgoingData[1024];
+ float voipPower;
+#endif
+
// big stuff at end of structure so most offsets are 15 bits or less
netchan_t netchan;
} clientConnection_t;
@@ -372,6 +400,12 @@ extern cvar_t *cl_useMumble;
extern cvar_t *cl_mumbleScale;
#endif
+#if USE_VOIP
+extern cvar_t *cl_voipSend;
+extern cvar_t *cl_voipGainDuringCapture;
+extern cvar_t *voip;
+#endif
+
//=================================================
//
@@ -426,6 +460,10 @@ extern kbutton_t in_mlook, in_klook;
extern kbutton_t in_strafe;
extern kbutton_t in_speed;
+#if USE_VOIP
+extern kbutton_t in_voiprecord;
+#endif
+
void CL_InitInput (void);
void CL_SendCmd (void);
void CL_ClearState (void);
@@ -447,6 +485,11 @@ void Key_SetCatcher( int catcher );
extern int cl_connectedToPureServer;
extern int cl_connectedToCheatServer;
+#if USE_VOIP
+extern int cl_connectedToVoipServer;
+void CL_Voip_f( void );
+#endif
+
void CL_SystemInfoChanged( void );
void CL_ParseServerMessage( msg_t *msg );
diff --git a/code/client/snd_dma.c b/code/client/snd_dma.c
index c1fb41e..0848fe4 100644
--- a/code/client/snd_dma.c
+++ b/code/client/snd_dma.c
@@ -90,8 +90,8 @@ cvar_t *s_mixPreStep;
static loopSound_t loopSounds[MAX_GENTITIES];
static channel_t *freelist = NULL;
-int s_rawend;
-portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES];
+int s_rawend[MAX_RAW_STREAMS];
+portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES];
// ====================================================================
@@ -120,6 +120,42 @@ void S_Base_SoundInfo(void) {
Com_Printf("----------------------\n" );
}
+
+#if USE_VOIP
+static
+void S_Base_StartCapture( void )
+{
+ // !!! FIXME: write me.
+}
+
+static
+int S_Base_AvailableCaptureSamples( void )
+{
+ // !!! FIXME: write me.
+ return 0;
+}
+
+static
+void S_Base_Capture( int samples, byte *data )
+{
+ // !!! FIXME: write me.
+}
+
+static
+void S_Base_StopCapture( void )
+{
+ // !!! FIXME: write me.
+}
+
+static
+void S_Base_MasterGain( float val )
+{
+ // !!! FIXME: write me.
+}
+#endif
+
+
+
/*
=================
S_Base_SoundList
@@ -608,7 +644,7 @@ void S_Base_ClearSoundBuffer( void ) {
S_ChannelSetup();
- s_rawend = 0;
+ Com_Memset(s_rawend, '\0', sizeof (s_rawend));
if (dma.samplebits == 8)
clear = 0x80;
@@ -879,10 +915,6 @@ void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *d
}
}
-portable_samplepair_t *S_GetRawSamplePointer( void ) {
- return s_rawsamples;
-}
-
/*
============
S_RawSamples
@@ -890,36 +922,42 @@ S_RawSamples
Music streaming
============
*/
-void S_Base_RawSamples( int samples, int rate, int width, int s_channels, const byte *data, float volume ) {
+void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_channels, const byte *data, float volume ) {
int i;
int src, dst;
float scale;
int intVolume;
+ portable_samplepair_t *rawsamples;
if ( !s_soundStarted || s_soundMuted ) {
return;
}
+ if ( (stream < 0) || (stream >= MAX_RAW_STREAMS) ) {
+ return;
+ }
+ rawsamples = s_rawsamples[stream];
+
intVolume = 256 * volume;
- if ( s_rawend < s_soundtime ) {
- Com_DPrintf( "S_RawSamples: resetting minimum: %i < %i\n", s_rawend, s_soundtime );
- s_rawend = s_soundtime;
+ if ( s_rawend[stream] < s_soundtime ) {
+ Com_DPrintf( "S_RawSamples: resetting minimum: %i < %i\n", s_rawend[stream], s_soundtime );
+ s_rawend[stream] = s_soundtime;
}
scale = (float)rate / dma.speed;
-//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend);
+//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend[stream]);
if (s_channels == 2 && width == 2)
{
if (scale == 1.0)
{ // optimized case
for (i=0 ; i<samples ; i++)
{
- dst = s_rawend&(MAX_RAW_SAMPLES-1);
- s_rawend++;
- s_rawsamples[dst].left = ((short *)data)[i*2] * intVolume;
- s_rawsamples[dst].right = ((short *)data)[i*2+1] * intVolume;
+ dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+ s_rawend[stream]++;
+ rawsamples[dst].left = ((short *)data)[i*2] * intVolume;
+ rawsamples[dst].right = ((short *)data)[i*2+1] * intVolume;
}
}
else
@@ -929,10 +967,10 @@ void S_Base_RawSamples( int samples, int rate, int width, int s_channels, const
src = i*scale;
if (src >= samples)
break;
- dst = s_rawend&(MAX_RAW_SAMPLES-1);
- s_rawend++;
- s_rawsamples[dst].left = ((short *)data)[src*2] * intVolume;
- s_rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume;
+ dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+ s_rawend[stream]++;
+ rawsamples[dst].left = ((short *)data)[src*2] * intVolume;
+ rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume;
}
}
}
@@ -943,10 +981,10 @@ void S_Base_RawSamples( int samples, int rate, int width, int s_channels, const
src = i*scale;
if (src >= samples)
break;
- dst = s_rawend&(MAX_RAW_SAMPLES-1);
- s_rawend++;
- s_rawsamples[dst].left = ((short *)data)[src] * intVolume;
- s_rawsamples[dst].right = ((short *)data)[src] * intVolume;
+ dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+ s_rawend[stream]++;
+ rawsamples[dst].left = ((short *)data)[src] * intVolume;
+ rawsamples[dst].right = ((short *)data)[src] * intVolume;
}
}
else if (s_channels == 2 && width == 1)
@@ -958,10 +996,10 @@ void S_Base_RawSamples( int samples, int rate, int width, int s_channels, const
src = i*scale;
if (src >= samples)
break;
- dst = s_rawend&(MAX_RAW_SAMPLES-1);
- s_rawend++;
- s_rawsamples[dst].left = ((char *)data)[src*2] * intVolume;
- s_rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume;
+ dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+ s_rawend[stream]++;
+ rawsamples[dst].left = ((char *)data)[src*2] * intVolume;
+ rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume;
}
}
else if (s_channels == 1 && width == 1)
@@ -973,15 +1011,15 @@ void S_Base_RawSamples( int samples, int rate, int width, int s_channels, const
src = i*scale;
if (src >= samples)
break;
- dst = s_rawend&(MAX_RAW_SAMPLES-1);
- s_rawend++;
- s_rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume;
- s_rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume;
+ dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
+ s_rawend[stream]++;
+ rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume;
+ rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume;
}
}
- if ( s_rawend > s_soundtime + MAX_RAW_SAMPLES ) {
- Com_DPrintf( "S_RawSamples: overflowed %i > %i\n", s_rawend, s_soundtime );
+ if ( s_rawend[stream] > s_soundtime + MAX_RAW_SAMPLES ) {
+ Com_DPrintf( "S_RawSamples: overflowed %i > %i\n", s_rawend[stream], s_soundtime );
}
}
@@ -1258,7 +1296,7 @@ void S_Base_StopBackgroundTrack( void ) {
return;
S_CodecCloseStream(s_backgroundStream);
s_backgroundStream = NULL;
- s_rawend = 0;
+ s_rawend[0] = 0;
}
/*
@@ -1331,12 +1369,12 @@ void S_UpdateBackgroundTrack( void ) {
}
// see how many samples should be copied into the raw buffer
- if ( s_rawend < s_soundtime ) {
- s_rawend = s_soundtime;
+ if ( s_rawend[0] < s_soundtime ) {
+ s_rawend[0] = s_soundtime;
}
- while ( s_rawend < s_soundtime + MAX_RAW_SAMPLES ) {
- bufferSamples = MAX_RAW_SAMPLES - (s_rawend - s_soundtime);
+ while ( s_rawend[0] < s_soundtime + MAX_RAW_SAMPLES ) {
+ bufferSamples = MAX_RAW_SAMPLES - (s_rawend[0] - s_soundtime);
// decide how much data needs to be read from the file
fileSamples = bufferSamples * s_backgroundStream->info.rate / dma.speed;
@@ -1359,7 +1397,7 @@ void S_UpdateBackgroundTrack( void ) {
if(r > 0)
{
// add to raw buffer
- S_Base_RawSamples( fileSamples, s_backgroundStream->info.rate,
+ S_Base_RawSamples( 0, fileSamples, s_backgroundStream->info.rate,
s_backgroundStream->info.width, s_backgroundStream->info.channels, raw, musicVolume );
}
else
@@ -1492,5 +1530,13 @@ qboolean S_Base_Init( soundInterface_t *si ) {
si->SoundInfo = S_Base_SoundInfo;
si->SoundList = S_Base_SoundList;
+#if USE_VOIP
+ si->StartCapture = S_Base_StartCapture;
+ si->AvailableCaptureSamples = S_Base_AvailableCaptureSamples;
+ si->Capture = S_Base_Capture;
+ si->StopCapture = S_Base_StopCapture;
+ si->MasterGain = S_Base_MasterGain;
+#endif
+
return qtrue;
}
diff --git a/code/client/snd_local.h b/code/client/snd_local.h
index 3b45a99..1a8b5dc 100644
--- a/code/client/snd_local.h
+++ b/code/client/snd_local.h
@@ -125,7 +125,7 @@ typedef struct
void (*StartLocalSound)( sfxHandle_t sfx, int channelNum );
void (*StartBackgroundTrack)( const char *intro, const char *loop );
void (*StopBackgroundTrack)( void );
- void (*RawSamples)(int samples, int rate, int width, int channels, const byte *data, float volume);
+ void (*RawSamples)(int stream, int samples, int rate, int width, int channels, const byte *data, float volume);
void (*StopAllSounds)( void );
void (*ClearLoopingSounds)( qboolean killall );
void (*AddLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
@@ -140,6 +140,13 @@ typedef struct
void (*ClearSoundBuffer)( void );
void (*SoundInfo)( void );
void (*SoundList)( void );
+#if USE_VOIP
+ void (*StartCapture)( void );
+ int (*AvailableCaptureSamples)( void );
+ void (*Capture)( int samples, byte *data );
+ void (*StopCapture)( void );
+ void (*MasterGain)( float gain );
+#endif
} soundInterface_t;
@@ -173,14 +180,15 @@ extern channel_t loop_channels[MAX_CHANNELS];
extern int numLoopChannels;
extern int s_paintedtime;
-extern int s_rawend;
extern vec3_t listener_forward;
extern vec3_t listener_right;
extern vec3_t listener_up;
extern dma_t dma;
#define MAX_RAW_SAMPLES 16384
-extern portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES];
+#define MAX_RAW_STREAMS 128
+extern portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES];
+extern int s_rawend[MAX_RAW_STREAMS];
extern cvar_t *s_volume;
extern cvar_t *s_musicVolume;
@@ -197,7 +205,6 @@ void SND_setup( void );
void S_PaintChannels(int endtime);
void S_memoryLoad(sfx_t *sfx);
-portable_samplepair_t *S_GetRawSamplePointer( void );
// spatializes a channel
void S_Spatialize(channel_t *ch);
diff --git a/code/client/snd_main.c b/code/client/snd_main.c
index 2526336..3da9fd0 100644
--- a/code/client/snd_main.c
+++ b/code/client/snd_main.c
@@ -62,6 +62,14 @@ static qboolean S_ValidSoundInterface( soundInterface_t *si )
if( !si->SoundInfo ) return qfalse;
if( !si->SoundList ) return qfalse;
+#if USE_VOIP
+ if( !si->StartCapture ) return qfalse;
+ if( !si->AvailableCaptureSamples ) return qfalse;
+ if( !si->Capture ) return qfalse;
+ if( !si->StopCapture ) return qfalse;
+ if( !si->MasterGain ) return qfalse;
+#endif
+
return qtrue;
}
@@ -118,11 +126,11 @@ void S_StopBackgroundTrack( void )
S_RawSamples
=================
*/
-void S_RawSamples (int samples, int rate, int width, int channels,
+void S_RawSamples (int stream, int samples, int rate, int width, int channels,
const byte *data, float volume)
{
if( si.RawSamples ) {
- si.RawSamples( samples, rate, width, channels, data, volume );
+ si.RawSamples( stream, samples, rate, width, channels, data, volume );
}
}
@@ -304,6 +312,70 @@ void S_SoundList( void )
}
}
+
+#if USE_VOIP
+/*
+=================
+S_StartCapture
+=================
+*/
+void S_StartCapture( void )
+{
+ if( si.StartCapture ) {
+ si.StartCapture( );
+ }
+}
+
+/*
+=================
+S_AvailableCaptureSamples
+=================
+*/
+int S_AvailableCaptureSamples( void )
+{
+ if( si.AvailableCaptureSamples ) {
+ return si.AvailableCaptureSamples( );
+ }
+ return 0;
+}
+
+/*
+=================
+S_Capture
+=================
+*/
+void S_Capture( int samples, byte *data )
+{
+ if( si.Capture ) {
+ si.Capture( samples, data );
+ }
+}
+
+/*
+=================
+S_StopCapture
+=================
+*/
+void S_StopCapture( void )
+{
+ if( si.StopCapture ) {
+ si.StopCapture( );
+ }
+}
+
+/*
+=================
+S_MasterGain
+=================
+*/
+void S_MasterGain( float gain )
+{
+ if( si.MasterGain ) {
+ si.MasterGain( gain );
+ }
+}
+#endif
+
//=============================================================================
/*
diff --git a/code/client/snd_mix.c b/code/client/snd_mix.c
index 62e6841..cf0a999 100644
--- a/code/client/snd_mix.c
+++ b/code/client/snd_mix.c
@@ -631,12 +631,12 @@ S_PaintChannels
void S_PaintChannels( int endtime ) {
int i;
int end;
+ int stream;
channel_t *ch;
sfx_t *sc;
int ltime, count;
int sampleOffset;
-
snd_vol = s_volume->value*255;
//Com_Printf ("%i to %i\n", s_paintedtime, endtime);
@@ -648,30 +648,18 @@ void S_PaintChannels( int endtime ) {
end = s_paintedtime + PAINTBUFFER_SIZE;
}
- // clear the paint buffer to either music or zeros
- if ( s_rawend < s_paintedtime ) {
- if ( s_rawend ) {
- //Com_DPrintf ("background sound underrun\n");
- }
- Com_Memset(paintbuffer, 0, (end - s_paintedtime) * sizeof(portable_samplepair_t));
- } else {
- // copy from the streaming sound source
- int s;
- int stop;
-
- stop = (end < s_rawend) ? end : s_rawend;
-
- for ( i = s_paintedtime ; i < stop ; i++ ) {
- s = i&(MAX_RAW_SAMPLES-1);
- paintbuffer[i-s_paintedtime] = s_rawsamples[s];
- }
-// if (i != end)
-// Com_Printf ("partial stream\n");
-// else
-// Com_Printf ("full stream\n");
- for ( ; i < end ; i++ ) {
- paintbuffer[i-s_paintedtime].left =
- paintbuffer[i-s_paintedtime].right = 0;
+ // clear the paint buffer and mix any raw samples...
+ Com_Memset(paintbuffer, 0, sizeof (paintbuffer));
+ for (stream = 0; stream < MAX_RAW_STREAMS; stream++) {
+ if ( s_rawend[stream] >= s_paintedtime ) {
+ // copy from the streaming sound source
+ const portable_samplepair_t *rawsamples = s_rawsamples[stream];
+ const int stop = (end < s_rawend[stream]) ? end : s_rawend[stream];
+ for ( i = s_paintedtime ; i < stop ; i++ ) {
+ const int s = i&(MAX_RAW_SAMPLES-1);
+ paintbuffer[i-s_paintedtime].left += rawsamples[s].left;
+ paintbuffer[i-s_paintedtime].right += rawsamples[s].right;
+ }
}
}
diff --git a/code/client/snd_openal.c b/code/client/snd_openal.c
index 74be8f2..3295e3e 100644
--- a/code/client/snd_openal.c
+++ b/code/client/snd_openal.c
@@ -1253,35 +1253,37 @@ ALuint S_AL_SrcGet(srcHandle_t src)
//===========================================================================
-
-static srcHandle_t streamSourceHandle = -1;
-static qboolean streamPlaying = qfalse;
-static ALuint streamSource;
+static srcHandle_t streamSourceHandles[MAX_RAW_STREAMS];
+static qboolean streamPlaying[MAX_RAW_STREAMS];
+static ALuint streamSources[MAX_RAW_STREAMS];
/*
=================
S_AL_AllocateStreamChannel
=================
*/
-static void S_AL_AllocateStreamChannel( void )
+static void S_AL_AllocateStreamChannel( int stream )
{
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
// Allocate a streamSource at high priority
- streamSourceHandle = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0);
- if(streamSourceHandle == -1)
+ streamSourceHandles[stream] = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0);
+ if(streamSourceHandles[stream] == -1)
return;
// Lock the streamSource so nobody else can use it, and get the raw streamSource
- S_AL_SrcLock(streamSourceHandle);
- streamSource = S_AL_SrcGet(streamSourceHandle);
+ S_AL_SrcLock(streamSourceHandles[stream]);
+ streamSources[stream] = S_AL_SrcGet(streamSourceHandles[stream]);
// Set some streamSource parameters
- qalSourcei (streamSource, AL_BUFFER, 0 );
- qalSourcei (streamSource, AL_LOOPING, AL_FALSE );
- qalSource3f(streamSource, AL_POSITION, 0.0, 0.0, 0.0);
- qalSource3f(streamSource, AL_VELOCITY, 0.0, 0.0, 0.0);
- qalSource3f(streamSource, AL_DIRECTION, 0.0, 0.0, 0.0);
- qalSourcef (streamSource, AL_ROLLOFF_FACTOR, 0.0 );
- qalSourcei (streamSource, AL_SOURCE_RELATIVE, AL_TRUE );
+ qalSourcei (streamSources[stream], AL_BUFFER, 0 );
+ qalSourcei (streamSources[stream], AL_LOOPING, AL_FALSE );
+ qalSource3f(streamSources[stream], AL_POSITION, 0.0, 0.0, 0.0);
+ qalSource3f(streamSources[stream], AL_VELOCITY, 0.0, 0.0, 0.0);
+ qalSource3f(streamSources[stream], AL_DIRECTION, 0.0, 0.0, 0.0);
+ qalSourcef (streamSources[stream], AL_ROLLOFF_FACTOR, 0.0 );
+ qalSourcei (streamSources[stream], AL_SOURCE_RELATIVE, AL_TRUE );
}
/*
@@ -1289,12 +1291,15 @@ static void S_AL_AllocateStreamChannel( void )
S_AL_FreeStreamChannel
=================
*/
-static void S_AL_FreeStreamChannel( void )
+static void S_AL_FreeStreamChannel( int stream )
{
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
// Release the output streamSource
- S_AL_SrcUnlock(streamSourceHandle);
- streamSource = 0;
- streamSourceHandle = -1;
+ S_AL_SrcUnlock(streamSourceHandles[stream]);
+ streamSources[stream] = 0;
+ streamSourceHandles[stream] = -1;
}
/*
@@ -1303,20 +1308,23 @@ S_AL_RawSamples
=================
*/
static
-void S_AL_RawSamples(int samples, int rate, int width, int channels, const byte *data, float volume)
+void S_AL_RawSamples(int stream, int samples, int rate, int width, int channels, const byte *data, float volume)
{
ALuint buffer;
ALuint format;
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
format = S_AL_Format( width, channels );
// Create the streamSource if necessary
- if(streamSourceHandle == -1)
+ if(streamSourceHandles[stream] == -1)
{
- S_AL_AllocateStreamChannel();
+ S_AL_AllocateStreamChannel(stream);
// Failed?
- if(streamSourceHandle == -1)
+ if(streamSourceHandles[stream] == -1)
{
Com_Printf( S_COLOR_RED "ERROR: Can't allocate streaming streamSource\n");
return;
@@ -1328,10 +1336,10 @@ void S_AL_RawSamples(int samples, int rate, int width, int channels, const byte
qalBufferData(buffer, format, (ALvoid *)data, (samples * width * channels), rate);
// Shove the data onto the streamSource
- qalSourceQueueBuffers(streamSource, 1, &buffer);
+ qalSourceQueueBuffers(streamSources[stream], 1, &buffer);
// Volume
- qalSourcef (streamSource, AL_GAIN, volume * s_volume->value * s_alGain->value);
+ qalSourcef (streamSources[stream], AL_GAIN, volume * s_volume->value * s_alGain->value);
}
/*
@@ -1340,40 +1348,43 @@ S_AL_StreamUpdate
=================
*/
static
-void S_AL_StreamUpdate( void )
+void S_AL_StreamUpdate( int stream )
{
int numBuffers;
ALint state;
- if(streamSourceHandle == -1)
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
+ if(streamSourceHandles[stream] == -1)
return;
// Un-queue any buffers, and delete them
- qalGetSourcei( streamSource, AL_BUFFERS_PROCESSED, &numBuffers );
+ qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers );
while( numBuffers-- )
{
ALuint buffer;
- qalSourceUnqueueBuffers(streamSource, 1, &buffer);
+ qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer);
qalDeleteBuffers(1, &buffer);
}
// Start the streamSource playing if necessary
- qalGetSourcei( streamSource, AL_BUFFERS_QUEUED, &numBuffers );
+ qalGetSourcei( streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers );
- qalGetSourcei(streamSource, AL_SOURCE_STATE, &state);
+ qalGetSourcei(streamSources[stream], AL_SOURCE_STATE, &state);
if(state == AL_STOPPED)
{
- streamPlaying = qfalse;
+ streamPlaying[stream] = qfalse;
// If there are no buffers queued up, release the streamSource
if( !numBuffers )
- S_AL_FreeStreamChannel( );
+ S_AL_FreeStreamChannel( stream );
}
- if( !streamPlaying && numBuffers )
+ if( !streamPlaying[stream] && numBuffers )
{
- qalSourcePlay( streamSource );
- streamPlaying = qtrue;
+ qalSourcePlay( streamSources[stream] );
+ streamPlaying[stream] = qtrue;
}
}
@@ -1383,14 +1394,17 @@ S_AL_StreamDie
=================
*/
static
-void S_AL_StreamDie( void )
+void S_AL_StreamDie( int stream )
{
- if(streamSourceHandle == -1)
+ if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
+ return;
+
+ if(streamSourceHandles[stream] == -1)
return;
- streamPlaying = qfalse;
- qalSourceStop(streamSource);
- S_AL_FreeStreamChannel();
+ streamPlaying[stream] = qfalse;
+ qalSourceStop(streamSources[stream]);
+ S_AL_FreeStreamChannel(stream);
}
@@ -1682,6 +1696,11 @@ void S_AL_MusicUpdate( void )
static ALCdevice *alDevice;
static ALCcontext *alContext;
+#if USE_VOIP
+static ALCdevice *alCaptureDevice;
+static cvar_t *s_alCapture;
+#endif
+
#ifdef _WIN32
#define ALDRIVER_DEFAULT "OpenAL32.dll"
#define ALDEVICE_DEFAULT "Generic Software"
@@ -1699,9 +1718,11 @@ S_AL_StopAllSounds
static
void S_AL_StopAllSounds( void )
{
+ int i;
S_AL_SrcShutup();
S_AL_StopBackgroundTrack();
- S_AL_StreamDie();
+ for (i = 0; i < MAX_RAW_STREAMS; i++)
+ S_AL_StreamDie(i);
}
/*
@@ -1742,11 +1763,14 @@ S_AL_Update
static
void S_AL_Update( void )
{
+ int i;
+
// Update SFX channels
S_AL_SrcUpdate();
// Update streams
- S_AL_StreamUpdate();
+ for (i = 0; i < MAX_RAW_STREAMS; i++)
+ S_AL_StreamUpdate(i);
S_AL_MusicUpdate();
// Doppler
@@ -1820,6 +1844,47 @@ void S_AL_SoundList( void )
{
}
+#if USE_VOIP
+static
+void S_AL_StartCapture( void )
+{
+ if (alCaptureDevice != NULL)
+ qalcCaptureStart(alCaptureDevice);
+}
+
+static
+int S_AL_AvailableCaptureSamples( void )
+{
+ int retval = 0;
+ if (alCaptureDevice != NULL)
+ {
+ ALint samples = 0;
+ qalcGetIntegerv(alCaptureDevice, ALC_CAPTURE_SAMPLES, sizeof (samples), &samples);
+ retval = (int) samples;
+ }
+ return retval;
+}
+
+static
+void S_AL_Capture( int samples, byte *data )
+{
+ if (alCaptureDevice != NULL)
+ qalcCaptureSamples(alCaptureDevice, data, samples);
+}
+
+void S_AL_StopCapture( void )
+{
+ if (alCaptureDevice != NULL)
+ qalcCaptureStop(alCaptureDevice);
+}
+
+void S_AL_MasterGain( float gain )
+{
+ qalListenerf(AL_GAIN, gain);
+}
+#endif
+
+
/*
=================
S_AL_SoundInfo
@@ -1832,7 +1897,8 @@ void S_AL_SoundInfo( void )
Com_Printf( " Vendor: %s\n", qalGetString( AL_VENDOR ) );
Com_Printf( " Version: %s\n", qalGetString( AL_VERSION ) );
Com_Printf( " Renderer: %s\n", qalGetString( AL_RENDERER ) );
- Com_Printf( " Extensions: %s\n", qalGetString( AL_EXTENSIONS ) );
+ Com_Printf( " AL Extensions: %s\n", qalGetString( AL_EXTENSIONS ) );
+ Com_Printf( " ALC Extensions: %s\n", qalcGetString( NULL, ALC_EXTENSIONS ) );
if(qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"))
{
Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_DEVICE_SPECIFIER));
@@ -1849,7 +1915,9 @@ static
void S_AL_Shutdown( void )
{
// Shut down everything
- S_AL_StreamDie( );
+ int i;
+ for (i = 0; i < MAX_RAW_STREAMS; i++)
+ S_AL_StreamDie(i);
S_AL_StopBackgroundTrack( );
S_AL_SrcShutdown( );
S_AL_BufferShutdown( );
@@ -1857,6 +1925,21 @@ void S_AL_Shutdown( void )
qalcDestroyContext(alContext);
qalcCloseDevice(alDevice);
+#if USE_VOIP
+ if (alCaptureDevice != NULL) {
+ qalcCaptureStop(alCaptureDevice);
+ qalcCaptureCloseDevice(alCaptureDevice);
+ alCaptureDevice = NULL;
+ Com_Printf( "OpenAL capture device closed.\n" );
+ }
+#endif
+
+ for (i = 0; i < MAX_RAW_STREAMS; i++) {
+ streamSourceHandles[i] = -1;
+ streamPlaying[i] = qfalse;
+ streamSources[i] = 0;
+ }
+
QAL_Shutdown();
}
@@ -1872,11 +1955,18 @@ qboolean S_AL_Init( soundInterface_t *si )
#ifdef USE_OPENAL
qboolean enumsupport, founddev = qfalse;
+ int i;
if( !si ) {
return qfalse;
}
+ for (i = 0; i < MAX_RAW_STREAMS; i++) {
+ streamSourceHandles[i] = -1;
+ streamPlaying[i] = qfalse;
+ streamSources[i] = 0;
+ }
+
// New console variables
s_alPrecache = Cvar_Get( "s_alPrecache", "1", CVAR_ARCHIVE );
s_alGain = Cvar_Get( "s_alGain", "0.4", CVAR_ARCHIVE );
@@ -1977,6 +2067,36 @@ qboolean S_AL_Init( soundInterface_t *si )
qalDopplerFactor( s_alDopplerFactor->value );
qalDopplerVelocity( s_alDopplerSpeed->value );
+#if USE_VOIP
+ // !!! FIXME: some of these alcCaptureOpenDevice() values should be cvars.
+ // !!! FIXME: add support for capture device enumeration.
+ // !!! FIXME: add some better error reporting.
+ s_alCapture = Cvar_Get( "s_alCapture", "1", CVAR_ARCHIVE );
+ if (!s_alCapture->integer) {
+ Com_Printf("OpenAL capture support disabled by user ('+set s_alCapture 1' to enable)\n");
+#if USE_MUMBLE
+ } else if (cl_useMumble->integer) {
+ Com_Printf("OpenAL capture support disabled for Mumble support\n");
+#endif
+ } else {
+ // !!! FIXME: Apple has a 1.1-compliant OpenAL, which includes
+ // !!! FIXME: capture support, but they don't list it in the
+ // !!! FIXME: extension string. We need to check the version string,
+ // !!! FIXME: then the extension string, but that's too much trouble,
+ // !!! FIXME: so we'll just check the function pointer for now.
+ //if (qalcIsExtensionPresent(NULL, "ALC_EXT_capture")) {
+ if (qalcCaptureOpenDevice == NULL) {
+ Com_Printf("No ALC_EXT_capture support, can't record audio.\n");
+ } else {
+ Com_Printf("OpenAL default capture device is '%s'\n",
+ qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER));
+ alCaptureDevice = qalcCaptureOpenDevice(NULL, 8000, AL_FORMAT_MONO16, 4096);
+ Com_Printf( "OpenAL capture device %s.\n",
+ (alCaptureDevice == NULL) ? "failed to open" : "opened");
+ }
+ }
+#endif
+
si->Shutdown = S_AL_Shutdown;
si->StartSound = S_AL_StartSound;
si->StartLocalSound = S_AL_StartLocalSound;
@@ -1998,6 +2118,14 @@ qboolean S_AL_Init( soundInterface_t *si )
si->SoundInfo = S_AL_SoundInfo;
si->SoundList = S_AL_SoundList;
+#if USE_VOIP
+ si->StartCapture = S_AL_StartCapture;
+ si->AvailableCaptureSamples = S_AL_AvailableCaptureSamples;
+ si->Capture = S_AL_Capture;
+ si->StopCapture = S_AL_StopCapture;
+ si->MasterGain = S_AL_MasterGain;
+#endif
+
return qtrue;
#else
return qfalse;
diff --git a/code/client/snd_public.h b/code/client/snd_public.h
index 030c292..e1c2309 100644
--- a/code/client/snd_public.h
+++ b/code/client/snd_public.h
@@ -33,7 +33,7 @@ void S_StopBackgroundTrack( void );
// cinematics and voice-over-network will send raw samples
// 1.0 volume will be direct output of source samples
-void S_RawSamples (int samples, int rate, int width, int channels,
+void S_RawSamples (int stream, int samples, int rate, int width, int channels,
const byte *data, float volume);
// stop all sounds and the background track
@@ -70,3 +70,13 @@ void S_ClearSoundBuffer( void );
void SNDDMA_Activate( void );
void S_UpdateBackgroundTrack( void );
+
+
+#if 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
+
diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h
index 396042e..f535d68 100644
--- a/code/qcommon/qcommon.h
+++ b/code/qcommon/qcommon.h
@@ -274,7 +274,11 @@ enum svc_ops_e {
svc_serverCommand, // [string] to be executed by client game module
svc_download, // [short] size [size bytes]
svc_snapshot,
- svc_EOF
+ svc_EOF,
+
+#if USE_VOIP
+ svc_voip
+#endif
};
@@ -287,7 +291,11 @@ enum clc_ops_e {
clc_move, // [[usercmd_t]
clc_moveNoDelta, // [[usercmd_t]
clc_clientCommand, // [string] message
- clc_EOF
+ clc_EOF,
+
+#if USE_VOIP
+ clc_voip, // packet of voice data.
+#endif
};
/*
diff --git a/code/server/server.h b/code/server/server.h
index 3d03aee..61fd882 100644
--- a/code/server/server.h
+++ b/code/server/server.h
@@ -33,6 +33,18 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define MAX_ENT_CLUSTERS 16
+#if USE_VOIP
+typedef struct voipServerPacket_s
+{
+ int generation;
+ int sequence;
+ int frames;
+ int len;
+ int sender;
+ byte data[1024];
+} voipServerPacket_t;
+#endif
+
typedef struct svEntity_s {
struct worldSector_s *worldSector;
struct svEntity_s *nextEntityInWorldSector;
@@ -167,6 +179,14 @@ typedef struct client_s {
netchan_buffer_t *netchan_start_queue;
netchan_buffer_t **netchan_end_queue;
+#if USE_VOIP
+ qboolean hasVoip;
+ qboolean muteAllVoip;
+ qboolean ignoreVoipFromClient[MAX_CLIENTS];
+ voipServerPacket_t voipPacket[64]; // !!! FIXME: WAY too much memory!
+ int queuedVoipPackets;
+#endif
+
int oldServerTime;
qboolean csUpdated[MAX_CONFIGSTRINGS+1];
} client_t;
@@ -264,6 +284,11 @@ extern cvar_t *sv_strictAuth;
extern serverBan_t serverBans[SERVER_MAXBANS];
extern int serverBansCount;
+#if USE_VOIP
+extern cvar_t *sv_voip;
+#endif
+
+
//===========================================================
//
@@ -320,6 +345,11 @@ void SV_ClientThink (client_t *cl, usercmd_t *cmd);
void SV_WriteDownloadToClient( client_t *cl , msg_t *msg );
+#if USE_VOIP
+void SV_WriteVoipToClient( client_t *cl, msg_t *msg );
+#endif
+
+
//
// sv_ccmds.c
//
diff --git a/code/server/sv_client.c b/code/server/sv_client.c
index c9915b9..6f932d1 100644
--- a/code/server/sv_client.c
+++ b/code/server/sv_client.c
@@ -1083,6 +1083,50 @@ void SV_WriteDownloadToClient( client_t *cl , msg_t *msg )
}
}
+#if USE_VOIP
+/*
+==================
+SV_WriteVoipToClient
+
+Check to see if there is any VoIP queued for a client, and send if there is.
+==================
+*/
+void SV_WriteVoipToClient( client_t *cl, msg_t *msg )
+{
+ voipServerPacket_t *packet = &cl->voipPacket[0];
+ int totalbytes = 0;
+ int i;
+
+ if (*cl->downloadName) {
+ cl->queuedVoipPackets = 0;
+ return; // no VoIP allowed if download is going, to save bandwidth.
+ }
+
+ // Write as many VoIP packets as we reasonably can...
+ for (i = 0; i < cl->queuedVoipPackets; i++, packet++) {
+ totalbytes += packet->len;
+ if (totalbytes > MAX_DOWNLOAD_BLKSIZE)
+ break;
+
+ MSG_WriteByte( msg, svc_voip );
+ MSG_WriteShort( msg, packet->sender );
+ MSG_WriteByte( msg, (byte) packet->generation );
+ MSG_WriteLong( msg, packet->sequence );
+ MSG_WriteByte( msg, packet->frames );
+ MSG_WriteShort( msg, packet->len );
+ MSG_WriteData( msg, packet->data, packet->len );
+ }
+
+ // !!! FIXME: I hate this queue system.
+ cl->queuedVoipPackets -= i;
+ if (cl->queuedVoipPackets > 0) {
+ memmove( &cl->voipPacket[0], &cl->voipPacket[i],
+ sizeof (voipServerPacket_t) * i);
+ }
+}
+#endif
+
+
/*
=================
SV_Disconnect_f
@@ -1326,6 +1370,11 @@ void SV_UserinfoChanged( client_t *cl ) {
cl->snapshotMsec = 50;
}
+#if USE_VOIP
+ val = Info_ValueForKey (cl->userinfo, "voip");
+ cl->hasVoip = (strlen(val) && atoi(val)) ? qtrue : qfalse;
+#endif
+
// TTimo
// maintain the IP information
// the banning code relies on this being consistently present
@@ -1361,6 +1410,39 @@ static void SV_UpdateUserinfo_f( client_t *cl ) {
VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients );
}
+
+#if USE_VOIP
+static
+void SV_UpdateVoipIgnore(client_t *cl, const char *idstr, qboolean ignore)
+{
+ if ((*idstr >= '0') && (*idstr <= '9')) {
+ const int id = atoi(idstr);
+ if ((id >= 0) && (id < MAX_CLIENTS)) {
+ cl->ignoreVoipFromClient[id] = ignore;
+ }
+ }
+}
+
+/*
+==================
+SV_UpdateUserinfo_f
+==================
+*/
+static void SV_Voip_f( client_t *cl ) {
+ const char *cmd = Cmd_Argv(1);
+ if (strcmp(cmd, "ignore") == 0) {
+ SV_UpdateVoipIgnore(cl, Cmd_Argv(2), qtrue);
+ } else if (strcmp(cmd, "unignore") == 0) {
+ SV_UpdateVoipIgnore(cl, Cmd_Argv(2), qfalse);
+ } else if (strcmp(cmd, "muteall") == 0) {
+ cl->muteAllVoip = qtrue;
+ } else if (strcmp(cmd, "unmuteall") == 0) {
+ cl->muteAllVoip = qfalse;
+ }
+}
+#endif
+
+
typedef struct {
char *name;
void (*func)( client_t *cl );
@@ -1376,6 +1458,10 @@ static ucmd_t ucmds[] = {
{"stopdl", SV_StopDownload_f},
{"donedl", SV_DoneDownload_f},
+#if USE_VOIP
+ {"voip", SV_Voip_f},
+#endif
+
{NULL, NULL}
};
@@ -1596,6 +1682,118 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) {
}
+#if USE_VOIP
+static
+qboolean SV_ShouldIgnoreVoipSender(const client_t *cl)
+{
+ if (!sv_voip->integer)
+ return qtrue; // VoIP disabled on this server.
+ else if (!cl->hasVoip) // client doesn't have VoIP support?!
+ return qtrue;
+
+ // !!! FIXME: implement player blacklist.
+
+ return qfalse; // don't ignore.
+}
+
+static
+void SV_UserVoip( client_t *cl, msg_t *msg ) {
+ const int sender = (int) (cl - svs.clients);
+ const int generation = MSG_ReadByte(msg);
+ const int sequence = MSG_ReadLong(msg);
+ const int frames = MSG_ReadByte(msg);
+ const int recip1 = MSG_ReadLong(msg);
+ const int recip2 = MSG_ReadLong(msg);
+ const int recip3 = MSG_ReadLong(msg);
+ const int packetsize = MSG_ReadShort(msg);
+ byte encoded[sizeof (cl->voipPacket[0].data)];
+ client_t *client = NULL;
+ voipServerPacket_t *packet = NULL;
+ int i;
+
+ 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 (recip1 < 0)
+ return; // short/invalid packet, bail.
+ else if (recip2 < 0)
+ return; // short/invalid packet, bail.
+ else if (recip3 < 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 (SV_ShouldIgnoreVoipSender(cl))
+ return; // Blacklisted, disabled, etc.
+
+ // !!! FIXME: see if we read past end of msg...
+
+ // !!! FIXME: reject if not speex narrowband codec.
+ // !!! FIXME: decide if this is bogus data?
+
+ // (the three recip* values are 31 bits each (ignores sign bit so we can
+ // get a -1 error from MSG_ReadLong() ... ), allowing for 93 clients.)
+ assert( sv_maxclients->integer < 93 );
+
+ // decide who needs this VoIP packet sent to them...
+ for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) {
+ if (client->state != CS_ACTIVE)
+ continue; // not in the game yet, don't send to this guy.
+ else if (i == sender)
+ continue; // don't send voice packet back to original author.
+ else if (!client->hasVoip)
+ continue; // no VoIP support, or support disabled.
+ else if (client->muteAllVoip)
+ continue; // client is ignoring everyone.
+ else if (client->ignoreVoipFromClient[sender])
+ continue; // client is ignoring this talker.
+ else if (*cl->downloadName) // !!! FIXME: possible to DoS?
+ continue; // no VoIP allowed if downloading, to save bandwidth.
+ else if ( ((i >= 0) && (i < 31)) && ((recip1 & (1 << (i-0))) == 0) )
+ continue; // not addressed to this player.
+ else if ( ((i >= 31) && (i < 62)) && ((recip2 & (1 << (i-31))) == 0) )
+ continue; // not addressed to this player.
+ else if ( ((i >= 62) && (i < 93)) && ((recip3 & (1 << (i-62))) == 0) )
+ continue; // not addressed to this player.
+
+ // Transmit this packet to the client.
+ // !!! FIXME: I don't like this queueing system.
+ if (client->queuedVoipPackets >= (sizeof (client->voipPacket) / sizeof (client->voipPacket[0]))) {
+ Com_Printf("Too many VoIP packets queued for client #%d\n", i);
+ continue; // no room for another packet right now.
+ }
+
+ packet = &client->voipPacket[client->queuedVoipPackets];
+ packet->sender = sender;
+ packet->frames = frames;
+ packet->len = packetsize;
+ packet->generation = generation;
+ packet->sequence = sequence;
+ memcpy(packet->data, encoded, packetsize);
+ client->queuedVoipPackets++;
+ }
+}
+#endif
+
+
+
/*
===========================================================================
@@ -1699,6 +1897,10 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) {
SV_UserMove( cl, msg, qtrue );
} else if ( c == clc_moveNoDelta ) {
SV_UserMove( cl, msg, qfalse );
+#if USE_VOIP
+ } else if ( c == clc_voip ) {
+ SV_UserVoip( cl, msg );
+#endif
} else if ( c != clc_EOF ) {
Com_Printf( "WARNING: bad command byte for client %i\n", (int) (cl - svs.clients) );
}
diff --git a/code/server/sv_init.c b/code/server/sv_init.c
index aca7691..d46f273 100644
--- a/code/server/sv_init.c
+++ b/code/server/sv_init.c
@@ -654,6 +654,9 @@ void SV_Init (void) {
Cvar_Get ("sv_cheats", "1", CVAR_SYSTEMINFO | CVAR_ROM );
sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM );
sv_pure = Cvar_Get ("sv_pure", "1", CVAR_SYSTEMINFO );
+#if USE_VOIP
+ sv_voip = Cvar_Get ("sv_voip", "1", CVAR_SYSTEMINFO );
+#endif
Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM );
Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM );
Cvar_Get ("sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM );
diff --git a/code/server/sv_main.c b/code/server/sv_main.c
index 552c12b..a12da03 100644
--- a/code/server/sv_main.c
+++ b/code/server/sv_main.c
@@ -22,6 +22,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "server.h"
+#if USE_VOIP
+cvar_t *sv_voip;
+#endif
+
serverStatic_t svs; // persistant server info
server_t sv; // local server
vm_t *gvm = NULL; // game virtual machine
@@ -407,6 +411,10 @@ void SVC_Info( netadr_t from ) {
Info_SetValueForKey( infostring, "gametype", va("%i", sv_gametype->integer ) );
Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) );
+#if USE_VOIP
+ Info_SetValueForKey( infostring, "voip", va("%i", sv_voip->integer ) );
+#endif
+
if( sv_minPing->integer ) {
Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) );
}
diff --git a/code/server/sv_snapshot.c b/code/server/sv_snapshot.c
index 80b6af4..71fdc8b 100644
--- a/code/server/sv_snapshot.c
+++ b/code/server/sv_snapshot.c
@@ -653,6 +653,10 @@ void SV_SendClientSnapshot( client_t *client ) {
// Add any download data if the client is downloading
SV_WriteDownloadToClient( client, &msg );
+#if USE_VOIP
+ SV_WriteVoipToClient( client, &msg );
+#endif
+
// check for overflow
if ( msg.overflowed ) {
Com_Printf ("WARNING: msg overflowed for %s\n", client->name);