|
|
@ -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 ); |
|
|
|
|
|
|
|
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 ); |
|
|
|
Voice_GetFrameSize |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
========================= |
|
|
|
|
|
|
|
*/ |
|
|
|
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 ) |
|
|
|
/*
|
|
|
|
|
|
|
|
========================= |
|
|
|
|
|
|
|
Voice_ShutdownOpusDecoder |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
========================= |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
static void Voice_ShutdownOpusDecoder( void ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if( !voice.initialized ) |
|
|
|
if( voice.decoder ) |
|
|
|
return; |
|
|
|
{ |
|
|
|
|
|
|
|
opus_decoder_destroy( voice.decoder ); |
|
|
|
|
|
|
|
voice.decoder = NULL; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Voice_RecordStop(); |
|
|
|
/*
|
|
|
|
|
|
|
|
========================= |
|
|
|
|
|
|
|
Voice_ShutdownOpusEncoder |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
========================= |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
static void Voice_ShutdownOpusEncoder( void ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if( voice.encoder ) |
|
|
|
|
|
|
|
{ |
|
|
|
opus_encoder_destroy( voice.encoder ); |
|
|
|
opus_encoder_destroy( voice.encoder ); |
|
|
|
opus_decoder_destroy( voice.decoder ); |
|
|
|
voice.encoder = NULL; |
|
|
|
|
|
|
|
} |
|
|
|
voice.initialized = false; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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++ ) { |
|
|
|
|
|
|
|
|
|
|
|
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_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; |
|
|
|
|
|
|
|
} |
|
|
|