Initial patch for in-game VoIP support!

This commit is contained in:
Ryan C. Gordon 2008-06-01 07:51:23 +00:00
parent 0ee3960225
commit 12326a9eac
19 changed files with 1185 additions and 127 deletions

View file

@ -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 );