diff --git a/code/client/cl_cgame.c b/code/client/cl_cgame.c index 7a564848..63cd84c0 100644 --- a/code/client/cl_cgame.c +++ b/code/client/cl_cgame.c @@ -952,8 +952,8 @@ void CL_FirstSnapshot( void ) { clc.speexInitialized = qtrue; clc.voipMuteAll = qfalse; Cmd_AddCommand ("voip", CL_Voip_f); - Cvar_Set("cl_voipSendTarget", "all"); - clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0x7FFFFFFF; + Cvar_Set("cl_voipSendTarget", "spatial"); + Com_Memset(clc.voipTargets, ~0, sizeof(clc.voipTargets)); } #endif } diff --git a/code/client/cl_cin.c b/code/client/cl_cin.c index 54e72b4b..ed2529ce 100644 --- a/code/client/cl_cin.c +++ b/code/client/cl_cin.c @@ -1147,7 +1147,7 @@ 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( 0, ssize, 22050, 2, 1, (byte *)sbuf, 1.0f ); + S_RawSamples(0, ssize, 22050, 2, 1, (byte *)sbuf, 1.0f, -1); } break; case ZA_SOUND_STEREO: @@ -1157,7 +1157,7 @@ redump: s_rawend[0] = s_soundtime; } ssize = RllDecodeStereoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); - S_RawSamples( 0, ssize, 22050, 2, 2, (byte *)sbuf, 1.0f ); + S_RawSamples(0, ssize, 22050, 2, 2, (byte *)sbuf, 1.0f, -1); } break; case ROQ_QUAD_INFO: diff --git a/code/client/cl_input.c b/code/client/cl_input.c index a35a522f..3f2f3e06 100644 --- a/code/client/cl_input.c +++ b/code/client/cl_input.c @@ -799,83 +799,53 @@ void CL_WritePacket( void ) { } #ifdef USE_VOIP - if (clc.voipOutgoingDataSize > 0) { // only send if data. - // Move cl_voipSendTarget from a string to the bitmasks if needed. - if (cl_voipSendTarget->modified) { - char buffer[32]; - const char *target = cl_voipSendTarget->string; + if (clc.voipOutgoingDataSize > 0) + { + if((clc.voipFlags & VOIP_SPATIAL) || Com_IsVoipTarget(clc.voipTargets, sizeof(clc.voipTargets), -1)) + { + MSG_WriteByte (&buf, clc_voip); + MSG_WriteByte (&buf, clc.voipOutgoingGeneration); + MSG_WriteLong (&buf, clc.voipOutgoingSequence); + MSG_WriteByte (&buf, clc.voipOutgoingDataFrames); + MSG_WriteData (&buf, clc.voipTargets, sizeof(clc.voipTargets)); + MSG_WriteByte(&buf, clc.voipFlags); + MSG_WriteShort (&buf, clc.voipOutgoingDataSize); + MSG_WriteData (&buf, clc.voipOutgoingData, clc.voipOutgoingDataSize); - if (Q_stricmp(target, "attacker") == 0) { - int player = VM_Call( cgvm, CG_LAST_ATTACKER ); - Com_sprintf(buffer, sizeof (buffer), "%d", player); - target = buffer; - } else if (Q_stricmp(target, "crosshair") == 0) { - int player = VM_Call( cgvm, CG_CROSSHAIR_PLAYER ); - Com_sprintf(buffer, sizeof (buffer), "%d", player); - target = buffer; + // If we're recording a demo, we have to fake a server packet with + // this VoIP data so it gets to disk; the server doesn't send it + // back to us, and we might as well eliminate concerns about dropped + // and misordered packets here. + if(clc.demorecording && !clc.demowaiting) + { + const int voipSize = clc.voipOutgoingDataSize; + msg_t fakemsg; + byte fakedata[MAX_MSGLEN]; + MSG_Init (&fakemsg, fakedata, sizeof (fakedata)); + MSG_Bitstream (&fakemsg); + MSG_WriteLong (&fakemsg, clc.reliableAcknowledge); + MSG_WriteByte (&fakemsg, svc_voip); + MSG_WriteShort (&fakemsg, clc.clientNum); + MSG_WriteByte (&fakemsg, clc.voipOutgoingGeneration); + MSG_WriteLong (&fakemsg, clc.voipOutgoingSequence); + MSG_WriteByte (&fakemsg, clc.voipOutgoingDataFrames); + MSG_WriteShort (&fakemsg, clc.voipOutgoingDataSize ); + MSG_WriteData (&fakemsg, clc.voipOutgoingData, voipSize); + MSG_WriteByte (&fakemsg, svc_EOF); + CL_WriteDemoMessage (&fakemsg, 0); } - if ((*target == '\0') || (Q_stricmp(target, "all") == 0)) { - const int all = 0x7FFFFFFF; - clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = all; - } else if (Q_stricmp(target, "none") == 0) { - clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0; - } else { - const char *ptr = target; - clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0; - do { - if ((*ptr == ',') || (*ptr == '\0')) { - const int val = atoi(target); - target = ptr + 1; - if ((val >= 0) && (val < 31)) { - clc.voipTarget1 |= (1 << (val-0)); - } else if ((val >= 31) && (val < 62)) { - clc.voipTarget2 |= (1 << (val-31)); - } else if ((val >= 62) && (val < 93)) { - clc.voipTarget3 |= (1 << (val-62)); - } - } - } while (*(ptr++)); - } - cl_voipSendTarget->modified = qfalse; + clc.voipOutgoingSequence += clc.voipOutgoingDataFrames; + clc.voipOutgoingDataSize = 0; + clc.voipOutgoingDataFrames = 0; } - - MSG_WriteByte (&buf, clc_voip); - MSG_WriteByte (&buf, clc.voipOutgoingGeneration); - MSG_WriteLong (&buf, clc.voipOutgoingSequence); - MSG_WriteByte (&buf, clc.voipOutgoingDataFrames); - MSG_WriteLong (&buf, clc.voipTarget1); - MSG_WriteLong (&buf, clc.voipTarget2); - MSG_WriteLong (&buf, clc.voipTarget3); - MSG_WriteShort (&buf, clc.voipOutgoingDataSize); - MSG_WriteData (&buf, clc.voipOutgoingData, clc.voipOutgoingDataSize); - - // If we're recording a demo, we have to fake a server packet with - // this VoIP data so it gets to disk; the server doesn't send it - // back to us, and we might as well eliminate concerns about dropped - // and misordered packets here. - if ( clc.demorecording && !clc.demowaiting ) { - const int voipSize = clc.voipOutgoingDataSize; - msg_t fakemsg; - byte fakedata[MAX_MSGLEN]; - MSG_Init (&fakemsg, fakedata, sizeof (fakedata)); - MSG_Bitstream (&fakemsg); - MSG_WriteLong (&fakemsg, clc.reliableAcknowledge); - MSG_WriteByte (&fakemsg, svc_voip); - MSG_WriteShort (&fakemsg, clc.clientNum); - MSG_WriteByte (&fakemsg, clc.voipOutgoingGeneration); - MSG_WriteLong (&fakemsg, clc.voipOutgoingSequence); - MSG_WriteByte (&fakemsg, clc.voipOutgoingDataFrames); - MSG_WriteShort (&fakemsg, clc.voipOutgoingDataSize ); - MSG_WriteData (&fakemsg, clc.voipOutgoingData, voipSize); - MSG_WriteByte (&fakemsg, svc_EOF); - CL_WriteDemoMessage (&fakemsg, 0); + else + { + // We have data, but no targets. Silently discard all data + clc.voipOutgoingDataSize = 0; + clc.voipOutgoingDataFrames = 0; } - - clc.voipOutgoingSequence += clc.voipOutgoingDataFrames; - clc.voipOutgoingDataSize = 0; - clc.voipOutgoingDataFrames = 0; - } else + } #endif if ( count >= 1 ) { diff --git a/code/client/cl_main.c b/code/client/cl_main.c index 55d79cb6..c110c69d 100644 --- a/code/client/cl_main.c +++ b/code/client/cl_main.c @@ -291,6 +291,88 @@ void CL_VoipNewGeneration(void) clc.voipOutgoingSequence = 0; } +/* +=============== +CL_VoipParseTargets + +sets clc.voipTargets according to cl_voipSendTarget +Generally we don't want who's listening to change during a transmission, +so this is only called when the key is first pressed +=============== +*/ +void CL_VoipParseTargets(void) +{ + const char *target = cl_voipSendTarget->string; + char *end; + int val; + + Com_Memset(clc.voipTargets, 0, sizeof(clc.voipTargets)); + clc.voipFlags &= ~VOIP_SPATIAL; + + while(target) + { + while(*target == ',' || *target == ' ') + target++; + + if(!*target) + break; + + if(isdigit(*target)) + { + val = strtol(target, &end, 10); + target = end; + } + else + { + if(!Q_stricmpn(target, "all", 3)) + { + Com_Memset(clc.voipTargets, ~0, sizeof(clc.voipTargets)); + return; + } + if(!Q_stricmpn(target, "spatial", 7)) + { + clc.voipFlags |= VOIP_SPATIAL; + target += 7; + continue; + } + else + { + if(!Q_stricmpn(target, "attacker", 8)) + { + val = VM_Call(cgvm, CG_LAST_ATTACKER); + target += 8; + } + else if(!Q_stricmpn(target, "crosshair", 9)) + { + val = VM_Call(cgvm, CG_CROSSHAIR_PLAYER); + target += 9; + } + else + { + while(*target && *target != ',' && *target != ' ') + target++; + + continue; + } + + if(val < 0) + continue; + } + } + + if(val < 0 || val >= MAX_CLIENTS) + { + Com_Printf(S_COLOR_YELLOW "WARNING: VoIP " + "target %d is not a valid client " + "number\n", val); + + continue; + } + + clc.voipTargets[val / 8] |= 1 << (val % 8); + } +} + /* =============== CL_CaptureVoip @@ -342,8 +424,9 @@ void CL_CaptureVoip(void) cl_voipSend->modified = qfalse; - if (dontCapture) { - cl_voipSend->integer = 0; + if(dontCapture) + { + Cvar_Set("cl_voipSend", "0"); return; } @@ -362,11 +445,12 @@ void CL_CaptureVoip(void) S_MasterGain(cl_voipGainDuringCapture->value); S_StartCapture(); CL_VoipNewGeneration(); + CL_VoipParseTargets(); } if ((cl_voipSend->integer) || (finalFrame)) { // user wants to capture audio? int samples = S_AvailableCaptureSamples(); - const int mult = (finalFrame) ? 1 : 12; // 12 == 240ms of audio. + const int mult = (finalFrame) ? 1 : 4; // 4 == 80ms of audio. // enough data buffered in audio hardware to process yet? if (samples >= (clc.speexFrameSize * mult)) { @@ -378,8 +462,8 @@ void CL_CaptureVoip(void) int wpos = 0; int pos = 0; - if (samples > (clc.speexFrameSize * 12)) - samples = (clc.speexFrameSize * 12); + if (samples > (clc.speexFrameSize * 4)) + samples = (clc.speexFrameSize * 4); // !!! FIXME: maybe separate recording from encoding, so voipPower // !!! FIXME: updates faster than 4Hz? @@ -3420,7 +3504,7 @@ void CL_Init( void ) { #ifdef USE_VOIP cl_voipSend = Cvar_Get ("cl_voipSend", "0", 0); - cl_voipSendTarget = Cvar_Get ("cl_voipSendTarget", "all", 0); + cl_voipSendTarget = Cvar_Get ("cl_voipSendTarget", "spatial", 0); cl_voipGainDuringCapture = Cvar_Get ("cl_voipGainDuringCapture", "0.2", CVAR_ARCHIVE); cl_voipCaptureMult = Cvar_Get ("cl_voipCaptureMult", "2.0", CVAR_ARCHIVE); cl_voipUseVAD = Cvar_Get ("cl_voipUseVAD", "0", CVAR_ARCHIVE); diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c index cc49cd7e..da982034 100644 --- a/code/client/cl_parse.c +++ b/code/client/cl_parse.c @@ -663,6 +663,29 @@ qboolean CL_ShouldIgnoreVoipSender(int sender) return qfalse; } +/* +===================== +CL_PlayVoip + +Play raw data +===================== +*/ + +static void CL_PlayVoip(int sender, int samplecnt, const byte *data, int flags) +{ + if(flags & VOIP_DIRECT) + { + S_RawSamples(sender + 1, samplecnt, clc.speexSampleRate, 2, 1, + data, clc.voipGain[sender], -1); + } + + if(flags & VOIP_SPATIAL) + { + S_RawSamples(sender + MAX_CLIENTS + 1, samplecnt, clc.speexSampleRate, 2, 1, + data, 1.0f, sender); + } +} + /* ===================== CL_ParseVoip @@ -679,6 +702,7 @@ void CL_ParseVoip ( msg_t *msg ) { const int sequence = MSG_ReadLong(msg); const int frames = MSG_ReadByte(msg); const int packetsize = MSG_ReadShort(msg); + const int flags = MSG_ReadBits(msg, VOIP_FLAGCNT); char encoded[1024]; int seqdiff = sequence - clc.voipIncomingSequence[sender]; int written = 0; @@ -769,8 +793,8 @@ void CL_ParseVoip ( msg_t *msg ) { 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, clc.speexSampleRate, 2, 1, - (const byte *) decoded, clc.voipGain[sender]); + + CL_PlayVoip(sender, written, (const byte *) decoded, flags); written = 0; } @@ -793,10 +817,8 @@ void CL_ParseVoip ( msg_t *msg ) { Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n", written * 2, written, i); - if (written > 0) { - S_RawSamples(sender + 1, written, clc.speexSampleRate, 2, 1, - (const byte *) decoded, clc.voipGain[sender]); - } + if(written > 0) + CL_PlayVoip(sender, written, (const byte *) decoded, flags); clc.voipIncomingSequence[sender] = sequence + frames; } diff --git a/code/client/client.h b/code/client/client.h index f1670e7f..c9d0eaca 100644 --- a/code/client/client.h +++ b/code/client/client.h @@ -250,9 +250,10 @@ typedef struct { qboolean voipMuteAll; // outgoing data... - int voipTarget1; // these three ints make up a bit mask of 92 bits. - int voipTarget2; // the bits say who a VoIP pack is addressed to: - int voipTarget3; // (1 << clientnum). See cl_voipSendTarget cvar. + // if voipTargets[i / 8] & (1 << (i % 8)), + // then we are sending to clientnum i. + uint8_t voipTargets[(MAX_CLIENTS + 7) / 8]; + uint8_t voipFlags; SpeexPreprocessState *speexPreprocessor; SpeexBits speexEncoderBits; void *speexEncoder; diff --git a/code/client/snd_dma.c b/code/client/snd_dma.c index 45cef4c6..b7d0ecb0 100644 --- a/code/client/snd_dma.c +++ b/code/client/snd_dma.c @@ -916,7 +916,8 @@ S_Base_RawSamples Music streaming ============ */ -void S_Base_RawSamples( int stream, 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 entityNum) +{ int i; int src, dst; float scale; @@ -927,9 +928,16 @@ void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_chan return; } + if(entityNum >= 0) + { + // FIXME: support spatialized raw streams, e.g. for VoIP + return; + } + if ( (stream < 0) || (stream >= MAX_RAW_STREAMS) ) { return; } + rawsamples = s_rawsamples[stream]; if(s_muted->integer) @@ -1395,8 +1403,8 @@ void S_UpdateBackgroundTrack( void ) { if(r > 0) { // add to raw buffer - S_Base_RawSamples( 0, fileSamples, s_backgroundStream->info.rate, - s_backgroundStream->info.width, s_backgroundStream->info.channels, raw, s_musicVolume->value ); + S_Base_RawSamples(0, fileSamples, s_backgroundStream->info.rate, + s_backgroundStream->info.width, s_backgroundStream->info.channels, raw, s_musicVolume->value, -1); } else { diff --git a/code/client/snd_local.h b/code/client/snd_local.h index dbdcc334..29783c1c 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 stream, 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, int entityNum); void (*StopAllSounds)( void ); void (*ClearLoopingSounds)( qboolean killall ); void (*AddLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); @@ -186,7 +186,7 @@ extern vec3_t listener_up; extern dma_t dma; #define MAX_RAW_SAMPLES 16384 -#define MAX_RAW_STREAMS 128 +#define MAX_RAW_STREAMS (MAX_CLIENTS * 2 + 1) extern portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES]; extern int s_rawend[MAX_RAW_STREAMS]; diff --git a/code/client/snd_main.c b/code/client/snd_main.c index 441f8c3c..796e7444 100644 --- a/code/client/snd_main.c +++ b/code/client/snd_main.c @@ -129,11 +129,10 @@ S_RawSamples ================= */ void S_RawSamples (int stream, int samples, int rate, int width, int channels, - const byte *data, float volume) + const byte *data, float volume, int entityNum) { - if( si.RawSamples ) { - si.RawSamples( stream, samples, rate, width, channels, data, volume ); - } + if(si.RawSamples) + si.RawSamples(stream, samples, rate, width, channels, data, volume, entityNum); } /* diff --git a/code/client/snd_openal.c b/code/client/snd_openal.c index e48d1f28..6fc3a3e1 100644 --- a/code/client/snd_openal.c +++ b/code/client/snd_openal.c @@ -521,6 +521,7 @@ typedef struct src_s qboolean isLocked; // This is locked (un-allocatable) qboolean isLooping; // Is this a looping effect (attached to an entity) qboolean isTracking; // Is this object tracking its owner + qboolean isStream; // Is this source a stream float curGain; // gain employed if source is within maxdistance. float scaleGain; // Last gain value for this source. 0 if muted. @@ -744,13 +745,8 @@ S_AL_SrcSetup static void S_AL_SrcSetup(srcHandle_t src, sfxHandle_t sfx, alSrcPriority_t priority, int entity, int channel, qboolean local) { - ALuint buffer; src_t *curSource; - // Mark the SFX as used, and grab the raw AL buffer - S_AL_BufferUse(sfx); - buffer = S_AL_BufferGet(sfx); - // Set up src struct curSource = &srcList[src]; @@ -763,12 +759,19 @@ static void S_AL_SrcSetup(srcHandle_t src, sfxHandle_t sfx, alSrcPriority_t prio curSource->isLocked = qfalse; curSource->isLooping = qfalse; curSource->isTracking = qfalse; + curSource->isStream = qfalse; curSource->curGain = s_alGain->value * s_volume->value; curSource->scaleGain = curSource->curGain; curSource->local = local; // Set up OpenAL source - qalSourcei(curSource->alSource, AL_BUFFER, buffer); + if(sfx >= 0) + { + // Mark the SFX as used, and grab the raw AL buffer + S_AL_BufferUse(sfx); + qalSourcei(curSource->alSource, AL_BUFFER, S_AL_BufferGet(sfx)); + } + qalSourcef(curSource->alSource, AL_PITCH, 1.0f); S_AL_Gain(curSource->alSource, curSource->curGain); qalSourcefv(curSource->alSource, AL_POSITION, vec3_origin); @@ -1320,7 +1323,7 @@ static void S_AL_SrcLoop( alSrcPriority_t priority, sfxHandle_t sfx, VectorClear(sorigin); qalSourcefv(curSource->alSource, AL_POSITION, sorigin); - qalSourcefv(curSource->alSource, AL_VELOCITY, sorigin); + qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin); } else { @@ -1343,8 +1346,8 @@ static void S_AL_SrcLoop( alSrcPriority_t priority, sfxHandle_t sfx, else VectorClear(svelocity); - qalSourcefv( curSource->alSource, AL_POSITION, (ALfloat *)sorigin ); - qalSourcefv( curSource->alSource, AL_VELOCITY, (ALfloat *)velocity ); + qalSourcefv(curSource->alSource, AL_POSITION, (ALfloat *) sorigin); + qalSourcefv(curSource->alSource, AL_VELOCITY, (ALfloat *) svelocity); } } @@ -1557,14 +1560,17 @@ void S_AL_SrcUpdate( void ) continue; } - // Check if it's done, and flag it - qalGetSourcei(curSource->alSource, AL_SOURCE_STATE, &state); - if(state == AL_STOPPED) + if(!curSource->isStream) { - curSource->isPlaying = qfalse; - S_AL_SrcKill(i); - continue; - } + // Check if it's done, and flag it + qalGetSourcei(curSource->alSource, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + curSource->isPlaying = qfalse; + S_AL_SrcKill(i); + continue; + } + } // Query relativity of source, don't move if it's true qalGetSourcei(curSource->alSource, AL_SOURCE_RELATIVE, &state); @@ -1614,32 +1620,57 @@ static ALuint streamSources[MAX_RAW_STREAMS]; S_AL_AllocateStreamChannel ================= */ -static void S_AL_AllocateStreamChannel( int stream ) +static void S_AL_AllocateStreamChannel(int stream, int entityNum) { + srcHandle_t cursrc; + ALuint alsrc; + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) return; - // Allocate a streamSource at high priority - streamSourceHandles[stream] = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); - if(streamSourceHandles[stream] == -1) - return; + if(entityNum >= 0) + { + // This is a stream that tracks an entity + // Allocate a streamSource at normal priority + cursrc = S_AL_SrcAlloc(SRCPRI_ENTITY, entityNum, 0); + if(cursrc < 0) + return; - // Lock the streamSource so nobody else can use it, and get the raw streamSource - S_AL_SrcLock(streamSourceHandles[stream]); - streamSources[stream] = S_AL_SrcGet(streamSourceHandles[stream]); + S_AL_SrcSetup(cursrc, -1, SRCPRI_ENTITY, entityNum, 0, qfalse); + alsrc = S_AL_SrcGet(cursrc); + srcList[cursrc].isTracking = qtrue; + srcList[cursrc].isStream = qtrue; + } + else + { + // Unspatialized stream source - // make sure that after unmuting the S_AL_Gain in S_Update() does not turn - // volume up prematurely for this source - srcList[streamSourceHandles[stream]].scaleGain = 0.0f; + // Allocate a streamSource at high priority + cursrc = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); + if(cursrc < 0) + return; - // Set some streamSource parameters - 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 ); + alsrc = S_AL_SrcGet(cursrc); + + // Lock the streamSource so nobody else can use it, and get the raw streamSource + S_AL_SrcLock(cursrc); + + // make sure that after unmuting the S_AL_Gain in S_Update() does not turn + // volume up prematurely for this source + srcList[cursrc].scaleGain = 0.0f; + + // Set some streamSource parameters + qalSourcei (alsrc, AL_BUFFER, 0 ); + qalSourcei (alsrc, AL_LOOPING, AL_FALSE ); + qalSource3f(alsrc, AL_POSITION, 0.0, 0.0, 0.0); + qalSource3f(alsrc, AL_VELOCITY, 0.0, 0.0, 0.0); + qalSource3f(alsrc, AL_DIRECTION, 0.0, 0.0, 0.0); + qalSourcef (alsrc, AL_ROLLOFF_FACTOR, 0.0 ); + qalSourcei (alsrc, AL_SOURCE_RELATIVE, AL_TRUE ); + } + + streamSourceHandles[stream] = cursrc; + streamSources[stream] = alsrc; } /* @@ -1654,6 +1685,7 @@ static void S_AL_FreeStreamChannel( int stream ) // Release the output streamSource S_AL_SrcUnlock(streamSourceHandles[stream]); + S_AL_SrcKill(streamSourceHandles[stream]); streamSources[stream] = 0; streamSourceHandles[stream] = -1; } @@ -1664,7 +1696,7 @@ S_AL_RawSamples ================= */ static -void S_AL_RawSamples(int stream, 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, int entityNum) { ALuint buffer; ALuint format; @@ -1677,7 +1709,7 @@ void S_AL_RawSamples(int stream, int samples, int rate, int width, int channels, // Create the streamSource if necessary if(streamSourceHandles[stream] == -1) { - S_AL_AllocateStreamChannel(stream); + S_AL_AllocateStreamChannel(stream, entityNum); // Failed? if(streamSourceHandles[stream] == -1) @@ -1694,8 +1726,11 @@ void S_AL_RawSamples(int stream, int samples, int rate, int width, int channels, // Shove the data onto the streamSource qalSourceQueueBuffers(streamSources[stream], 1, &buffer); - // Volume - S_AL_Gain (streamSources[stream], volume * s_volume->value * s_alGain->value); + if(entityNum < 0) + { + // Volume + S_AL_Gain (streamSources[stream], volume * s_volume->value * s_alGain->value); + } } /* @@ -2106,7 +2141,6 @@ S_AL_Respatialize static void S_AL_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) { - float velocity[3] = {0.0f, 0.0f, 0.0f}; float orientation[6]; vec3_t sorigin; @@ -2124,7 +2158,7 @@ void S_AL_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int // Set OpenAL listener paramaters qalListenerfv(AL_POSITION, (ALfloat *)sorigin); - qalListenerfv(AL_VELOCITY, velocity); + qalListenerfv(AL_VELOCITY, vec3_origin); qalListenerfv(AL_ORIENTATION, orientation); } diff --git a/code/client/snd_public.h b/code/client/snd_public.h index 1587f33f..aeb9b2ea 100644 --- a/code/client/snd_public.h +++ b/code/client/snd_public.h @@ -33,8 +33,8 @@ 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 stream, int samples, int rate, int width, int channels, - const byte *data, float volume); +void S_RawSamples(int stream, int samples, int rate, int width, int channels, + const byte *data, float volume, int entityNum); // stop all sounds and the background track void S_StopAllSounds( void ); diff --git a/code/qcommon/common.c b/code/qcommon/common.c index 0a929f06..8f31d523 100644 --- a/code/qcommon/common.c +++ b/code/qcommon/common.c @@ -3585,3 +3585,33 @@ void Com_RandomBytes( byte *string, int len ) string[i] = (unsigned char)( rand() % 255 ); } + +/* +================== +Com_IsVoipTarget + +Returns non-zero if given clientNum is enabled in voipTargets, zero otherwise. +If clientNum is negative return if any bit is set. +================== +*/ +qboolean Com_IsVoipTarget(uint8_t *voipTargets, int voipTargetsSize, int clientNum) +{ + int index; + if(clientNum < 0) + { + for(index = 0; index < voipTargetsSize; index++) + { + if(voipTargets[index]) + return qtrue; + } + + return qfalse; + } + + index = clientNum >> 3; + + if(index < voipTargetsSize) + return (voipTargets[index] & (1 << (clientNum & 0x07))); + + return qfalse; +} diff --git a/code/qcommon/q_shared.h b/code/qcommon/q_shared.h index b15dbf7e..a8968643 100644 --- a/code/qcommon/q_shared.h +++ b/code/qcommon/q_shared.h @@ -928,6 +928,23 @@ typedef struct { char string[MAX_CVAR_VALUE_STRING]; } vmCvar_t; + +/* +============================================================== + +VoIP + +============================================================== +*/ + +// if you change the count of flags be sure to also change VOIP_FLAGNUM +#define VOIP_SPATIAL 0x01 // spatialized voip message +#define VOIP_DIRECT 0x02 // non-spatialized voip message + +// number of flags voip knows. You will have to bump protocol version number if you +// change this. +#define VOIP_FLAGCNT 2 + /* ============================================================== diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index 1fbaf847..48f9e120 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -252,7 +252,7 @@ PROTOCOL ============================================================== */ -#define PROTOCOL_VERSION 70 +#define PROTOCOL_VERSION 71 #define PROTOCOL_LEGACY_VERSION 68 // 1.31 - 67 @@ -834,6 +834,8 @@ int Com_RealTime(qtime_t *qtime); qboolean Com_SafeMode( void ); void Com_RunAndTimeServerPacket(netadr_t *evFrom, msg_t *buf); +qboolean Com_IsVoipTarget(uint8_t *voipTargets, int voipTargetsSize, int clientNum); + void Com_StartupVariable( const char *match ); // checks for and removes command line "+set var arg" constructs // if match is NULL, all set commands will be executed, otherwise diff --git a/code/server/server.h b/code/server/server.h index 6fd46eb7..4d08aef9 100644 --- a/code/server/server.h +++ b/code/server/server.h @@ -43,6 +43,7 @@ typedef struct voipServerPacket_s int frames; int len; int sender; + int flags; byte data[1024]; } voipServerPacket_t; #endif @@ -362,10 +363,6 @@ int SV_WriteDownloadToClient(client_t *cl , msg_t *msg); int SV_SendDownloadMessages(void); int SV_SendQueuedMessages(void); -#ifdef 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 a08c3ff1..7a609fb9 100644 --- a/code/server/sv_client.c +++ b/code/server/sv_client.c @@ -1176,56 +1176,6 @@ int SV_SendDownloadMessages(void) return numDLs; } -#ifdef 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 ) -{ - if(*cl->downloadName) - { - cl->queuedVoipPackets = 0; - return; // no VoIP allowed if download is going, to save bandwidth. - } - - if(cl->queuedVoipPackets) - { - int totalbytes = 0; - int i; - voipServerPacket_t *packet; - - // Write as many VoIP packets as we reasonably can... - for(i = cl->queuedVoipIndex; i < cl->queuedVoipPackets; i++) - { - packet = cl->voipPacket[i % ARRAY_LEN(cl->voipPacket)]; - - totalbytes += packet->len; - if (totalbytes > (msg->maxsize - msg->cursize) / 2) - 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); - - Z_Free(packet); - } - - cl->queuedVoipPackets -= i; - cl->queuedVoipIndex += i; - cl->queuedVoipIndex %= ARRAY_LEN(cl->voipPacket); - } -} -#endif - - /* ================= SV_Disconnect_f @@ -1800,8 +1750,15 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) { #ifdef USE_VOIP -static -qboolean SV_ShouldIgnoreVoipSender(const client_t *cl) +/* +================== +SV_ShouldIgnoreVoipSender + +Blocking of voip packets based on source client +================== +*/ + +static qboolean SV_ShouldIgnoreVoipSender(const client_t *cl) { if (!sv_voip->integer) return qtrue; // VoIP disabled on this server. @@ -1814,33 +1771,25 @@ qboolean SV_ShouldIgnoreVoipSender(const client_t *cl) } 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); +void SV_UserVoip(client_t *cl, msg_t *msg) +{ + int sender, generation, sequence, frames, packetsize; + uint8_t recips[(MAX_CLIENTS + 7) / 8]; + int flags; 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) + sender = cl - svs.clients; + generation = MSG_ReadByte(msg); + sequence = MSG_ReadLong(msg); + frames = MSG_ReadByte(msg); + MSG_ReadData(msg, recips, sizeof(recips)); + flags = MSG_ReadByte(msg); + packetsize = MSG_ReadShort(msg); + + if (msg->readcount > msg->cursize) return; // short/invalid packet, bail. if (packetsize > sizeof (encoded)) { // overlarge packet? @@ -1865,10 +1814,6 @@ void SV_UserVoip( client_t *cl, msg_t *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) @@ -1876,18 +1821,20 @@ void SV_UserVoip( client_t *cl, msg_t *msg ) { 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. + continue; // no VoIP support, or unsupported protocol 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) ) + + if(Com_IsVoipTarget(recips, sizeof(recips), i)) + flags |= VOIP_DIRECT; + else + flags &= ~VOIP_DIRECT; + + if (!(flags & (VOIP_SPATIAL | VOIP_DIRECT))) continue; // not addressed to this player. // Transmit this packet to the client. @@ -1902,6 +1849,7 @@ void SV_UserVoip( client_t *cl, msg_t *msg ) { packet->len = packetsize; packet->generation = generation; packet->sequence = sequence; + packet->flags = flags; memcpy(packet->data, encoded, packetsize); client->voipPacket[(client->queuedVoipIndex + client->queuedVoipPackets) % ARRAY_LEN(client->voipPacket)] = packet; diff --git a/code/server/sv_init.c b/code/server/sv_init.c index 92e519c2..1ad51009 100644 --- a/code/server/sv_init.c +++ b/code/server/sv_init.c @@ -653,8 +653,8 @@ void SV_Init (void) sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM ); sv_pure = Cvar_Get ("sv_pure", "1", CVAR_SYSTEMINFO ); #ifdef USE_VOIP - sv_voip = Cvar_Get ("sv_voip", "1", CVAR_SYSTEMINFO | CVAR_LATCH); - Cvar_CheckRange( sv_voip, 0, 1, qtrue ); + sv_voip = Cvar_Get("sv_voip", "1", CVAR_SYSTEMINFO | CVAR_LATCH); + Cvar_CheckRange(sv_voip, 0, 1, qtrue); #endif Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM ); Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); diff --git a/code/server/sv_snapshot.c b/code/server/sv_snapshot.c index 73439193..2499f040 100644 --- a/code/server/sv_snapshot.c +++ b/code/server/sv_snapshot.c @@ -521,6 +521,53 @@ static void SV_BuildClientSnapshot( client_t *client ) { } } +#ifdef USE_VOIP +/* +================== +SV_WriteVoipToClient + +Check to see if there is any VoIP queued for a client, and send if there is. +================== +*/ +static void SV_WriteVoipToClient(client_t *cl, msg_t *msg) +{ + int totalbytes = 0; + int i; + voipServerPacket_t *packet; + + if(cl->queuedVoipPackets) + { + // Write as many VoIP packets as we reasonably can... + for(i = 0; i < cl->queuedVoipPackets; i++) + { + packet = cl->voipPacket[(i + cl->queuedVoipIndex) % ARRAY_LEN(cl->voipPacket)]; + + if(!*cl->downloadName) + { + totalbytes += packet->len; + if (totalbytes > (msg->maxsize - msg->cursize) / 2) + 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_WriteBits(msg, packet->flags, VOIP_FLAGCNT); + MSG_WriteData(msg, packet->data, packet->len); + } + + Z_Free(packet); + } + + cl->queuedVoipPackets -= i; + cl->queuedVoipIndex += i; + cl->queuedVoipIndex %= ARRAY_LEN(cl->voipPacket); + } +} +#endif + /* ======================= SV_SendMessageToClient @@ -610,11 +657,11 @@ void SV_SendClientMessages(void) if(*c->downloadName) continue; // Client is downloading, don't send snapshots - if(c->netchan.unsentFragments || c->netchan_start_queue) - { - c->rateDelayed = qtrue; + if(c->netchan.unsentFragments || c->netchan_start_queue) + { + c->rateDelayed = qtrue; continue; // Drop this snapshot if the packet queue is still full or delta compression will break - } + } if(!(c->netchan.remoteAddress.type == NA_LOOPBACK || (sv_lanForceRate->integer && Sys_IsLANAddress(c->netchan.remoteAddress)))) diff --git a/voip-readme.txt b/voip-readme.txt index 832379f6..920e3db2 100644 --- a/voip-readme.txt +++ b/voip-readme.txt @@ -49,9 +49,12 @@ s_alCapture: set to "1" (the default) to have the audio layer open an OpenAL cl_voipSendTarget: a string: "all" to broadcast to everyone, "none" to send to no one, "attacker" to send to the last person that hit you, "crosshair" to send to the people currently in your - crosshair, or a comma-separated list of client numbers, like - "0,7,2,23" ... an empty string is treated like "all". This - is reset to "all" when connecting to a new server. + crosshair, "spatial" to talk to all people in hearing + range or a comma-separated list of client numbers, like + "0,7,2,23" ... an empty string is treated like "spatial". + You can also use a mixed string like + "0, spatial, 2, crosshair". + This is reset to "spatial" when connecting to a new server. Presumably mods will manage this cvar, not people, but keybind could be useful for the general cases. To send to just your team, or the opposing team, or a buddy list, you