mirror of
https://github.com/YGGverse/xash3d-fwgs.git
synced 2025-01-30 08:44:31 +00:00
engine: voice support
This commit is contained in:
parent
9d4fe707bb
commit
2b9e050f57
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -13,3 +13,6 @@
|
||||
[submodule "vgui_support"]
|
||||
path = vgui_support
|
||||
url = https://github.com/FWGS/vgui_support
|
||||
[submodule "opus"]
|
||||
path = 3rdparty/opus
|
||||
url = https://github.com/xiph/opus
|
||||
|
1
3rdparty/opus
vendored
Submodule
1
3rdparty/opus
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit dfd6c88aaa54a03a61434c413e30c217eb98f1d5
|
@ -864,6 +864,9 @@ void CL_WritePacket( void )
|
||||
cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].sendsize = MSG_GetNumBytesWritten( &buf );
|
||||
cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].heldback = false;
|
||||
|
||||
// send voice data to the server
|
||||
CL_AddVoiceToDatagram();
|
||||
|
||||
// composite the rest of the datagram..
|
||||
if( MSG_GetNumBitsWritten( &cls.datagram ) <= MSG_GetNumBitsLeft( &buf ))
|
||||
MSG_WriteBits( &buf, MSG_GetData( &cls.datagram ), MSG_GetNumBitsWritten( &cls.datagram ));
|
||||
@ -2823,6 +2826,8 @@ void CL_InitLocal( void )
|
||||
Cvar_RegisterVariable( &cl_logocolor );
|
||||
Cvar_RegisterVariable( &cl_test_bandwidth );
|
||||
|
||||
Voice_RegisterCvars();
|
||||
|
||||
// register our variables
|
||||
cl_crosshair = Cvar_Get( "crosshair", "1", FCVAR_ARCHIVE, "show weapon chrosshair" );
|
||||
cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0, "disable delta-compression for server messages" );
|
||||
@ -3024,8 +3029,8 @@ void Host_ClientFrame( void )
|
||||
// a new portion updates from server
|
||||
CL_RedoPrediction ();
|
||||
|
||||
// TODO: implement
|
||||
// Voice_Idle( host.frametime );
|
||||
// update voice
|
||||
Voice_Idle( host.frametime );
|
||||
|
||||
// emit visible entities
|
||||
CL_EmitEntities ();
|
||||
@ -3079,6 +3084,7 @@ void CL_Init( void )
|
||||
|
||||
VID_Init(); // init video
|
||||
S_Init(); // init sound
|
||||
Voice_Init( "opus", 0 ); // init voice
|
||||
|
||||
// unreliable buffer. unsed for unreliable commands and voice stream
|
||||
MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf ));
|
||||
|
@ -1685,7 +1685,10 @@ CL_ParseVoiceInit
|
||||
*/
|
||||
void CL_ParseVoiceInit( sizebuf_t *msg )
|
||||
{
|
||||
// TODO: ???
|
||||
char *pszCodec = MSG_ReadString( msg );
|
||||
int quality = MSG_ReadByte( msg );
|
||||
|
||||
Voice_Init( pszCodec, quality );
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1696,7 +1699,28 @@ CL_ParseVoiceData
|
||||
*/
|
||||
void CL_ParseVoiceData( sizebuf_t *msg )
|
||||
{
|
||||
// TODO: ???
|
||||
int size, idx, frames;
|
||||
unsigned char received[8192];
|
||||
|
||||
idx = MSG_ReadByte( msg ) + 1;
|
||||
|
||||
frames = MSG_ReadByte( msg );
|
||||
|
||||
size = MSG_ReadShort( msg );
|
||||
size = Q_min( size, 8192 );
|
||||
|
||||
MSG_ReadBytes( msg, received, size );
|
||||
|
||||
if ( idx <= 0 || idx > cl.maxclients )
|
||||
return;
|
||||
|
||||
if ( idx == cl.playernum + 1 )
|
||||
Voice_LocalPlayerTalkingAck();
|
||||
|
||||
if ( !size )
|
||||
return;
|
||||
|
||||
Voice_AddIncomingData( idx, received, size, frames );
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2340,6 +2364,7 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message )
|
||||
break;
|
||||
case svc_voicedata:
|
||||
CL_ParseVoiceData( msg );
|
||||
cl.frames[cl.parsecountmod].graphdata.voicebytes += MSG_GetNumBytesRead( msg ) - bufStart;
|
||||
break;
|
||||
case svc_resourcelocation:
|
||||
CL_ParseResLocation( msg );
|
||||
@ -3127,6 +3152,7 @@ void CL_ParseLegacyServerMessage( sizebuf_t *msg, qboolean normal_message )
|
||||
break;
|
||||
case svc_voicedata:
|
||||
CL_ParseVoiceData( msg );
|
||||
cl.frames[cl.parsecountmod].graphdata.voicebytes += MSG_GetNumBytesRead( msg ) - bufStart;
|
||||
break;
|
||||
case svc_resourcelocation:
|
||||
CL_ParseResLocation( msg );
|
||||
|
@ -1127,7 +1127,7 @@ static uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend,
|
||||
S_RawEntSamples
|
||||
===================
|
||||
*/
|
||||
static void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol )
|
||||
void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol )
|
||||
{
|
||||
rawchan_t *ch;
|
||||
|
||||
@ -1286,6 +1286,9 @@ static void S_FreeIdleRawChannels( void )
|
||||
|
||||
if( ch->s_rawend >= paintedtime )
|
||||
continue;
|
||||
|
||||
if ( ch->entnum > 0 )
|
||||
SND_ForceCloseMouth( ch->entnum );
|
||||
|
||||
if(( paintedtime - ch->s_rawend ) / SOUND_DMA_SPEED >= S_RAW_SOUND_IDLE_SEC )
|
||||
{
|
||||
@ -1858,6 +1861,33 @@ void S_SoundInfo_f( void )
|
||||
S_PrintBackgroundTrackState ();
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
S_VoiceRecordStart_f
|
||||
=================
|
||||
*/
|
||||
void S_VoiceRecordStart_f( void )
|
||||
{
|
||||
if( cls.state != ca_active )
|
||||
return;
|
||||
|
||||
Voice_RecordStart();
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
S_VoiceRecordStop_f
|
||||
=================
|
||||
*/
|
||||
void S_VoiceRecordStop_f( void )
|
||||
{
|
||||
if( cls.state != ca_active || !Voice_IsRecording() )
|
||||
return;
|
||||
|
||||
CL_AddVoiceToDatagram();
|
||||
Voice_RecordStop();
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
S_Init
|
||||
@ -1892,8 +1922,8 @@ qboolean S_Init( void )
|
||||
Cmd_AddCommand( "soundlist", S_SoundList_f, "display loaded sounds" );
|
||||
Cmd_AddCommand( "s_info", S_SoundInfo_f, "print sound system information" );
|
||||
Cmd_AddCommand( "s_fade", S_SoundFade_f, "fade all sounds then stop all" );
|
||||
Cmd_AddCommand( "+voicerecord", Cmd_Null_f, "start voice recording (non-implemented)" );
|
||||
Cmd_AddCommand( "-voicerecord", Cmd_Null_f, "stop voice recording (non-implemented)" );
|
||||
Cmd_AddCommand( "+voicerecord", S_VoiceRecordStart_f, "start voice recording" );
|
||||
Cmd_AddCommand( "-voicerecord", S_VoiceRecordStop_f, "stop voice recording" );
|
||||
Cmd_AddCommand( "spk", S_SayReliable_f, "reliable play a specified sententce" );
|
||||
Cmd_AddCommand( "speak", S_Say_f, "playing a specified sententce" );
|
||||
|
||||
|
@ -958,6 +958,9 @@ void MIX_MixRawSamplesBuffer( int end )
|
||||
pbuf[j-paintedtime].left += ( ch->rawsamples[j & ( ch->max_samples - 1 )].left * ch->leftvol ) >> 8;
|
||||
pbuf[j-paintedtime].right += ( ch->rawsamples[j & ( ch->max_samples - 1 )].right * ch->rightvol ) >> 8;
|
||||
}
|
||||
|
||||
if ( ch->entnum > 0 )
|
||||
SND_MoveMouthRaw( ch, &ch->rawsamples[paintedtime & ( ch->max_samples - 1 )], stop - paintedtime );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,3 +150,68 @@ void SND_MoveMouth16( channel_t *ch, wavdata_t *pSource, int count )
|
||||
pMouth->sndcount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SND_ForceInitMouth( int entnum )
|
||||
{
|
||||
cl_entity_t *clientEntity;
|
||||
|
||||
clientEntity = CL_GetEntityByIndex( entnum );
|
||||
|
||||
if ( clientEntity )
|
||||
{
|
||||
clientEntity->mouth.mouthopen = 0;
|
||||
clientEntity->mouth.sndavg = 0;
|
||||
clientEntity->mouth.sndcount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SND_ForceCloseMouth( int entnum )
|
||||
{
|
||||
cl_entity_t *clientEntity;
|
||||
|
||||
clientEntity = CL_GetEntityByIndex( entnum );
|
||||
|
||||
if ( clientEntity )
|
||||
clientEntity->mouth.mouthopen = 0;
|
||||
}
|
||||
|
||||
void SND_MoveMouthRaw( rawchan_t *ch, portable_samplepair_t *pData, int count )
|
||||
{
|
||||
cl_entity_t *clientEntity;
|
||||
mouth_t *pMouth = NULL;
|
||||
int savg, data;
|
||||
int scount = 0;
|
||||
uint i;
|
||||
|
||||
clientEntity = CL_GetEntityByIndex( ch->entnum );
|
||||
if( !clientEntity ) return;
|
||||
|
||||
pMouth = &clientEntity->mouth;
|
||||
|
||||
if( pData == NULL )
|
||||
return;
|
||||
|
||||
i = 0;
|
||||
scount = pMouth->sndcount;
|
||||
savg = 0;
|
||||
|
||||
while ( i < count && scount < CAVGSAMPLES )
|
||||
{
|
||||
data = pData[i].left; // mono sound anyway
|
||||
data = ( bound( -32767, data, 0x7ffe ) >> 8 );
|
||||
savg += abs( data );
|
||||
|
||||
i += 80 + ( (byte)data & 0x1F );
|
||||
scount++;
|
||||
}
|
||||
|
||||
pMouth->sndavg += savg;
|
||||
pMouth->sndcount = (byte)scount;
|
||||
|
||||
if ( pMouth->sndcount >= CAVGSAMPLES )
|
||||
{
|
||||
pMouth->mouthopen = pMouth->sndavg / CAVGSAMPLES;
|
||||
pMouth->sndavg = 0;
|
||||
pMouth->sndcount = 0;
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ extern poolhandle_t sndpool;
|
||||
#define SOUND_22k 22050 // 22khz sample rate
|
||||
#define SOUND_32k 32000 // 32khz sample rate
|
||||
#define SOUND_44k 44100 // 44khz sample rate
|
||||
#define SOUND_48k 48000 // 48khz sample rate
|
||||
#define DMA_MSEC_PER_SAMPLE ((float)(1000.0 / SOUND_DMA_SPEED))
|
||||
|
||||
// fixed point stuff for real-time resampling
|
||||
@ -202,7 +203,7 @@ typedef struct
|
||||
|
||||
#define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS)
|
||||
#define MAX_CHANNELS (256 + MAX_DYNAMIC_CHANNELS) // Scourge Of Armagon has too many static sounds on hip2m4.bsp
|
||||
#define MAX_RAW_CHANNELS 16
|
||||
#define MAX_RAW_CHANNELS 48
|
||||
#define MAX_RAW_SAMPLES 8192
|
||||
|
||||
extern sound_t ambient_sfx[NUM_AMBIENTS];
|
||||
@ -271,6 +272,7 @@ int S_GetCurrentStaticSounds( soundlist_t *pout, int size );
|
||||
int S_GetCurrentDynamicSounds( soundlist_t *pout, int size );
|
||||
sfx_t *S_GetSfxByHandle( sound_t handle );
|
||||
rawchan_t *S_FindRawChannel( int entnum, qboolean create );
|
||||
void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol );
|
||||
void S_RawSamples( uint samples, uint rate, word width, word channels, const byte *data, int entnum );
|
||||
void S_StopSound( int entnum, int channel, const char *soundname );
|
||||
void S_UpdateFrame( struct ref_viewpass_s *rvp );
|
||||
@ -283,9 +285,12 @@ void S_FreeSounds( void );
|
||||
// s_mouth.c
|
||||
//
|
||||
void SND_InitMouth( int entnum, int entchannel );
|
||||
void SND_ForceInitMouth( int entnum );
|
||||
void SND_MoveMouth8( channel_t *ch, wavdata_t *pSource, int count );
|
||||
void SND_MoveMouth16( channel_t *ch, wavdata_t *pSource, int count );
|
||||
void SND_MoveMouthRaw( rawchan_t *ch, portable_samplepair_t *pData, int count );
|
||||
void SND_CloseMouth( channel_t *ch );
|
||||
void SND_ForceCloseMouth( int entnum );
|
||||
|
||||
//
|
||||
// s_stream.c
|
||||
|
234
engine/client/voice.c
Normal file
234
engine/client/voice.c
Normal file
@ -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 );
|
||||
}
|
56
engine/client/voice.h
Normal file
56
engine/client/voice.h
Normal file
@ -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
|
@ -254,4 +254,19 @@ void SNDDMA_BeginPainting( void )
|
||||
{
|
||||
pthread_mutex_lock( &snddma_android_mutex );
|
||||
}
|
||||
|
||||
qboolean VoiceCapture_Init( void )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
qboolean VoiceCapture_RecordStart( void )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void VoiceCapture_RecordStop( void )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
@ -161,5 +161,8 @@ void SNDDMA_Activate( qboolean active ); // pause audio
|
||||
// void SNDDMA_PrintDeviceName( void ); // unused
|
||||
// void SNDDMA_LockSound( void ); // unused
|
||||
// void SNDDMA_UnlockSound( void ); // unused
|
||||
qboolean VoiceCapture_Init( void );
|
||||
qboolean VoiceCapture_RecordStart( void );
|
||||
void VoiceCapture_RecordStop( void );
|
||||
|
||||
#endif // PLATFORM_H
|
||||
|
@ -18,6 +18,7 @@ GNU General Public License for more details.
|
||||
#if XASH_SOUND == SOUND_SDL
|
||||
|
||||
#include "sound.h"
|
||||
#include "voice.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
@ -43,6 +44,8 @@ so it can unlock and free the data block after it has been played.
|
||||
=======================================================================
|
||||
*/
|
||||
static int sdl_dev;
|
||||
static SDL_AudioDeviceID in_dev;
|
||||
static SDL_AudioFormat sdl_format;
|
||||
|
||||
//static qboolean snd_firsttime = true;
|
||||
//static qboolean primary_format_set;
|
||||
@ -133,6 +136,8 @@ qboolean SNDDMA_Init( void )
|
||||
dma.buffer = Z_Calloc( dma.samples * 2 );
|
||||
dma.samplepos = 0;
|
||||
|
||||
sdl_format = obtained.format;
|
||||
|
||||
Con_Printf( "Using SDL audio driver: %s @ %d Hz\n", SDL_GetCurrentAudioDriver( ), obtained.freq );
|
||||
|
||||
dma.initialized = true;
|
||||
@ -220,4 +225,70 @@ void SNDDMA_Activate( qboolean active )
|
||||
|
||||
SDL_PauseAudioDevice( sdl_dev, !active );
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
SDL_SoundInputCallback
|
||||
===========
|
||||
*/
|
||||
void SDL_SoundInputCallback( void *userdata, Uint8 *stream, int len )
|
||||
{
|
||||
int size;
|
||||
|
||||
size = Q_min( len, sizeof( voice.buffer ) - voice.buffer_pos );
|
||||
SDL_memset( voice.buffer + voice.buffer_pos, 0, size );
|
||||
SDL_MixAudioFormat( voice.buffer + voice.buffer_pos, stream, sdl_format, size, SDL_MIX_MAXVOLUME );
|
||||
voice.buffer_pos += size;
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
VoiceCapture_Init
|
||||
===========
|
||||
*/
|
||||
qboolean VoiceCapture_Init( void )
|
||||
{
|
||||
SDL_AudioSpec wanted, spec;
|
||||
|
||||
SDL_zero( wanted );
|
||||
wanted.freq = voice.samplerate;
|
||||
wanted.format = AUDIO_S16LSB;
|
||||
wanted.channels = voice.channels;
|
||||
wanted.samples = voice.frame_size / voice.width;
|
||||
wanted.callback = SDL_SoundInputCallback;
|
||||
|
||||
in_dev = SDL_OpenAudioDevice( NULL, SDL_TRUE, &wanted, &spec, 0 );
|
||||
|
||||
if( SDLash_IsAudioError( in_dev ) )
|
||||
{
|
||||
Con_Printf( "VoiceCapture_Init: error creating capture device (%s)\n", SDL_GetError() );
|
||||
return false;
|
||||
}
|
||||
|
||||
Con_Printf( S_NOTE "VoiceCapture_Init: capture device creation success (%i: %s)\n", in_dev, SDL_GetAudioDeviceName( in_dev, SDL_TRUE ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
VoiceCapture_RecordStart
|
||||
===========
|
||||
*/
|
||||
qboolean VoiceCapture_RecordStart( void )
|
||||
{
|
||||
SDL_PauseAudioDevice( in_dev, SDL_FALSE );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
VoiceCapture_RecordStop
|
||||
===========
|
||||
*/
|
||||
void VoiceCapture_RecordStop( void )
|
||||
{
|
||||
SDL_PauseAudioDevice( in_dev, SDL_TRUE );
|
||||
}
|
||||
|
||||
#endif // XASH_SOUND == SOUND_SDL
|
||||
|
@ -94,5 +94,20 @@ void SNDDMA_Shutdown( void )
|
||||
}
|
||||
}
|
||||
|
||||
qboolean VoiceCapture_Init( void )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
qboolean VoiceCapture_RecordStart( void )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void VoiceCapture_RecordStop( void )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -2561,6 +2561,58 @@ void SV_ParseCvarValue2( sv_client_t *cl, sizebuf_t *msg )
|
||||
Con_Reportf( "Cvar query response: name:%s, request ID %d, cvar:%s, value:%s\n", cl->name, requestID, name, value );
|
||||
}
|
||||
|
||||
/*
|
||||
===================
|
||||
SV_ParseVoiceData
|
||||
===================
|
||||
*/
|
||||
void SV_ParseVoiceData( sv_client_t *cl, sizebuf_t *msg )
|
||||
{
|
||||
char received[4096];
|
||||
sv_client_t *cur;
|
||||
int i, client;
|
||||
uint length, size, frames;
|
||||
|
||||
cl->m_bLoopback = MSG_ReadByte( msg );
|
||||
|
||||
frames = MSG_ReadByte( msg );
|
||||
|
||||
size = MSG_ReadShort( msg );
|
||||
client = cl - svs.clients;
|
||||
|
||||
if ( size > sizeof( received ) )
|
||||
{
|
||||
Con_DPrintf( "SV_ParseVoiceData: invalid incoming packet.\n" );
|
||||
SV_DropClient( cl, false );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !Cvar_VariableInteger( "sv_voiceenable" ) )
|
||||
return;
|
||||
|
||||
MSG_ReadBytes( msg, received, size );
|
||||
|
||||
for( i = 0, cur = svs.clients; i < svs.maxclients; i++, cur++ )
|
||||
{
|
||||
if ( cur->state < cs_connected && cl != cur )
|
||||
continue;
|
||||
|
||||
length = size;
|
||||
|
||||
if ( MSG_GetNumBytesLeft( &cur->datagram ) < length + 6 )
|
||||
continue;
|
||||
|
||||
if ( cl == cur && !cur->m_bLoopback )
|
||||
length = 0;
|
||||
|
||||
MSG_BeginServerCmd( &cur->datagram, svc_voicedata );
|
||||
MSG_WriteByte( &cur->datagram, client );
|
||||
MSG_WriteByte( &cur->datagram, frames );
|
||||
MSG_WriteShort( &cur->datagram, length );
|
||||
MSG_WriteBytes( &cur->datagram, received, length );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===================
|
||||
SV_ExecuteClientMessage
|
||||
@ -2631,6 +2683,9 @@ void SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg )
|
||||
case clc_fileconsistency:
|
||||
SV_ParseConsistencyResponse( cl, msg );
|
||||
break;
|
||||
case clc_voicedata:
|
||||
SV_ParseVoiceData( cl, msg );
|
||||
break;
|
||||
case clc_requestcvarvalue:
|
||||
SV_ParseCvarValue( cl, msg );
|
||||
break;
|
||||
|
@ -386,6 +386,18 @@ void SV_CreateResourceList( void )
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_WriteVoiceCodec
|
||||
================
|
||||
*/
|
||||
void SV_WriteVoiceCodec( sizebuf_t *msg )
|
||||
{
|
||||
MSG_BeginServerCmd( msg, svc_voiceinit );
|
||||
MSG_WriteString( msg, "opus" );
|
||||
MSG_WriteByte( msg, 0 );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
SV_CreateBaseline
|
||||
@ -404,6 +416,8 @@ void SV_CreateBaseline( void )
|
||||
int delta_type;
|
||||
int entnum;
|
||||
|
||||
SV_WriteVoiceCodec( &sv.signon );
|
||||
|
||||
if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
|
||||
playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_QUAKE );
|
||||
else playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_HALFLIFE );
|
||||
|
@ -112,6 +112,9 @@ CVAR_DEFINE_AUTO( violence_ablood, "1", 0, "draw alien blood" );
|
||||
CVAR_DEFINE_AUTO( violence_hgibs, "1", 0, "show human gib entities" );
|
||||
CVAR_DEFINE_AUTO( violence_agibs, "1", 0, "show alien gib entities" );
|
||||
|
||||
// voice chat
|
||||
CVAR_DEFINE_AUTO( sv_voiceenable, "1", FCVAR_ARCHIVE|FCVAR_SERVER, "enable voice support" );
|
||||
|
||||
convar_t *sv_novis; // disable server culling entities by vis
|
||||
convar_t *sv_pausable;
|
||||
convar_t *timeout; // seconds without any message
|
||||
@ -974,6 +977,7 @@ void SV_Init( void )
|
||||
Cvar_RegisterVariable( &listipcfgfile );
|
||||
Cvar_RegisterVariable( &mapchangecfgfile );
|
||||
|
||||
Cvar_RegisterVariable( &sv_voiceenable );
|
||||
Cvar_RegisterVariable( &sv_trace_messages );
|
||||
|
||||
sv_allow_joystick = Cvar_Get( "sv_allow_joystick", "1", FCVAR_ARCHIVE, "allow connect with joystick enabled" );
|
||||
|
@ -35,7 +35,7 @@ def options(opt):
|
||||
grp.add_option('--enable-engine-fuzz', action = 'store_true', dest = 'ENGINE_FUZZ', default = False,
|
||||
help = 'add LLVM libFuzzer [default: %default]' )
|
||||
|
||||
opt.load('sdl2')
|
||||
opt.load('sdl2 opus')
|
||||
|
||||
def configure(conf):
|
||||
# check for dedicated server build
|
||||
@ -65,7 +65,7 @@ def configure(conf):
|
||||
else:
|
||||
conf.load('sdl2')
|
||||
if not conf.env.HAVE_SDL2:
|
||||
conf.fatal('SDL2 not availiable! If you want to build dedicated server, specify --dedicated')
|
||||
conf.fatal('SDL2 not available! If you want to build dedicated server, specify --dedicated')
|
||||
conf.define('XASH_SDL', 2)
|
||||
|
||||
if conf.env.DEST_OS == 'haiku':
|
||||
@ -165,6 +165,8 @@ def build(bld):
|
||||
'client/*.c',
|
||||
'client/vgui/*.c',
|
||||
'client/avi/*.c'])
|
||||
|
||||
libs.append('OPUS')
|
||||
|
||||
includes = ['common', 'server', 'client', 'client/vgui', 'tests', '.', '../public', '../common', '../filesystem', '../pm_shared' ]
|
||||
|
||||
|
42
scripts/waifulib/opus.py
Normal file
42
scripts/waifulib/opus.py
Normal file
@ -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…
x
Reference in New Issue
Block a user