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
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue