r1348 - in trunk: . code/client code/qcommon code/server

DONOTREPLY at icculus.org DONOTREPLY at icculus.org
Sun Jun 1 03:51:24 EDT 2008


Author: icculus
Date: 2008-06-01 03:51:23 -0400 (Sun, 01 Jun 2008)
New Revision: 1348

Modified:
   trunk/Makefile
   trunk/code/client/cl_cgame.c
   trunk/code/client/cl_cin.c
   trunk/code/client/cl_input.c
   trunk/code/client/cl_main.c
   trunk/code/client/cl_parse.c
   trunk/code/client/client.h
   trunk/code/client/snd_dma.c
   trunk/code/client/snd_local.h
   trunk/code/client/snd_main.c
   trunk/code/client/snd_mix.c
   trunk/code/client/snd_openal.c
   trunk/code/client/snd_public.h
   trunk/code/qcommon/qcommon.h
   trunk/code/server/server.h
   trunk/code/server/sv_client.c
   trunk/code/server/sv_init.c
   trunk/code/server/sv_main.c
   trunk/code/server/sv_snapshot.c
Log:
Initial patch for in-game VoIP support!


Modified: trunk/Makefile
===================================================================
--- trunk/Makefile	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/Makefile	2008-06-01 07:51:23 UTC (rev 1348)
@@ -129,6 +129,10 @@
 USE_MUMBLE=1
 endif
 
+ifndef USE_VOIP
+USE_VOIP=1
+endif
+
 ifndef USE_LOCAL_HEADERS
 USE_LOCAL_HEADERS=1
 endif
@@ -240,6 +244,10 @@
     BASE_CFLAGS += -DUSE_MUMBLE
   endif
 
+  ifeq ($(USE_VOIP),1)
+    BASE_CFLAGS += -DUSE_VOIP
+  endif
+
   OPTIMIZE = -O3 -ffast-math -funroll-loops -fomit-frame-pointer
 
   ifeq ($(ARCH),x86_64)
@@ -295,6 +303,10 @@
     CLIENT_LDFLAGS += -lrt
   endif
 
+  ifeq ($(USE_VOIP),1)
+    CLIENT_LDFLAGS += -lspeex
+  endif
+
   ifeq ($(ARCH),i386)
     # linux32 make ...
     BASE_CFLAGS += -m32
@@ -362,6 +374,11 @@
     BASE_CFLAGS += -DUSE_MUMBLE
   endif
 
+  ifeq ($(USE_VOIP),1)
+    BASE_CFLAGS += -DUSE_VOIP
+    CLIENT_LDFLAGS += -lspeex
+  endif
+
   BASE_CFLAGS += -D_THREAD_SAFE=1
 
   ifeq ($(USE_LOCAL_HEADERS),1)
@@ -432,6 +449,10 @@
     BASE_CFLAGS += -DUSE_MUMBLE
   endif
 
+  ifeq ($(USE_VOIP),1)
+    BASE_CFLAGS += -DUSE_VOIP
+  endif
+
   OPTIMIZE = -O3 -march=i586 -fno-omit-frame-pointer -ffast-math \
     -falign-loops=2 -funroll-loops -falign-jumps=2 -falign-functions=2 \
     -fstrength-reduce
@@ -464,6 +485,10 @@
     CLIENT_LDFLAGS += -lvorbisfile -lvorbis -logg
   endif
 
+  ifeq ($(USE_VOIP),1)
+    CLIENT_LDFLAGS += -lspeex
+  endif
+
   ifeq ($(ARCH),x86)
     # build 32bit
     BASE_CFLAGS += -m32
@@ -521,6 +546,10 @@
     BASE_CFLAGS += -DUSE_MUMBLE
   endif
 
+  ifeq ($(USE_VOIP),1)
+    BASE_CFLAGS += -DUSE_VOIP
+  endif
+
   ifeq ($(ARCH),axp)
     BASE_CFLAGS += -DNO_VM_COMPILED
     RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O3 -ffast-math -funroll-loops \
@@ -561,7 +590,11 @@
     CLIENT_LDFLAGS += -lvorbisfile -lvorbis -logg
   endif
 
+  ifeq ($(USE_VOIP),1)
+    CLIENT_LDFLAGS += -lspeex
+  endif
 
+
 else # ifeq freebsd
 
 #############################################################################
@@ -592,6 +625,10 @@
     BASE_CFLAGS += -DUSE_MUMBLE
   endif
 
+  ifeq ($(USE_VOIP),1)
+    BASE_CFLAGS += -DUSE_VOIP
+  endif
+
   BASE_CFLAGS += -DNO_VM_COMPILED -I/usr/X11R6/include -I/usr/local/include
   RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O3 \
     -march=pentium -fomit-frame-pointer -pipe -ffast-math \
@@ -622,7 +659,11 @@
     CLIENT_LDFLAGS += -lvorbisfile -lvorbis -logg
   endif
 
+  ifeq ($(USE_VOIP),1)
+    CLIENT_LDFLAGS += -lspeex
+  endif
 
+
 else # ifeq openbsd
 
 #############################################################################

Modified: trunk/code/client/cl_cgame.c
===================================================================
--- trunk/code/client/cl_cgame.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/cl_cgame.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -916,6 +916,26 @@
 		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
 }
 
 /*

Modified: trunk/code/client/cl_cin.c
===================================================================
--- trunk/code/client/cl_cin.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/cl_cin.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -53,8 +53,6 @@
 #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 @@
 		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 @@
 		
 		Con_Close();
 
-		s_rawend = s_soundtime;
+		s_rawend[0] = s_soundtime;
 
 		return currentHandle;
 	}

Modified: trunk/code/client/cl_input.c
===================================================================
--- trunk/code/client/cl_input.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/cl_input.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -52,6 +52,10 @@
 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_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 @@
 	// 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 @@
 		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 @@
 	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);
 }

Modified: trunk/code/client/cl_main.c
===================================================================
--- trunk/code/client/cl_main.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/cl_main.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -33,6 +33,12 @@
 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,7 +174,201 @@
 #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
+
+/*
 =======================================================================
 
 CLIENT RELIABLE COMMAND COMMUNICATION
@@ -905,6 +1105,25 @@
 	}
 #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 @@
 	// 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 @@
 	// 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 @@
 	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 );

Modified: trunk/code/client/cl_parse.c
===================================================================
--- trunk/code/client/cl_parse.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/cl_parse.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -32,7 +32,12 @@
 	"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 @@
 int cl_connectedToPureServer;
 int cl_connectedToCheatServer;
 
+#if USE_VOIP
+int cl_connectedToVoipServer;
+#endif
+
 /*
 ==================
 CL_SystemInfoChanged
@@ -355,6 +364,11 @@
 		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,8 +635,164 @@
 	}
 }
 
+#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
 
 Command strings are just saved off until cgame asks for them
@@ -714,6 +884,11 @@
 		case svc_download:
 			CL_ParseDownload( msg );
 			break;
+#if USE_VOIP
+		case svc_voip:
+			CL_ParseVoip( msg );
+			break;
+#endif
 		}
 	}
 }

Modified: trunk/code/client/client.h
===================================================================
--- trunk/code/client/client.h	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/client.h	2008-06-01 07:51:23 UTC (rev 1348)
@@ -34,6 +34,10 @@
 #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 @@
 	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_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_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 @@
 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 );
 

Modified: trunk/code/client/snd_dma.c
===================================================================
--- trunk/code/client/snd_dma.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/snd_dma.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -90,8 +90,8 @@
 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 @@
 	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 @@
 
 	S_ChannelSetup();
 
-	s_rawend = 0;
+	Com_Memset(s_rawend, '\0', sizeof (s_rawend));
 
 	if (dma.samplebits == 8)
 		clear = 0x80;
@@ -879,10 +915,6 @@
 	}
 }
 
-portable_samplepair_t *S_GetRawSamplePointer( void ) {
-	return s_rawsamples;
-}
-
 /*
 ============
 S_RawSamples
@@ -890,36 +922,42 @@
 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 @@
 				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 @@
 			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 @@
 			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 @@
 			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 @@
 		return;
 	S_CodecCloseStream(s_backgroundStream);
 	s_backgroundStream = NULL;
-	s_rawend = 0;
+	s_rawend[0] = 0;
 }
 
 /*
@@ -1331,12 +1369,12 @@
 	}
 
 	// 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 @@
 		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 @@
 	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;
 }

Modified: trunk/code/client/snd_local.h
===================================================================
--- trunk/code/client/snd_local.h	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/snd_local.h	2008-06-01 07:51:23 UTC (rev 1348)
@@ -125,7 +125,7 @@
 	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 @@
 	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	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 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);

Modified: trunk/code/client/snd_main.c
===================================================================
--- trunk/code/client/snd_main.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/snd_main.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -62,6 +62,14 @@
 	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 @@
 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 @@
 	}
 }
 
+
+#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
+
 //=============================================================================
 
 /*

Modified: trunk/code/client/snd_mix.c
===================================================================
--- trunk/code/client/snd_mix.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/snd_mix.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -631,12 +631,12 @@
 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,31 +648,19 @@
 			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");
+		// 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;
+				}
 			}
-			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;
-			}
 		}
 
 		// paint in the channels.

Modified: trunk/code/client/snd_openal.c
===================================================================
--- trunk/code/client/snd_openal.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/snd_openal.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -1253,35 +1253,37 @@
 
 //===========================================================================
 
+static srcHandle_t streamSourceHandles[MAX_RAW_STREAMS];
+static qboolean streamPlaying[MAX_RAW_STREAMS];
+static ALuint streamSources[MAX_RAW_STREAMS];
 
-static srcHandle_t streamSourceHandle = -1;
-static qboolean streamPlaying = qfalse;
-static ALuint streamSource;
-
 /*
 =================
 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 @@
 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 @@
 =================
 */
 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 @@
 	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 @@
 =================
 */
 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 @@
 =================
 */
 static
-void S_AL_StreamDie( void )
+void S_AL_StreamDie( int stream )
 {
-	if(streamSourceHandle == -1)
+	if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
 		return;
 
-	streamPlaying = qfalse;
-	qalSourceStop(streamSource);
-	S_AL_FreeStreamChannel();
+	if(streamSourceHandles[stream] == -1)
+		return;
+
+	streamPlaying[stream] = qfalse;
+	qalSourceStop(streamSources[stream]);
+	S_AL_FreeStreamChannel(stream);
 }
 
 
@@ -1682,6 +1696,11 @@
 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 @@
 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 @@
 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 @@
 {
 }
 
+#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 @@
 	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 @@
 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 @@
 	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 @@
 #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 @@
 	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 @@
 	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;

Modified: trunk/code/client/snd_public.h
===================================================================
--- trunk/code/client/snd_public.h	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/client/snd_public.h	2008-06-01 07:51:23 UTC (rev 1348)
@@ -33,7 +33,7 @@
 
 // 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 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
+

Modified: trunk/code/qcommon/qcommon.h
===================================================================
--- trunk/code/qcommon/qcommon.h	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/qcommon/qcommon.h	2008-06-01 07:51:23 UTC (rev 1348)
@@ -274,7 +274,11 @@
 	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 @@
 	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
 };
 
 /*

Modified: trunk/code/server/server.h
===================================================================
--- trunk/code/server/server.h	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/server/server.h	2008-06-01 07:51:23 UTC (rev 1348)
@@ -33,6 +33,18 @@
 
 #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 @@
 	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	serverBan_t serverBans[SERVER_MAXBANS];
 extern	int serverBansCount;
 
+#if USE_VOIP
+extern	cvar_t	*sv_voip;
+#endif
+
+
 //===========================================================
 
 //
@@ -320,6 +345,11 @@
 
 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
 //

Modified: trunk/code/server/sv_client.c
===================================================================
--- trunk/code/server/sv_client.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/server/sv_client.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -1083,7 +1083,51 @@
 	}
 }
 
+#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 @@
 		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 @@
 	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 @@
 	{"stopdl", SV_StopDownload_f},
 	{"donedl", SV_DoneDownload_f},
 
+#if USE_VOIP
+	{"voip", SV_Voip_f},
+#endif
+
 	{NULL, NULL}
 };
 
@@ -1596,6 +1682,118 @@
 }
 
 
+#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 @@
 		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) );
 	}

Modified: trunk/code/server/sv_init.c
===================================================================
--- trunk/code/server/sv_init.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/server/sv_init.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -654,6 +654,9 @@
 	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 );

Modified: trunk/code/server/sv_main.c
===================================================================
--- trunk/code/server/sv_main.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/server/sv_main.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -22,6 +22,10 @@
 
 #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 @@
 	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) );
 	}

Modified: trunk/code/server/sv_snapshot.c
===================================================================
--- trunk/code/server/sv_snapshot.c	2008-05-30 17:19:31 UTC (rev 1347)
+++ trunk/code/server/sv_snapshot.c	2008-06-01 07:51:23 UTC (rev 1348)
@@ -653,6 +653,10 @@
 	// 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);




More information about the quake3-commits mailing list