Use Opus for VoIP

Server/client VoIP protocol is handled by adding new cvars
cl_voipProtocol and sv_voipProtocol, sv_voip and cl_voip
are used to auto set/clear them. All users need to touch
are cl/sv_voip as 0 or 1 just like before.

Old Speex VoIP packets in demos are skipped.
New VoIP packets are skipped in demos if sv_voipProtocol
doesn't match cl_voipProtocol.

Notable difference between usage of speex and opus codecs,
when using Speex client would be sent 80ms at a time.
Using Opus, 60ms is sent at a time. This was changed because
the Opus codec supports encoding up to 60ms at a time.
(Simpler to send only one codec frame in a packet.)
This commit is contained in:
Zack Middleton 2013-12-10 21:14:13 -06:00
parent fe619680f8
commit 615b73288f
13 changed files with 167 additions and 240 deletions

View File

@ -202,10 +202,6 @@ ifndef USE_INTERNAL_LIBS
USE_INTERNAL_LIBS=1
endif
ifndef USE_INTERNAL_SPEEX
USE_INTERNAL_SPEEX=$(USE_INTERNAL_LIBS)
endif
ifndef USE_INTERNAL_OGG
USE_INTERNAL_OGG=$(USE_INTERNAL_LIBS)
endif
@ -258,7 +254,6 @@ NDIR=$(MOUNT_DIR)/null
UIDIR=$(MOUNT_DIR)/ui
Q3UIDIR=$(MOUNT_DIR)/q3_ui
JPDIR=$(MOUNT_DIR)/jpeg-8c
SPEEXDIR=$(MOUNT_DIR)/libspeex
OGGDIR=$(MOUNT_DIR)/libogg-1.3.1
VORBISDIR=$(MOUNT_DIR)/libvorbis-1.3.4
OPUSDIR=$(MOUNT_DIR)/opus-1.1
@ -991,8 +986,18 @@ ifeq ($(USE_CURL),1)
endif
endif
ifeq ($(USE_VOIP),1)
CLIENT_CFLAGS += -DUSE_VOIP
SERVER_CFLAGS += -DUSE_VOIP
NEED_OPUS=1
endif
ifeq ($(USE_CODEC_OPUS),1)
CLIENT_CFLAGS += -DUSE_CODEC_OPUS
NEED_OPUS=1
endif
ifeq ($(NEED_OPUS),1)
ifeq ($(USE_INTERNAL_OPUS),1)
OPUS_CFLAGS = -DOPUS_BUILD -DHAVE_LRINTF -DFLOATING_POINT -DUSE_ALLOCA \
-I$(OPUSDIR)/include -I$(OPUSDIR)/celt -I$(OPUSDIR)/silk \
@ -1038,19 +1043,6 @@ ifeq ($(USE_MUMBLE),1)
CLIENT_CFLAGS += -DUSE_MUMBLE
endif
ifeq ($(USE_VOIP),1)
CLIENT_CFLAGS += -DUSE_VOIP
SERVER_CFLAGS += -DUSE_VOIP
ifeq ($(USE_INTERNAL_SPEEX),1)
SPEEX_CFLAGS += -DFLOATING_POINT -DUSE_ALLOCA -I$(SPEEXDIR)/include
else
SPEEX_CFLAGS ?= $(shell pkg-config --silence-errors --cflags speex speexdsp || true)
SPEEX_LIBS ?= $(shell pkg-config --silence-errors --libs speex speexdsp || echo -lspeex -lspeexdsp)
endif
CLIENT_CFLAGS += $(SPEEX_CFLAGS)
CLIENT_LIBS += $(SPEEX_LIBS)
endif
ifeq ($(USE_INTERNAL_ZLIB),1)
ZLIB_CFLAGS = -DNO_GZIP -I$(ZDIR)
else
@ -1828,53 +1820,7 @@ ifeq ($(ARCH),x86_64)
$(B)/client/ftola.o
endif
ifeq ($(USE_VOIP),1)
ifeq ($(USE_INTERNAL_SPEEX),1)
Q3OBJ += \
$(B)/client/bits.o \
$(B)/client/buffer.o \
$(B)/client/cb_search.o \
$(B)/client/exc_10_16_table.o \
$(B)/client/exc_10_32_table.o \
$(B)/client/exc_20_32_table.o \
$(B)/client/exc_5_256_table.o \
$(B)/client/exc_5_64_table.o \
$(B)/client/exc_8_128_table.o \
$(B)/client/fftwrap.o \
$(B)/client/filterbank.o \
$(B)/client/filters.o \
$(B)/client/gain_table.o \
$(B)/client/gain_table_lbr.o \
$(B)/client/hexc_10_32_table.o \
$(B)/client/hexc_table.o \
$(B)/client/high_lsp_tables.o \
$(B)/client/jitter.o \
$(B)/client/kiss_fft.o \
$(B)/client/kiss_fftr.o \
$(B)/client/lpc.o \
$(B)/client/lsp.o \
$(B)/client/lsp_tables_nb.o \
$(B)/client/ltp.o \
$(B)/client/mdf.o \
$(B)/client/modes.o \
$(B)/client/modes_wb.o \
$(B)/client/nb_celp.o \
$(B)/client/preprocess.o \
$(B)/client/quant_lsp.o \
$(B)/client/resample.o \
$(B)/client/sb_celp.o \
$(B)/client/smallft.o \
$(B)/client/speex.o \
$(B)/client/speex_callbacks.o \
$(B)/client/speex_header.o \
$(B)/client/stereo.o \
$(B)/client/vbr.o \
$(B)/client/vq.o \
$(B)/client/window.o
endif
endif
ifeq ($(USE_CODEC_OPUS),1)
ifeq ($(NEED_OPUS),1)
ifeq ($(USE_INTERNAL_OPUS),1)
Q3OBJ += \
$(B)/client/opus/analysis.o \
@ -2581,9 +2527,6 @@ $(B)/client/%.o: $(CMDIR)/%.c
$(B)/client/%.o: $(BLIBDIR)/%.c
$(DO_BOT_CC)
$(B)/client/%.o: $(SPEEXDIR)/%.c
$(DO_CC)
$(B)/client/%.o: $(OGGDIR)/src/%.c
$(DO_CC)
@ -2874,7 +2817,6 @@ ifdef MINGW
USE_OPENAL_DLOPEN=$(USE_OPENAL_DLOPEN) \
USE_CURL_DLOPEN=$(USE_CURL_DLOPEN) \
USE_INTERNAL_OPUS=$(USE_INTERNAL_OPUS) \
USE_INTERNAL_SPEEX=$(USE_INTERNAL_SPEEX) \
USE_INTERNAL_ZLIB=$(USE_INTERNAL_ZLIB) \
USE_INTERNAL_JPEG=$(USE_INTERNAL_JPEG)
else

View File

@ -905,37 +905,27 @@ void CL_FirstSnapshot( void ) {
#endif
#ifdef USE_VOIP
if (!clc.speexInitialized) {
if (!clc.voipCodecInitialized) {
int i;
speex_bits_init(&clc.speexEncoderBits);
speex_bits_reset(&clc.speexEncoderBits);
int error;
clc.speexEncoder = speex_encoder_init(&speex_nb_mode);
clc.opusEncoder = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, &error);
speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_FRAME_SIZE,
&clc.speexFrameSize);
speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_SAMPLING_RATE,
&clc.speexSampleRate);
clc.speexPreprocessor = speex_preprocess_state_init(clc.speexFrameSize,
clc.speexSampleRate);
i = 1;
speex_preprocess_ctl(clc.speexPreprocessor,
SPEEX_PREPROCESS_SET_DENOISE, &i);
i = 1;
speex_preprocess_ctl(clc.speexPreprocessor,
SPEEX_PREPROCESS_SET_AGC, &i);
if ( error ) {
Com_DPrintf("VoIP: Error opus_encoder_create %d\n", error);
return;
}
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.opusDecoder[i] = opus_decoder_create(48000, 1, &error);
if ( error ) {
Com_DPrintf("VoIP: Error opus_decoder_create(%d) %d\n", i, error);
return;
}
clc.voipIgnore[i] = qfalse;
clc.voipGain[i] = 1.0f;
}
clc.speexInitialized = qtrue;
clc.voipCodecInitialized = qtrue;
clc.voipMuteAll = qfalse;
Cmd_AddCommand ("voip", CL_Voip_f);
Cvar_Set("cl_voipSendTarget", "spatial");

View File

@ -788,7 +788,7 @@ void CL_WritePacket( void ) {
{
if((clc.voipFlags & VOIP_SPATIAL) || Com_IsVoipTarget(clc.voipTargets, sizeof(clc.voipTargets), -1))
{
MSG_WriteByte (&buf, clc_voip);
MSG_WriteByte (&buf, clc_voipOpus);
MSG_WriteByte (&buf, clc.voipOutgoingGeneration);
MSG_WriteLong (&buf, clc.voipOutgoingSequence);
MSG_WriteByte (&buf, clc.voipOutgoingDataFrames);
@ -809,7 +809,7 @@ void CL_WritePacket( void ) {
MSG_Init (&fakemsg, fakedata, sizeof (fakedata));
MSG_Bitstream (&fakemsg);
MSG_WriteLong (&fakemsg, clc.reliableAcknowledge);
MSG_WriteByte (&fakemsg, svc_voip);
MSG_WriteByte (&fakemsg, svc_voipOpus);
MSG_WriteShort (&fakemsg, clc.clientNum);
MSG_WriteByte (&fakemsg, clc.voipOutgoingGeneration);
MSG_WriteLong (&fakemsg, clc.voipOutgoingSequence);

View File

@ -44,6 +44,7 @@ cvar_t *cl_voipSendTarget;
cvar_t *cl_voipGainDuringCapture;
cvar_t *cl_voipCaptureMult;
cvar_t *cl_voipShowMeter;
cvar_t *cl_voipProtocol;
cvar_t *cl_voip;
#endif
@ -250,8 +251,8 @@ void CL_Voip_f( void )
if (clc.state != CA_ACTIVE)
reason = "Not connected to a server";
else if (!clc.speexInitialized)
reason = "Speex not initialized";
else if (!clc.voipCodecInitialized)
reason = "Voip codec not initialized";
else if (!clc.voipEnabled)
reason = "Server doesn't support VoIP";
else if (!clc.demoplaying && (Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")))
@ -306,6 +307,8 @@ void CL_VoipNewGeneration(void)
clc.voipOutgoingGeneration = 1;
clc.voipPower = 0.0f;
clc.voipOutgoingSequence = 0;
opus_encoder_ctl(clc.opusEncoder, OPUS_RESET_STATE);
}
/*
@ -394,7 +397,7 @@ void CL_VoipParseTargets(void)
===============
CL_CaptureVoip
Record more audio from the hardware if required and encode it into Speex
Record more audio from the hardware if required and encode it into Opus
data for later transmission.
===============
*/
@ -424,11 +427,12 @@ void CL_CaptureVoip(void)
Com_Printf("Until then, VoIP is disabled.\n");
Cvar_Set("cl_voip", "0");
}
Cvar_Set("cl_voipProtocol", cl_voip->integer ? "opus" : "");
cl_voip->modified = qfalse;
cl_rate->modified = qfalse;
}
if (!clc.speexInitialized)
if (!clc.voipCodecInitialized)
return; // just in case this gets called at a bad time.
if (clc.voipOutgoingDataSize > 0)
@ -481,80 +485,67 @@ void CL_CaptureVoip(void)
if ((cl_voipSend->integer) || (finalFrame)) { // user wants to capture audio?
int samples = S_AvailableCaptureSamples();
const int mult = (finalFrame) ? 1 : 4; // 4 == 80ms of audio.
const int packetSamples = (finalFrame) ? VOIP_MAX_FRAME_SAMPLES : VOIP_MAX_PACKET_SAMPLES;
// 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!).
// 2048 will cover 12 uncompressed frames in narrowband mode.
static int16_t sampbuffer[2048];
if (samples >= packetSamples) {
// audio capture is always MONO16.
static int16_t sampbuffer[VOIP_MAX_PACKET_SAMPLES];
float voipPower = 0.0f;
int speexFrames = 0;
int wpos = 0;
int pos = 0;
int voipFrames;
int i, bytes;
if (samples > (clc.speexFrameSize * 4))
samples = (clc.speexFrameSize * 4);
if (samples > VOIP_MAX_PACKET_SAMPLES)
samples = VOIP_MAX_PACKET_SAMPLES;
// !!! FIXME: maybe separate recording from encoding, so voipPower
// !!! FIXME: updates faster than 4Hz?
samples -= samples % clc.speexFrameSize;
samples -= samples % VOIP_MAX_FRAME_SAMPLES;
if (samples != 120 && samples != 240 && samples != 480 && samples != 960 && samples != 1920 && samples != 2880 ) {
Com_Printf("Voip: bad number of samples %d\n", samples);
return;
}
voipFrames = samples / VOIP_MAX_FRAME_SAMPLES;
S_Capture(samples, (byte *) sampbuffer); // grab from audio card.
// this will probably generate multiple speex packets each time.
while (samples > 0) {
int16_t *sampptr = &sampbuffer[pos];
int i, bytes;
// check the "power" of this packet...
for (i = 0; i < samples; i++) {
const float flsamp = (float) sampbuffer[i];
const float s = fabs(flsamp);
voipPower += s * s;
sampbuffer[i] = (int16_t) ((flsamp) * audioMult);
}
// preprocess samples to remove noise...
speex_preprocess_run(clc.speexPreprocessor, sampptr);
// check the "power" of this packet...
for (i = 0; i < clc.speexFrameSize; i++) {
const float flsamp = (float) sampptr[i];
const float s = fabs(flsamp);
voipPower += s * s;
sampptr[i] = (int16_t) ((flsamp) * audioMult);
}
// encode raw audio samples into Speex data...
speex_bits_reset(&clc.speexEncoderBits);
speex_encode_int(clc.speexEncoder, sampptr,
&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++;
// encode raw audio samples into Opus data...
bytes = opus_encode(clc.opusEncoder, sampbuffer, samples,
(unsigned char *) clc.voipOutgoingData,
sizeof (clc.voipOutgoingData));
if ( bytes <= 0 ) {
Com_DPrintf("VoIP: Error encoding %d samples\n", samples);
bytes = 0;
}
clc.voipPower = (voipPower / (32768.0f * 32768.0f *
((float) (clc.speexFrameSize * speexFrames)))) *
100.0f;
((float) samples))) * 100.0f;
if ((useVad) && (clc.voipPower < cl_voipVADThreshold->value)) {
CL_VoipNewGeneration(); // no "talk" for at least 1/4 second.
} else {
clc.voipOutgoingDataSize = wpos;
clc.voipOutgoingDataFrames = speexFrames;
clc.voipOutgoingDataSize = bytes;
clc.voipOutgoingDataFrames = voipFrames;
Com_DPrintf("VoIP: Send %d frames, %d bytes, %f power\n",
speexFrames, wpos, clc.voipPower);
voipFrames, bytes, clc.voipPower);
#if 0
static FILE *encio = NULL;
if (encio == NULL) encio = fopen("voip-outgoing-encoded.bin", "wb");
if (encio != NULL) { fwrite(clc.voipOutgoingData, wpos, 1, encio); fflush(encio); }
if (encio != NULL) { fwrite(clc.voipOutgoingData, bytes, 1, encio); fflush(encio); }
static FILE *decio = NULL;
if (decio == NULL) decio = fopen("voip-outgoing-decoded.bin", "wb");
if (decio != NULL) { fwrite(sampbuffer, speexFrames * clc.speexFrameSize * 2, 1, decio); fflush(decio); }
if (decio != NULL) { fwrite(sampbuffer, voipFrames * VOIP_MAX_FRAME_SAMPLES * 2, 1, decio); fflush(decio); }
#endif
}
}
@ -1420,14 +1411,11 @@ void CL_Disconnect( qboolean showMainMenu ) {
cl_voipUseVAD->integer = tmp;
}
if (clc.speexInitialized) {
if (clc.voipCodecInitialized) {
int i;
speex_bits_destroy(&clc.speexEncoderBits);
speex_encoder_destroy(clc.speexEncoder);
speex_preprocess_state_destroy(clc.speexPreprocessor);
opus_encoder_destroy(clc.opusEncoder);
for (i = 0; i < MAX_CLIENTS; i++) {
speex_bits_destroy(&clc.speexDecoderBits[i]);
speex_decoder_destroy(clc.speexDecoder[i]);
opus_decoder_destroy(clc.opusDecoder[i]);
}
}
Cmd_RemoveCommand ("voip");
@ -3653,9 +3641,9 @@ void CL_Init( void ) {
cl_voipVADThreshold = Cvar_Get ("cl_voipVADThreshold", "0.25", CVAR_ARCHIVE);
cl_voipShowMeter = Cvar_Get ("cl_voipShowMeter", "1", CVAR_ARCHIVE);
// This is a protocol version number.
cl_voip = Cvar_Get ("cl_voip", "1", CVAR_USERINFO | CVAR_ARCHIVE);
cl_voip = Cvar_Get ("cl_voip", "1", CVAR_ARCHIVE);
Cvar_CheckRange( cl_voip, 0, 1, qtrue );
cl_voipProtocol = Cvar_Get ("cl_voipProtocol", cl_voip->integer ? "opus" : "", CVAR_USERINFO | CVAR_ROM);
#endif

View File

@ -34,7 +34,8 @@ char *svc_strings[256] = {
"svc_download",
"svc_snapshot",
"svc_EOF",
"svc_voip",
"svc_voipSpeex",
"svc_voipOpus",
};
void SHOWNET( msg_t *msg, char *s) {
@ -359,8 +360,8 @@ void CL_SystemInfoChanged( void ) {
else
#endif
{
s = Info_ValueForKey( systemInfo, "sv_voip" );
clc.voipEnabled = atoi(s);
s = Info_ValueForKey( systemInfo, "sv_voipProtocol" );
clc.voipEnabled = !Q_stricmp(s, "opus");
}
#endif
@ -680,13 +681,13 @@ 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,
S_RawSamples(sender + 1, samplecnt, 48000, 2, 1,
data, clc.voipGain[sender], -1);
}
if(flags & VOIP_SPATIAL)
{
S_RawSamples(sender + MAX_CLIENTS + 1, samplecnt, clc.speexSampleRate, 2, 1,
S_RawSamples(sender + MAX_CLIENTS + 1, samplecnt, 48000, 2, 1,
data, 1.0f, sender);
}
}
@ -699,8 +700,8 @@ A VoIP message has been received from the server
=====================
*/
static
void CL_ParseVoip ( msg_t *msg ) {
static short decoded[4096]; // !!! FIXME: don't hardcode.
void CL_ParseVoip ( msg_t *msg, qboolean ignoreData ) {
static short decoded[VOIP_MAX_PACKET_SAMPLES*4]; // !!! FIXME: don't hard code
const int sender = MSG_ReadShort(msg);
const int generation = MSG_ReadByte(msg);
@ -708,7 +709,8 @@ void CL_ParseVoip ( msg_t *msg ) {
const int frames = MSG_ReadByte(msg);
const int packetsize = MSG_ReadShort(msg);
const int flags = MSG_ReadBits(msg, VOIP_FLAGCNT);
char encoded[1024];
unsigned char encoded[4000];
int numSamples;
int seqdiff;
int written = 0;
int i;
@ -738,14 +740,15 @@ void CL_ParseVoip ( msg_t *msg ) {
return; // overlarge packet, bail.
}
if (!clc.speexInitialized) {
MSG_ReadData(msg, encoded, packetsize); // skip payload.
return; // can't handle VoIP without libspeex!
MSG_ReadData(msg, encoded, packetsize);
if (ignoreData) {
return; // just ignore legacy speex voip data
} else if (!clc.voipCodecInitialized) {
return; // can't handle VoIP without libopus!
} 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.
}
@ -758,70 +761,59 @@ void CL_ParseVoip ( msg_t *msg ) {
// 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]);
opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE);
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]);
// reset the decoder just in case.
opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE);
seqdiff = 0;
} else if (seqdiff * clc.speexFrameSize * 2 >= sizeof (decoded)) { // dropped more than we can handle?
} else if (seqdiff * VOIP_MAX_PACKET_SAMPLES*2 >= sizeof (decoded)) { // dropped more than we can handle?
// just start over.
Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n",
seqdiff, sender);
speex_bits_reset(&clc.speexDecoderBits[sender]);
opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE);
seqdiff = 0;
}
if (seqdiff != 0) {
Com_DPrintf("VoIP: Dropped %d frames from client #%d\n",
seqdiff, sender);
// tell speex that we're missing frames...
// tell opus 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;
assert((written + VOIP_MAX_PACKET_SAMPLES) * 2 < sizeof (decoded));
numSamples = opus_decode(clc.opusDecoder[sender], NULL, VOIP_MAX_PACKET_SAMPLES * 2, decoded + written, sizeof (decoded) - written, 0);
if ( numSamples <= 0 ) {
Com_DPrintf("VoIP: Error decoding frame %d from client #%d\n", i, sender);
continue;
}
written += numSamples;
}
}
for (i = 0; i < frames; i++) {
const int len = MSG_ReadByte(msg);
if (len < 0) {
Com_DPrintf("VoIP: Short packet!\n");
break;
}
MSG_ReadData(msg, encoded, len);
numSamples = opus_decode(clc.opusDecoder[sender], encoded, packetsize, decoded + written, sizeof (decoded) - written, 0);
// 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);
CL_PlayVoip(sender, written, (const byte *) decoded, flags);
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("voip-incoming-encoded.bin", "wb");
if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); }
static FILE *decio = NULL;
if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb");
if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); }
#endif
written += clc.speexFrameSize;
if ( numSamples <= 0 ) {
Com_DPrintf("VoIP: Error decoding voip data from client #%d\n", sender);
numSamples = 0;
}
#if 0
static FILE *encio = NULL;
if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb");
if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); }
static FILE *decio = NULL;
if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb");
if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); }
#endif
written += numSamples;
Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
written * 2, written, i);
written * 2, written, frames);
if(written > 0)
CL_PlayVoip(sender, written, (const byte *) decoded, flags);
@ -924,9 +916,14 @@ void CL_ParseServerMessage( msg_t *msg ) {
case svc_download:
CL_ParseDownload( msg );
break;
case svc_voip:
case svc_voipSpeex:
#ifdef USE_VOIP
CL_ParseVoip( msg );
CL_ParseVoip( msg, qtrue );
#endif
break;
case svc_voipOpus:
#ifdef USE_VOIP
CL_ParseVoip( msg, !clc.voipEnabled );
#endif
break;
}

View File

@ -35,8 +35,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#endif /* USE_CURL */
#ifdef USE_VOIP
#include "speex/speex.h"
#include "speex/speex_preprocess.h"
#include <opus.h>
#endif
// file full of random crap that gets used to create cl_guid
@ -238,14 +237,11 @@ typedef struct {
#ifdef USE_VOIP
qboolean voipEnabled;
qboolean speexInitialized;
int speexFrameSize;
int speexSampleRate;
qboolean voipCodecInitialized;
// incoming data...
// !!! FIXME: convert from parallel arrays to array of a struct.
SpeexBits speexDecoderBits[MAX_CLIENTS];
void *speexDecoder[MAX_CLIENTS];
OpusDecoder *opusDecoder[MAX_CLIENTS];
byte voipIncomingGeneration[MAX_CLIENTS];
int voipIncomingSequence[MAX_CLIENTS];
float voipGain[MAX_CLIENTS];
@ -257,9 +253,7 @@ typedef struct {
// then we are sending to clientnum i.
uint8_t voipTargets[(MAX_CLIENTS + 7) / 8];
uint8_t voipFlags;
SpeexPreprocessState *speexPreprocessor;
SpeexBits speexEncoderBits;
void *speexEncoder;
OpusEncoder *opusEncoder;
int voipOutgoingDataSize;
int voipOutgoingDataFrames;
int voipOutgoingSequence;
@ -447,6 +441,13 @@ extern cvar_t *cl_voipGainDuringCapture;
extern cvar_t *cl_voipCaptureMult;
extern cvar_t *cl_voipShowMeter;
extern cvar_t *cl_voip;
// 20ms at 48k
#define VOIP_MAX_FRAME_SAMPLES ( 20 * 48 )
// 3 frame is 60ms of audio, the max opus will encode at once
#define VOIP_MAX_PACKET_FRAMES 3
#define VOIP_MAX_PACKET_SAMPLES ( VOIP_MAX_FRAME_SAMPLES * VOIP_MAX_PACKET_FRAMES )
#endif
//=================================================

View File

@ -2681,16 +2681,12 @@ qboolean S_AL_Init( soundInterface_t *si )
s_alAvailableInputDevices = Cvar_Get("s_alAvailableInputDevices", inputdevicenames, CVAR_ROM | CVAR_NORESTART);
// !!! FIXME: 8000Hz is what Speex narrowband mode needs, but we
// !!! FIXME: should probably open the capture device after
// !!! FIXME: initializing Speex so we can change to wideband
// !!! FIXME: if we like.
Com_Printf("OpenAL default capture device is '%s'\n", defaultinputdevice ? defaultinputdevice : "none");
alCaptureDevice = qalcCaptureOpenDevice(inputdevice, 8000, AL_FORMAT_MONO16, 4096);
alCaptureDevice = qalcCaptureOpenDevice(inputdevice, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4);
if( !alCaptureDevice && inputdevice )
{
Com_Printf( "Failed to open OpenAL Input device '%s', trying default.\n", inputdevice );
alCaptureDevice = qalcCaptureOpenDevice(NULL, 8000, AL_FORMAT_MONO16, 4096);
alCaptureDevice = qalcCaptureOpenDevice(NULL, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4);
}
Com_Printf( "OpenAL capture device %s.\n",
(alCaptureDevice == NULL) ? "failed to open" : "opened");

View File

@ -300,7 +300,8 @@ enum svc_ops_e {
svc_EOF,
// new commands, supported only by ioquake3 protocol but not legacy
svc_voip, // not wrapped in USE_VOIP, so this value is reserved.
svc_voipSpeex, // not wrapped in USE_VOIP, so this value is reserved.
svc_voipOpus, //
};
@ -316,7 +317,8 @@ enum clc_ops_e {
clc_EOF,
// new commands, supported only by ioquake3 protocol but not legacy
clc_voip, // not wrapped in USE_VOIP, so this value is reserved.
clc_voipSpeex, // not wrapped in USE_VOIP, so this value is reserved.
clc_voipOpus, //
};
/*

View File

@ -44,7 +44,7 @@ typedef struct voipServerPacket_s
int len;
int sender;
int flags;
byte data[1024];
byte data[4000];
} voipServerPacket_t;
#endif
@ -299,6 +299,7 @@ extern int serverBansCount;
#ifdef USE_VOIP
extern cvar_t *sv_voip;
extern cvar_t *sv_voipProtocol;
#endif

View File

@ -1459,8 +1459,8 @@ void SV_UserinfoChanged( client_t *cl ) {
else
#endif
{
val = Info_ValueForKey(cl->userinfo, "cl_voip");
cl->hasVoip = atoi(val);
val = Info_ValueForKey(cl->userinfo, "cl_voipProtocol");
cl->hasVoip = !Q_stricmp( val, "opus" );
}
#endif
@ -1794,7 +1794,7 @@ static qboolean SV_ShouldIgnoreVoipSender(const client_t *cl)
}
static
void SV_UserVoip(client_t *cl, msg_t *msg)
void SV_UserVoip(client_t *cl, msg_t *msg, qboolean ignoreData)
{
int sender, generation, sequence, frames, packetsize;
uint8_t recips[(MAX_CLIENTS + 7) / 8];
@ -1829,12 +1829,12 @@ void SV_UserVoip(client_t *cl, msg_t *msg)
MSG_ReadData(msg, encoded, packetsize);
if (SV_ShouldIgnoreVoipSender(cl))
if (ignoreData || SV_ShouldIgnoreVoipSender(cl))
return; // Blacklisted, disabled, etc.
// !!! FIXME: see if we read past end of msg...
// !!! FIXME: reject if not speex narrowband codec.
// !!! FIXME: reject if not opus data.
// !!! FIXME: decide if this is bogus data?
// decide who needs this VoIP packet sent to them...
@ -1983,10 +1983,18 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) {
}
} while ( 1 );
// read optional voip data
if ( c == clc_voip ) {
// skip legacy speex voip data
if ( c == clc_voipSpeex ) {
#ifdef USE_VOIP
SV_UserVoip( cl, msg );
SV_UserVoip( cl, msg, qtrue );
c = MSG_ReadByte( msg );
#endif
}
// read optional voip data
if ( c == clc_voipOpus ) {
#ifdef USE_VOIP
SV_UserVoip( cl, msg, qfalse );
c = MSG_ReadByte( msg );
#endif
}

View File

@ -656,8 +656,9 @@ 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);
sv_voip = Cvar_Get("sv_voip", "1", CVAR_LATCH);
Cvar_CheckRange(sv_voip, 0, 1, qtrue);
sv_voipProtocol = Cvar_Get("sv_voipProtocol", sv_voip->integer ? "opus" : "", CVAR_SYSTEMINFO | CVAR_ROM );
#endif
Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM );
Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM );

View File

@ -24,6 +24,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#ifdef USE_VOIP
cvar_t *sv_voip;
cvar_t *sv_voipProtocol;
#endif
serverStatic_t svs; // persistant server info
@ -665,8 +666,8 @@ void SVC_Info( netadr_t from ) {
Info_SetValueForKey(infostring, "g_needpass", va("%d", Cvar_VariableIntegerValue("g_needpass")));
#ifdef USE_VOIP
if (sv_voip->integer) {
Info_SetValueForKey( infostring, "voip", va("%i", sv_voip->integer ) );
if (sv_voipProtocol->string && *sv_voipProtocol->string) {
Info_SetValueForKey( infostring, "voip", sv_voipProtocol->string );
}
#endif

View File

@ -547,7 +547,7 @@ static void SV_WriteVoipToClient(client_t *cl, msg_t *msg)
if (totalbytes > (msg->maxsize - msg->cursize) / 2)
break;
MSG_WriteByte(msg, svc_voip);
MSG_WriteByte(msg, svc_voipOpus);
MSG_WriteShort(msg, packet->sender);
MSG_WriteByte(msg, (byte) packet->generation);
MSG_WriteLong(msg, packet->sequence);