You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
5.1 KiB
234 lines
5.1 KiB
4 years ago
|
#include "voice.h"
|
||
|
|
||
|
wavdata_t *input_file;
|
||
|
fs_offset_t input_pos;
|
||
|
|
||
|
voice_state_t voice;
|
||
|
|
||
|
CVAR_DEFINE_AUTO( voice_enable, "1", FCVAR_ARCHIVE, "enable voice chat" );
|
||
|
CVAR_DEFINE_AUTO( voice_loopback, "0", 0, "loopback voice back to the speaker" );
|
||
|
CVAR_DEFINE_AUTO( voice_scale, "1.0", FCVAR_ARCHIVE, "incoming voice volume scale" );
|
||
|
CVAR_DEFINE_AUTO( voice_inputfromfile, "0", 0, "input voice from voice_input.wav" );
|
||
|
|
||
|
void Voice_RegisterCvars( void )
|
||
|
{
|
||
|
Cvar_RegisterVariable( &voice_enable );
|
||
|
Cvar_RegisterVariable( &voice_loopback );
|
||
|
Cvar_RegisterVariable( &voice_scale );
|
||
|
Cvar_RegisterVariable( &voice_inputfromfile );
|
||
|
}
|
||
|
|
||
|
static void Voice_Status( int entindex, qboolean bTalking )
|
||
|
{
|
||
|
clgame.dllFuncs.pfnVoiceStatus( entindex, bTalking );
|
||
|
}
|
||
|
|
||
|
// parameters currently unused
|
||
|
qboolean Voice_Init( const char *pszCodecName, int quality )
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
if ( !voice_enable.value )
|
||
|
return false;
|
||
|
|
||
|
Voice_DeInit();
|
||
|
|
||
|
voice.was_init = true;
|
||
|
|
||
|
voice.channels = 1;
|
||
|
voice.width = 2;
|
||
|
voice.samplerate = SOUND_48k;
|
||
|
voice.frame_size = voice.channels * ( (float)voice.samplerate / ( 1000.0f / 20.0f ) ) * voice.width;
|
||
|
|
||
|
if ( !VoiceCapture_Init() )
|
||
|
{
|
||
|
Voice_DeInit();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
voice.encoder = opus_encoder_create( voice.samplerate, voice.channels, OPUS_APPLICATION_VOIP, &err );
|
||
|
voice.decoder = opus_decoder_create( voice.samplerate, voice.channels, &err );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void Voice_DeInit( void )
|
||
|
{
|
||
|
if ( !voice.was_init )
|
||
|
return;
|
||
|
|
||
|
Voice_RecordStop();
|
||
|
|
||
|
opus_encoder_destroy( voice.encoder );
|
||
|
opus_decoder_destroy( voice.decoder );
|
||
|
|
||
|
voice.was_init = false;
|
||
|
}
|
||
|
|
||
|
uint Voice_GetCompressedData( byte *out, uint maxsize, uint *frames )
|
||
|
{
|
||
|
uint ofs, size = 0;
|
||
|
|
||
|
if ( input_file )
|
||
|
{
|
||
|
uint numbytes;
|
||
|
double time;
|
||
|
|
||
|
time = Sys_DoubleTime();
|
||
|
|
||
|
numbytes = ( time - voice.start_time ) * voice.samplerate;
|
||
|
numbytes = Q_min( numbytes, input_file->size - input_pos );
|
||
|
numbytes = Q_min( numbytes, sizeof( voice.buffer ) - voice.buffer_pos );
|
||
|
|
||
|
memcpy( voice.buffer + voice.buffer_pos, input_file->buffer + input_pos, numbytes );
|
||
|
voice.buffer_pos += numbytes;
|
||
|
input_pos += numbytes;
|
||
|
|
||
|
voice.start_time = time;
|
||
|
}
|
||
|
|
||
|
for ( ofs = 0; voice.buffer_pos - ofs >= voice.frame_size && ofs <= voice.buffer_pos; ofs += voice.frame_size )
|
||
|
{
|
||
|
int bytes;
|
||
|
|
||
|
bytes = opus_encode( voice.encoder, (const opus_int16*)(voice.buffer + ofs), voice.frame_size / voice.width, out + size, maxsize );
|
||
|
memmove( voice.buffer, voice.buffer + voice.frame_size, sizeof( voice.buffer ) - voice.frame_size );
|
||
|
voice.buffer_pos -= voice.frame_size;
|
||
|
|
||
|
if ( bytes > 0 )
|
||
|
{
|
||
|
size += bytes;
|
||
|
(*frames)++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
void Voice_Idle( float frametime )
|
||
|
{
|
||
|
if ( !voice_enable.value )
|
||
|
{
|
||
|
Voice_DeInit();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( voice.talking_ack )
|
||
|
{
|
||
|
voice.talking_timeout += frametime;
|
||
|
|
||
|
if ( voice.talking_timeout > 0.2f )
|
||
|
{
|
||
|
voice.talking_ack = false;
|
||
|
Voice_Status( -2, false );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
qboolean Voice_IsRecording( void )
|
||
|
{
|
||
|
return voice.is_recording;
|
||
|
}
|
||
|
|
||
|
void Voice_RecordStop( void )
|
||
|
{
|
||
|
if ( input_file )
|
||
|
{
|
||
|
FS_FreeSound( input_file );
|
||
|
input_file = NULL;
|
||
|
}
|
||
|
|
||
|
voice.buffer_pos = 0;
|
||
|
memset( voice.buffer, 0, sizeof( voice.buffer ) );
|
||
|
|
||
|
if ( Voice_IsRecording() )
|
||
|
Voice_Status( -1, false );
|
||
|
|
||
|
VoiceCapture_RecordStop();
|
||
|
|
||
|
voice.is_recording = false;
|
||
|
}
|
||
|
|
||
|
void Voice_RecordStart( void )
|
||
|
{
|
||
|
Voice_RecordStop();
|
||
|
|
||
|
if ( voice_inputfromfile.value )
|
||
|
{
|
||
|
input_file = FS_LoadSound( "voice_input.wav", NULL, 0 );
|
||
|
|
||
|
if ( input_file )
|
||
|
{
|
||
|
Sound_Process( &input_file, voice.samplerate, voice.width, SOUND_RESAMPLE );
|
||
|
input_pos = 0;
|
||
|
|
||
|
voice.start_time = Sys_DoubleTime();
|
||
|
voice.is_recording = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FS_FreeSound( input_file );
|
||
|
input_file = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !Voice_IsRecording() )
|
||
|
voice.is_recording = VoiceCapture_RecordStart();
|
||
|
|
||
|
if ( Voice_IsRecording() )
|
||
|
Voice_Status( -1, true );
|
||
|
}
|
||
|
|
||
|
void Voice_AddIncomingData( int ent, byte *data, uint size, uint frames )
|
||
|
{
|
||
|
byte decompressed[MAX_RAW_SAMPLES];
|
||
|
int samples;
|
||
|
|
||
|
samples = opus_decode( voice.decoder, (const byte*)data, size, (short *)decompressed, voice.frame_size / voice.width * frames, false );
|
||
|
|
||
|
if ( samples > 0 )
|
||
|
Voice_StartChannel( samples, decompressed, ent );
|
||
|
}
|
||
|
|
||
|
void CL_AddVoiceToDatagram( void )
|
||
|
{
|
||
|
uint size, frames = 0;
|
||
|
byte data[MAX_RAW_SAMPLES];
|
||
|
|
||
|
if ( cls.state != ca_active || !Voice_IsRecording() )
|
||
|
return;
|
||
|
|
||
|
size = Voice_GetCompressedData( data, sizeof( data ), &frames );
|
||
|
|
||
|
if ( size > 0 && MSG_GetNumBytesLeft( &cls.datagram ) >= size + 32 )
|
||
|
{
|
||
|
MSG_BeginClientCmd( &cls.datagram, clc_voicedata );
|
||
|
MSG_WriteByte( &cls.datagram, Voice_GetLoopback() );
|
||
|
MSG_WriteByte( &cls.datagram, frames );
|
||
|
MSG_WriteShort( &cls.datagram, size );
|
||
|
MSG_WriteBytes( &cls.datagram, data, size );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
qboolean Voice_GetLoopback( void )
|
||
|
{
|
||
|
return voice_loopback.value;
|
||
|
}
|
||
|
|
||
|
void Voice_LocalPlayerTalkingAck( void )
|
||
|
{
|
||
|
if ( !voice.talking_ack )
|
||
|
{
|
||
|
Voice_Status( -2, true );
|
||
|
}
|
||
|
|
||
|
voice.talking_ack = true;
|
||
|
voice.talking_timeout = 0.0f;
|
||
|
}
|
||
|
|
||
|
void Voice_StartChannel( uint samples, byte *data, int entnum )
|
||
|
{
|
||
|
SND_ForceInitMouth( entnum );
|
||
|
Voice_Status( entnum, true );
|
||
|
S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, voice.channels, data, 128.0f * voice_scale.value );
|
||
|
}
|