From 9e8d2480448f569a11ace4fdab91739b6e567db4 Mon Sep 17 00:00:00 2001 From: TheEVolk Date: Thu, 9 Apr 2026 22:02:36 +0300 Subject: [PATCH] engine: client: voice api --- common/sound_api.h | 15 +++++++++++++-- engine/cdll_exp.h | 1 + engine/client/cl_game.c | 3 ++- engine/client/s_main.c | 7 +++++-- engine/client/s_stream.c | 2 +- engine/client/sound.h | 2 +- engine/client/voice.c | 18 +++++++++++++++++- engine/client/voice.h | 1 + engine/physint.h | 2 ++ engine/server/server.h | 1 + engine/server/sv_client.c | 11 ++++++++++- engine/server/sv_init.c | 2 +- engine/server/sv_main.c | 2 ++ 13 files changed, 57 insertions(+), 10 deletions(-) diff --git a/common/sound_api.h b/common/sound_api.h index d25c18be..c4f8cd3c 100644 --- a/common/sound_api.h +++ b/common/sound_api.h @@ -159,11 +159,22 @@ typedef struct snd_globals_s qboolean have_ambient_sfx; } snd_globals_t; +typedef struct voice_audio_info_s +{ + uint width; + uint samplerate; + uint frame_size; // in samples +} voice_audio_info_t; + // API from engine to client (client calls these) typedef struct sound_api_s { - qboolean (*CL_GetEntitySpatialization)( channel_t *ch ); - sfx_t* (*S_GetSfxByHandle)( sound_t handle ); + qboolean (*CL_GetEntitySpatialization)( channel_t *ch ); + sfx_t* (*S_GetSfxByHandle)( sound_t handle ); + // Voice extensions + void (*pfnS_RawEntSamples)( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol, float attn ); + void (*pfnSND_ForceInitMouth)( int entnum ); + voice_audio_info_t (*pfnVoice_GetAudioInfo)( void ); } sound_api_t; // Callbacks from client to engine (engine calls these when custom sound is active) diff --git a/engine/cdll_exp.h b/engine/cdll_exp.h index 0cd04636..474364a9 100644 --- a/engine/cdll_exp.h +++ b/engine/cdll_exp.h @@ -82,6 +82,7 @@ typedef struct cldll_func_s void (*pfnLookEvent)( float relyaw, float relpitch ); // Sound API int (*pfnGetSoundInterface)( int version, const sound_api_t *api, sound_interface_t *callback ); + int (*pfnVoice_StartChannel)( int samples, byte *data, int entnum ); } cldll_func_t; #endif//CDLL_EXP_H diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 2b9d7c43..16ded8fd 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -105,7 +105,8 @@ static const dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and high { "IN_ClientTouchEvent", (void **)&clgame.dllFuncs.pfnTouchEvent}, // Xash3D FWGS ext { "IN_ClientMoveEvent", (void **)&clgame.dllFuncs.pfnMoveEvent}, // Xash3D FWGS ext { "IN_ClientLookEvent", (void **)&clgame.dllFuncs.pfnLookEvent}, // Xash3D FWGS ext -{ "HUD_GetSoundInterface", (void **)&clgame.dllFuncs.pfnGetSoundInterface }, // Xash3D ext +{ "HUD_GetSoundInterface", (void **)&clgame.dllFuncs.pfnGetSoundInterface }, // Xash3D FWGS ext +{ "Voice_StartChannel", (void **)&clgame.dllFuncs.pfnVoice_StartChannel }, // Xash3D FWGS ext }; static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc ); diff --git a/engine/client/s_main.c b/engine/client/s_main.c index a4e38723..1160606c 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -1287,7 +1287,7 @@ uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint ma S_RawEntSamples =================== */ -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, float attn ) { rawchan_t *ch; @@ -1298,7 +1298,7 @@ void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word chan return; ch->master_vol = snd_vol; - ch->dist_mult = (ATTN_NONE / SND_CLIP_DISTANCE); + ch->dist_mult = (attn / SND_CLIP_DISTANCE); ch->s_rawend = S_RawSamplesStereo( ch->rawsamples, ch->s_rawend, ch->max_samples, samples, rate, width, channels, data ); ch->leftvol = ch->rightvol = snd_vol; } @@ -1938,6 +1938,9 @@ static void S_VoiceRecordStop_f( void ) static const sound_api_t gSoundAPI = { CL_GetEntitySpatialization, S_GetSfxByHandle, + S_RawEntSamples, + SND_ForceInitMouth, + Voice_GetAudioInfo, }; /* diff --git a/engine/client/s_stream.c b/engine/client/s_stream.c index 8e1dd9aa..8fecea05 100644 --- a/engine/client/s_stream.c +++ b/engine/client/s_stream.c @@ -253,7 +253,7 @@ void S_StreamBackgroundTrack( void ) { // add to raw buffer int music_vol = (int)(255.0f * S_GetMusicVolume()); - S_RawEntSamples( S_RAW_SOUND_BACKGROUNDTRACK, fileSamples, info->rate, info->width, info->channels, raw, music_vol ); + S_RawEntSamples( S_RAW_SOUND_BACKGROUNDTRACK, fileSamples, info->rate, info->width, info->channels, raw, music_vol, ATTN_NONE ); } else { diff --git a/engine/client/sound.h b/engine/client/sound.h index 4c8ea4c7..94a44094 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -111,7 +111,7 @@ int S_GetCurrentDynamicSounds( soundlist_t *pout, int size ); sfx_t *S_GetSfxByHandle( sound_t handle ); rawchan_t *S_FindRawChannel( int entnum, qboolean create ); uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data ); -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, float attn ); void S_StopSound( int entnum, int channel, const char *soundname ); void S_UpdateFrame( struct ref_viewpass_s *rvp ); void S_StopAllSounds( qboolean ambient ); diff --git a/engine/client/voice.c b/engine/client/voice.c index c0ddfc5a..0ac97807 100644 --- a/engine/client/voice.c +++ b/engine/client/voice.c @@ -939,8 +939,14 @@ Feed the decoded data to engine sound subsystem */ static void Voice_StartChannel( uint samples, byte *data, int entnum ) { + if( clgame.dllFuncs.pfnVoice_StartChannel != NULL ) + { + if( clgame.dllFuncs.pfnVoice_StartChannel( samples, data, entnum )) + return; + } + SND_ForceInitMouth( entnum ); - S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, data, bound( 0, 255 * voice_scale.value, 255 )); + S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, data, bound( 0, 255 * voice_scale.value, 255 ), ATTN_NONE ); Voice_Status( entnum, true ); } @@ -1194,3 +1200,13 @@ qboolean Voice_Init( const char *pszCodecName, int quality, qboolean preinit ) voice.initialized = true; return true; } + +voice_audio_info_t Voice_GetAudioInfo( void ) +{ + voice_audio_info_t info; + + info.width = voice.width; + info.samplerate = voice.samplerate; + info.frame_size = voice.frame_size; + return info; +} diff --git a/engine/client/voice.h b/engine/client/voice.h index 832d2d6a..69e0ec2d 100644 --- a/engine/client/voice.h +++ b/engine/client/voice.h @@ -131,5 +131,6 @@ void Voice_Disconnect( void ); void Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames ); void Voice_StopChannel( int entnum ); void Voice_LoopbackAck( void ); // sends VOICE_LOOPBACK_INDEX to client, gets disabled on timeout +voice_audio_info_t Voice_GetAudioInfo( void ); #endif // VOICE_H diff --git a/engine/physint.h b/engine/physint.h index 5e4538fc..5b69695f 100644 --- a/engine/physint.h +++ b/engine/physint.h @@ -168,6 +168,8 @@ typedef struct physics_interface_s void *(*SV_HullForBsp)( edict_t *ent, const float *mins, const float *maxs, float *offset ); // handle player custom think function int (*SV_PlayerThink)( edict_t *ent, float frametime, double time ); + // voice data + qboolean (*pfnVoiceData)( int client, uint frames, uint size, qboolean loopback, const char *received ); } physics_interface_t; #endif//PHYSINT_H diff --git a/engine/server/server.h b/engine/server/server.h index d32c5215..3660c180 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -406,6 +406,7 @@ extern convar_t sv_wateralpha; extern convar_t sv_wateramp; extern convar_t sv_voiceenable; extern convar_t sv_voicequality; +extern convar_t sv_voice_singleplayer; extern convar_t sv_maxvelocity; extern convar_t sv_stepsize; extern convar_t sv_skyname; diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index 65a0de4b..5a8aa4d0 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -3571,7 +3571,16 @@ static void SV_ParseVoiceData( sv_client_t *cl, sizebuf_t *msg ) MSG_ReadBytes( msg, received, size ); - if( !sv_voiceenable.value || svs.maxclients <= 1 || cl->state != cs_spawned ) + if( !sv_voiceenable.value || cl->state != cs_spawned ) + return; + + if( svgame.physFuncs.pfnVoiceData != NULL ) + { + if( svgame.physFuncs.pfnVoiceData( client, frames, size, loopback, received )) + return; + } + + if( svs.maxclients <= 1 && sv_voice_singleplayer.value == 0.0f ) return; for( i = 0; i < svs.maxclients; i++ ) diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index 4ca597c0..dcf1b326 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -463,7 +463,7 @@ static void SV_CreateBaseline( void ) int delta_type; int entnum; - if( svs.maxclients > 1 ) + if( svs.maxclients > 1 || sv_voice_singleplayer.value ) SV_WriteVoiceCodec( &sv.signon ); if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 6f4d08c3..cc6fcb9c 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -124,6 +124,7 @@ static 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" ); CVAR_DEFINE_AUTO( sv_voicequality, "3", FCVAR_ARCHIVE, "voice chat quality level, from 0 to 5, higher is better" ); +CVAR_DEFINE_AUTO( sv_voice_singleplayer, "0", FCVAR_ARCHIVE, "enable voice subsystem in singleplayer games (maxclients == 1)" ); // enttools CVAR_DEFINE_AUTO( sv_enttools_enable, "0", FCVAR_ARCHIVE|FCVAR_PROTECTED, "enable powerful and dangerous entity tools" ); @@ -975,6 +976,7 @@ void SV_Init( void ) Cvar_RegisterVariable( &sv_voiceenable ); Cvar_RegisterVariable( &sv_voicequality ); + Cvar_RegisterVariable( &sv_voice_singleplayer ); Cvar_RegisterVariable( &sv_trace_messages ); Cvar_RegisterVariable( &sv_enttools_enable ); Cvar_RegisterVariable( &sv_enttools_maxfire );