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 USE_INTERNAL_LIBS=1
endif endif
ifndef USE_INTERNAL_SPEEX
USE_INTERNAL_SPEEX=$(USE_INTERNAL_LIBS)
endif
ifndef USE_INTERNAL_OGG ifndef USE_INTERNAL_OGG
USE_INTERNAL_OGG=$(USE_INTERNAL_LIBS) USE_INTERNAL_OGG=$(USE_INTERNAL_LIBS)
endif endif
@ -258,7 +254,6 @@ NDIR=$(MOUNT_DIR)/null
UIDIR=$(MOUNT_DIR)/ui UIDIR=$(MOUNT_DIR)/ui
Q3UIDIR=$(MOUNT_DIR)/q3_ui Q3UIDIR=$(MOUNT_DIR)/q3_ui
JPDIR=$(MOUNT_DIR)/jpeg-8c JPDIR=$(MOUNT_DIR)/jpeg-8c
SPEEXDIR=$(MOUNT_DIR)/libspeex
OGGDIR=$(MOUNT_DIR)/libogg-1.3.1 OGGDIR=$(MOUNT_DIR)/libogg-1.3.1
VORBISDIR=$(MOUNT_DIR)/libvorbis-1.3.4 VORBISDIR=$(MOUNT_DIR)/libvorbis-1.3.4
OPUSDIR=$(MOUNT_DIR)/opus-1.1 OPUSDIR=$(MOUNT_DIR)/opus-1.1
@ -991,8 +986,18 @@ ifeq ($(USE_CURL),1)
endif endif
endif endif
ifeq ($(USE_VOIP),1)
CLIENT_CFLAGS += -DUSE_VOIP
SERVER_CFLAGS += -DUSE_VOIP
NEED_OPUS=1
endif
ifeq ($(USE_CODEC_OPUS),1) ifeq ($(USE_CODEC_OPUS),1)
CLIENT_CFLAGS += -DUSE_CODEC_OPUS CLIENT_CFLAGS += -DUSE_CODEC_OPUS
NEED_OPUS=1
endif
ifeq ($(NEED_OPUS),1)
ifeq ($(USE_INTERNAL_OPUS),1) ifeq ($(USE_INTERNAL_OPUS),1)
OPUS_CFLAGS = -DOPUS_BUILD -DHAVE_LRINTF -DFLOATING_POINT -DUSE_ALLOCA \ OPUS_CFLAGS = -DOPUS_BUILD -DHAVE_LRINTF -DFLOATING_POINT -DUSE_ALLOCA \
-I$(OPUSDIR)/include -I$(OPUSDIR)/celt -I$(OPUSDIR)/silk \ -I$(OPUSDIR)/include -I$(OPUSDIR)/celt -I$(OPUSDIR)/silk \
@ -1038,19 +1043,6 @@ ifeq ($(USE_MUMBLE),1)
CLIENT_CFLAGS += -DUSE_MUMBLE CLIENT_CFLAGS += -DUSE_MUMBLE
endif 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) ifeq ($(USE_INTERNAL_ZLIB),1)
ZLIB_CFLAGS = -DNO_GZIP -I$(ZDIR) ZLIB_CFLAGS = -DNO_GZIP -I$(ZDIR)
else else
@ -1828,53 +1820,7 @@ ifeq ($(ARCH),x86_64)
$(B)/client/ftola.o $(B)/client/ftola.o
endif endif
ifeq ($(USE_VOIP),1) ifeq ($(NEED_OPUS),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 ($(USE_INTERNAL_OPUS),1) ifeq ($(USE_INTERNAL_OPUS),1)
Q3OBJ += \ Q3OBJ += \
$(B)/client/opus/analysis.o \ $(B)/client/opus/analysis.o \
@ -2581,9 +2527,6 @@ $(B)/client/%.o: $(CMDIR)/%.c
$(B)/client/%.o: $(BLIBDIR)/%.c $(B)/client/%.o: $(BLIBDIR)/%.c
$(DO_BOT_CC) $(DO_BOT_CC)
$(B)/client/%.o: $(SPEEXDIR)/%.c
$(DO_CC)
$(B)/client/%.o: $(OGGDIR)/src/%.c $(B)/client/%.o: $(OGGDIR)/src/%.c
$(DO_CC) $(DO_CC)
@ -2874,7 +2817,6 @@ ifdef MINGW
USE_OPENAL_DLOPEN=$(USE_OPENAL_DLOPEN) \ USE_OPENAL_DLOPEN=$(USE_OPENAL_DLOPEN) \
USE_CURL_DLOPEN=$(USE_CURL_DLOPEN) \ USE_CURL_DLOPEN=$(USE_CURL_DLOPEN) \
USE_INTERNAL_OPUS=$(USE_INTERNAL_OPUS) \ USE_INTERNAL_OPUS=$(USE_INTERNAL_OPUS) \
USE_INTERNAL_SPEEX=$(USE_INTERNAL_SPEEX) \
USE_INTERNAL_ZLIB=$(USE_INTERNAL_ZLIB) \ USE_INTERNAL_ZLIB=$(USE_INTERNAL_ZLIB) \
USE_INTERNAL_JPEG=$(USE_INTERNAL_JPEG) USE_INTERNAL_JPEG=$(USE_INTERNAL_JPEG)
else else

View file

@ -905,37 +905,27 @@ void CL_FirstSnapshot( void ) {
#endif #endif
#ifdef USE_VOIP #ifdef USE_VOIP
if (!clc.speexInitialized) { if (!clc.voipCodecInitialized) {
int i; int i;
speex_bits_init(&clc.speexEncoderBits); int error;
speex_bits_reset(&clc.speexEncoderBits);
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, if ( error ) {
&clc.speexFrameSize); Com_DPrintf("VoIP: Error opus_encoder_create %d\n", error);
speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_SAMPLING_RATE, return;
&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);
for (i = 0; i < MAX_CLIENTS; i++) { for (i = 0; i < MAX_CLIENTS; i++) {
speex_bits_init(&clc.speexDecoderBits[i]); clc.opusDecoder[i] = opus_decoder_create(48000, 1, &error);
speex_bits_reset(&clc.speexDecoderBits[i]); if ( error ) {
clc.speexDecoder[i] = speex_decoder_init(&speex_nb_mode); Com_DPrintf("VoIP: Error opus_decoder_create(%d) %d\n", i, error);
return;
}
clc.voipIgnore[i] = qfalse; clc.voipIgnore[i] = qfalse;
clc.voipGain[i] = 1.0f; clc.voipGain[i] = 1.0f;
} }
clc.speexInitialized = qtrue; clc.voipCodecInitialized = qtrue;
clc.voipMuteAll = qfalse; clc.voipMuteAll = qfalse;
Cmd_AddCommand ("voip", CL_Voip_f); Cmd_AddCommand ("voip", CL_Voip_f);
Cvar_Set("cl_voipSendTarget", "spatial"); 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)) 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_WriteByte (&buf, clc.voipOutgoingGeneration);
MSG_WriteLong (&buf, clc.voipOutgoingSequence); MSG_WriteLong (&buf, clc.voipOutgoingSequence);
MSG_WriteByte (&buf, clc.voipOutgoingDataFrames); MSG_WriteByte (&buf, clc.voipOutgoingDataFrames);
@ -809,7 +809,7 @@ void CL_WritePacket( void ) {
MSG_Init (&fakemsg, fakedata, sizeof (fakedata)); MSG_Init (&fakemsg, fakedata, sizeof (fakedata));
MSG_Bitstream (&fakemsg); MSG_Bitstream (&fakemsg);
MSG_WriteLong (&fakemsg, clc.reliableAcknowledge); MSG_WriteLong (&fakemsg, clc.reliableAcknowledge);
MSG_WriteByte (&fakemsg, svc_voip); MSG_WriteByte (&fakemsg, svc_voipOpus);
MSG_WriteShort (&fakemsg, clc.clientNum); MSG_WriteShort (&fakemsg, clc.clientNum);
MSG_WriteByte (&fakemsg, clc.voipOutgoingGeneration); MSG_WriteByte (&fakemsg, clc.voipOutgoingGeneration);
MSG_WriteLong (&fakemsg, clc.voipOutgoingSequence); MSG_WriteLong (&fakemsg, clc.voipOutgoingSequence);

View file

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

View file

@ -34,7 +34,8 @@ char *svc_strings[256] = {
"svc_download", "svc_download",
"svc_snapshot", "svc_snapshot",
"svc_EOF", "svc_EOF",
"svc_voip", "svc_voipSpeex",
"svc_voipOpus",
}; };
void SHOWNET( msg_t *msg, char *s) { void SHOWNET( msg_t *msg, char *s) {
@ -359,8 +360,8 @@ void CL_SystemInfoChanged( void ) {
else else
#endif #endif
{ {
s = Info_ValueForKey( systemInfo, "sv_voip" ); s = Info_ValueForKey( systemInfo, "sv_voipProtocol" );
clc.voipEnabled = atoi(s); clc.voipEnabled = !Q_stricmp(s, "opus");
} }
#endif #endif
@ -680,13 +681,13 @@ static void CL_PlayVoip(int sender, int samplecnt, const byte *data, int flags)
{ {
if(flags & VOIP_DIRECT) 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); data, clc.voipGain[sender], -1);
} }
if(flags & VOIP_SPATIAL) 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); data, 1.0f, sender);
} }
} }
@ -699,8 +700,8 @@ A VoIP message has been received from the server
===================== =====================
*/ */
static static
void CL_ParseVoip ( msg_t *msg ) { void CL_ParseVoip ( msg_t *msg, qboolean ignoreData ) {
static short decoded[4096]; // !!! FIXME: don't hardcode. static short decoded[VOIP_MAX_PACKET_SAMPLES*4]; // !!! FIXME: don't hard code
const int sender = MSG_ReadShort(msg); const int sender = MSG_ReadShort(msg);
const int generation = MSG_ReadByte(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 frames = MSG_ReadByte(msg);
const int packetsize = MSG_ReadShort(msg); const int packetsize = MSG_ReadShort(msg);
const int flags = MSG_ReadBits(msg, VOIP_FLAGCNT); const int flags = MSG_ReadBits(msg, VOIP_FLAGCNT);
char encoded[1024]; unsigned char encoded[4000];
int numSamples;
int seqdiff; int seqdiff;
int written = 0; int written = 0;
int i; int i;
@ -738,14 +740,15 @@ void CL_ParseVoip ( msg_t *msg ) {
return; // overlarge packet, bail. return; // overlarge packet, bail.
} }
if (!clc.speexInitialized) { MSG_ReadData(msg, encoded, packetsize);
MSG_ReadData(msg, encoded, packetsize); // skip payload.
return; // can't handle VoIP without libspeex! 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) { } else if (sender >= MAX_CLIENTS) {
MSG_ReadData(msg, encoded, packetsize); // skip payload.
return; // bogus sender. return; // bogus sender.
} else if (CL_ShouldIgnoreVoipSender(sender)) { } else if (CL_ShouldIgnoreVoipSender(sender)) {
MSG_ReadData(msg, encoded, packetsize); // skip payload.
return; // Channel is muted, bail. 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. // This is a new "generation" ... a new recording started, reset the bits.
if (generation != clc.voipIncomingGeneration[sender]) { if (generation != clc.voipIncomingGeneration[sender]) {
Com_DPrintf("VoIP: new generation %d!\n", generation); 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; clc.voipIncomingGeneration[sender] = generation;
seqdiff = 0; seqdiff = 0;
} else if (seqdiff < 0) { // we're ahead of the sequence?! } else if (seqdiff < 0) { // we're ahead of the sequence?!
// This shouldn't happen unless the packet is corrupted or something. // This shouldn't happen unless the packet is corrupted or something.
Com_DPrintf("VoIP: misordered sequence! %d < %d!\n", Com_DPrintf("VoIP: misordered sequence! %d < %d!\n",
sequence, clc.voipIncomingSequence[sender]); sequence, clc.voipIncomingSequence[sender]);
// reset the bits just in case. // reset the decoder just in case.
speex_bits_reset(&clc.speexDecoderBits[sender]); opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE);
seqdiff = 0; 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. // just start over.
Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n", Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n",
seqdiff, sender); seqdiff, sender);
speex_bits_reset(&clc.speexDecoderBits[sender]); opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE);
seqdiff = 0; seqdiff = 0;
} }
if (seqdiff != 0) { if (seqdiff != 0) {
Com_DPrintf("VoIP: Dropped %d frames from client #%d\n", Com_DPrintf("VoIP: Dropped %d frames from client #%d\n",
seqdiff, sender); seqdiff, sender);
// tell speex that we're missing frames... // tell opus that we're missing frames...
for (i = 0; i < seqdiff; i++) { for (i = 0; i < seqdiff; i++) {
assert((written + clc.speexFrameSize) * 2 < sizeof (decoded)); assert((written + VOIP_MAX_PACKET_SAMPLES) * 2 < sizeof (decoded));
speex_decode_int(clc.speexDecoder[sender], NULL, decoded + written); numSamples = opus_decode(clc.opusDecoder[sender], NULL, VOIP_MAX_PACKET_SAMPLES * 2, decoded + written, sizeof (decoded) - written, 0);
written += clc.speexFrameSize; 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++) { numSamples = opus_decode(clc.opusDecoder[sender], encoded, packetsize, decoded + written, sizeof (decoded) - written, 0);
const int len = MSG_ReadByte(msg);
if (len < 0) {
Com_DPrintf("VoIP: Short packet!\n");
break;
}
MSG_ReadData(msg, encoded, len);
// shouldn't happen, but just in case... if ( numSamples <= 0 ) {
if ((written + clc.speexFrameSize) * 2 > sizeof (decoded)) { Com_DPrintf("VoIP: Error decoding voip data from client #%d\n", sender);
Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n", numSamples = 0;
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 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", Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
written * 2, written, i); written * 2, written, frames);
if(written > 0) if(written > 0)
CL_PlayVoip(sender, written, (const byte *) decoded, flags); CL_PlayVoip(sender, written, (const byte *) decoded, flags);
@ -924,9 +916,14 @@ void CL_ParseServerMessage( msg_t *msg ) {
case svc_download: case svc_download:
CL_ParseDownload( msg ); CL_ParseDownload( msg );
break; break;
case svc_voip: case svc_voipSpeex:
#ifdef USE_VOIP #ifdef USE_VOIP
CL_ParseVoip( msg ); CL_ParseVoip( msg, qtrue );
#endif
break;
case svc_voipOpus:
#ifdef USE_VOIP
CL_ParseVoip( msg, !clc.voipEnabled );
#endif #endif
break; break;
} }

View file

@ -35,8 +35,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#endif /* USE_CURL */ #endif /* USE_CURL */
#ifdef USE_VOIP #ifdef USE_VOIP
#include "speex/speex.h" #include <opus.h>
#include "speex/speex_preprocess.h"
#endif #endif
// file full of random crap that gets used to create cl_guid // file full of random crap that gets used to create cl_guid
@ -238,14 +237,11 @@ typedef struct {
#ifdef USE_VOIP #ifdef USE_VOIP
qboolean voipEnabled; qboolean voipEnabled;
qboolean speexInitialized; qboolean voipCodecInitialized;
int speexFrameSize;
int speexSampleRate;
// incoming data... // incoming data...
// !!! FIXME: convert from parallel arrays to array of a struct. // !!! FIXME: convert from parallel arrays to array of a struct.
SpeexBits speexDecoderBits[MAX_CLIENTS]; OpusDecoder *opusDecoder[MAX_CLIENTS];
void *speexDecoder[MAX_CLIENTS];
byte voipIncomingGeneration[MAX_CLIENTS]; byte voipIncomingGeneration[MAX_CLIENTS];
int voipIncomingSequence[MAX_CLIENTS]; int voipIncomingSequence[MAX_CLIENTS];
float voipGain[MAX_CLIENTS]; float voipGain[MAX_CLIENTS];
@ -257,9 +253,7 @@ typedef struct {
// then we are sending to clientnum i. // then we are sending to clientnum i.
uint8_t voipTargets[(MAX_CLIENTS + 7) / 8]; uint8_t voipTargets[(MAX_CLIENTS + 7) / 8];
uint8_t voipFlags; uint8_t voipFlags;
SpeexPreprocessState *speexPreprocessor; OpusEncoder *opusEncoder;
SpeexBits speexEncoderBits;
void *speexEncoder;
int voipOutgoingDataSize; int voipOutgoingDataSize;
int voipOutgoingDataFrames; int voipOutgoingDataFrames;
int voipOutgoingSequence; int voipOutgoingSequence;
@ -447,6 +441,13 @@ extern cvar_t *cl_voipGainDuringCapture;
extern cvar_t *cl_voipCaptureMult; extern cvar_t *cl_voipCaptureMult;
extern cvar_t *cl_voipShowMeter; extern cvar_t *cl_voipShowMeter;
extern cvar_t *cl_voip; 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 #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); 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"); 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 ) if( !alCaptureDevice && inputdevice )
{ {
Com_Printf( "Failed to open OpenAL Input device '%s', trying default.\n", 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", Com_Printf( "OpenAL capture device %s.\n",
(alCaptureDevice == NULL) ? "failed to open" : "opened"); (alCaptureDevice == NULL) ? "failed to open" : "opened");

View file

@ -300,7 +300,8 @@ enum svc_ops_e {
svc_EOF, svc_EOF,
// new commands, supported only by ioquake3 protocol but not legacy // 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, clc_EOF,
// new commands, supported only by ioquake3 protocol but not legacy // 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 len;
int sender; int sender;
int flags; int flags;
byte data[1024]; byte data[4000];
} voipServerPacket_t; } voipServerPacket_t;
#endif #endif
@ -299,6 +299,7 @@ extern int serverBansCount;
#ifdef USE_VOIP #ifdef USE_VOIP
extern cvar_t *sv_voip; extern cvar_t *sv_voip;
extern cvar_t *sv_voipProtocol;
#endif #endif

View file

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

View file

@ -656,8 +656,9 @@ void SV_Init (void)
sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM ); sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM );
sv_pure = Cvar_Get ("sv_pure", "1", CVAR_SYSTEMINFO ); sv_pure = Cvar_Get ("sv_pure", "1", CVAR_SYSTEMINFO );
#ifdef USE_VOIP #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); Cvar_CheckRange(sv_voip, 0, 1, qtrue);
sv_voipProtocol = Cvar_Get("sv_voipProtocol", sv_voip->integer ? "opus" : "", CVAR_SYSTEMINFO | CVAR_ROM );
#endif #endif
Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM ); Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM );
Cvar_Get ("sv_pakNames", "", 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 #ifdef USE_VOIP
cvar_t *sv_voip; cvar_t *sv_voip;
cvar_t *sv_voipProtocol;
#endif #endif
serverStatic_t svs; // persistant server info 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"))); Info_SetValueForKey(infostring, "g_needpass", va("%d", Cvar_VariableIntegerValue("g_needpass")));
#ifdef USE_VOIP #ifdef USE_VOIP
if (sv_voip->integer) { if (sv_voipProtocol->string && *sv_voipProtocol->string) {
Info_SetValueForKey( infostring, "voip", va("%i", sv_voip->integer ) ); Info_SetValueForKey( infostring, "voip", sv_voipProtocol->string );
} }
#endif #endif

View file

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