Velaron
4 years ago
committed by
Alibek Omarov
20 changed files with 664 additions and 11 deletions
@ -0,0 +1,234 @@ |
|||||||
|
#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 ); |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
#ifndef VOICE_H |
||||||
|
#define VOICE_H |
||||||
|
|
||||||
|
#include <opus.h> |
||||||
|
|
||||||
|
#include "common.h" |
||||||
|
#include "client.h" |
||||||
|
#include "sound.h" |
||||||
|
#include "soundlib/soundlib.h" |
||||||
|
#include "library.h" |
||||||
|
|
||||||
|
#define SAMPLES_PER_SEC ( SOUND_48k / BYTES_PER_SAMPLE ) |
||||||
|
|
||||||
|
extern convar_t voice_scale; |
||||||
|
|
||||||
|
typedef struct voice_state_s |
||||||
|
{ |
||||||
|
qboolean was_init; |
||||||
|
qboolean is_recording; |
||||||
|
float start_time; |
||||||
|
qboolean talking_ack; |
||||||
|
float talking_timeout; |
||||||
|
|
||||||
|
// opus stuff
|
||||||
|
OpusEncoder *encoder; |
||||||
|
OpusDecoder *decoder; |
||||||
|
|
||||||
|
// audio info
|
||||||
|
uint channels; |
||||||
|
uint width; |
||||||
|
uint samplerate; |
||||||
|
uint frame_size; |
||||||
|
|
||||||
|
// input buffer
|
||||||
|
byte buffer[MAX_RAW_SAMPLES]; |
||||||
|
fs_offset_t buffer_pos; |
||||||
|
} voice_state_t; |
||||||
|
|
||||||
|
extern voice_state_t voice; |
||||||
|
|
||||||
|
void CL_AddVoiceToDatagram( void ); |
||||||
|
|
||||||
|
void Voice_RegisterCvars( void ); |
||||||
|
qboolean Voice_Init( const char *pszCodecName, int quality ); |
||||||
|
void Voice_DeInit( void ); |
||||||
|
uint Voice_GetCompressedData( byte *out, uint maxsize, uint *frames ); |
||||||
|
void Voice_Idle( float frametime ); |
||||||
|
qboolean Voice_IsRecording( void ); |
||||||
|
void Voice_RecordStop( void ); |
||||||
|
void Voice_RecordStart( void ); |
||||||
|
void Voice_AddIncomingData( int ent, byte *data, uint size, uint frames ); |
||||||
|
qboolean Voice_GetLoopback( void ); |
||||||
|
void Voice_LocalPlayerTalkingAck( void ); |
||||||
|
void Voice_StartChannel( uint samples, byte *data, int entnum ); |
||||||
|
|
||||||
|
#endif // VOICE_H
|
@ -0,0 +1,42 @@ |
|||||||
|
# encoding: utf-8 |
||||||
|
|
||||||
|
import os |
||||||
|
|
||||||
|
def options(opt): |
||||||
|
pass |
||||||
|
|
||||||
|
def configure(conf): |
||||||
|
path = conf.path.find_dir('3rdparty/opus') |
||||||
|
conf.env.LIB_OPUS = ['opus'] |
||||||
|
conf.env.INCLUDES_OPUS = [path.find_dir('include/').abspath()] |
||||||
|
|
||||||
|
def build(bld): |
||||||
|
path = bld.path.find_dir('3rdparty/opus') |
||||||
|
|
||||||
|
sources = path.ant_glob([ |
||||||
|
'src/*.c', |
||||||
|
'celt/*.c', |
||||||
|
'silk/*.c', |
||||||
|
'silk/float/*.c']) |
||||||
|
|
||||||
|
includes = [ |
||||||
|
path.find_dir('include/'), |
||||||
|
path.find_dir('celt/'), |
||||||
|
path.find_dir('silk/'), |
||||||
|
path.find_dir('silk/float/') |
||||||
|
] |
||||||
|
|
||||||
|
defines = [ |
||||||
|
'USE_ALLOCA', |
||||||
|
'OPUS_BUILD', |
||||||
|
'PACKAGE_VERSION="1.3.1"' |
||||||
|
] |
||||||
|
|
||||||
|
bld.stlib( |
||||||
|
source = sources, |
||||||
|
target = 'opus', |
||||||
|
features = 'c', |
||||||
|
includes = includes, |
||||||
|
defines = defines, |
||||||
|
subsystem = bld.env.MSVC_SUBSYSTEM |
||||||
|
) |
Loading…
Reference in new issue