From 10afaeef337e0d19b37cb7dbe3fccee48bf18a43 Mon Sep 17 00:00:00 2001 From: icculus Date: Sun, 1 Jun 2008 07:51:23 +0000 Subject: Initial patch for in-game VoIP support! git-svn-id: svn://svn.icculus.org/quake3/trunk@1348 edf5b092-35ff-0310-97b2-ce42778d08ea --- code/client/cl_cgame.c | 20 ++++ code/client/cl_cin.c | 10 +- code/client/cl_input.c | 40 ++++++++ code/client/cl_main.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++ code/client/cl_parse.c | 177 +++++++++++++++++++++++++++++++++- code/client/client.h | 43 +++++++++ code/client/snd_dma.c | 126 +++++++++++++++++-------- code/client/snd_local.h | 15 ++- code/client/snd_main.c | 76 ++++++++++++++- code/client/snd_mix.c | 38 +++----- code/client/snd_openal.c | 220 ++++++++++++++++++++++++++++++++++--------- code/client/snd_public.h | 12 ++- code/qcommon/qcommon.h | 12 ++- code/server/server.h | 30 ++++++ code/server/sv_client.c | 202 +++++++++++++++++++++++++++++++++++++++ code/server/sv_init.c | 3 + code/server/sv_main.c | 8 ++ code/server/sv_snapshot.c | 4 + 18 files changed, 1144 insertions(+), 127 deletions(-) (limited to 'code') 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) 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); -- cgit v1.2.3