Browse Source

engine: voice support

pull/2/head
Velaron 4 years ago committed by Alibek Omarov
parent
commit
2b9e050f57
  1. 3
      .gitmodules
  2. 1
      3rdparty/opus
  3. 10
      engine/client/cl_main.c
  4. 30
      engine/client/cl_parse.c
  5. 36
      engine/client/s_main.c
  6. 3
      engine/client/s_mix.c
  7. 65
      engine/client/s_mouth.c
  8. 7
      engine/client/sound.h
  9. 234
      engine/client/voice.c
  10. 56
      engine/client/voice.h
  11. 15
      engine/platform/android/snd_opensles.c
  12. 3
      engine/platform/platform.h
  13. 71
      engine/platform/sdl/s_sdl.c
  14. 15
      engine/platform/stub/s_stub.c
  15. 55
      engine/server/sv_client.c
  16. 14
      engine/server/sv_init.c
  17. 4
      engine/server/sv_main.c
  18. 6
      engine/wscript
  19. 42
      scripts/waifulib/opus.py
  20. 5
      wscript

3
.gitmodules vendored

@ -13,3 +13,6 @@
[submodule "vgui_support"] [submodule "vgui_support"]
path = vgui_support path = vgui_support
url = https://github.com/FWGS/vgui_support url = https://github.com/FWGS/vgui_support
[submodule "opus"]
path = 3rdparty/opus
url = https://github.com/xiph/opus

1
3rdparty/opus vendored

@ -0,0 +1 @@
Subproject commit dfd6c88aaa54a03a61434c413e30c217eb98f1d5

10
engine/client/cl_main.c

@ -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].sendsize = MSG_GetNumBytesWritten( &buf );
cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].heldback = false; 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.. // composite the rest of the datagram..
if( MSG_GetNumBitsWritten( &cls.datagram ) <= MSG_GetNumBitsLeft( &buf )) if( MSG_GetNumBitsWritten( &cls.datagram ) <= MSG_GetNumBitsLeft( &buf ))
MSG_WriteBits( &buf, MSG_GetData( &cls.datagram ), MSG_GetNumBitsWritten( &cls.datagram )); 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_logocolor );
Cvar_RegisterVariable( &cl_test_bandwidth ); Cvar_RegisterVariable( &cl_test_bandwidth );
Voice_RegisterCvars();
// register our variables // register our variables
cl_crosshair = Cvar_Get( "crosshair", "1", FCVAR_ARCHIVE, "show weapon chrosshair" ); 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" ); 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 // a new portion updates from server
CL_RedoPrediction (); CL_RedoPrediction ();
// TODO: implement // update voice
// Voice_Idle( host.frametime ); Voice_Idle( host.frametime );
// emit visible entities // emit visible entities
CL_EmitEntities (); CL_EmitEntities ();
@ -3079,6 +3084,7 @@ void CL_Init( void )
VID_Init(); // init video VID_Init(); // init video
S_Init(); // init sound S_Init(); // init sound
Voice_Init( "opus", 0 ); // init voice
// unreliable buffer. unsed for unreliable commands and voice stream // unreliable buffer. unsed for unreliable commands and voice stream
MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf )); MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf ));

30
engine/client/cl_parse.c

@ -1685,7 +1685,10 @@ CL_ParseVoiceInit
*/ */
void CL_ParseVoiceInit( sizebuf_t *msg ) 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 ) 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; break;
case svc_voicedata: case svc_voicedata:
CL_ParseVoiceData( msg ); CL_ParseVoiceData( msg );
cl.frames[cl.parsecountmod].graphdata.voicebytes += MSG_GetNumBytesRead( msg ) - bufStart;
break; break;
case svc_resourcelocation: case svc_resourcelocation:
CL_ParseResLocation( msg ); CL_ParseResLocation( msg );
@ -3127,6 +3152,7 @@ void CL_ParseLegacyServerMessage( sizebuf_t *msg, qboolean normal_message )
break; break;
case svc_voicedata: case svc_voicedata:
CL_ParseVoiceData( msg ); CL_ParseVoiceData( msg );
cl.frames[cl.parsecountmod].graphdata.voicebytes += MSG_GetNumBytesRead( msg ) - bufStart;
break; break;
case svc_resourcelocation: case svc_resourcelocation:
CL_ParseResLocation( msg ); CL_ParseResLocation( msg );

36
engine/client/s_main.c

@ -1127,7 +1127,7 @@ static uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend,
S_RawEntSamples 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; rawchan_t *ch;
@ -1286,6 +1286,9 @@ static void S_FreeIdleRawChannels( void )
if( ch->s_rawend >= paintedtime ) if( ch->s_rawend >= paintedtime )
continue; continue;
if ( ch->entnum > 0 )
SND_ForceCloseMouth( ch->entnum );
if(( paintedtime - ch->s_rawend ) / SOUND_DMA_SPEED >= S_RAW_SOUND_IDLE_SEC ) 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_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 S_Init
@ -1892,8 +1922,8 @@ qboolean S_Init( void )
Cmd_AddCommand( "soundlist", S_SoundList_f, "display loaded sounds" ); Cmd_AddCommand( "soundlist", S_SoundList_f, "display loaded sounds" );
Cmd_AddCommand( "s_info", S_SoundInfo_f, "print sound system information" ); 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( "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", S_VoiceRecordStart_f, "start voice recording" );
Cmd_AddCommand( "-voicerecord", Cmd_Null_f, "stop voice recording (non-implemented)" ); Cmd_AddCommand( "-voicerecord", S_VoiceRecordStop_f, "stop voice recording" );
Cmd_AddCommand( "spk", S_SayReliable_f, "reliable play a specified sententce" ); Cmd_AddCommand( "spk", S_SayReliable_f, "reliable play a specified sententce" );
Cmd_AddCommand( "speak", S_Say_f, "playing a specified sententce" ); Cmd_AddCommand( "speak", S_Say_f, "playing a specified sententce" );

3
engine/client/s_mix.c

@ -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].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; 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 );
} }
} }

65
engine/client/s_mouth.c

@ -150,3 +150,68 @@ void SND_MoveMouth16( channel_t *ch, wavdata_t *pSource, int count )
pMouth->sndcount = 0; 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;
}
}

7
engine/client/sound.h

@ -27,6 +27,7 @@ extern poolhandle_t sndpool;
#define SOUND_22k 22050 // 22khz sample rate #define SOUND_22k 22050 // 22khz sample rate
#define SOUND_32k 32000 // 32khz sample rate #define SOUND_32k 32000 // 32khz sample rate
#define SOUND_44k 44100 // 44khz 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)) #define DMA_MSEC_PER_SAMPLE ((float)(1000.0 / SOUND_DMA_SPEED))
// fixed point stuff for real-time resampling // fixed point stuff for real-time resampling
@ -202,7 +203,7 @@ typedef struct
#define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS) #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_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 #define MAX_RAW_SAMPLES 8192
extern sound_t ambient_sfx[NUM_AMBIENTS]; 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 ); int S_GetCurrentDynamicSounds( soundlist_t *pout, int size );
sfx_t *S_GetSfxByHandle( sound_t handle ); sfx_t *S_GetSfxByHandle( sound_t handle );
rawchan_t *S_FindRawChannel( int entnum, qboolean create ); 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_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_StopSound( int entnum, int channel, const char *soundname );
void S_UpdateFrame( struct ref_viewpass_s *rvp ); void S_UpdateFrame( struct ref_viewpass_s *rvp );
@ -283,9 +285,12 @@ void S_FreeSounds( void );
// s_mouth.c // s_mouth.c
// //
void SND_InitMouth( int entnum, int entchannel ); 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_MoveMouth8( channel_t *ch, wavdata_t *pSource, int count );
void SND_MoveMouth16( 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_CloseMouth( channel_t *ch );
void SND_ForceCloseMouth( int entnum );
// //
// s_stream.c // s_stream.c

234
engine/client/voice.c

@ -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

@ -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

15
engine/platform/android/snd_opensles.c

@ -254,4 +254,19 @@ void SNDDMA_BeginPainting( void )
{ {
pthread_mutex_lock( &snddma_android_mutex ); 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 #endif

3
engine/platform/platform.h

@ -161,5 +161,8 @@ void SNDDMA_Activate( qboolean active ); // pause audio
// void SNDDMA_PrintDeviceName( void ); // unused // void SNDDMA_PrintDeviceName( void ); // unused
// void SNDDMA_LockSound( void ); // unused // void SNDDMA_LockSound( void ); // unused
// void SNDDMA_UnlockSound( void ); // unused // void SNDDMA_UnlockSound( void ); // unused
qboolean VoiceCapture_Init( void );
qboolean VoiceCapture_RecordStart( void );
void VoiceCapture_RecordStop( void );
#endif // PLATFORM_H #endif // PLATFORM_H

71
engine/platform/sdl/s_sdl.c

@ -18,6 +18,7 @@ GNU General Public License for more details.
#if XASH_SOUND == SOUND_SDL #if XASH_SOUND == SOUND_SDL
#include "sound.h" #include "sound.h"
#include "voice.h"
#include <SDL.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 int sdl_dev;
static SDL_AudioDeviceID in_dev;
static SDL_AudioFormat sdl_format;
//static qboolean snd_firsttime = true; //static qboolean snd_firsttime = true;
//static qboolean primary_format_set; //static qboolean primary_format_set;
@ -133,6 +136,8 @@ qboolean SNDDMA_Init( void )
dma.buffer = Z_Calloc( dma.samples * 2 ); dma.buffer = Z_Calloc( dma.samples * 2 );
dma.samplepos = 0; dma.samplepos = 0;
sdl_format = obtained.format;
Con_Printf( "Using SDL audio driver: %s @ %d Hz\n", SDL_GetCurrentAudioDriver( ), obtained.freq ); Con_Printf( "Using SDL audio driver: %s @ %d Hz\n", SDL_GetCurrentAudioDriver( ), obtained.freq );
dma.initialized = true; dma.initialized = true;
@ -220,4 +225,70 @@ void SNDDMA_Activate( qboolean active )
SDL_PauseAudioDevice( sdl_dev, !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 #endif // XASH_SOUND == SOUND_SDL

15
engine/platform/stub/s_stub.c

@ -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
#endif #endif

55
engine/server/sv_client.c

@ -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 ); 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 SV_ExecuteClientMessage
@ -2631,6 +2683,9 @@ void SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg )
case clc_fileconsistency: case clc_fileconsistency:
SV_ParseConsistencyResponse( cl, msg ); SV_ParseConsistencyResponse( cl, msg );
break; break;
case clc_voicedata:
SV_ParseVoiceData( cl, msg );
break;
case clc_requestcvarvalue: case clc_requestcvarvalue:
SV_ParseCvarValue( cl, msg ); SV_ParseCvarValue( cl, msg );
break; break;

14
engine/server/sv_init.c

@ -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 SV_CreateBaseline
@ -404,6 +416,8 @@ void SV_CreateBaseline( void )
int delta_type; int delta_type;
int entnum; int entnum;
SV_WriteVoiceCodec( &sv.signon );
if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_QUAKE ); playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_QUAKE );
else playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_HALFLIFE ); else playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_HALFLIFE );

4
engine/server/sv_main.c

@ -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_hgibs, "1", 0, "show human gib entities" );
CVAR_DEFINE_AUTO( violence_agibs, "1", 0, "show alien 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_novis; // disable server culling entities by vis
convar_t *sv_pausable; convar_t *sv_pausable;
convar_t *timeout; // seconds without any message convar_t *timeout; // seconds without any message
@ -974,6 +977,7 @@ void SV_Init( void )
Cvar_RegisterVariable( &listipcfgfile ); Cvar_RegisterVariable( &listipcfgfile );
Cvar_RegisterVariable( &mapchangecfgfile ); Cvar_RegisterVariable( &mapchangecfgfile );
Cvar_RegisterVariable( &sv_voiceenable );
Cvar_RegisterVariable( &sv_trace_messages ); Cvar_RegisterVariable( &sv_trace_messages );
sv_allow_joystick = Cvar_Get( "sv_allow_joystick", "1", FCVAR_ARCHIVE, "allow connect with joystick enabled" ); sv_allow_joystick = Cvar_Get( "sv_allow_joystick", "1", FCVAR_ARCHIVE, "allow connect with joystick enabled" );

6
engine/wscript

@ -35,7 +35,7 @@ def options(opt):
grp.add_option('--enable-engine-fuzz', action = 'store_true', dest = 'ENGINE_FUZZ', default = False, grp.add_option('--enable-engine-fuzz', action = 'store_true', dest = 'ENGINE_FUZZ', default = False,
help = 'add LLVM libFuzzer [default: %default]' ) help = 'add LLVM libFuzzer [default: %default]' )
opt.load('sdl2') opt.load('sdl2 opus')
def configure(conf): def configure(conf):
# check for dedicated server build # check for dedicated server build
@ -65,7 +65,7 @@ def configure(conf):
else: else:
conf.load('sdl2') conf.load('sdl2')
if not conf.env.HAVE_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) conf.define('XASH_SDL', 2)
if conf.env.DEST_OS == 'haiku': if conf.env.DEST_OS == 'haiku':
@ -165,6 +165,8 @@ def build(bld):
'client/*.c', 'client/*.c',
'client/vgui/*.c', 'client/vgui/*.c',
'client/avi/*.c']) 'client/avi/*.c'])
libs.append('OPUS')
includes = ['common', 'server', 'client', 'client/vgui', 'tests', '.', '../public', '../common', '../filesystem', '../pm_shared' ] includes = ['common', 'server', 'client', 'client/vgui', 'tests', '.', '../public', '../common', '../filesystem', '../pm_shared' ]

42
scripts/waifulib/opus.py

@ -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
)

5
wscript

@ -353,9 +353,12 @@ int main(int argc, char **argv) { strcasestr(argv[1], argv[2]); return 0; }'''
continue continue
conf.add_subproject(i.name) conf.add_subproject(i.name)
conf.load('opus')
def build(bld): def build(bld):
bld.load('xshlib') bld.load('opus xshlib')
for i in SUBDIRS: for i in SUBDIRS:
if not i.is_enabled(bld): if not i.is_enabled(bld):
continue continue

Loading…
Cancel
Save