From 615b73288fb589650f446ebf4e7e9a40192bf7dc Mon Sep 17 00:00:00 2001 From: Zack Middleton Date: Tue, 10 Dec 2013 21:14:13 -0600 Subject: [PATCH] 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.) --- Makefile | 80 ++++------------------------ code/client/cl_cgame.c | 36 +++++-------- code/client/cl_input.c | 4 +- code/client/cl_main.c | 108 +++++++++++++++++--------------------- code/client/cl_parse.c | 107 ++++++++++++++++++------------------- code/client/client.h | 21 ++++---- code/client/snd_openal.c | 8 +-- code/qcommon/qcommon.h | 6 ++- code/server/server.h | 3 +- code/server/sv_client.c | 24 ++++++--- code/server/sv_init.c | 3 +- code/server/sv_main.c | 5 +- code/server/sv_snapshot.c | 2 +- 13 files changed, 167 insertions(+), 240 deletions(-) diff --git a/Makefile b/Makefile index 26ef512e..6515cb53 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/code/client/cl_cgame.c b/code/client/cl_cgame.c index d1e84806..d69ae752 100644 --- a/code/client/cl_cgame.c +++ b/code/client/cl_cgame.c @@ -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"); diff --git a/code/client/cl_input.c b/code/client/cl_input.c index 6e19263c..6669a406 100644 --- a/code/client/cl_input.c +++ b/code/client/cl_input.c @@ -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); diff --git a/code/client/cl_main.c b/code/client/cl_main.c index 75ce886b..205fd63c 100644 --- a/code/client/cl_main.c +++ b/code/client/cl_main.c @@ -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 diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c index 5c9c1471..5f2fcc0c 100644 --- a/code/client/cl_parse.c +++ b/code/client/cl_parse.c @@ -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; } diff --git a/code/client/client.h b/code/client/client.h index a9b3e51e..0fe3889c 100644 --- a/code/client/client.h +++ b/code/client/client.h @@ -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 #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 //================================================= diff --git a/code/client/snd_openal.c b/code/client/snd_openal.c index d6498a98..fa5a721a 100644 --- a/code/client/snd_openal.c +++ b/code/client/snd_openal.c @@ -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"); diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index 1df3214c..b47a41f4 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -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, // }; /* diff --git a/code/server/server.h b/code/server/server.h index 407d6ab6..39d6f4d3 100644 --- a/code/server/server.h +++ b/code/server/server.h @@ -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 diff --git a/code/server/sv_client.c b/code/server/sv_client.c index 4b1895e5..d45c49d7 100644 --- a/code/server/sv_client.c +++ b/code/server/sv_client.c @@ -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 } diff --git a/code/server/sv_init.c b/code/server/sv_init.c index 7332180b..ae9086ed 100644 --- a/code/server/sv_init.c +++ b/code/server/sv_init.c @@ -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 ); diff --git a/code/server/sv_main.c b/code/server/sv_main.c index 4169fd2e..993910fe 100644 --- a/code/server/sv_main.c +++ b/code/server/sv_main.c @@ -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 diff --git a/code/server/sv_snapshot.c b/code/server/sv_snapshot.c index 6e4f2907..2237abd3 100644 --- a/code/server/sv_snapshot.c +++ b/code/server/sv_snapshot.c @@ -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);