mirror of
https://github.com/ioquake/ioq3.git
synced 2024-11-10 07:11:46 +00:00
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:
parent
fe619680f8
commit
615b73288f
13 changed files with 167 additions and 240 deletions
80
Makefile
80
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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
// 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];
|
||||
for (i = 0; i < samples; i++) {
|
||||
const float flsamp = (float) sampbuffer[i];
|
||||
const float s = fabs(flsamp);
|
||||
voipPower += s * s;
|
||||
sampptr[i] = (int16_t) ((flsamp) * audioMult);
|
||||
sampbuffer[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
|
||||
|
||||
|
||||
|
|
|
@ -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,55 +761,45 @@ 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;
|
||||
numSamples = opus_decode(clc.opusDecoder[sender], encoded, packetsize, decoded + written, sizeof (decoded) - written, 0);
|
||||
|
||||
if ( numSamples <= 0 ) {
|
||||
Com_DPrintf("VoIP: Error decoding voip data from client #%d\n", sender);
|
||||
numSamples = 0;
|
||||
}
|
||||
MSG_ReadData(msg, encoded, len);
|
||||
|
||||
// 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;
|
||||
|
@ -817,11 +810,10 @@ void CL_ParseVoip ( msg_t *msg ) {
|
|||
if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); }
|
||||
#endif
|
||||
|
||||
written += clc.speexFrameSize;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
//=================================================
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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, //
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue