Browse Source

engine: client: voice: refactor, fix issues when missing mic disables voice chat, fix few possible crashes and memory leaks

pull/2/head
Alibek Omarov 2 years ago
parent
commit
0b50678912
  1. 2
      engine/client/cl_parse.c
  2. 397
      engine/client/voice.c
  3. 6
      engine/client/voice.h

2
engine/client/cl_parse.c

@ -1715,7 +1715,7 @@ void CL_ParseVoiceData( sizebuf_t *msg )
return; return;
if( idx == cl.playernum + 1 ) if( idx == cl.playernum + 1 )
Voice_StatusAck( &voice.local, VOICE_LOCALPLAYER_INDEX ); Voice_StatusAck( &voice.local, VOICE_LOOPBACK_INDEX );
else else
Voice_StatusAck( &voice.players_status[idx], idx ); Voice_StatusAck( &voice.players_status[idx], idx );

397
engine/client/voice.c

@ -31,6 +31,20 @@ CVAR_DEFINE_AUTO( voice_avggain, "0.5", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "automat
CVAR_DEFINE_AUTO( voice_maxgain, "5.0", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "automatic voice gain control (maximum)" ); CVAR_DEFINE_AUTO( voice_maxgain, "5.0", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "automatic voice gain control (maximum)" );
CVAR_DEFINE_AUTO( voice_inputfromfile, "0", FCVAR_PRIVILEGED, "input voice from voice_input.wav" ); CVAR_DEFINE_AUTO( voice_inputfromfile, "0", FCVAR_PRIVILEGED, "input voice from voice_input.wav" );
/*
===============================================================================
OPUS INTEGRATION
===============================================================================
*/
/*
=========================
Voice_GetBandwithTypeName
=========================
*/
static const char* Voice_GetBandwidthTypeName( int bandwidthType ) static const char* Voice_GetBandwidthTypeName( int bandwidthType )
{ {
switch( bandwidthType ) switch( bandwidthType )
@ -44,6 +58,12 @@ static const char* Voice_GetBandwidthTypeName( int bandwidthType )
} }
} }
/*
=========================
Voice_CodecInfo_f
=========================
*/
static void Voice_CodecInfo_f( void ) static void Voice_CodecInfo_f( void )
{ {
int encoderComplexity; int encoderComplexity;
@ -63,31 +83,26 @@ static void Voice_CodecInfo_f( void )
Con_Printf( "Encoder:\n" ); Con_Printf( "Encoder:\n" );
Con_Printf( " Bitrate: %.3f kbps\n", encoderBitrate / 1000.0f ); Con_Printf( " Bitrate: %.3f kbps\n", encoderBitrate / 1000.0f );
Con_Printf( " Complexity: %d\n", encoderComplexity ); Con_Printf( " Complexity: %d\n", encoderComplexity );
Con_Printf( " Bandwidth: %s", Voice_GetBandwidthTypeName( encoderBandwidthType )); Con_Printf( " Bandwidth: %s\n", Voice_GetBandwidthTypeName( encoderBandwidthType ));
Con_Printf( "\n" );
} }
void Voice_RegisterCvars( void ) /*
{ =========================
Cvar_RegisterVariable( &voice_enable ); Voice_GetFrameSize
Cvar_RegisterVariable( &voice_loopback );
Cvar_RegisterVariable( &voice_scale );
Cvar_RegisterVariable( &voice_avggain );
Cvar_RegisterVariable( &voice_maxgain );
Cvar_RegisterVariable( &voice_inputfromfile );
Cmd_AddClientCommand( "voice_codecinfo", Voice_CodecInfo_f );
}
static void Voice_Status( int entindex, qboolean bTalking )
{
clgame.dllFuncs.pfnVoiceStatus( entindex, bTalking );
}
=========================
*/
static uint Voice_GetFrameSize( float durationMsec ) static uint Voice_GetFrameSize( float durationMsec )
{ {
return voice.channels * voice.width * (( float )voice.samplerate / ( 1000.0f / durationMsec )); return voice.channels * voice.width * (( float )voice.samplerate / ( 1000.0f / durationMsec ));
} }
/*
=========================
Voice_ApplyGainAdjust
=========================
*/
static void Voice_ApplyGainAdjust( opus_int16 *samples, int count ) static void Voice_ApplyGainAdjust( opus_int16 *samples, int count )
{ {
float gain, modifiedMax; float gain, modifiedMax;
@ -129,35 +144,44 @@ static void Voice_ApplyGainAdjust( opus_int16 *samples, int count )
} }
} }
// parameters currently unused /*
qboolean Voice_Init( const char *pszCodecName, int quality ) =========================
Voice_InitOpusDecoder
=========================
*/
static qboolean Voice_InitOpusDecoder( void )
{ {
int err; int err;
voice.decoder = opus_decoder_create( voice.samplerate, voice.channels, &err );
if( !voice_enable.value ) if( !voice.decoder )
{
Con_Printf( S_ERROR "Can't create Opus encoder: %s", opus_strerror( err ));
return false; return false;
}
Voice_DeInit(); return true;
}
voice.initialized = false;
voice.channels = 1;
voice.width = 2;
voice.samplerate = SOUND_48k;
voice.frame_size = Voice_GetFrameSize( 40.0f );
voice.autogain.block_size = 128;
if( !VoiceCapture_Init() ) /*
{ =========================
Voice_DeInit(); Voice_InitOpusEncoder
return voice.initialized;
}
voice.encoder = opus_encoder_create( voice.samplerate, voice.channels, OPUS_APPLICATION_VOIP, &err ); =========================
*/
static qboolean Voice_InitOpusEncoder( int quality )
{
int err;
int app = quality == 5 ? OPUS_APPLICATION_AUDIO : OPUS_APPLICATION_VOIP;
if( err != OPUS_OK ) voice.encoder = opus_encoder_create( voice.samplerate, voice.channels, app, &err );
return voice.initialized;
voice.decoder = opus_decoder_create( voice.samplerate, voice.channels, &err ); if( !voice.encoder )
{
Con_Printf( S_ERROR "Can't create Opus encoder: %s", opus_strerror( err ));
return false;
}
switch( quality ) switch( quality )
{ {
@ -183,31 +207,46 @@ qboolean Voice_Init( const char *pszCodecName, int quality )
break; break;
} }
if (quality == 5) { return true;
opus_encoder_ctl( voice.encoder, OPUS_SET_APPLICATION( OPUS_APPLICATION_AUDIO ));
}
else {
opus_encoder_ctl( voice.encoder, OPUS_SET_APPLICATION( OPUS_APPLICATION_VOIP ));
}
voice.initialized = (err == OPUS_OK);
return voice.initialized;
} }
void Voice_DeInit( void ) /*
{ =========================
if( !voice.initialized ) Voice_ShutdownOpusDecoder
return;
Voice_RecordStop(); =========================
*/
static void Voice_ShutdownOpusDecoder( void )
{
if( voice.decoder )
{
opus_decoder_destroy( voice.decoder );
voice.decoder = NULL;
}
}
opus_encoder_destroy( voice.encoder ); /*
opus_decoder_destroy( voice.decoder ); =========================
Voice_ShutdownOpusEncoder
voice.initialized = false; =========================
*/
static void Voice_ShutdownOpusEncoder( void )
{
if( voice.encoder )
{
opus_encoder_destroy( voice.encoder );
voice.encoder = NULL;
}
} }
static uint Voice_GetCompressedData( byte *out, uint maxsize, uint *frames ) /*
=========================
Voice_GetOpusCompressedData
=========================
*/
static uint Voice_GetOpusCompressedData( byte *out, uint maxsize, uint *frames )
{ {
uint ofs, size = 0; uint ofs, size = 0;
@ -250,6 +289,35 @@ static uint Voice_GetCompressedData( byte *out, uint maxsize, uint *frames )
return size; return size;
} }
/*
===============================================================================
VOICE CHAT INTEGRATION
===============================================================================
*/
/*
=========================
Voice_Status
Notify user dll aboit voice transmission
=========================
*/
static void Voice_Status( int entindex, qboolean bTalking )
{
if( cls.state == ca_active && clgame.dllFuncs.pfnVoiceStatus )
clgame.dllFuncs.pfnVoiceStatus( entindex, bTalking );
}
/*
=========================
Voice_StatusTimeout
Waits few milliseconds and if there was no
voice transmission, sends notification
=========================
*/
static void Voice_StatusTimeout( voice_status_t *status, int entindex, double frametime ) static void Voice_StatusTimeout( voice_status_t *status, int entindex, double frametime )
{ {
if( status->talking_ack ) if( status->talking_ack )
@ -263,6 +331,14 @@ static void Voice_StatusTimeout( voice_status_t *status, int entindex, double fr
} }
} }
/*
=========================
Voice_StatusAck
Sends notification to user dll and
zeroes timeouts for this client
=========================
*/
void Voice_StatusAck( voice_status_t *status, int playerIndex ) void Voice_StatusAck( voice_status_t *status, int playerIndex )
{ {
if( !status->talking_ack ) if( !status->talking_ack )
@ -272,28 +348,23 @@ void Voice_StatusAck( voice_status_t *status, int playerIndex )
status->talking_timeout = 0.0; status->talking_timeout = 0.0;
} }
void Voice_Idle( double frametime ) /*
{ =========================
int i; Voice_IsRecording
if( !voice_enable.value )
{
Voice_DeInit();
return;
}
// update local player status first
Voice_StatusTimeout( &voice.local, VOICE_LOCALPLAYER_INDEX, frametime );
for( i = 0; i < 32; i++ )
Voice_StatusTimeout( &voice.players_status[i], i, frametime );
}
=========================
*/
qboolean Voice_IsRecording( void ) qboolean Voice_IsRecording( void )
{ {
return voice.is_recording; return voice.is_recording;
} }
/*
=========================
Voice_RecordStop
=========================
*/
void Voice_RecordStop( void ) void Voice_RecordStop( void )
{ {
if( input_file ) if( input_file )
@ -303,16 +374,22 @@ void Voice_RecordStop( void )
} }
voice.input_buffer_pos = 0; voice.input_buffer_pos = 0;
memset( voice.input_buffer, 0, sizeof( voice.input_buffer ) ); memset( voice.input_buffer, 0, sizeof( voice.input_buffer ));
if( Voice_IsRecording() ) if( Voice_IsRecording( ))
Voice_Status( -1, false ); Voice_Status( VOICE_LOCALCLIENT_INDEX, false );
VoiceCapture_RecordStop(); VoiceCapture_RecordStop();
voice.is_recording = false; voice.is_recording = false;
} }
/*
=========================
Voice_RecordStart
=========================
*/
void Voice_RecordStart( void ) void Voice_RecordStart( void )
{ {
Voice_RecordStop(); Voice_RecordStop();
@ -340,30 +417,64 @@ void Voice_RecordStart( void )
voice.is_recording = VoiceCapture_RecordStart(); voice.is_recording = VoiceCapture_RecordStart();
if( Voice_IsRecording() ) if( Voice_IsRecording() )
Voice_Status( -1, true ); Voice_Status( VOICE_LOCALCLIENT_INDEX, true );
} }
/*
=========================
Voice_Disconnect
We're disconnected from server
stop recording and notify user dlls
=========================
*/
void Voice_Disconnect( void ) void Voice_Disconnect( void )
{ {
int i; int i;
Voice_RecordStop(); Voice_RecordStop();
for( i = 0; i <= 32; i++ ) {
Voice_Status( i, false ); if( voice.local.talking_ack )
{
Voice_Status( VOICE_LOOPBACK_INDEX, false );
voice.local.talking_ack = false;
}
for( i = 0; i < MAX_CLIENTS; i++ )
{
if( voice.players_status[i].talking_ack )
{
Voice_Status( i, false );
voice.players_status[i].talking_ack = false;
}
} }
} }
/*
=========================
Voice_StartChannel
Feed the decoded data to engine sound subsystem
=========================
*/
static void Voice_StartChannel( uint samples, byte *data, int entnum ) static void Voice_StartChannel( uint samples, byte *data, int entnum )
{ {
SND_ForceInitMouth( entnum ); SND_ForceInitMouth( entnum );
S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, voice.channels, data, 255 ); S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, voice.channels, data, 255 );
} }
/*
=========================
Voice_AddIncomingData
Received encoded voice data, decode it
=========================
*/
void Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames ) void Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames )
{ {
int samples; int samples;
if( !voice.initialized ) if( !voice.decoder )
return; return;
samples = opus_decode( voice.decoder, data, size, (short *)voice.decompress_buffer, voice.frame_size / voice.width * frames, false ); samples = opus_decode( voice.decoder, data, size, (short *)voice.decompress_buffer, voice.frame_size / voice.width * frames, false );
@ -372,14 +483,21 @@ void Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames )
Voice_StartChannel( samples, voice.decompress_buffer, ent ); Voice_StartChannel( samples, voice.decompress_buffer, ent );
} }
/*
=========================
CL_AddVoiceToDatagram
Encode our voice data and send it to server
=========================
*/
void CL_AddVoiceToDatagram( void ) void CL_AddVoiceToDatagram( void )
{ {
uint size, frames = 0; uint size, frames = 0;
if( cls.state != ca_active || !Voice_IsRecording() ) if( cls.state != ca_active || !Voice_IsRecording() || !voice.encoder )
return; return;
size = Voice_GetCompressedData( voice.output_buffer, sizeof( voice.output_buffer ), &frames ); size = Voice_GetOpusCompressedData( voice.output_buffer, sizeof( voice.output_buffer ), &frames );
if( size > 0 && MSG_GetNumBytesLeft( &cls.datagram ) >= size + 32 ) if( size > 0 && MSG_GetNumBytesLeft( &cls.datagram ) >= size + 32 )
{ {
@ -390,3 +508,122 @@ void CL_AddVoiceToDatagram( void )
MSG_WriteBytes( &cls.datagram, voice.output_buffer, size ); MSG_WriteBytes( &cls.datagram, voice.output_buffer, size );
} }
} }
/*
=========================
Voice_RegisterCvars
Register voice related cvars and commands
=========================
*/
void Voice_RegisterCvars( void )
{
Cvar_RegisterVariable( &voice_enable );
Cvar_RegisterVariable( &voice_loopback );
Cvar_RegisterVariable( &voice_scale );
Cvar_RegisterVariable( &voice_avggain );
Cvar_RegisterVariable( &voice_maxgain );
Cvar_RegisterVariable( &voice_inputfromfile );
Cmd_AddClientCommand( "voice_codecinfo", Voice_CodecInfo_f );
}
/*
=========================
Voice_Shutdown
Completely shutdown the voice subsystem
=========================
*/
static void Voice_Shutdown( void )
{
int i;
Voice_RecordStop();
Voice_ShutdownOpusEncoder();
Voice_ShutdownOpusDecoder();
VoiceCapture_Shutdown();
if( voice.local.talking_ack )
Voice_Status( VOICE_LOOPBACK_INDEX, false );
for( i = 0; i < MAX_CLIENTS; i++ )
{
if( voice.players_status[i].talking_ack )
Voice_Status( i, false );
}
memset( &voice, 0, sizeof( voice ));
}
/*
=========================
Voice_Idle
Run timeout for all clients
=========================
*/
void Voice_Idle( double frametime )
{
int i;
if( !voice_enable.value )
{
Voice_Shutdown();
return;
}
// update local player status first
Voice_StatusTimeout( &voice.local, VOICE_LOOPBACK_INDEX, frametime );
for( i = 0; i < MAX_CLIENTS; i++ )
Voice_StatusTimeout( &voice.players_status[i], i, frametime );
}
/*
=========================
Voice_Init
Initialize the voice subsystem
=========================
*/
qboolean Voice_Init( const char *pszCodecName, int quality )
{
if( !voice_enable.value )
return false;
Voice_Shutdown();
if( Q_strcmp( pszCodecName, "opus" ))
{
Con_Printf( S_ERROR "Server requested unsupported codec: %s", pszCodecName );
return false;
}
voice.initialized = false;
voice.channels = 1;
voice.width = 2;
voice.samplerate = SOUND_48k;
voice.frame_size = Voice_GetFrameSize( 40.0f );
voice.autogain.block_size = 128;
if( !Voice_InitOpusDecoder( ))
{
// no reason to init encoder and open audio device
// if we can't hear other players
Con_Printf( S_ERROR "Voice chat disabled.\n" );
Voice_Shutdown();
return false;
}
// we can hear others players, so it's fine to fail now
voice.initialized = true;
if( !Voice_InitOpusEncoder( quality ) || !VoiceCapture_Init() )
{
Voice_ShutdownOpusEncoder();
Con_Printf( S_WARN "Other players will not be able to hear you.\n" );
return true;
}
return true;
}

6
engine/client/voice.h

@ -20,12 +20,11 @@ GNU General Public License for more details.
#include "protocol.h" // MAX_CLIENTS #include "protocol.h" // MAX_CLIENTS
#include "sound.h" #include "sound.h"
extern convar_t voice_scale;
typedef struct OpusDecoder OpusDecoder; typedef struct OpusDecoder OpusDecoder;
typedef struct OpusEncoder OpusEncoder; typedef struct OpusEncoder OpusEncoder;
#define VOICE_LOCALPLAYER_INDEX (-2) #define VOICE_LOOPBACK_INDEX (-2)
#define VOICE_LOCALCLIENT_INDEX (-1)
typedef struct voice_status_s typedef struct voice_status_s
{ {
@ -73,7 +72,6 @@ void CL_AddVoiceToDatagram( void );
void Voice_RegisterCvars( void ); void Voice_RegisterCvars( void );
qboolean Voice_Init( const char *pszCodecName, int quality ); qboolean Voice_Init( const char *pszCodecName, int quality );
void Voice_DeInit( void );
void Voice_Idle( double frametime ); void Voice_Idle( double frametime );
qboolean Voice_IsRecording( void ); qboolean Voice_IsRecording( void );
void Voice_RecordStop( void ); void Voice_RecordStop( void );

Loading…
Cancel
Save