/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // snd_dma.c -- main control for any streaming sound output devices #include "quakedef.h" #ifdef __GNUC__ #define fte_weakstruct __attribute__((weak)) #else //msvc's uninitialised symbols are always weak, so this is fine. #define fte_weakstruct #endif #ifdef CSQC_DAT //for sounds following csqc ents #include "pr_common.h" extern world_t csqc_world; #endif static void S_Play_f(void); static void S_SoundList_f(void); #ifdef HAVE_MIXER static void S_Update_(soundcardinfo_t *sc); #endif void S_StopAllSounds(qboolean clear); static void S_StopAllSounds_f (void); static void S_UpdateCard(soundcardinfo_t *sc); static void S_ClearBuffer (soundcardinfo_t *sc); // ======================================================================= // Internal sound data & structures // ======================================================================= soundcardinfo_t *sndcardinfo; //the master card. int snd_blocked = 0; static qboolean snd_ambient = 1; qboolean snd_initialized = false; int snd_speed; float voicevolumemod = 1; static struct listener_s { int entnum; vec3_t origin; vec3_t velocity; vec3_t forward; vec3_t right; vec3_t up; } listener[MAX_SPLITS]; cvar_t snd_nominaldistance = CVARAFD("s_nominaldistance", "1000", "snd_soundradius", CVAR_CHEAT, "This cvar defines how far an attenuation=1 sound can be heard."); #define MAX_SFX 8192 sfx_t *known_sfx; // hunk allocated [MAX_SFX] int num_sfx; sfx_t *ambient_sfx[NUM_AMBIENTS]; //int desired_speed = 44100; int desired_bits = 16; int sound_started=0; cvar_t mastervolume = CVARFD( "mastervolume", "1", CVAR_ARCHIVE, "Additional multiplier for all other sounds."); cvar_t bgmvolume = CVARAFD( "musicvolume", "0.3", "bgmvolume", CVAR_ARCHIVE, "Volume level for background music."); cvar_t volume = CVARAFD( "volume", "0.7", /*q3*/"s_volume",CVAR_ARCHIVE, "Volume level for game sounds (does not affect music, voice, or cinematics)."); cvar_t nosound = CVARFD( "nosound", "0", CVAR_ARCHIVE, "Disable all sound from the engine. Cannot be overriden by configs or anything if set via the -nosound commandline argument."); cvar_t snd_precache = CVARAF( "s_precache", "1", "precache", 0); cvar_t snd_loadas8bit = CVARAFD( "s_loadas8bit", "0", "loadas8bit", CVAR_ARCHIVE, "Downsample sounds on load as lower quality 8-bit sound, to save memory."); #ifdef FTE_TARGET_WEB cvar_t snd_loadasstereo = CVARD( "snd_loadasstereo", "1", "Force mono sounds to load as if stereo ones, to waste memory. Used to work around stupid browser bugs."); #else cvar_t snd_loadasstereo = CVARD( "snd_loadasstereo", "0", "Force mono sounds to load as if stereo ones, to waste memory. Not normally useful."); #endif cvar_t ambient_level = CVARAFD( "s_ambientlevel", "0.3", "ambient_level", CVAR_ARCHIVE, "This controls the volume levels of automatic area-based sounds (like water or sky), and is quite annoying. If you're playing deathmatch you'll definitely want this OFF."); cvar_t ambient_fade = CVARAF( "s_ambientfade", "100", "ambient_fade", CVAR_ARCHIVE); cvar_t snd_noextraupdate = CVARAF( "s_noextraupdate", "0", "snd_noextraupdate", 0); cvar_t snd_show = CVARAF( "s_show", "0", "snd_show", 0); #ifdef __DJGPP__ #define DEFAULT_SND_KHZ "11" #else //fixme: are android devices more likely to use 44.1khz? #define DEFAULT_SND_KHZ "48" //most modern systems should go with 48khz audio (dvd quality). various hardware codecs support nothing else. #endif cvar_t snd_khz = CVARAFD( "s_khz", DEFAULT_SND_KHZ, "snd_khz", CVAR_ARCHIVE, "Sound speed, in kilohertz. Common values are 11, 22, 44, 48. Values above 1000 are explicitly in hertz."); cvar_t snd_inactive = CVARAFD( "s_inactive", "1", "snd_inactive", CVAR_ARCHIVE, "Play sound while application is inactive (ie: tabbed out). Needs a snd_restart if changed." ); //set if you want sound even when tabbed out. cvar_t _snd_mixahead = CVARAFD( "s_mixahead", "0.1", "_snd_mixahead", CVAR_ARCHIVE, "Specifies how many seconds to prebuffer audio. Lower values give less latency, but might result in crackling. Different hardware/drivers have different tolerances, and this setting may be ignored completely where drivers are expected to provide their own tolerances."); cvar_t snd_leftisright = CVARAF( "s_swapstereo", "0", "snd_leftisright", CVAR_ARCHIVE); cvar_t snd_eax = CVARAF( "s_eax", "0", "snd_eax", 0); cvar_t snd_speakers = CVARAFD( "s_numspeakers", "2", "snd_numspeakers", CVAR_ARCHIVE, "Number of hardware audio channels to use. "FULLENGINENAME" supports up to 6."); cvar_t snd_buffersize = CVARAF( "s_buffersize", "0", "snd_buffersize", 0); cvar_t snd_samplebits = CVARAF( "s_bits", "16", "snd_samplebits", CVAR_ARCHIVE); cvar_t snd_playersoundvolume = CVARAFD( "s_localvolume", "1", "snd_localvolume", CVAR_ARCHIVE, "Sound level for sounds local or originating from the player such as firing and pain sounds."); //sugested by crunch cvar_t snd_doppler = CVARAFD( "s_doppler", "0", "snd_doppler", CVAR_ARCHIVE, "Enables doppler, with a multiplier for the scale."); cvar_t snd_doppler_min = CVARAFD( "s_doppler_min", "0.5", "snd_doppler_min", CVAR_ARCHIVE, "Slowest allowed doppler scale."); cvar_t snd_doppler_max = CVARAFD( "s_doppler_max", "2", "snd_doppler_max", CVAR_ARCHIVE, "Highest allowed doppler scale, to avoid things getting too weird."); cvar_t snd_playbackrate = CVARFD( "snd_playbackrate", "1", CVAR_CHEAT, "Debugging cvar that changes the playback rate of all new sounds."); cvar_t snd_ignoregamespeed = CVARFD( "snd_ignoregamespeed", "0", 0, "When set, allows sounds to desynchronise with game time or demo speeds."); cvar_t snd_ignorecueloops = CVARD( "snd_ignorecueloops", "0", "Ignores cue commands in wav files, for q3 compat."); cvar_t snd_linearresample = CVARAF( "s_linearresample", "1", "snd_linearresample", 0); cvar_t snd_linearresample_stream = CVARAF( "s_linearresample_stream", "0", "snd_linearresample_stream", 0); cvar_t snd_mixerthread = CVARAD( "s_mixerthread", "1", "snd_mixerthread", "When enabled sound mixing will be run on a separate thread. Currently supported only by directsound. Other drivers may unconditionally thread audio. Set to 0 only if you have issues."); cvar_t snd_device = CVARAFD( "s_device", "", "snd_device", CVAR_ARCHIVE, "This is the sound device(s) to use, in the form of driver:device.\nIf desired, multiple devices can be listed in space-seperated (quoted) tokens. _s_device_opts contains any enumerated options.\nIn all seriousness, use the menu to set this if you wish to keep your hair."); cvar_t snd_device_opts = CVARFD( "_s_device_opts", "", CVAR_NOSET, "The possible audio output devices, in \"value\" \"description\" pairs, for gamecode to read."); #ifdef VOICECHAT static void QDECL S_Voip_Play_Callback(cvar_t *var, char *oldval); cvar_t snd_voip_capturedevice = CVARF("cl_voip_capturedevice", "", CVAR_ARCHIVE); cvar_t snd_voip_capturedevice_opts = CVARFD("_cl_voip_capturedevice_opts", "", CVAR_NOSET, "The possible audio capture devices, in \"value\" \"description\" pairs, for gamecode to read."); int voipbutton; //+voip, no longer part of cl_voip_send to avoid it getting saved cvar_t snd_voip_send = CVARFD("cl_voip_send", "0", CVAR_ARCHIVE, "Sends voice-over-ip data to the server whenever it is set.\n0: only send voice if +voip is pressed.\n1: voice activation.\n2: constantly send.\n+4: Do not send to game, only to rtp sessions."); cvar_t snd_voip_test = CVARD("cl_voip_test", "0", "If 1, enables you to hear your own voice directly, bypassing the server and thus without networking latency, but is fine for checking audio levels. Note that sv_voip_echo can be set if you want to include latency and packetloss considerations, but setting that cvar requires server admin access and is thus much harder to use."); cvar_t snd_voip_vad_threshhold = CVARFD("cl_voip_vad_threshhold", "15", CVAR_ARCHIVE, "This is the threshhold for voice-activation-detection when sending voip data"); cvar_t snd_voip_vad_delay = CVARD("cl_voip_vad_delay", "0.3", "Keeps sending voice data for this many seconds after voice activation would normally stop"); cvar_t snd_voip_capturingvol = CVARAFD("cl_voip_capturingvol", "0.5", NULL, CVAR_ARCHIVE, "Volume multiplier applied while capturing, to avoid your audio from being heard by others. Does not affect game volume when others speak (minimum of cl_voip_capturingvol and cl_voip_ducking is used)."); cvar_t snd_voip_showmeter = CVARAFD("cl_voip_showmeter", "1", NULL, CVAR_ARCHIVE, "Shows your speech volume above the standard hud. 0=hide, 1=show when transmitting, 2=ignore voice-activation disable"); cvar_t snd_voip_play = CVARAFCD("cl_voip_play", "1", NULL, CVAR_ARCHIVE, S_Voip_Play_Callback, "Enables voip playback. Value is a volume scaler."); cvar_t snd_voip_ducking = CVARAFD("cl_voip_ducking", "0.5", NULL, CVAR_ARCHIVE, "Scales game audio by this much when someone is talking to you. Does not affect your speaker volume when you speak (minimum of cl_voip_capturingvol and cl_voip_ducking is used)."); cvar_t snd_voip_micamp = CVARAFD("cl_voip_micamp", "2", NULL, CVAR_ARCHIVE, "Amplifies your microphone when using voip."); cvar_t snd_voip_codec = CVARAFD("cl_voip_codec", "", NULL, CVAR_ARCHIVE, "0: speex(@11khz). 1: raw. 2: opus. 3: speex(@8khz). 4: speex(@16). 5:speex(@32). 6: pcma. 7: pcmu."); #ifdef HAVE_SPEEX cvar_t snd_voip_noisefilter = CVARAFD("cl_voip_noisefilter", "1", NULL, CVAR_ARCHIVE, "Enable the use of the noise cancelation filter."); cvar_t snd_voip_autogain = CVARAFD("cl_voip_autogain", "0", NULL, CVAR_ARCHIVE, "Attempts to normalize your voice levels to a standard level. Useful for lazy people, but interferes with voice activation levels."); #endif cvar_t snd_voip_bitrate = CVARAFD("cl_voip_bitrate", "3000", NULL, CVAR_ARCHIVE, "For codecs with non-specific bitrates, this specifies the target bitrate to use."); #endif extern vfsfile_t *rawwritefile; #ifdef MULTITHREAD void *mixermutex; void S_LockMixer(void) { Sys_LockMutex(mixermutex); } void S_UnlockMixer(void) { Sys_UnlockMutex(mixermutex); } #else void S_LockMixer(void) { } void S_UnlockMixer(void) { } #endif void S_AmbientOff (void) { snd_ambient = false; } void S_AmbientOn (void) { snd_ambient = true; } qboolean S_HaveOutput(void) { return sound_started && sndcardinfo; } void S_SoundInfo_f(void) { int i, j; int active, known; soundcardinfo_t *sc; if (!sound_started) { Con_Printf ("sound system not started\n"); return; } if (!sndcardinfo) { Con_Printf ("No sound cards\n"); return; } for (sc = sndcardinfo; sc; sc = sc->next) { Con_Printf("Audio Device: %s\n", sc->name); Con_Printf(" %d channels, %gkhz, %d bit audio%s\n", sc->sn.numchannels, sc->sn.speed/1000.0, sc->sn.samplebytes*8, sc->selfpainting?", threaded":""); Con_Printf(" %d samples in buffer\n", sc->sn.samples); for (i = 0, active = 0, known = 0; i < sc->total_chans; i++) { if (sc->channel[i].sfx) { known++; for (j = 0; j < MAXSOUNDCHANNELS; j++) { if (sc->channel[i].vol[j]) { active++; break; } } if (jchannel[i].sfx->name, sc->channel[i].entnum, sc->channel[i].entchannel, sc->channel[i].origin[0], sc->channel[i].origin[1], sc->channel[i].origin[2]); else Con_DPrintf(" %s (%i %i, %g %g %g, inactive)\n", sc->channel[i].sfx->name, sc->channel[i].entnum, sc->channel[i].entchannel, sc->channel[i].origin[0], sc->channel[i].origin[1], sc->channel[i].origin[2]); } } Con_Printf(" %d/%d/%"PRIiSIZE"/%"PRIiSIZE" active/known/highest/max\n", active, known, sc->total_chans, sc->max_chans); for (i = 0; i < sc->sn.numchannels; i++) { Con_Printf(" chan %i: fwd:%-4g rt:%-4g up:%-4g dist:%-4g\n", i, sc->speakerdir[i][0], sc->speakerdir[i][1], sc->speakerdir[i][2], sc->dist[i]); } } } #ifdef VOICECHAT #ifdef SPEEX_STATIC #include #include #else typedef struct {int stuff[15];} SpeexBits; typedef struct SpeexMode SpeexMode; typedef struct SpeexPreprocessState SpeexPreprocessState; typedef qint16_t spx_int16_t; #define SPEEX_MODEID_NB 0 #define SPEEX_MODEID_WB 1 #define SPEEX_MODEID_UWB 2 #define SPEEX_GET_FRAME_SIZE 3 #define SPEEX_SET_SAMPLING_RATE 24 #define SPEEX_GET_SAMPLING_RATE 25 #define SPEEX_PREPROCESS_SET_DENOISE 0 #define SPEEX_PREPROCESS_SET_AGC 2 #define SPEEX_PREPROCESS_SET_AGC_MAX_GAIN 30 #endif enum { VOIP_SPEEX_OLD = 0, //original supported codec (with needless padding and at the wrong rate to keep quake implementations easy) VOIP_RAW16 = 1, //support is not recommended. VOIP_OPUS = 2, //supposed to be better than speex. VOIP_SPEEX_NARROW = 3, //narrowband speex. packed data. VOIP_SPEEX_WIDE = 4, //wideband speex. packed data. VOIP_SPEEX_ULTRAWIDE = 5,//wideband speex. packed data. VOIP_PCMA = 6, //G711 is kinda shit, encoding audio at 8khz with funny truncation for 13bit to 8bit VOIP_PCMU = 7, //ulaw version of g711 (instead of alaw) VOIP_INVALID = 16 //not currently generating audio. }; #if defined(HAVE_LEGACY) && defined(HAVE_OPUS) && defined(HAVE_SPEEX) #define VOIP_DEFAULT_CODEC ((cls.protocol==CP_QUAKEWORLD && !(cls.fteprotocolextensions2&PEXT2_REPLACEMENTDELTAS))?VOIP_SPEEX_OLD:VOIP_OPUS) //opus is preferred, but ezquake is still common and only supports my first attempt at voice compression so favour that for mvdsv servers. #elif defined(HAVE_OPUS) #define VOIP_DEFAULT_CODEC VOIP_OPUS #elif defined(HAVE_SPEEX) #define VOIP_DEFAULT_CODEC VOIP_SPEEX_OLD #else #define VOIP_DEFAULT_CODEC VOIP_PCMA #endif static struct { #ifdef HAVE_SPEEX struct { qboolean inited; qboolean loaded; dllhandle_t *speexlib; SpeexBits encbits; SpeexBits decbits[MAX_CLIENTS]; const SpeexMode *modenb; const SpeexMode *modewb; const SpeexMode *modeuwb; } speex; struct { qboolean inited; qboolean loaded; dllhandle_t *speexdsplib; SpeexPreprocessState *preproc; //filter out noise int curframesize; int cursamplerate; } speexdsp; #endif #ifdef HAVE_OPUS struct { qboolean inited; qboolean loaded; dllhandle_t *opuslib; } opus; #endif unsigned char enccodec; void *encoder; unsigned int encframesize; unsigned int encsamplerate; void *decoder[MAX_CLIENTS]; float declevel[MAX_CLIENTS]; unsigned char deccodec[MAX_CLIENTS]; unsigned char decseq[MAX_CLIENTS]; /*sender's sequence, to detect+cover minor packetloss*/ unsigned char decgen[MAX_CLIENTS]; /*last generation. if it changes, we flush speex to reset packet loss*/ unsigned int decsamplerate[MAX_CLIENTS]; unsigned int decframesize[MAX_CLIENTS]; float lastspoke[MAX_CLIENTS]; /*time when they're no longer considered talking. if future, they're talking (timeout avoids flickering, and harder to troll with fake-tourettes when noone is looking)*/ float lastspoke_any; unsigned char capturebuf[32768]; /*pending data*/ unsigned int capturepos;/*amount of pending data*/ unsigned int encsequence;/*the outgoing sequence count*/ unsigned int enctimestamp;/*for rtp streaming*/ unsigned int generation;/*incremented whenever capture is restarted*/ qboolean wantsend; /*set if we're capturing data to send*/ float voiplevel; /*your own voice level*/ unsigned int dumps; /*trigger a new generation thing after a bit*/ unsigned int keeps; /*for vad_delay*/ int curbitrate; snd_capture_driver_t *cdriver;/*capture driver's functions*/ void *cdriverctx; /*capture driver context*/ } s_voip; #ifdef HAVE_OPUS #define OPUS_APPLICATION_VOIP 2048 #define OPUS_SET_BITRATE_REQUEST 4002 #define OPUS_RESET_STATE 4028 #ifdef OPUS_STATIC #include "opus.h" #define qopus_encoder_create opus_encoder_create #define qopus_encoder_destroy opus_encoder_destroy #define qopus_encoder_ctl opus_encoder_ctl #define qopus_encode opus_encode #define qopus_decoder_create opus_decoder_create #define qopus_decoder_destroy opus_decoder_destroy #define qopus_decoder_ctl opus_decoder_ctl #define qopus_decode opus_decode #else #define opus_int32 int #define opus_int16 short #define OpusEncoder void #define OpusDecoder void static OpusEncoder *(VARGS *qopus_encoder_create)(opus_int32 Fs, int channels, int application, int *error); static void (VARGS *qopus_encoder_destroy)(OpusEncoder *st); static int (VARGS *qopus_encoder_ctl)(OpusEncoder *st, int request, ...); static opus_int32 (VARGS *qopus_encode)(OpusEncoder *st, const opus_int16 *pcm, int frame_size, unsigned char *data, opus_int32 max_data_bytes); static OpusDecoder *(VARGS *qopus_decoder_create)(opus_int32 Fs, int channels, int *error); static void (VARGS *qopus_decoder_destroy)(OpusDecoder *st); static int (VARGS *qopus_decoder_ctl)(OpusDecoder *st, int request, ...); static int (VARGS *qopus_decode)(OpusDecoder *st, const unsigned char *data, opus_int32 len, opus_int16 *pcm, int frame_size, int decode_fec); static dllfunction_t qopusfuncs[] = { {(void*)&qopus_encoder_create, "opus_encoder_create"}, {(void*)&qopus_encoder_destroy, "opus_encoder_destroy"}, {(void*)&qopus_encoder_ctl, "opus_encoder_ctl"}, {(void*)&qopus_encode, "opus_encode"}, {(void*)&qopus_decoder_create, "opus_decoder_create"}, {(void*)&qopus_decoder_destroy, "opus_decoder_destroy"}, {(void*)&qopus_decoder_ctl, "opus_decoder_ctl"}, {(void*)&qopus_decode, "opus_decode"}, {NULL} }; #endif static qboolean S_Opus_Init(void) { #ifndef OPUS_STATIC #ifdef _WIN32 char *modulename = "libopus-0" ARCH_DL_POSTFIX; #else char *modulename = "libopus"ARCH_DL_POSTFIX".0"; #endif if (s_voip.opus.inited) return s_voip.opus.loaded; s_voip.opus.inited = true; s_voip.opus.opuslib = Sys_LoadLibrary(modulename, qopusfuncs); if (!s_voip.opus.opuslib) { Con_Printf("%s not found. Voice chat is not available.\n", modulename); return false; } #endif s_voip.opus.loaded = true; return s_voip.opus.loaded; } #endif #ifdef HAVE_SPEEX #ifdef SPEEX_STATIC #define qspeex_lib_get_mode speex_lib_get_mode #define qspeex_bits_init speex_bits_init #define qspeex_bits_reset speex_bits_reset #define qspeex_bits_write speex_bits_write #define qspeex_preprocess_state_init speex_preprocess_state_init #define qspeex_preprocess_state_destroy speex_preprocess_state_destroy #define qspeex_preprocess_ctl speex_preprocess_ctl #define qspeex_preprocess_run speex_preprocess_run #define qspeex_encoder_init speex_encoder_init #define qspeex_encoder_destroy speex_encoder_destroy #define qspeex_encoder_ctl speex_encoder_ctl #define qspeex_encode_int speex_encode_int #define qspeex_decoder_init speex_decoder_init #define qspeex_decoder_destroy speex_decoder_destroy #define qspeex_decode_int speex_decode_int #define qspeex_bits_read_from speex_bits_read_from #else static const SpeexMode *(VARGS *qspeex_lib_get_mode)(int mode); static void (VARGS *qspeex_bits_init)(SpeexBits *bits); static void (VARGS *qspeex_bits_reset)(SpeexBits *bits); static int (VARGS *qspeex_bits_write)(SpeexBits *bits, char *bytes, int max_len); static SpeexPreprocessState *(VARGS *qspeex_preprocess_state_init)(int frame_size, int sampling_rate); static void (VARGS *qspeex_preprocess_state_destroy)(SpeexPreprocessState *st); static int (VARGS *qspeex_preprocess_ctl)(SpeexPreprocessState *st, int request, void *ptr); static int (VARGS *qspeex_preprocess_run)(SpeexPreprocessState *st, spx_int16_t *x); static void * (VARGS *qspeex_encoder_init)(const SpeexMode *mode); static int (VARGS *qspeex_encoder_ctl)(void *state, int request, void *ptr); static int (VARGS *qspeex_encode_int)(void *state, spx_int16_t *in, SpeexBits *bits); static void *(VARGS *qspeex_decoder_init)(const SpeexMode *mode); static void (VARGS *qspeex_decoder_destroy)(void *state); static int (VARGS *qspeex_decode_int)(void *state, SpeexBits *bits, spx_int16_t *out); static void (VARGS *qspeex_bits_read_from)(SpeexBits *bits, char *bytes, int len); static dllfunction_t qspeexfuncs[] = { {(void*)&qspeex_lib_get_mode, "speex_lib_get_mode"}, {(void*)&qspeex_bits_init, "speex_bits_init"}, {(void*)&qspeex_bits_reset, "speex_bits_reset"}, {(void*)&qspeex_bits_write, "speex_bits_write"}, {(void*)&qspeex_encoder_init, "speex_encoder_init"}, {(void*)&qspeex_encoder_ctl, "speex_encoder_ctl"}, {(void*)&qspeex_encode_int, "speex_encode_int"}, {(void*)&qspeex_decoder_init, "speex_decoder_init"}, {(void*)&qspeex_decoder_destroy, "speex_decoder_destroy"}, {(void*)&qspeex_decode_int, "speex_decode_int"}, {(void*)&qspeex_bits_read_from, "speex_bits_read_from"}, {NULL} }; static dllfunction_t qspeexdspfuncs[] = { {(void*)&qspeex_preprocess_state_init, "speex_preprocess_state_init"}, {(void*)&qspeex_preprocess_state_destroy, "speex_preprocess_state_destroy"}, {(void*)&qspeex_preprocess_ctl, "speex_preprocess_ctl"}, {(void*)&qspeex_preprocess_run, "speex_preprocess_run"}, {NULL} }; #endif static qboolean S_SpeexDSP_Init(void) { #ifndef SPEEX_STATIC if (s_voip.speexdsp.inited) return s_voip.speexdsp.loaded; s_voip.speexdsp.inited = true; s_voip.speexdsp.speexdsplib = Sys_LoadLibrary("libspeexdsp", qspeexdspfuncs); if (!s_voip.speexdsp.speexdsplib) { Con_Printf("libspeexdsp not found. Your mic may be noisy.\n"); return false; } #endif s_voip.speexdsp.loaded = true; return s_voip.speexdsp.loaded; } static qboolean S_Speex_Init(void) { #ifndef SPEEX_STATIC if (s_voip.speex.inited) return s_voip.speex.loaded; s_voip.speex.inited = true; s_voip.speex.speexlib = Sys_LoadLibrary("libspeex", qspeexfuncs); if (!s_voip.speex.speexlib) { Con_Printf("libspeex not found. Voice chat is not available.\n"); return false; } #endif s_voip.speex.modenb = qspeex_lib_get_mode(SPEEX_MODEID_NB); s_voip.speex.modewb = qspeex_lib_get_mode(SPEEX_MODEID_WB); s_voip.speex.modeuwb = qspeex_lib_get_mode(SPEEX_MODEID_UWB); s_voip.speex.loaded = true; return s_voip.speex.loaded; } #endif #ifdef AVAIL_OPENAL extern snd_capture_driver_t OPENAL_Capture; #endif #ifdef _WIN32 snd_capture_driver_t fte_weakstruct DSOUND_Capture; #endif snd_capture_driver_t fte_weakstruct OSS_Capture; snd_capture_driver_t fte_weakstruct SDL_Capture; snd_capture_driver_t *capturedrivers[] = { #ifdef _WIN32 &DSOUND_Capture, #endif &SDL_Capture, &OSS_Capture, #ifdef AVAIL_OPENAL &OPENAL_Capture, #endif NULL }; size_t PCMA_Decode(short *out, unsigned char *in, size_t samples) { size_t i = 0; for (i = 0; i < samples; i++) { unsigned char inv = in[i]^0x55; //g711 alaw inverts every other bit int m = inv&0xf; int e = (inv&0x70)>>4; if (e) m = (((m)<<1)|0x21) << (e-1); else m = (((m)<<1)|1); if (inv & 0x80) out[i] = -m; else out[i] = m; } return i; } size_t PCMA_Encode(unsigned char *out, size_t outsize, short *in, size_t samples) { size_t i = 0; for (i = 0; i < samples; i++) { int o = in[i]; unsigned char b; if (o < 0) { o = -o; b = 0x80; } else b = 0; if (o >= 0x0800) b |= ((o>>7)&0xf) | 0x70; else if (o >= 0x0400) b |= ((o>>6)&0xf) | 0x60; else if (o >= 0x0200) b |= ((o>>5)&0xf) | 0x50; else if (o >= 0x0100) b |= ((o>>4)&0xf) | 0x40; else if (o >= 0x0080) b |= ((o>>3)&0xf) | 0x30; else if (o >= 0x0040) b |= ((o>>2)&0xf) | 0x20; else if (o >= 0x0020) b |= ((o>>1)&0xf) | 0x10; else b |= ((o>>1)&0xf) | 0x00; out[i] = b^0x55; //invert every-other bit. } return samples; } size_t PCMU_Decode(short *out, unsigned char *in, size_t samples) { size_t i = 0; for (i = 0; i < samples; i++) { unsigned char inv = in[i]^0xff; int m = (((inv&0xf)<<1)|0x21) << ((inv&0x70)>>4); m -= 33; if (inv & 0x80) out[i] = -m; else out[i] = m; } return i; } size_t PCMU_Encode(unsigned char *out, size_t outsize, short *in, size_t samples) { size_t i = 0; for (i = 0; i < samples; i++) { int o = in[i]; unsigned char b; if (o < 0) { o = ~o; b = 0x80; } else b = 0; o+=33; if (o >= 0x1000) b |= ((o>>8)&0xf) | 0x70; else if (o >= 0x0800) b |= ((o>>7)&0xf) | 0x60; else if (o >= 0x0400) b |= ((o>>6)&0xf) | 0x50; else if (o >= 0x0200) b |= ((o>>5)&0xf) | 0x40; else if (o >= 0x0100) b |= ((o>>4)&0xf) | 0x30; else if (o >= 0x0080) b |= ((o>>3)&0xf) | 0x20; else if (o >= 0x0040) b |= ((o>>2)&0xf) | 0x10; else b |= ((o>>1)&0xf) | 0x00; out[i] = b^0xff; } return samples; } void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, unsigned char seq, unsigned int bytes, unsigned char *data) { unsigned char *start; short decodebuf[8192]; unsigned int decodesamps, len, drops; int r; if (sender >= MAX_CLIENTS) return; decodesamps = 0; drops = 0; start = data; s_voip.lastspoke[sender] = realtime + 0.5; if (s_voip.lastspoke[sender] > s_voip.lastspoke_any) s_voip.lastspoke_any = s_voip.lastspoke[sender]; //if they re-started speaking, flush any old state to avoid things getting weirdly delayed and reset the codec properly. if (s_voip.decgen[sender] != gen || s_voip.deccodec[sender] != codec) { S_RawAudio(sender, NULL, s_voip.decsamplerate[sender], 0, 1, 2, 0); if (s_voip.deccodec[sender] != codec) { //make sure old state is closed properly. switch(s_voip.deccodec[sender]) { #ifdef HAVE_SPEEX case VOIP_SPEEX_OLD: case VOIP_SPEEX_NARROW: case VOIP_SPEEX_WIDE: case VOIP_SPEEX_ULTRAWIDE: qspeex_decoder_destroy(s_voip.decoder[sender]); break; #endif case VOIP_RAW16: break; #ifdef HAVE_OPUS case VOIP_OPUS: qopus_decoder_destroy(s_voip.decoder[sender]); break; #endif } s_voip.decoder[sender] = NULL; s_voip.deccodec[sender] = VOIP_INVALID; } switch(codec) { default: //codec not supported. return; case VOIP_RAW16: s_voip.decsamplerate[sender] = 11025; break; case VOIP_PCMA: case VOIP_PCMU: s_voip.decsamplerate[sender] = 8000; s_voip.decframesize[sender] = 8000/20; break; #ifdef HAVE_SPEEX case VOIP_SPEEX_OLD: case VOIP_SPEEX_NARROW: case VOIP_SPEEX_WIDE: case VOIP_SPEEX_ULTRAWIDE: { const SpeexMode *smode; if (!S_Speex_Init()) return; //speex not usable. if (codec == VOIP_SPEEX_NARROW) { s_voip.decsamplerate[sender] = 8000; s_voip.decframesize[sender] = 160; smode = s_voip.speex.modenb; } else if (codec == VOIP_SPEEX_WIDE) { s_voip.decsamplerate[sender] = 16000; s_voip.decframesize[sender] = 320; smode = s_voip.speex.modewb; } else if (codec == VOIP_SPEEX_ULTRAWIDE) { s_voip.decsamplerate[sender] = 32000; s_voip.decframesize[sender] = 640; smode = s_voip.speex.modeuwb; } else { s_voip.decsamplerate[sender] = 11025; s_voip.decframesize[sender] = 160; smode = s_voip.speex.modenb; } if (!s_voip.decoder[sender]) { qspeex_bits_init(&s_voip.speex.decbits[sender]); qspeex_bits_reset(&s_voip.speex.decbits[sender]); s_voip.decoder[sender] = qspeex_decoder_init(smode); if (!s_voip.decoder[sender]) return; } else qspeex_bits_reset(&s_voip.speex.decbits[sender]); } break; #endif #ifdef HAVE_OPUS case VOIP_OPUS: if (!S_Opus_Init()) return; //the lazy way to reset the codec! if (!s_voip.decoder[sender]) { //opus outputs to 8, 12, 16, 24, or 48khz. pick whichever has least excess samples and resample to fit it. if (snd_speed <= 8000) s_voip.decsamplerate[sender] = 8000; else if (snd_speed <= 12000) s_voip.decsamplerate[sender] = 12000; else if (snd_speed <= 16000) s_voip.decsamplerate[sender] = 16000; else if (snd_speed <= 24000) s_voip.decsamplerate[sender] = 24000; else s_voip.decsamplerate[sender] = 48000; s_voip.decoder[sender] = qopus_decoder_create(s_voip.decsamplerate[sender], 1/*FIXME: support stereo where possible*/, NULL); if (!s_voip.decoder[sender]) return; s_voip.decframesize[sender] = s_voip.decsamplerate[sender]/400; //this is the maximum size in a single frame. } else qopus_decoder_ctl(s_voip.decoder[sender], OPUS_RESET_STATE); break; #endif } s_voip.deccodec[sender] = codec; s_voip.decgen[sender] = gen; s_voip.decseq[sender] = seq; s_voip.declevel[sender] = 0; } //if there's packetloss, tell the decoder about the missing parts. //no infinite loops please. if ((unsigned)(seq - s_voip.decseq[sender]) > 10) s_voip.decseq[sender] = seq - 10; while(s_voip.decseq[sender] != seq) { if (decodesamps + s_voip.decframesize[sender] > sizeof(decodebuf)/sizeof(decodebuf[0])) { S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, snd_voip_play.value); decodesamps = 0; } switch(codec) { case VOIP_RAW16: case VOIP_PCMA: case VOIP_PCMU: break; #ifdef HAVE_SPEEX case VOIP_SPEEX_OLD: case VOIP_SPEEX_NARROW: case VOIP_SPEEX_WIDE: case VOIP_SPEEX_ULTRAWIDE: qspeex_decode_int(s_voip.decoder[sender], NULL, decodebuf + decodesamps); decodesamps += s_voip.decframesize[sender]; break; #endif #ifdef HAVE_OPUS case VOIP_OPUS: r = qopus_decode(s_voip.decoder[sender], NULL, 0, decodebuf + decodesamps, min(s_voip.decframesize[sender], sizeof(decodebuf)/sizeof(decodebuf[0]) - decodesamps), false); if (r > 0) decodesamps += r; break; #endif } s_voip.decseq[sender]++; } while (bytes > 0) { if (decodesamps + s_voip.decframesize[sender] >= sizeof(decodebuf)/sizeof(decodebuf[0])) { S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, snd_voip_play.value); decodesamps = 0; } switch(codec) { default: bytes = 0; break; #ifdef HAVE_SPEEX case VOIP_SPEEX_OLD: case VOIP_SPEEX_NARROW: case VOIP_SPEEX_WIDE: case VOIP_SPEEX_ULTRAWIDE: if (codec == VOIP_SPEEX_OLD) { //older versions support only this, and require this extra bit. bytes--; len = *start++; if (bytes < len) break; } else len = bytes; qspeex_bits_read_from(&s_voip.speex.decbits[sender], start, len); bytes -= len; start += len; while (qspeex_decode_int(s_voip.decoder[sender], &s_voip.speex.decbits[sender], decodebuf + decodesamps) == 0) { decodesamps += s_voip.decframesize[sender]; s_voip.decseq[sender]++; seq++; if (decodesamps + s_voip.decframesize[sender] >= sizeof(decodebuf)/sizeof(decodebuf[0])) { S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, snd_voip_play.value); decodesamps = 0; } } break; #endif case VOIP_RAW16: len = min(bytes, sizeof(decodebuf)-(sizeof(decodebuf[0])*decodesamps)); memcpy(decodebuf+decodesamps, start, len); decodesamps += len / sizeof(decodebuf[0]); s_voip.decseq[sender]++; bytes -= len; start += len; break; case VOIP_PCMA: case VOIP_PCMU: len = min(bytes, sizeof(decodebuf)-(sizeof(decodebuf[0])*decodesamps)); if (len > s_voip.decframesize[sender]*2) len = s_voip.decframesize[sender]*2; if (codec == VOIP_PCMA) decodesamps += PCMA_Decode(decodebuf+decodesamps, start, len); else decodesamps += PCMU_Decode(decodebuf+decodesamps, start, len); s_voip.decseq[sender]++; bytes -= len; start += len; break; #ifdef HAVE_OPUS case VOIP_OPUS: len = bytes; if (decodesamps > 0) { S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, snd_voip_play.value); decodesamps = 0; } r = qopus_decode(s_voip.decoder[sender], start, len, decodebuf + decodesamps, sizeof(decodebuf)/sizeof(decodebuf[0]) - decodesamps, false); // Con_Printf("Decoded %i frames from %i bytes\n", r, len); if (r > 0) { int frames = r / s_voip.decframesize[sender]; decodesamps += r; s_voip.decseq[sender] = (s_voip.decseq[sender] + frames) & 0xff; seq = (seq+frames)&0xff; } else if (r < 0) Con_Printf("Opus decoding error %i\n", r); bytes -= len; start += len; break; #endif } } if (drops) Con_DPrintf("%i dropped audio frames\n", drops); if (decodesamps > 0) { //calculate levels of other people. eukara demanded this. float level = 0; float f; for (len = 0; len < decodesamps; len++) { f = decodebuf[len]; level += f*f; } level = (3000*level) / (32767.0f*32767*decodesamps); s_voip.declevel[sender] = (s_voip.declevel[sender]*7 + level)/8; S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, snd_voip_play.value); } } #ifdef SUPPORT_ICE static int S_Voip_NameToId(const char *codec) { if (!Q_strcasecmp(codec, "speex@8000")) return VOIP_SPEEX_NARROW; else if (!Q_strcasecmp(codec, "speex@11025")) return VOIP_SPEEX_OLD; else if (!Q_strcasecmp(codec, "speex@16000")) return VOIP_SPEEX_WIDE; else if (!Q_strcasecmp(codec, "speex@32000")) return VOIP_SPEEX_ULTRAWIDE; else if (!Q_strcasecmp(codec, "opus") || !strcmp(codec, "opus@48000")) return VOIP_OPUS; else if (!Q_strcasecmp(codec, "pcma@8000")) return VOIP_PCMA; else if (!Q_strcasecmp(codec, "pcmu@8000")) return VOIP_PCMU; else return VOIP_INVALID; } qboolean S_Voip_RTP_CodecOkay(const char *codec) { switch(S_Voip_NameToId(codec)) { #ifdef HAVE_SPEEX case VOIP_SPEEX_NARROW: case VOIP_SPEEX_OLD: case VOIP_SPEEX_WIDE: case VOIP_SPEEX_ULTRAWIDE: return S_Speex_Init(); #endif case VOIP_PCMA: case VOIP_PCMU: return true; #ifdef HAVE_OPUS case VOIP_OPUS: return S_Opus_Init(); #endif default: return false; } } void S_Voip_RTP_Parse(unsigned short sequence, char *codec, unsigned char *data, unsigned int datalen) { S_Voip_Decode(MAX_CLIENTS-1, S_Voip_NameToId(codec), 0, sequence&0xff, datalen, data); } qboolean NET_RTP_Transmit(unsigned int sequence, unsigned int timestamp, const char *codec, char *cdata, int clength); qboolean NET_RTP_Active(void); #else #define NET_RTP_Active() false #endif void S_Voip_Parse(void) { unsigned int sender; unsigned int bytes; unsigned char data[1024]; unsigned char seq, gen; unsigned char codec; sender = MSG_ReadByte(); gen = MSG_ReadByte(); codec = gen>>4; gen &= 0x0f; seq = MSG_ReadByte(); bytes = MSG_ReadShort(); if (bytes > sizeof(data) || snd_voip_play.value <= 0) { MSG_ReadSkip(bytes); return; } MSG_ReadData(data, bytes); sender %= MAX_CLIENTS; //if testing, don't get confused if the server is echoing voice too! if (snd_voip_test.ival) if (sender == cl.playerview[0].playernum) return; S_Voip_Decode(sender, codec, gen, seq, bytes, data); } static float S_Voip_Preprocess(short *start, unsigned int samples, float micamp) { int i; float level = 0, f; int framesize = s_voip.encframesize; while(samples >= framesize) { #ifdef HAVE_SPEEX if (s_voip.speexdsp.preproc) qspeex_preprocess_run(s_voip.speexdsp.preproc, start); #endif for (i = 0; i < framesize; i++) { f = start[i] * micamp; start[i] = bound(-32768, f, 32767); //clamp it carefully, so it doesn't go to crap when given far too high a mic amp level += f*f; } start += framesize; samples -= framesize; } return level; } static void S_Voip_TryInitCaptureContext(char *driver, char *device, int rate) { int i; s_voip.cdriver = NULL; /*Add new drivers in order of priority*/ for (i = 0; capturedrivers[i]; i++) { if (capturedrivers[i]->Init && (!driver || !strcmp(capturedrivers[i]->drivername, driver))) { s_voip.cdriver = capturedrivers[i]; s_voip.cdriverctx = s_voip.cdriver->Init(s_voip.encsamplerate, device); if (s_voip.cdriverctx) { //success! return; } } } if (!s_voip.cdriver) { if (!driver) Con_Printf("No microphone drivers supported\n"); else Con_Printf("Microphone driver \"%s\" is not valid\n", driver); } else Con_Printf("No microphone detected\n"); s_voip.cdriver = NULL; } static void S_Voip_InitCaptureContext(int rate) { char *s; s_voip.cdriver = NULL; s_voip.cdriverctx = NULL; for (s = snd_voip_capturedevice.string; ; ) { char *sep; s = COM_Parse(s); if (!*com_token) break; sep = strchr(com_token, ':'); if (sep) *sep++ = 0; S_Voip_TryInitCaptureContext(com_token, sep, rate); } if (!s_voip.cdriver) S_Voip_TryInitCaptureContext(NULL, NULL, rate); } void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) { unsigned char outbuf[8192]; unsigned int outpos;//in bytes unsigned int encpos;//in bytes short *start; unsigned int initseq;//in frames #ifdef SUPPORT_ICE unsigned int inittimestamp;//in samples #endif unsigned int samps; float level; int len; float micamp = snd_voip_micamp.value; qboolean voipsendenable = true; int voipcodec = *snd_voip_codec.string?snd_voip_codec.ival:VOIP_DEFAULT_CODEC; qboolean rtpstream = NET_RTP_Active(); if (buf) { /*if you're sending sound, you should be prepared to accept others yelling at you to shut up*/ if (snd_voip_play.value <= 0) voipsendenable = false; /*don't send sound if its not supported. that'll break stuff*/ if (!(cls.fteprotocolextensions2 & PEXT2_VOICECHAT)) voipsendenable = false; } else { /*we're not sending it to a server. the above considerations don't matter*/ voipsendenable = snd_voip_test.ival; } /*don't send sound if mic volume won't send anything anyway*/ if (micamp <= 0) voipsendenable = false; if (rtpstream) { voipsendenable = true; //if rtp streaming is enabled, hack the codec to something better supported #ifdef HAVE_SPEEX if (voipcodec == VOIP_SPEEX_OLD) voipcodec = VOIP_SPEEX_WIDE; #endif } voicevolumemod = s_voip.lastspoke_any > realtime?snd_voip_ducking.value:1; voicevolumemod *= mastervolume.value; if (!voipsendenable || (voipcodec != s_voip.enccodec && s_voip.cdriver)) { if (s_voip.cdriver) { if (s_voip.cdriverctx) { if (s_voip.wantsend) { s_voip.cdriver->Stop(s_voip.cdriverctx); s_voip.wantsend = false; } s_voip.cdriver->Shutdown(s_voip.cdriverctx); s_voip.cdriverctx = NULL; } s_voip.cdriver = NULL; } switch(s_voip.enccodec) { #ifdef HAVE_SPEEX case VOIP_SPEEX_OLD: case VOIP_SPEEX_NARROW: case VOIP_SPEEX_WIDE: case VOIP_SPEEX_ULTRAWIDE: break; #endif #ifdef HAVE_OPUS case VOIP_OPUS: qopus_encoder_destroy(s_voip.encoder); break; #endif } s_voip.encoder = NULL; s_voip.enccodec = VOIP_INVALID; if (!voipsendenable) return; } voipsendenable = voipbutton || (snd_voip_send.ival>0); if (!s_voip.cdriver) { s_voip.voiplevel = -1; /*only init the first time capturing is requested*/ if (!voipsendenable) return; /*see if we can init our encoding codec...*/ switch(voipcodec) { #ifdef HAVE_SPEEX case VOIP_SPEEX_OLD: case VOIP_SPEEX_NARROW: case VOIP_SPEEX_WIDE: case VOIP_SPEEX_ULTRAWIDE: { const SpeexMode *smode; if (!S_Speex_Init()) { Con_Printf("Unable to use speex codec - not installed\n"); return; } if (voipcodec == VOIP_SPEEX_ULTRAWIDE) smode = s_voip.speex.modeuwb; else if (voipcodec == VOIP_SPEEX_WIDE) smode = s_voip.speex.modewb; else smode = s_voip.speex.modenb; qspeex_bits_init(&s_voip.speex.encbits); qspeex_bits_reset(&s_voip.speex.encbits); s_voip.encoder = qspeex_encoder_init(smode); if (!s_voip.encoder) return; qspeex_encoder_ctl(s_voip.encoder, SPEEX_GET_FRAME_SIZE, &s_voip.encframesize); qspeex_encoder_ctl(s_voip.encoder, SPEEX_GET_SAMPLING_RATE, &s_voip.encsamplerate); if (voipcodec == VOIP_SPEEX_NARROW) s_voip.encsamplerate = 8000; else if (voipcodec == VOIP_SPEEX_WIDE) s_voip.encsamplerate = 16000; else if (voipcodec == VOIP_SPEEX_ULTRAWIDE) s_voip.encsamplerate = 32000; else s_voip.encsamplerate = 11025; qspeex_encoder_ctl(s_voip.encoder, SPEEX_SET_SAMPLING_RATE, &s_voip.encsamplerate); } break; #endif case VOIP_PCMA: case VOIP_PCMU: s_voip.encsamplerate = 8000; s_voip.encframesize = 8000/20; break; case VOIP_RAW16: s_voip.encsamplerate = 11025; s_voip.encframesize = 256; break; #ifdef HAVE_OPUS case VOIP_OPUS: if (!S_Opus_Init()) { Con_Printf("Unable to use opus codec - not installed\n"); return; } //use whatever is convienient. s_voip.encsamplerate = 48000; s_voip.encframesize = s_voip.encsamplerate / 400; //2.5ms frames, at a minimum. s_voip.encoder = qopus_encoder_create(s_voip.encsamplerate, 1, OPUS_APPLICATION_VOIP, NULL); if (!s_voip.encoder) return; s_voip.curbitrate = 0; // opus_encoder_ctl(s_voip.encoder, OPUS_SET_BITRATE(bitrate_bps)); // opus_encoder_ctl(s_voip.encoder, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_NARROWBAND)); // opus_encoder_ctl(s_voip.encoder, OPUS_SET_VBR(use_vbr)); // opus_encoder_ctl(s_voip.encoder, OPUS_SET_VBR_CONSTRAINT(cvbr)); // opus_encoder_ctl(s_voip.encoder, OPUS_SET_COMPLEXITY(complexity)); // opus_encoder_ctl(s_voip.encoder, OPUS_SET_INBAND_FEC(use_inbandfec)); // opus_encoder_ctl(s_voip.encoder, OPUS_SET_FORCE_CHANNELS(forcechannels)); // opus_encoder_ctl(s_voip.encoder, OPUS_SET_DTX(use_dtx)); // opus_encoder_ctl(s_voip.encoder, OPUS_SET_PACKET_LOSS_PERC(packet_loss_perc)); // opus_encoder_ctl(s_voip.encoder, OPUS_GET_LOOKAHEAD(&skip)); // opus_encoder_ctl(s_voip.encoder, OPUS_SET_LSB_DEPTH(16)); break; #endif default: Con_Printf("Unable to use that codec - not implemented\n"); //can't start up other coedcs, cos we're too lame. return; } s_voip.enccodec = voipcodec; S_Voip_InitCaptureContext(s_voip.encsamplerate); //sets cdriver+cdriverctx } /*couldn't init a driver?*/ if (!s_voip.cdriverctx || !s_voip.cdriver) { return; } if (!voipsendenable && s_voip.wantsend) { s_voip.wantsend = false; s_voip.capturepos += s_voip.cdriver->Update(s_voip.cdriverctx, (unsigned char*)s_voip.capturebuf + s_voip.capturepos, 1, sizeof(s_voip.capturebuf) - s_voip.capturepos); s_voip.cdriver->Stop(s_voip.cdriverctx); /*note: we still grab audio to flush everything that was captured while it was active*/ } else if (voipsendenable && !s_voip.wantsend) { s_voip.wantsend = true; if (!s_voip.capturepos) { /*if we were actually still sending, it was probably only off for a single frame, in which case don't reset it*/ s_voip.dumps = 0; s_voip.generation++; s_voip.encsequence = 0; //reset codecs so they start with a clean slate when new audio blocks are generated. switch(s_voip.enccodec) { #ifdef HAVE_SPEEX case VOIP_SPEEX_OLD: case VOIP_SPEEX_NARROW: case VOIP_SPEEX_WIDE: case VOIP_SPEEX_ULTRAWIDE: qspeex_bits_reset(&s_voip.speex.encbits); break; #endif case VOIP_RAW16: break; #ifdef HAVE_OPUS case VOIP_OPUS: qopus_encoder_ctl(s_voip.encoder, OPUS_RESET_STATE); break; #endif } } else { s_voip.capturepos += s_voip.cdriver->Update(s_voip.cdriverctx, (unsigned char*)s_voip.capturebuf + s_voip.capturepos, 1, sizeof(s_voip.capturebuf) - s_voip.capturepos); } s_voip.cdriver->Start(s_voip.cdriverctx); } if (s_voip.wantsend) voicevolumemod = min(voicevolumemod, snd_voip_capturingvol.value); s_voip.capturepos += s_voip.cdriver->Update(s_voip.cdriverctx, (unsigned char*)s_voip.capturebuf + s_voip.capturepos, s_voip.encframesize*2, sizeof(s_voip.capturebuf) - s_voip.capturepos); if (!s_voip.wantsend && s_voip.capturepos < s_voip.encframesize*2) { s_voip.voiplevel = -1; s_voip.capturepos = 0; return; } initseq = s_voip.encsequence; #ifdef SUPPORT_ICE inittimestamp = s_voip.enctimestamp; #endif level = 0; samps=0; //*2 for 16bit audio input. for (encpos = 0, outpos = 0; encpos+s_voip.encframesize*2 <= s_voip.capturepos && outpos+256 < sizeof(outbuf); ) { start = (short*)(s_voip.capturebuf + encpos); #ifdef HAVE_SPEEX if (snd_voip_noisefilter.ival || snd_voip_autogain.ival) { if (!s_voip.speexdsp.preproc || snd_voip_noisefilter.modified || snd_voip_noisefilter.modified || s_voip.speexdsp.curframesize != s_voip.encframesize || s_voip.speexdsp.cursamplerate != s_voip.encsamplerate) { if (s_voip.speexdsp.preproc) qspeex_preprocess_state_destroy(s_voip.speexdsp.preproc); s_voip.speexdsp.preproc = NULL; if (S_SpeexDSP_Init()) { int i; s_voip.speexdsp.preproc = qspeex_preprocess_state_init(s_voip.encframesize, s_voip.encsamplerate); i = snd_voip_noisefilter.ival; qspeex_preprocess_ctl(s_voip.speexdsp.preproc, SPEEX_PREPROCESS_SET_DENOISE, &i); i = snd_voip_autogain.ival; qspeex_preprocess_ctl(s_voip.speexdsp.preproc, SPEEX_PREPROCESS_SET_AGC, &i); s_voip.speexdsp.curframesize = s_voip.encframesize; s_voip.speexdsp.cursamplerate = s_voip.encsamplerate; } } } else if (s_voip.speexdsp.preproc) { qspeex_preprocess_state_destroy(s_voip.speexdsp.preproc); s_voip.speexdsp.preproc = NULL; } #endif switch(s_voip.enccodec) { #ifdef HAVE_SPEEX case VOIP_SPEEX_OLD: //this is from before I understood speex properly. level += S_Voip_Preprocess(start, s_voip.encframesize, micamp); qspeex_bits_reset(&s_voip.speex.encbits); qspeex_encode_int(s_voip.encoder, start, &s_voip.speex.encbits); len = qspeex_bits_write(&s_voip.speex.encbits, outbuf+(outpos+1), sizeof(outbuf) - (outpos+1)); if (len < 0 || len > 255) len = 0; outbuf[outpos] = len; outpos += 1+len; s_voip.encsequence++; samps+=s_voip.encframesize; encpos += s_voip.encframesize*2; break; case VOIP_SPEEX_NARROW: case VOIP_SPEEX_WIDE: case VOIP_SPEEX_ULTRAWIDE: //write multiple speex frames into a single merged frame qspeex_bits_reset(&s_voip.speex.encbits); for (; encpos+s_voip.encframesize*2 <= s_voip.capturepos; ) { start = (short*)(s_voip.capturebuf + encpos); level += S_Voip_Preprocess(start, s_voip.encframesize, micamp); qspeex_encode_int(s_voip.encoder, start, &s_voip.speex.encbits); s_voip.encsequence++; samps+=s_voip.encframesize; encpos += s_voip.encframesize*2; if (rtpstream) //FIXME: why? break; } len = qspeex_bits_write(&s_voip.speex.encbits, outbuf+outpos, sizeof(outbuf) - outpos); outpos += len; break; #endif case VOIP_RAW16: len = s_voip.capturepos-encpos; //amount of data to be eaten in this frame len = min(len, sizeof(outbuf)-outpos); len &= ~((s_voip.encframesize*2)-1); level += S_Voip_Preprocess(start, len/2, micamp); memcpy(outbuf+outpos, start, len); //'encode' outpos += len; //bytes written to output encpos += len; //number of bytes consumed s_voip.encsequence++; //increment number of packets, for packetloss detection. samps+=len / 2; //number of samplepairs eaten in this packet. for stats. break; case VOIP_PCMA: case VOIP_PCMU: //FIXME: what's with this /2? these are just 8-bit mono (logarithmic) pcm... len = s_voip.capturepos-encpos; //amount of data to be eaten in this frame len = min(len, sizeof(outbuf)-outpos); len = min(len, s_voip.encframesize*2); level += S_Voip_Preprocess(start, len/2, micamp); if (s_voip.enccodec == VOIP_PCMA) outpos += PCMA_Encode(outbuf+outpos, sizeof(outbuf)-outpos, start, len/2); else outpos += PCMU_Encode(outbuf+outpos, sizeof(outbuf)-outpos, start, len/2); encpos += len; //number of bytes consumed s_voip.encsequence++; //increment number of packets, for packetloss detection. samps+=len / 2; //number of samplepairs eaten in this packet. for stats. break; #ifdef HAVE_OPUS case VOIP_OPUS: { //opus rtp only supports/allows a single chunk in each packet. int frames; int nrate; //densely pack the frames. start = (short*)(s_voip.capturebuf + encpos); frames = (s_voip.capturepos-encpos)/2; nrate = snd_voip_bitrate.value; if (nrate != s_voip.curbitrate) { s_voip.curbitrate = nrate; if (nrate == 0) nrate = -1000; qopus_encoder_ctl(s_voip.encoder, OPUS_SET_BITRATE_REQUEST, (int)nrate); nrate = 10000; } if (frames >= 2880) frames = 2880; else if (frames >= 1920 && nrate > 100) frames = 1920; else if (frames >= 960 && nrate > 500) frames = 960; else if (frames >= 480 && nrate > 1000) frames = 480; else if (snd_voip_send.ival & 4) break; //don't send small rtp packets, its abusive. else if (frames >= 240 && nrate > 2000) frames = 240; else if (frames >= 120 && nrate > 4000) frames = 120; else break; //invalid size, wait for more. level += S_Voip_Preprocess(start, frames, micamp); len = qopus_encode(s_voip.encoder, start, frames, outbuf+outpos, sizeof(outbuf) - outpos); if (len >= 0) { s_voip.encsequence += frames / s_voip.encframesize; outpos += len; samps+=frames; encpos += frames*2; } else { Con_Printf("Opus encoding error: %i\n", len); encpos = s_voip.capturepos; } } break; #endif default: outbuf[outpos] = 0; break; } //opus has no way to detect the end properly. //standard rtp favours many small packets. if (rtpstream || s_voip.enccodec == VOIP_OPUS) break; } if (samps) { float nl; s_voip.enctimestamp += samps; nl = (3000*level) / (32767.0f*32767*samps); s_voip.voiplevel = (s_voip.voiplevel*7 + nl)/8; if (s_voip.voiplevel < snd_voip_vad_threshhold.ival && !voipbutton && !(snd_voip_send.ival & 6)) { /*try and dump it, it was too quiet, and they're not pressing +voip*/ if (s_voip.keeps > samps) { /*but not instantly*/ s_voip.keeps -= samps; } else { outpos = 0; s_voip.dumps += samps; s_voip.keeps = 0; } } else s_voip.keeps = s_voip.encsamplerate * snd_voip_vad_delay.value; if (outpos) { if (s_voip.dumps > s_voip.encsamplerate/4) s_voip.generation++; s_voip.dumps = 0; } } if (outpos) { if (buf && !(snd_voip_send.ival & 4)) { if (buf->maxsize - buf->cursize >= 5+outpos) { qbyte cgen = ((s_voip.enccodec&0x7)<<4) | (s_voip.generation & 0x0f); if (s_voip.enccodec >= 8 || 0) cgen |= 0x80; MSG_WriteByte(buf, clc); MSG_WriteByte(buf, cgen); MSG_WriteByte(buf, initseq&0xff); /*if (cgen & 0x80) { MSG_WriteShort(buf, 1+outpos); MSG_WriteByte(buf, s_voip.enccodec>>3); } else*/ MSG_WriteShort(buf, outpos); //even with codecs where the size is easy to determine, this is still useful for servers (which are unaware of the actual codec) SZ_Write(buf, outbuf, outpos); } else Con_Printf("Audio frame too small %i vs %i\n", outpos+4, buf->maxsize - buf->cursize); } #ifdef SUPPORT_ICE if (rtpstream) { switch(s_voip.enccodec) { #ifdef HAVE_SPEEX case VOIP_SPEEX_NARROW: case VOIP_SPEEX_WIDE: case VOIP_SPEEX_ULTRAWIDE: case VOIP_SPEEX_OLD: NET_RTP_Transmit(initseq, inittimestamp, va("speex@%i", s_voip.encsamplerate), outbuf, outpos); break; #endif case VOIP_PCMA: NET_RTP_Transmit(initseq, inittimestamp, "pcma@8000", outbuf, outpos); break; case VOIP_PCMU: NET_RTP_Transmit(initseq, inittimestamp, "pcmu@8000", outbuf, outpos); break; #ifdef HAVE_OPUS case VOIP_OPUS: NET_RTP_Transmit(initseq, inittimestamp, "opus@48000", outbuf, outpos); break; #endif } } #endif if (snd_voip_test.ival) S_Voip_Decode(cl.playerview[0].playernum, s_voip.enccodec, s_voip.generation & 0x0f, initseq&0xff, outpos, outbuf); //update our own lastspoke, so queries shows that we're speaking when we're speaking in a generic way, even if we can't hear ourselves. //but don't update general lastspoke, so ducking applies only when others speak. use capturingvol for yourself. they're more explicit that way. s_voip.lastspoke[cl.playerview[0].playernum] = realtime + 0.5; } /*remove sent data*/ if (encpos) { memmove(s_voip.capturebuf, s_voip.capturebuf + encpos, s_voip.capturepos-encpos); s_voip.capturepos -= encpos; } } void S_Voip_Ignore(unsigned int slot, qboolean ignore) { CL_SendClientCommand(true, "vignore %i %i", slot, ignore); } static void S_Voip_Enable_f(void) { voipbutton = true; } static void S_Voip_Disable_f(void) { voipbutton = false; } static void S_Voip_f(void) { #ifdef HAVE_SPEEX if (!strcmp(Cmd_Argv(1), "maxgain")) { int i = atoi(Cmd_Argv(2)); if (s_voip.speexdsp.preproc) qspeex_preprocess_ctl(s_voip.speexdsp.preproc, SPEEX_PREPROCESS_SET_AGC_MAX_GAIN, &i); } else #endif { Con_Printf("unrecognised parameter \"%s\"\n", Cmd_Argv(1)); } } static void QDECL S_Voip_Play_Callback(cvar_t *var, char *oldval) { if (cls.fteprotocolextensions2 & PEXT2_VOICECHAT) { if (var->value > 0) CL_SendClientCommand(true, "unmuteall"); else CL_SendClientCommand(true, "muteall"); } } void S_Voip_MapChange(void) { voipbutton = false; Cvar_ForceCallback(&snd_voip_play); } int S_Voip_Loudness(qboolean ignorevad) { if (s_voip.voiplevel > 100) return 100; if (!s_voip.cdriverctx || (!ignorevad && s_voip.dumps)) return -1; return s_voip.voiplevel; } int S_Voip_ClientLoudness(unsigned int plno) { if (plno >= MAX_CLIENTS) return 0; if (s_voip.lastspoke[plno] > realtime) return s_voip.declevel[plno]; return -1; } qboolean S_Voip_Speaking(unsigned int plno) { if (plno >= MAX_CLIENTS) return false; return s_voip.lastspoke[plno] > realtime; } void S_Voip_Init(void) { int i; for (i = 0; i < MAX_CLIENTS; i++) s_voip.deccodec[i] = VOIP_INVALID; s_voip.enccodec = VOIP_INVALID; Cvar_Register(&snd_voip_capturedevice, "Voice Chat"); Cvar_Register(&snd_voip_capturedevice_opts, "Voice Chat"); Cvar_Register(&snd_voip_send, "Voice Chat"); Cvar_Register(&snd_voip_vad_threshhold, "Voice Chat"); Cvar_Register(&snd_voip_vad_delay, "Voice Chat"); Cvar_Register(&snd_voip_capturingvol, "Voice Chat"); Cvar_Register(&snd_voip_showmeter, "Voice Chat"); Cvar_Register(&snd_voip_play, "Voice Chat"); Cvar_Register(&snd_voip_test, "Voice Chat"); Cvar_Register(&snd_voip_ducking, "Voice Chat"); Cvar_Register(&snd_voip_micamp, "Voice Chat"); Cvar_Register(&snd_voip_codec, "Voice Chat"); #ifdef HAVE_SPEEX Cvar_Register(&snd_voip_noisefilter, "Voice Chat"); Cvar_Register(&snd_voip_autogain, "Voice Chat"); #endif Cvar_Register(&snd_voip_bitrate, "Voice Chat"); Cmd_AddCommand("+voip", S_Voip_Enable_f); Cmd_AddCommand("-voip", S_Voip_Disable_f); Cmd_AddCommand("voip", S_Voip_f); } #else void S_Voip_Parse(void) { unsigned int bytes; MSG_ReadByte(); MSG_ReadByte(); MSG_ReadByte(); bytes = MSG_ReadShort(); MSG_ReadSkip(bytes); } #endif void S_DefaultSpeakerConfiguration(soundcardinfo_t *sc) { sc->dist[0] = 1; sc->dist[1] = 1; sc->dist[2] = 1; sc->dist[3] = 1; sc->dist[4] = 1; sc->dist[5] = 1; switch (sc->sn.numchannels) { case 1: VectorSet(sc->speakerdir[0], 0, 0, 0); break; case 2: case 3: VectorSet(sc->speakerdir[0], 0, -1, 0); VectorSet(sc->speakerdir[1], 0, 1, 0); VectorSet(sc->speakerdir[2], 0, 0, 0); break; case 4: // quad case 5: VectorSet(sc->speakerdir[0], 0.7, -0.7, 0); VectorSet(sc->speakerdir[1], 0.7, 0.7, 0); VectorSet(sc->speakerdir[2], -0.7, -0.7, 0); VectorSet(sc->speakerdir[3], -0.7, 0.7, 0); VectorSet(sc->speakerdir[4], 0, 0, 0); break; case 6: // 5.1 case 7: VectorSet(sc->speakerdir[0], 0.7, -0.7, 0); //front-left VectorSet(sc->speakerdir[1], 0.7, 0.7, 0); //front-right VectorSet(sc->speakerdir[2], 1, 0, 0); //center VectorSet(sc->speakerdir[3], 0, 0, 0); //bass VectorSet(sc->speakerdir[4], -0.7, -0.7, 0);//back-left VectorSet(sc->speakerdir[5], -0.7, 0.7, 0); //back-right VectorSet(sc->speakerdir[6], 0, 0, 0); break; case 8: // 7.1 default: VectorSet(sc->speakerdir[0], 0.7, -0.7, 0); VectorSet(sc->speakerdir[1], 0.7, 0.7, 0); VectorSet(sc->speakerdir[2], 1, 0, 0); VectorSet(sc->speakerdir[3], 0, 0, 0); VectorSet(sc->speakerdir[4], -0.7, -0.7, 0); VectorSet(sc->speakerdir[5], -0.7, 0.7, 0); VectorSet(sc->speakerdir[6], 0, -1, 0); VectorSet(sc->speakerdir[7], 0, 1, 0); break; } } #ifdef AVAIL_WASAPI extern sounddriver_t WASAPI_Output; #endif #ifdef AVAIL_XAUDIO2 extern sounddriver_t XAUDIO2_Output; #endif #ifdef AVAIL_DSOUND extern sounddriver_t DSOUND_Output; #endif sounddriver_t fte_weakstruct SDL_Output; #ifdef __linux__ extern sounddriver_t ALSA_Output; extern sounddriver_t Pulse_Output; #endif sounddriver_t fte_weakstruct OSS_Output; #ifdef AVAIL_OPENAL extern sounddriver_t OPENAL_Output; #endif #ifdef __DJGPP__ extern sounddriver_t SBLASTER_Output; #endif #if defined(_WIN32) && !defined(WINRT) && !defined(FTE_SDL) extern sounddriver_t WaveOut_Output; #endif #ifdef MACOSX sounddriver_t fte_weakstruct MacOS_AudioOutput; //prefered on mac #endif #ifdef ANDROID sounddriver_t fte_weakstruct OSL_Output; //general audio library, but android has all kinds of quirks. sounddriver_t fte_weakstruct Droid_AudioOutput; #endif #if defined(__MORPHOS__) sounddriver_t fte_weakstruct AHI_AudioOutput; //prefered on morphos #endif #ifdef NACL extern sounddriver_t PPAPI_AudioOutput; //nacl #endif sounddriver_t fte_weakstruct SNDIO_AudioOutput; //bsd //in order of preference static sounddriver_t *outputdrivers[] = { #ifdef AVAIL_OPENAL &OPENAL_Output, //refuses to run as the default device, at least until its perfected. #endif #ifdef HAVE_MIXER #ifdef AVAIL_DSOUND &DSOUND_Output, #endif #ifdef AVAIL_XAUDIO2 &XAUDIO2_Output, #endif #ifdef AVAIL_WASAPI &WASAPI_Output, //this is last, so that we can default to exclusive. woot. #endif &SDL_Output, //prefered on linux. distros can ensure that its configured correctly. #ifdef AUDIO_PULSE &Pulse_Output, //wasteful, and availability generally means Alsa is broken/defective. #endif #ifdef AUDIO_ALSA &ALSA_Output, //pure shite, and availability generally means OSS is broken/defective. #endif #ifdef AUDIO_OSS &OSS_Output, //good for low latency audio, but not likely to work any more on linux (unlike every other unix system with a decent opengl driver) #endif #ifdef __DJGPP__ &SBLASTER_Output, //zomgwtfdos? #endif #if defined(_WIN32) && !defined(WINRT) && !defined(FTE_SDL) &WaveOut_Output, //doesn't work properly in vista, etc. #endif #ifdef MACOSX &MacOS_AudioOutput, //prefered on mac #endif #ifdef ANDROID &OSL_Output, //opensl(es) #endif #if defined(__MORPHOS__) &AHI_AudioOutput, //prefered on morphos #endif #ifdef NACL &PPAPI_AudioOutput, //google's native client #endif &SNDIO_AudioOutput, //prefered on OpenBSD #endif NULL }; static soundcardinfo_t *SNDDMA_Init(char *driver, char *device, int seat) { soundcardinfo_t *sc = Z_Malloc(sizeof(soundcardinfo_t)); sounddriver_t *sd; int i; int st; memset(sc, 0, sizeof(*sc)); sc->seat = seat; sc->next = sndcardinfo; sndcardinfo = sc; // set requested rate if (snd_khz.ival >= 1000) sc->sn.speed = snd_khz.ival; else if (snd_khz.ival <= 0) sc->sn.speed = 22050; /* else if (snd_khz.ival >= 195) sc->sn.speed = 200000; else if (snd_khz.ival >= 180) sc->sn.speed = 192000; else if (snd_khz.ival >= 90) sc->sn.speed = 96000; */ else if (snd_khz.ival >= 45) sc->sn.speed = 48000; else if (snd_khz.ival >= 30) sc->sn.speed = 44100; else if (snd_khz.ival >= 20) sc->sn.speed = 22050; else if (snd_khz.ival >= 10) sc->sn.speed = 11025; else sc->sn.speed = 8000; // set requested speaker count if (snd_speakers.ival > MAXSOUNDCHANNELS) sc->sn.numchannels = MAXSOUNDCHANNELS; else if (snd_speakers.ival > 1) sc->sn.numchannels = (int)snd_speakers.ival; else sc->sn.numchannels = 1; // set requested sample bits if (snd_samplebits.ival >= 32) sc->sn.samplebytes = 4; else if (snd_samplebits.ival >= 16) sc->sn.samplebytes = 2; else sc->sn.samplebytes = 1; // set requested buffer size if (snd_buffersize.ival > 0) sc->sn.samples = snd_buffersize.ival * sc->sn.numchannels; else sc->sn.samples = 0; for (i = 0; outputdrivers[i]; i++) { sd = outputdrivers[i]; if (sd && sd->name && (!driver || !Q_strcasecmp(sd->name, driver))) { //skip drivers which are not present. if (!sd->InitCard) continue; st = (**sd->InitCard)(sc, device); if (st) { if (!sc->sn.sampleformat) { Con_TPrintf("S_Startup: Ignoring soundcard %s due to unspecified sample format.\n", sc->name); S_ShutdownCard(sc); continue; } S_DefaultSpeakerConfiguration(sc); if (snd_speed) { //if the sample speeds of multiple soundcards do not match, it'll fail. if (snd_speed != sc->sn.speed) { Con_TPrintf("S_Startup: Ignoring soundcard %s due to mismatched sample speeds.\n", sc->name); S_ShutdownCard(sc); return NULL; } } else snd_speed = sc->sn.speed; if (sc->seat == -1 && sc->ListenerUpdate) sc->seat = 0; //hardware rendering won't cope with seat=-1 Z_ReallocElements((void**)&sc->channel, &sc->max_chans, MAX_DYNAMIC_CHANNELS+NUM_AMBIENTS+NUM_MUSICS, sizeof(*sc->channel)); return sc; } } } S_ShutdownCard(sc); if (!driver) Con_TPrintf("Could not start audio device \"%s\"\n", device?device:"default"); else Con_TPrintf("Could not start \"%s\" device \"%s\"\n", driver, device?device:"default"); return NULL; } soundcardinfo_t *S_SetupDeviceSeat(char *driver, char *device, int seat) { return SNDDMA_Init(driver, device, seat); /* soundcardinfo_t *sc; for (sc = sndcardinfo; sc; sc = sc->next) { sc->seat = seat; }*/ } static void QDECL S_EnumeratedOutDevice(const char *driver, const char *devicecode, const char *readabledevice) { const char *fullintname; char opts[8192]; char nbuf[1024]; char dbuf[1024]; if (devicecode) fullintname = va("%s:%s", driver, devicecode); else fullintname = driver; Q_snprintfz(opts, sizeof(opts), "%s%s%s %s", snd_device_opts.string, *snd_device_opts.string?" ":"", COM_QuotedString(fullintname, nbuf, sizeof(nbuf), false), COM_QuotedString(readabledevice, dbuf, sizeof(dbuf), false)); Cvar_ForceSet(&snd_device_opts, opts); } #ifdef VOICECHAT static void QDECL S_Voip_EnumeratedCaptureDevice(const char *driver, const char *devicecode, const char *readabledevice) { const char *fullintname; char opts[8192]; char nbuf[1024]; char dbuf[1024]; if (devicecode) fullintname = va("%s:%s", driver, devicecode); else fullintname = driver; Q_snprintfz(opts, sizeof(opts), "%s%s%s %s", snd_voip_capturedevice_opts.string, *snd_voip_capturedevice_opts.string?" ":"", COM_QuotedString(fullintname, nbuf, sizeof(nbuf), false), COM_QuotedString(readabledevice, dbuf, sizeof(dbuf), false)); Cvar_ForceSet(&snd_voip_capturedevice_opts, opts); } #endif void S_EnumerateDevices(void) { int i; sounddriver_t *sd; Cvar_ForceSet(&snd_device_opts, ""); S_EnumeratedOutDevice("", NULL, "Default"); S_EnumeratedOutDevice("none", NULL, "None"); for (i = 0; outputdrivers[i]; i++) { sd = outputdrivers[i]; if (sd && sd->name) { if (!sd->Enumerate || !sd->Enumerate(S_EnumeratedOutDevice)) S_EnumeratedOutDevice(sd->name, "", va("Default %s", sd->name)); } } #ifdef VOICECHAT Cvar_ForceSet(&snd_voip_capturedevice_opts, ""); S_Voip_EnumeratedCaptureDevice("", NULL, "Default"); for (i = 0; capturedrivers[i]; i++) { if (!capturedrivers[i]->Init) continue; if (!capturedrivers[i]->Enumerate || !capturedrivers[i]->Enumerate(S_Voip_EnumeratedCaptureDevice)) S_Voip_EnumeratedCaptureDevice(capturedrivers[i]->drivername, NULL, va("Default %s", capturedrivers[i]->drivername)); } #endif } /* ================ S_Startup ================ */ void S_ClearRaw(void); void S_Startup (void) { qboolean nodefault = false; char *s; if (!snd_initialized) return; if (sound_started) S_Shutdown(false); snd_blocked = 0; snd_speed = 0; S_UpdateReverb(0, NULL, 0); { //we can actually use underwater hints automatically easily enough. q3 also does this. //its other things that are more awkward. struct reverbproperties_s underwater = REVERB_PRESET_UNDERWATER; S_UpdateReverb(1, &underwater, sizeof(underwater)); } for (s = snd_device.string; ; ) { char *sep; s = COM_Parse(s); if (!*com_token) break; if (!Q_strcasecmp(com_token, "none")) nodefault = true; else { sep = strchr(com_token, ':'); if (sep) *sep++ = 0; SNDDMA_Init(com_token, sep, -1); } } if (!sndcardinfo && !nodefault) { #if defined(_WIN32) && !defined(FTE_SDL) INS_SetupControllerAudioDevices(true); #endif if (!sndcardinfo) SNDDMA_Init(NULL, NULL, -1); } sound_started = true; S_ClearRaw(); if (!known_sfx) known_sfx = Z_Malloc(MAX_SFX*sizeof(sfx_t)); num_sfx = 0; CL_InitTEntSounds(); ambient_sfx[AMBIENT_WATER] = S_PrecacheSound ("ambience/water1.wav"); ambient_sfx[AMBIENT_SKY] = S_PrecacheSound ("ambience/wind2.wav"); } //why isn't this part of S_Restart_f anymore? //so that the video code can call it directly without flushing the models it's just loaded. void S_DoRestart (qboolean onlyifneeded) { int i; if (onlyifneeded && sound_started) return; //don't need to if its already running. S_StopAllSounds (true); S_Shutdown(false); if (nosound.ival) return; S_Startup(); S_StopAllSounds (true); for (i=1 ; inext) ; if (!sc) { Con_Printf("Sound card %i is invalid (try resetting first)\n", card); return; } if (Cmd_Argc() < 3) { Con_Printf("Scard %i is %s\n", card, sc->name); return; } command = Cmd_Argv (2); if (!Q_strcasecmp(command, "mono")) { for (i = 0; i < MAXSOUNDCHANNELS; i++) { VectorSet(sc->speakerdir[i], 0, 0, 0); sc->dist[i] = 1; } } else if (!Q_strcasecmp(command, "standard") || !Q_strcasecmp(command, "stereo")) { for (i = 0; i < MAXSOUNDCHANNELS; i++) { VectorSet(sc->speakerdir[i], 0, (i&1)?1:-1, 0); sc->dist[i] = 1; } } else if (!Q_strcasecmp(command, "swap")) { for (i = 0; i < MAXSOUNDCHANNELS; i++) { sc->speakerdir[i][1] *= -1; } } else if (!Q_strcasecmp(command, "front")) { for (i = 0; i < MAXSOUNDCHANNELS; i++) { VectorSet(sc->speakerdir[i], 0.7, (i&1)?-0.7:0.7, 0); sc->dist[i] = 1; } } else if (!Q_strcasecmp(command, "back")) { for (i = 0; i < MAXSOUNDCHANNELS; i++) { VectorSet(sc->speakerdir[i], -0.7, (i&1)?-0.7:0.7, 0); sc->dist[i] = 1; } } return; } else Con_Printf("valid commands are: off, single, multi, cardX mono, cardX stereo, cardX front, cardX back\n"); } /* ================ S_Init ================ */ void S_Init (void) { int p, i; Con_DPrintf("\nSound Initialization\n"); Cmd_AddCommand("play", S_Play_f); //sound that doesn't follow the player Cmd_AddCommand("play2", S_Play_f); //sound that DOES follow the player Cmd_AddCommand("playvol", S_Play_f); Cmd_AddCommand("stopsound", S_StopAllSounds_f); Cmd_AddCommand("soundlist", S_SoundList_f); Cmd_AddCommand("soundinfo", S_SoundInfo_f); Cmd_AddCommand("snd_restart", S_Restart_f); Cmd_AddCommand("soundcontrol", S_Control_f); Cvar_Register(&nosound, "Sound controls"); Cvar_Register(&mastervolume, "Sound controls"); Cvar_Register(&volume, "Sound controls"); Cvar_Register(&snd_precache, "Sound controls"); Cvar_Register(&snd_loadas8bit, "Sound controls"); Cvar_Register(&snd_loadasstereo, "Sound controls"); Cvar_Register(&bgmvolume, "Sound controls"); Cvar_Register(&snd_nominaldistance, "Sound controls"); Cvar_Register(&ambient_level, "Sound controls"); Cvar_Register(&ambient_fade, "Sound controls"); Cvar_Register(&snd_noextraupdate, "Sound controls"); Cvar_Register(&snd_show, "Sound controls"); Cvar_Register(&_snd_mixahead, "Sound controls"); Cvar_Register(&snd_khz, "Sound controls"); Cvar_Register(&snd_leftisright, "Sound controls"); Cvar_Register(&snd_eax, "Sound controls"); Cvar_Register(&snd_speakers, "Sound controls"); Cvar_Register(&snd_buffersize, "Sound controls"); Cvar_Register(&snd_samplebits, "Sound controls"); Cvar_Register(&snd_playbackrate, "Sound controls"); Cvar_Register(&snd_ignoregamespeed, "Sound controls"); Cvar_Register(&snd_doppler, "Sound controls"); Cvar_Register(&snd_doppler_min, "Sound controls"); Cvar_Register(&snd_doppler_max, "Sound controls"); Cvar_Register(&snd_inactive, "Sound controls"); #ifdef MULTITHREAD Cvar_Register(&snd_mixerthread, "Sound controls"); #endif Cvar_Register(&snd_playersoundvolume, "Sound controls"); Cvar_Register(&snd_device, "Sound controls"); Cvar_Register(&snd_device_opts, "Sound controls"); Cvar_Register(&snd_ignorecueloops, "Sound controls"); Cvar_Register(&snd_linearresample, "Sound controls"); Cvar_Register(&snd_linearresample_stream, "Sound controls"); #ifdef VOICECHAT S_Voip_Init(); #endif #ifdef MULTITHREAD mixermutex = Sys_CreateMutex(); #endif for (i = 0; outputdrivers[i]; i++) { sounddriver_t *sd = outputdrivers[i]; if (sd && sd->name && sd->RegisterCvars) sd->RegisterCvars(); } if (COM_CheckParm("-nosound")) { Cvar_ForceSet(&nosound, "1"); nosound.flags |= CVAR_NOSET; return; } S_EnumerateDevices(); p = COM_CheckParm ("-soundspeed"); if (!p) p = COM_CheckParm ("-sspeed"); if (!p) p = COM_CheckParm ("-sndspeed"); if (p) { if (p < com_argc-1) Cvar_SetValue(&snd_khz, atof(com_argv[p+1])); else Sys_Error ("S_Init: you must specify a speed in KB after -soundspeed"); } snd_initialized = true; known_sfx = Z_Malloc(MAX_SFX*sizeof(sfx_t)); num_sfx = 0; } // ======================================================================= // Shutdown sound engine // ======================================================================= void S_ShutdownCard(soundcardinfo_t *sc) { soundcardinfo_t **link; for (link = &sndcardinfo; *link; link = &(*link)->next) { if (*link == sc) { *link = sc->next; if (sc->Shutdown) sc->Shutdown(sc); Z_Free(sc->channel); Z_Free(sc); break; } } } void S_Shutdown(qboolean final) { soundcardinfo_t *sc, *next; #if defined(_WIN32) && !defined(FTE_SDL) INS_SetupControllerAudioDevices(false); #endif for (sc = sndcardinfo; sc; sc=next) { next = sc->next; sc->Shutdown(sc); Z_Free(sc->channel); Z_Free(sc); sndcardinfo = next; } sound_started = 0; S_Purge(false); Z_Free(known_sfx); known_sfx = NULL; num_sfx = 0; if (final) { Z_Free(reverbproperties); reverbproperties = NULL; numreverbproperties = 0; } #ifdef MULTITHREAD if (final && mixermutex) { Sys_DestroyMutex(mixermutex); mixermutex = NULL; } #endif } // ======================================================================= // Load a sound // ======================================================================= /* ================== S_FindName also touches it ================== */ sfx_t *S_FindName (const char *name, qboolean create, qboolean syspath) { int i; sfx_t *sfx; if (!name) Sys_Error ("S_FindName: NULL\n"); if (Q_strlen(name) >= MAX_OSPATH) Sys_Error ("Sound name too long: %s", name); // see if already loaded for (i=0 ; i < num_sfx ; i++) if (!Q_strcmp(known_sfx[i].name, name) && known_sfx[i].syspath == syspath) { known_sfx[i].touched = true; return &known_sfx[i]; } if (num_sfx == MAX_SFX) Sys_Error ("S_FindName: out of sfx_t"); if (create && known_sfx) { sfx = &known_sfx[i]; strcpy (sfx->name, name); sfx->syspath = syspath; sfx->touched = true; num_sfx++; } else sfx = NULL; return sfx; } void S_Purge(qboolean retaintouched) { sfx_t *sfx; int i; //make sure ambients are kept. silly ambients. if (retaintouched) { ambient_sfx[AMBIENT_WATER] = S_PrecacheSound ("ambience/water1.wav"); ambient_sfx[AMBIENT_SKY] = S_PrecacheSound ("ambience/wind2.wav"); } if (!num_sfx) return; S_LockMixer(); for (i=0 ; i < num_sfx ; i++) { sfx = &known_sfx[i]; /*don't hurt sounds if they're being processed by a worker thread*/ if (sfx->loadstate == SLS_LOADING) { if (retaintouched) continue; //don't bother waiting //trying to shut down or something. //make sure there's no worker about to write to sfx after the memory is freed COM_WorkerPartialSync(sfx, &sfx->loadstate, SLS_LOADING); } /*don't purge the file if its still relevent*/ if (retaintouched && sfx->touched) continue; if (S_IsPlayingSomewhere(sfx)) continue; //eep?!? sfx->loadstate = SLS_NOTLOADED; /*nothing to do if there's no data within*/ if (!sfx->decoder.buf) continue; /*stop the decoder first*/ if (sfx->decoder.purge) sfx->decoder.purge(sfx); else if (sfx->decoder.ended) sfx->decoder.ended(sfx); /*if there's any data associated still, kill it. if present, it should be a single sfxcache_t (with data in same alloc)*/ if (sfx->decoder.buf) BZ_Free(sfx->decoder.buf); memset(&sfx->decoder, 0, sizeof(sfx->decoder)); } S_UnlockMixer(); } void S_ResetFailedLoad(void) { int i; for (i=0 ; i < num_sfx ; i++) { if (known_sfx[i].loadstate == SLS_FAILED) known_sfx[i].loadstate = SLS_NOTLOADED; } } void S_UntouchAll(void) { int i; for (i=0 ; i < num_sfx ; i++) known_sfx[i].touched = false; } /* ================== S_PrecacheSound ================== */ sfx_t *S_PrecacheSound2 (const char *name, qboolean syspath) { sfx_t *sfx; if (nosound.ival || !known_sfx || !*name) return NULL; sfx = S_FindName (name, true, syspath); // cache it in if (snd_precache.ival && snd_precache.ival != 2 && sndcardinfo) S_LoadSound (sfx, true); return sfx; } //============================================================================= /* ================= SND_PickChannel ================= */ channel_t *SND_PickChannel(soundcardinfo_t *sc, int entnum, int entchannel) { int ch_idx; int oldestpos; int oldest; // Check for replacement sound, or find the best one to replace oldest = -1; oldestpos = -1; for (ch_idx=DYNAMIC_FIRST; ch_idx < DYNAMIC_STOP ; ch_idx++) { if (entchannel != 0 // channel 0 never overrides && sc->channel[ch_idx].entnum == entnum && sc->channel[ch_idx].entchannel == entchannel) { // always override sound from same entity oldest = ch_idx; break; } // don't let monster sounds override player sounds if (sc->seat != -1 && sc->channel[ch_idx].entnum == listener[sc->seat].entnum && entnum != listener[sc->seat].entnum && sc->channel[ch_idx].sfx) continue; if (!sc->channel[ch_idx].sfx) { oldestpos = 0x7fffffff; oldest = ch_idx; } else if (sc->channel[ch_idx].pos > oldestpos) { oldestpos = sc->channel[ch_idx].pos; oldest = ch_idx; } } if (oldest == -1) return NULL; //if (sc->channel[oldest].sfx) sc->channel[oldest].sfx = NULL; if (sc->total_chans <= oldest) sc->total_chans = oldest+1; #ifdef Q3CLIENT //presumably we should be using this instead of pos for oldest, but blurgh. sc->channel[oldest].starttime = Sys_Milliseconds(); #endif return &sc->channel[oldest]; } static void SND_AccumulateSpacialization(soundcardinfo_t *sc, channel_t *ch, vec3_t origin) { vec3_t listener_vec; vec_t dist; vec_t scale; vec3_t world_vec; int i, v; float volscale; int seat; if (ch->flags & CF_CL_ABSVOLUME) volscale = mastervolume.value; else volscale = volume.value * voicevolumemod; if (sc->seat == -1) { seat = 0; VectorSubtract(origin, listener[seat].origin, world_vec); dist = DotProduct(world_vec,world_vec); for (i = 1; i < cl.splitclients; i++) { VectorSubtract(origin, listener[i].origin, world_vec); scale = DotProduct(world_vec,world_vec); if (scale < dist) { dist = scale; seat = i; } } } else { seat = sc->seat; } // anything coming from the view entity will always be full volume if (ch->entnum == listener[seat].entnum) { v = ch->master_vol * (ruleset_allow_localvolume.value ? snd_playersoundvolume.value : 1) * volscale; v = bound(0, v, 255); for (i = 0; i < sc->sn.numchannels; i++) ch->vol[i] = v; return; } // calculate stereo seperation and distance attenuation VectorSubtract(origin, listener[seat].origin, world_vec); dist = VectorNormalize(world_vec) * ch->dist_mult; if ((ch->flags & CF_NOSPACIALISE) || !ch->dist_mult) { scale = 1; scale = (1.0 - dist) * scale; v = ch->master_vol * scale * volscale; for (i = 0; i < sc->sn.numchannels; i++) ch->vol[i] += bound(0, v, 255); return; } //rotate the world_vec into listener space, so that the audio direction stored in the speakerdir array can be used directly. listener_vec[0] = DotProduct(listener[seat].forward, world_vec); listener_vec[1] = DotProduct(listener[seat].right, world_vec); listener_vec[2] = DotProduct(listener[seat].up, world_vec); if (snd_leftisright.ival) listener_vec[1] = -listener_vec[1]; for (i = 0; i < sc->sn.numchannels; i++) { scale = 1 + DotProduct(listener_vec, sc->speakerdir[i]); scale = (1.0 - dist) * scale * sc->dist[i]; v = ch->master_vol * scale * volscale; ch->vol[i] += bound(0, v, 255); } } /* ================= SND_Spatialize ================= */ static void SND_Spatialize(soundcardinfo_t *sc, channel_t *ch) { vec3_t listener_vec, sound_vel; vec_t dist; vec_t scale; vec3_t world_vec; int i, v; float volscale; int seat; if (ch->flags & CF_FOLLOW) { //sounds following ents should update their position to match that ent's position. //its important that they do not snap back to where they were if the entity vanishes, so we just overwrite the channel origin for that. its simpler. #ifdef CSQC_DAT if (ch->entnum < 0 && -ch->entnum < csqc_world.num_edicts) { wedict_t *ed = WEDICT_NUM_PB(csqc_world.progs, -ch->entnum); if (ed->ereftype == ER_ENTITY) { VectorCopy(ed->v->origin, ch->origin); VectorCopy(ed->v->velocity, ch->velocity); if (ed->v->solid == SOLID_BSP) { VectorMA(ch->origin, 0.5, ed->v->absmin, ch->origin); VectorMA(ch->origin, 0.5, ed->v->absmax, ch->origin); } } } else #endif if (ch->entnum > 0 && ch->entnum < cl.maxlerpents && cl.lerpents[ch->entnum].sequence == cl.lerpentssequence) { lerpents_t *le = cl.lerpents+ch->entnum; int midx = le->entstate->modelindex; VectorCopy(le->origin, ch->origin); //VectorCopy(le->velocity, ch->velocity); //fixme: bmodels should use their center rather than their origin. check le->state->solid? //bmodels should report the center of the entity rather than the origin (which is frequently at 0 0 0 or merely used as a pivot) if (le->entstate->solidsize == ES_SOLID_BSP && midx > 0 && midx < countof(cl.model_precache)) { if (cl.model_precache[midx] && cl.model_precache[midx]->loadstate == MLS_LOADED && cl.model_precache[midx]->type == mod_brush) { //fixme: should probably deal with rotations. VectorMA(ch->origin, 0.5, cl.model_precache[midx]->mins, ch->origin); VectorMA(ch->origin, 0.5, cl.model_precache[midx]->maxs, ch->origin); } } } //FIXME: update rate to provide doppler } //sounds with absvolume ignore all volume etc cvars+settings if (ch->flags & CF_CL_ABSVOLUME) volscale = mastervolume.value; else volscale = volume.value * voicevolumemod; if (!vid.activeapp && !snd_inactive.ival && !(ch->flags & CF_CLI_INACTIVE)) volscale = 0; if (sc->seat == -1) { seat = 0; VectorSubtract(ch->origin, listener[seat].origin, world_vec); dist = DotProduct(world_vec,world_vec); #if MAX_SPLITS > 1 for (i = 1; i < cl.splitclients; i++) { VectorSubtract(ch->origin, listener[i].origin, world_vec); scale = DotProduct(world_vec,world_vec); if (scale < dist) { dist = scale; seat = i; } } #endif } else { seat = sc->seat; } // anything coming from the view entity will always be full volume // (no, I don't like this hack) if (ch->entnum == listener[seat].entnum && ch->entnum) { v = ch->master_vol * (ruleset_allow_localvolume.value ? snd_playersoundvolume.value : 1) * volscale; v = bound(0, v, 255); for (i = 0; i < sc->sn.numchannels; i++) ch->vol[i] = v; return; } // calculate stereo seperation and distance attenuation VectorSubtract(ch->origin, listener[seat].origin, world_vec); dist = VectorNormalize(world_vec) * ch->dist_mult; if ((ch->flags & CF_NOSPACIALISE) || !ch->dist_mult) { scale = 1; scale = (1.0 - dist) * scale; v = ch->master_vol * scale * volscale; v = bound(0, v, 255); for (i = 0; i < sc->sn.numchannels; i++) ch->vol[i] = v; return; } //an attempt at doppler. if (snd_doppler.value) { //according to feh, the speed of sound is about 9000 qu/s. VectorAdd(listener[seat].velocity, ch->velocity, sound_vel); scale = 1 + snd_doppler.value * DotProduct(world_vec, sound_vel) / (9000.0); if (scale > snd_doppler_max.value) scale = snd_doppler_max.value; if (scale < snd_doppler_min.value) scale = snd_doppler_min.value; ch->rate = (1<rate < 1) //too small values result in crashes. ch->rate = 1; } //rotate the world_vec into listener space, so that the audio direction stored in the speakerdir array can be used directly. listener_vec[0] = DotProduct(listener[seat].forward, world_vec); listener_vec[1] = DotProduct(listener[seat].right, world_vec); listener_vec[2] = DotProduct(listener[seat].up, world_vec); if (snd_leftisright.ival) listener_vec[1] = -listener_vec[1]; for (i = 0; i < sc->sn.numchannels; i++) { scale = 1 + DotProduct(listener_vec, sc->speakerdir[i]); scale = (1.0 - dist) * scale * sc->dist[i]; v = ch->master_vol * scale * volscale; v = bound(0, v, 255); ch->vol[i] = v; } } // ======================================================================= // Start a sound effect // ======================================================================= static void S_UpdateSoundCard(soundcardinfo_t *sc, qboolean updateonly, channel_t *target_chan, int entnum, int entchannel, sfx_t *sfx, vec3_t origin, vec3_t velocity, float fvol, float attenuation, float timeoffset, float ratemul, unsigned int flags) { channel_t *check; int vol; int ch_idx; int skip; int absstartpos = updateonly?sc->GetChannelPos?sc->GetChannelPos(sc, target_chan)<pos:0; extern cvar_t cl_demospeed; chanupdatereason_t chanupdatetype = updateonly?CUR_UPDATE:CUR_EVERYTHING; if (!sfx) sfx = target_chan->sfx; if (fvol < 0 || !sfx) { //stopsound, apparently. target_chan->sfx = NULL; return; } if (!ratemul) //rate of 0 ratemul = 1; ratemul *= snd_playbackrate.value; if (!snd_ignoregamespeed.ival) ratemul *= (cls.state?cl.gamespeed:1) * (cls.demoplayback?cl_demospeed.value:1); if (ratemul <= 0) //in case the user set the cvars weirdly ratemul = 1; vol = fvol*255; // spatialize if (target_chan->sfx != sfx) chanupdatetype |= CUR_SOUNDCHANGE; memset (target_chan, 0, sizeof(*target_chan)); if (!origin) { if (sc->seat == -1) { VectorClear(target_chan->origin); attenuation = 0; flags |= CF_NOSPACIALISE; } else VectorCopy(listener[sc->seat].origin, target_chan->origin); } else { VectorCopy(origin, target_chan->origin); } if (velocity) VectorCopy(velocity, target_chan->velocity); else VectorClear(target_chan->velocity); target_chan->flags = flags; target_chan->dist_mult = attenuation / snd_nominaldistance.value; target_chan->master_vol = vol; target_chan->entnum = entnum; target_chan->entchannel = entchannel; SND_Spatialize(sc, target_chan); if (!S_LoadSound (sfx, false)) { target_chan->sfx = NULL; return; // couldn't load the sound's data } //FIXME: why does this only filter for openal devices? its weird. if (!updateonly && !target_chan->vol[0] && !target_chan->vol[1] && !target_chan->vol[2] && !target_chan->vol[3] && !target_chan->vol[4] && !target_chan->vol[5] && sc->ChannelUpdate) if (sfx->loopstart == -1 && !(flags&CF_FORCELOOP)) //only skip if its not looping. { target_chan->sfx = NULL; return; // not audible at all } target_chan->sfx = sfx; target_chan->rate = ((1<rate/sc->sn.speed; if (target_chan->rate < 1) /*make sure the rate won't crash us*/ target_chan->rate = 1; target_chan->pos = absstartpos + (int)(timeoffset*sc->sn.speed*target_chan->rate); if (!updateonly) { // if an identical sound has also been started this frame, offset the pos // a bit to keep it from just making the first one louder check = &sc->channel[DYNAMIC_FIRST]; for (ch_idx=DYNAMIC_FIRST; ch_idx < DYNAMIC_STOP; ch_idx++, check++) { if (check == target_chan) continue; if (check->sfx == sfx && !check->pos) { skip = rand () % (int)(0.1*sc->sn.speed); target_chan->pos -= skip*target_chan->rate; break; } } } if (sc->ChannelUpdate) sc->ChannelUpdate(sc, target_chan, chanupdatetype); } float S_UpdateSound(int entnum, int entchannel, sfx_t *sfx, vec3_t origin, vec3_t velocity, float fvol, float attenuation, float timeofs, float pitchadj, unsigned int flags) { int i; int result = 0; int cards = 0; soundcardinfo_t *sc; channel_t *chan; if (cls.demoseeking) return result; S_LockMixer(); for (sc = sndcardinfo; sc; sc = sc->next) { cards++; for (i = 0; i < sc->total_chans; i++) { if (sc->channel[i].entnum == entnum && sc->channel[i].entchannel == entchannel && sc->channel[i].sfx) { S_UpdateSoundCard(sc, true, &sc->channel[i], entnum, entchannel, sfx, origin, velocity, fvol, attenuation, timeofs, pitchadj, flags); result++; break; } } //start it if we couldn't find it. if (i == sc->total_chans && sfx) { chan = SND_PickChannel(sc, entnum, entchannel); if (chan) S_UpdateSoundCard(sc, false, chan, entnum, entchannel, sfx, origin, velocity, fvol, attenuation, timeofs, pitchadj, flags); } } S_UnlockMixer(); if (!cards) cards=1; return result / (float)cards; } void S_StartSound(int entnum, int entchannel, sfx_t *sfx, vec3_t origin, vec3_t velocity, float fvol, float attenuation, float timeofs, float pitchadj, unsigned int flags) { soundcardinfo_t *sc; channel_t *target_chan; if (!sfx || !*sfx->name) //no named sounds would need specific starting. return; if (cls.demoseeking) return; if (!sound_started) return; if (nosound.ival) return; S_LockMixer(); for (sc = sndcardinfo; sc; sc = sc->next) { #ifdef Q3CLIENT if (flags & CF_CLI_NODUPES) { //don't start too many simultaneous sounds. q3 sucks or something. int active = 0, i; unsigned int time = Sys_Milliseconds(); for (i = 0; i < sc->total_chans; i++) { //as per q3, channel isn't important. if (sc->channel[i].entnum == entnum && sc->channel[i].sfx == sfx) { //never allow a new sound within 50ms of the previous one if (time - sc->channel[i].starttime < 50) break; active++; } } if (active >= 4 || i < sc->total_chans) { Con_DPrintf("CF_CLI_NODUPES strikes again!\n"); break; } } #endif // pick a channel to play on target_chan = SND_PickChannel(sc, entnum, entchannel); if (!target_chan) break; S_UpdateSoundCard(sc, false, target_chan, entnum, entchannel, sfx, origin, velocity, fvol, attenuation, timeofs, pitchadj, flags); } S_UnlockMixer(); } qboolean S_GetMusicInfo(int musicchannel, float *time, float *duration, char *title, size_t titlesize) { qboolean result = false; soundcardinfo_t *sc; sfx_t *sfx; *time = 0; *duration = 0; if (titlesize) *title = 0; musicchannel += MUSIC_FIRST; S_LockMixer(); for (sc = sndcardinfo; sc; sc = sc->next) { sfx = sc->channel[musicchannel].sfx; if (sfx) { Q_strncpyz(title, COM_SkipPath(sfx->name), titlesize); if (sfx->loadstate == SLS_LOADED) { if (sfx->decoder.querydata) *duration = sfx->decoder.querydata(sfx, NULL, title, titlesize); else if (sfx->decoder.buf) { sfxcache_t *c = sfx->decoder.buf; *duration = (float)c->length / c->speed; } else *duration = 0; //FIXME: openal doesn't report the actual time. *time = (sc->channel[musicchannel].pos>>PITCHSHIFT) / (float)snd_speed; //the time into the sound, ignoring play rate. result = true; } } } S_UnlockMixer(); return result; } float S_GetSoundTime(int entnum, int entchannel) { int i; float result = -1; //if we didn't find one soundcardinfo_t *sc; S_LockMixer(); for (sc = sndcardinfo; sc && result == -1; sc = sc->next) { for (i = 0; i < sc->total_chans; i++) { if (sc->channel[i].entnum == entnum && sc->channel[i].entchannel == entchannel && sc->channel[i].sfx) { ssamplepos_t spos = sc->GetChannelPos?sc->GetChannelPos(sc, &sc->channel[i]):(sc->channel[i].pos>>PITCHSHIFT); result = spos / (float)snd_speed; //the time into the sound, ignoring play rate. break; } } //we found one on this sound device card, ignore others. if (result != -1) break; } S_UnlockMixer(); return result; } float S_GetChannelLevel(int entnum, int entchannel) { int i, j; float result = -1; //if we didn't find one soundcardinfo_t *sc; sfxcache_t scachebuf, *scache; S_LockMixer(); for (sc = sndcardinfo; sc && result == -1; sc = sc->next) { for (i = 0; i < sc->total_chans; i++) { if (sc->channel[i].entnum == entnum && sc->channel[i].entchannel == entchannel && sc->channel[i].sfx) { ssamplepos_t spos = sc->GetChannelPos?sc->GetChannelPos(sc, &sc->channel[i]):(sc->channel[i].pos>>PITCHSHIFT); if (sc->channel[i].sfx->decoder.decodedata) scache = sc->channel[i].sfx->decoder.decodedata(sc->channel[i].sfx, &scachebuf, spos, 1); else scache = NULL; if (!scache) scache = sc->channel[i].sfx->decoder.buf; if (scache && spos >= scache->soundoffset && spos < scache->soundoffset+scache->length) { spos -= scache->soundoffset; spos *= scache->numchannels; switch(scache->format) { #ifdef FTE_TARGET_WEB case QAF_BLOB: result = 0; //sorry. you're going to have to use .wav :( break; #endif case QAF_S8: for (j = 0; j < scache->numchannels; j++) //average the channels result += abs(((signed char*)scache->data)[spos+j]); result /= scache->numchannels*127.0; break; case QAF_S16: for (j = 0; j < scache->numchannels; j++) //average the channels result += abs(((signed short*)scache->data)[spos+j]); result /= scache->numchannels*32767.0; break; #ifdef MIXER_F32 case QAF_F32: for (j = 0; j < scache->numchannels; j++) //average the channels result += fabs(((float*)scache->data)[spos+j]); result /= scache->numchannels; break; #endif } } else result = 0; break; } } //we found one on this sound device card, ignore others. if (result != -1) break; } S_UnlockMixer(); return result; } qboolean S_IsPlayingSomewhere(sfx_t *s) { soundcardinfo_t *si; int i; for (si = sndcardinfo; si; si=si->next) { for (i = 0; i < si->total_chans; i++) if (si->channel[i].sfx == s) return true; } return false; } static void S_StopSoundCard(soundcardinfo_t *sc, int entnum, int entchannel) { int i; for (i=0 ; itotal_chans ; i++) { if (sc->channel[i].entnum == entnum && (!entchannel || sc->channel[i].entchannel == entchannel)) { sc->channel[i].sfx = NULL; if (sc->ChannelUpdate) sc->ChannelUpdate(sc, &sc->channel[i], CUR_EVERYTHING); if (entchannel) break; } } } void S_StopSound(int entnum, int entchannel) { soundcardinfo_t *sc; S_LockMixer(); for (sc = sndcardinfo; sc; sc = sc->next) S_StopSoundCard(sc, entnum, entchannel); S_UnlockMixer(); } void S_StopAllSounds(qboolean clear) { int i; sfx_t *s; channel_t musics[NUM_MUSICS]; soundcardinfo_t *sc; if (!sound_started) return; S_LockMixer(); for (sc = sndcardinfo; sc; sc = sc->next) { for (i=sc->total_chans ; i --> 0 ; ) { if (i >= MUSIC_FIRST && i < MUSIC_FIRST+NUM_MUSICS && sc->selfpainting) continue; //don't reset music if is safe to continue playing it without stuttering s = sc->channel[i].sfx; if (s) { sc->channel[i].sfx = NULL; if (s->loadstate == SLS_LOADED && s->decoder.ended) if (!S_IsPlayingSomewhere(s)) //if we aint playing it elsewhere, free it compleatly. { if (s->decoder.ended) s->decoder.ended(s); } if (sc->ChannelUpdate) sc->ChannelUpdate(sc, &sc->channel[i], CUR_EVERYTHING); } } sc->total_chans = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS + NUM_MUSICS; // no statics Z_ReallocElements((void**)&sc->channel, &sc->max_chans, sc->total_chans, sizeof(*sc->channel)); memcpy(musics, &sc->channel[MUSIC_FIRST], sizeof(musics)); Q_memset(sc->channel, 0, sc->max_chans * sizeof(channel_t)); memcpy(&sc->channel[MUSIC_FIRST], musics, sizeof(musics)); if (clear && !sc->selfpainting) //if its self-painting, then the mixer will continue painting anyway (which is important if its still painting music, but otherwise don't stutter at all when loading) S_ClearBuffer (sc); } S_UnlockMixer(); } static void S_StopAllSounds_f (void) { S_StopAllSounds (true); } static void S_ClearBuffer (soundcardinfo_t *sc) { void *buffer; unsigned int dummy; int clear; if (!sound_started || !sc->sn.buffer) return; if (sc->sn.sampleformat == QSF_U8) clear = 0x80; else clear = 0; dummy = 0; buffer = sc->Lock(sc, &dummy); if (buffer) { Q_memset(buffer, clear, sc->sn.samples * sc->sn.samplebytes); sc->Unlock(sc, buffer); } } /* ================= S_StaticSound ================= */ void S_StaticSound (sfx_t *sfx, vec3_t origin, float vol, float attenuation) { channel_t *ss; soundcardinfo_t *scard; if (!sfx) return; S_LockMixer(); for (scard = sndcardinfo; scard; scard = scard->next) { if (scard->total_chans == scard->max_chans) { if (!ZF_ReallocElements((void**)&scard->channel, &scard->max_chans, scard->max_chans+64, sizeof(*scard->channel))) { Con_Printf ("total_channels == MAX_CHANNELS\n"); continue; } } if (!S_LoadSound (sfx, true)) break; ss = &scard->channel[scard->total_chans]; scard->total_chans++; ss->entnum = 0; ss->sfx = sfx; ss->rate = 1<origin); ss->master_vol = vol*255; ss->dist_mult = attenuation / snd_nominaldistance.value; ss->pos = 0; ss->flags = CF_FORCELOOP; SND_Spatialize (scard, ss); if (scard->ChannelUpdate) scard->ChannelUpdate(scard, ss, CUR_EVERYTHING); } S_UnlockMixer(); } //============================================================================= void S_Music_Clear(sfx_t *onlyifsample) { //stops the current BGM music //calling this will trigger Media_NextTrack later sfx_t *s; soundcardinfo_t *sc; int i; for (i = MUSIC_FIRST; i < MUSIC_STOP; i++) { for (sc = sndcardinfo; sc; sc=sc->next) { s = sc->channel[i].sfx; if (!s) continue; if (onlyifsample && s != onlyifsample) continue; sc->channel[i].pos = 0; sc->channel[i].sfx = NULL; if (sc->ChannelUpdate) sc->ChannelUpdate(sc, &sc->channel[i], CUR_EVERYTHING); if (s && s->decoder.ended && !S_IsPlayingSomewhere(s)) //if we aint playing it elsewhere, free it compleatly. s->decoder.ended(s); } } } void S_Music_Seek(float time) { soundcardinfo_t *sc; int i; for (i = MUSIC_FIRST; i < MUSIC_STOP; i++) { for (sc = sndcardinfo; sc; sc=sc->next) { sc->channel[i].pos += sc->sn.speed*time * sc->channel[i].rate; if (sc->channel[i].pos < 0) { //clamp to the start of the track sc->channel[i].pos=0; } //if we seek over the end, ignore it. The sound playing code will spot that. } } } //mixer must be locked qboolean S_Music_Playing(int musicchannel) { soundcardinfo_t *sc; musicchannel += MUSIC_FIRST; for (sc = sndcardinfo; sc; sc=sc->next) { if (sc->channel[musicchannel].sfx) return true; } return false; } /* =================== S_UpdateAmbientSounds =================== */ mleaf_t *Q1BSP_LeafForPoint (model_t *model, vec3_t p); void S_UpdateAmbientSounds (soundcardinfo_t *sc) { float vol; channel_t *chan; int i; #ifdef Q1BSPS mleaf_t *l; float oldvol; int ambientlevel[NUM_AMBIENTS]; #endif if (!snd_ambient) return; for (i = MUSIC_FIRST; i < MUSIC_STOP; i++) { chanupdatereason_t changed = CUR_SPACIALISEONLY; chan = &sc->channel[i]; if (!chan->sfx) { float time = 0; sfx_t *newmusic; if (!S_Music_Playing(i-MUSIC_FIRST)) { newmusic = Media_NextTrack(i-MUSIC_FIRST, &time); if (newmusic && newmusic->loadstate != SLS_FAILED) { //okay, now we know which track we're meant to be playing, all devices can play it at once. soundcardinfo_t *sc2; for (sc2 = sndcardinfo; sc2; sc2=sc2->next) { channel_t *chan = &sc2->channel[i]; chan->sfx = newmusic; chan->rate = 1<pos = (int)(time * sc->sn.speed) * chan->rate; changed = CUR_EVERYTHING; chan->master_vol = bound(0, 1, 255); chan->vol[0] = chan->vol[1] = chan->vol[2] = chan->vol[3] = chan->vol[4] = chan->vol[5] = chan->master_vol; if (sc->ChannelUpdate) sc->ChannelUpdate(sc, chan, changed); } } } } if (chan->sfx) { chan->flags = /*CF_CL_INACTIVE|*/CF_CL_ABSVOLUME|CF_NOSPACIALISE|CF_NOREVERB; //bypasses volume cvar completely. vol = 255*bgmvolume.value*voicevolumemod; if (!vid.activeapp && !snd_inactive.ival && !(chan->flags & CF_CLI_INACTIVE)) vol = 0; vol = bound(0, vol, 255); vol = Media_CrossFade(i-MUSIC_FIRST, vol, (chan->pos>>PITCHSHIFT) / (float)snd_speed); if (vol < 0) { //cross fading wants to KILL this track now, apparently. sfx_t *s = chan->sfx; if (s->loadstate != SLS_LOADING) { chan->pos = 0; chan->sfx = NULL; if (sc->ChannelUpdate) sc->ChannelUpdate(sc, chan, CUR_EVERYTHING); if (s && s->decoder.ended && !S_IsPlayingSomewhere(s)) //if we aint playing it elsewhere, free it compleatly. s->decoder.ended(s); } } else { chan->master_vol = bound(0, vol, 255); chan->vol[0] = chan->vol[1] = chan->vol[2] = chan->vol[3] = chan->vol[4] = chan->vol[5] = chan->master_vol; if (sc->ChannelUpdate) sc->ChannelUpdate(sc, chan, changed); } } } #ifdef Q1BSPS // calc ambient sound levels for (i = 0; i < NUM_AMBIENTS; i++) ambientlevel[i] = 0; if (cl.worldmodel && cl.worldmodel->type == mod_brush && cl.worldmodel->fromgame == fg_quake && cl.worldmodel->loadstate == MLS_LOADED) { if (ambient_level.value) { if (sc->seat < 0) { int seat = max(1,cl.splitclients); while(seat --> 0) { l = Q1BSP_LeafForPoint(cl.worldmodel, listener[seat].origin); if (!l) continue; for (i = 0; i < NUM_AMBIENTS; i++) ambientlevel[i] = max(ambientlevel[i], l->ambient_sound_level[i]); } } else { l = Q1BSP_LeafForPoint(cl.worldmodel, listener[sc->seat].origin); if (l) for (i = 0; i < NUM_AMBIENTS; i++) ambientlevel[i] = l->ambient_sound_level[i]; } } } for (i = 0 ; i< NUM_AMBIENTS ; i++) { chan = &sc->channel[AMBIENT_FIRST+i]; chan->sfx = ambient_sfx[AMBIENT_FIRST+i]; chan->entnum = 0; chan->flags = CF_FORCELOOP | CF_NOSPACIALISE; chan->rate = 1<origin); vol = ambient_level.value * ambientlevel[i]; if (vol < 8) vol = 0; oldvol = sc->ambientlevels[i]; // don't adjust volume too fast if (sc->ambientlevels[i] < vol) { sc->ambientlevels[i] += host_frametime * ambient_fade.value; if (sc->ambientlevels[i] > vol) sc->ambientlevels[i] = vol; } else if (chan->master_vol > vol) { sc->ambientlevels[i] -= host_frametime * ambient_fade.value; if (sc->ambientlevels[i] < vol) sc->ambientlevels[i] = vol; } chan->master_vol = sc->ambientlevels[i]; chan->vol[0] = chan->vol[1] = chan->vol[2] = chan->vol[3] = chan->vol[4] = chan->vol[5] = bound(0, chan->master_vol * (volume.value*voicevolumemod), 255); if (sc->ChannelUpdate) sc->ChannelUpdate(sc, chan, ((oldvol == 0) ^ (sc->ambientlevels[i] == 0))?CUR_EVERYTHING:CUR_SPACIALISEONLY); } #endif } struct sndreverbproperties_s *reverbproperties; size_t numreverbproperties; qboolean S_UpdateReverb(size_t slot, void *reverb, size_t reverbsize) { struct reverbproperties_s newprops; if (slot >= 1024) return false; if (slot >= numreverbproperties) { int slots = slot+1; void *n = BZ_Realloc(reverbproperties, sizeof(*reverbproperties)*slots); if (!n) return false; reverbproperties = n; memset(reverbproperties+numreverbproperties, 0, sizeof(*reverbproperties) * (slots-numreverbproperties)); numreverbproperties = slots; } memset(&newprops, 0, sizeof(newprops)); if (reverb) { //clamp the size for possible future extensibility if (reverbsize > sizeof(newprops)) reverbsize = sizeof(newprops); memcpy(&newprops, reverb, reverbsize); } if (memcmp(&newprops, &reverbproperties[slot].props, sizeof(newprops))) { reverbproperties[slot].props = newprops; reverbproperties[slot].modificationcount++; } return true; } /* ============ S_Update Called once each time through the main loop ============ */ void S_UpdateListener(int seat, int entnum, vec3_t origin, vec3_t forward, vec3_t right, vec3_t up, size_t reverbtype, vec3_t velocity) { soundcardinfo_t *sc; listener[seat].entnum = entnum; VectorCopy(origin, listener[seat].origin); VectorCopy(forward, listener[seat].forward); VectorCopy(right, listener[seat].right); VectorCopy(up, listener[seat].up); VectorCopy(velocity, listener[seat].velocity); for (sc = sndcardinfo; sc; sc=sc->next) if (sc->SetEnvironmentReverb && (sc->seat == seat || (sc->seat == -1 && seat == 0))) sc->SetEnvironmentReverb(sc, reverbtype); } void S_GetListenerInfo(int seat, float *origin, float *forward, float *right, float *up) { VectorCopy(listener[seat].origin, origin); VectorCopy(listener[seat].forward, forward); VectorCopy(listener[seat].right, right); VectorCopy(listener[seat].up, up); } static void S_Q2_AddEntitySounds(soundcardinfo_t *sc) { vec3_t positions[2048]; int entnums[countof(positions)]; sfx_t *sounds[countof(positions)]; unsigned int count; unsigned int j; channel_t *c; #ifdef Q2CLIENT if (cls.protocol == CP_QUAKE2) count = CLQ2_GatherSounds(positions, entnums, sounds, countof(sounds)); else #endif #ifdef VM_CG if (cls.protocol == CP_QUAKE3) count = CG_GatherLoopingSounds(positions, entnums, sounds, countof(sounds)); else #endif return; while(count --> 0) { sfx_t *sfx = sounds[count]; if (!sfx) continue; if (sfx->loadstate == SLS_NOTLOADED) S_LoadSound(sfx, true); if (sfx->loadstate != SLS_LOADED) continue; //not ready yet if (sc->ChannelUpdate) { for (c = NULL, j=DYNAMIC_FIRST; j < DYNAMIC_STOP ; j++) { if (sc->channel[j].entnum == entnums[count] && !sc->channel[j].entchannel && (sc->channel[j].flags & CF_CLI_AUTOSOUND)) { c = &sc->channel[j]; break; } } } else { for (c = NULL, j=DYNAMIC_FIRST; j < DYNAMIC_STOP ; j++) { if (sc->channel[j].sfx == sfx && (sc->channel[j].flags & CF_CLI_AUTOSOUND)) { c = &sc->channel[j]; break; } } } if (!c) { c = SND_PickChannel(sc, 0, 0); if (!c) continue; c->flags = CF_CLI_AUTOSOUND|CF_FORCELOOP; c->entnum = sc->ChannelUpdate?entnums[count]:0; c->entchannel = 0; c->dist_mult = 3 / snd_nominaldistance.value; c->master_vol = 255 * 1; c->pos = 0<rate = 1<vol); j++) c->vol[j] = 0; c->sfx = NULL; } if (sc->ChannelUpdate) { //hardware mixing doesn't support merging VectorCopy(positions[count], c->origin); SND_Spatialize(sc, c); if (c->sfx) sc->ChannelUpdate(sc, c, CUR_SPACIALISEONLY); } else { //merge with any other ents, if we can for (j = 0; j <= count; j++) { if (sounds[j] == sfx) { sounds[j] = NULL; SND_AccumulateSpacialization(sc, c, positions[j]); } } } if (!c->sfx) { for (j = 0; j < countof(c->vol); j++) if (c->vol[j]) break; if (j == countof(c->vol)) c->sfx = NULL; //err, never mind else { c->sfx = sfx; if (sc->ChannelUpdate) sc->ChannelUpdate(sc, c, CUR_EVERYTHING); } } } } static void S_UpdateCard(soundcardinfo_t *sc) { int i, j; channel_t *ch; channel_t *combine; if (!sound_started) return; if ((snd_blocked > 0)) { if (!sc->inactive_sound) return; } #ifdef AVAIL_OPENAL if (sc->ListenerUpdate) { sc->ListenerUpdate(sc, listener[sc->seat].entnum, listener[sc->seat].origin, listener[sc->seat].forward, listener[sc->seat].right, listener[sc->seat].up, listener[sc->seat].velocity); } #endif // update general area ambient sound sources S_UpdateAmbientSounds (sc); combine = NULL; // update spatialization for static and dynamic sounds ch = sc->channel+DYNAMIC_FIRST; for (i=DYNAMIC_FIRST ; itotal_chans; i++, ch++) { if (!ch->sfx) continue; if (ch->flags & CF_CLI_AUTOSOUND) { if (!ch->vol[0] && !ch->vol[1] && !ch->vol[2] && !ch->vol[3] && !ch->vol[4] && !ch->vol[5]) { ch->sfx = NULL; if (sc->ChannelUpdate) sc->ChannelUpdate(sc, ch, CUR_EVERYTHING); } ch->vol[0] = ch->vol[1] = ch->vol[2] = ch->vol[3] = ch->vol[4] = ch->vol[5] = 0; continue; } if (sc->ChannelUpdate) { if (ch->flags & CF_FOLLOW) SND_Spatialize(sc, ch); //update it a little sc->ChannelUpdate(sc, ch, CUR_SPACIALISEONLY); continue; } SND_Spatialize(sc, ch); // respatialize channel if (!ch->vol[0] && !ch->vol[1] && !ch->vol[2] && !ch->vol[3] && !ch->vol[4] && !ch->vol[5]) continue; // try to combine static sounds with a previous channel of the same // sound effect so we don't mix five torches every frame if (i >= DYNAMIC_STOP) { // see if it can just use the last one if (combine && combine->sfx == ch->sfx) { combine->vol[0] += ch->vol[0]; combine->vol[1] += ch->vol[1]; combine->vol[2] += ch->vol[2]; combine->vol[3] += ch->vol[3]; combine->vol[4] += ch->vol[4]; combine->vol[5] += ch->vol[5]; ch->vol[0] = ch->vol[1] = ch->vol[2] = ch->vol[3] = ch->vol[4] = ch->vol[5] = 0; continue; } // search for one combine = sc->channel+DYNAMIC_FIRST; for (j=DYNAMIC_FIRST ; jsfx == ch->sfx) break; if (j == sc->total_chans) { combine = NULL; } else { if (combine != ch) { combine->vol[0] += ch->vol[0]; combine->vol[1] += ch->vol[1]; combine->vol[2] += ch->vol[2]; combine->vol[3] += ch->vol[3]; combine->vol[4] += ch->vol[4]; combine->vol[5] += ch->vol[5]; ch->vol[0] = ch->vol[1] = ch->vol[2] = ch->vol[3] = ch->vol[4] = ch->vol[5] = 0; } continue; } } } S_Q2_AddEntitySounds(sc); // // debugging output // if (snd_show.ival) { struct listener_s *l; int active, mute; active = 0; mute = 0; ch = sc->channel; for (i=0 ; itotal_chans; i++, ch++) { if (ch->sfx && (ch->vol[0] || ch->vol[1]) ) { if (snd_show.ival > 1) Con_Printf ("%i, %i %i %i %i %i %i %s\n", i, ch->vol[0], ch->vol[1], ch->vol[2], ch->vol[3], ch->vol[4], ch->vol[5], ch->sfx->name); active++; } else if (ch->sfx) mute++; } if (sc->seat < 0) l = &listener[0]; else l = &listener[sc->seat]; Con_Printf ("----(%i+%i %s %i %.1f %.1f %.1f)----\n", active, mute, sc->name, l->entnum, l->origin[0], l->origin[1], l->origin[2]); } #ifdef HAVE_MIXER // mix some sound if (sc->selfpainting) return; if (snd_blocked > 0) { if (!sc->inactive_sound) return; } S_Update_(sc); #endif } #ifdef HAVE_MIXER int S_GetMixerTime(soundcardinfo_t *sc) { int samplepos; int fullsamples; fullsamples = sc->sn.samples / sc->sn.numchannels; // it is possible to miscount buffers if it has wrapped twice between // calls to S_Update. Oh well. samplepos = sc->GetDMAPos(sc); if (sc->samplequeue > 0) samplepos -= sc->samplequeue; if (samplepos < 0) { samplepos = 0; } if (samplepos < sc->oldsamplepos) { int bias; sc->buffers++; // buffer wrapped if (sc->paintedtime > 0x40000000) { //when things get too large, we push everything back to prevent overflows bias = sc->paintedtime; bias -= bias % fullsamples; sc->paintedtime -= bias; sc->buffers -= bias / fullsamples; } } sc->oldsamplepos = samplepos; return sc->buffers*fullsamples + samplepos/sc->sn.numchannels; } #endif void S_Update (void) { soundcardinfo_t *sc; S_LockMixer(); for (sc = sndcardinfo; sc; sc = sc->next) S_UpdateCard(sc); S_UnlockMixer(); } void S_ExtraUpdate (void) { #ifdef HAVE_MIXER soundcardinfo_t *sc; #endif if (!sound_started) return; #if defined(_WIN32) && !defined(WINRT) INS_Accumulate (); #endif #ifdef HAVE_MIXER if (snd_noextraupdate.ival) return; // don't pollute timings for (sc = sndcardinfo; sc; sc = sc->next) { if (sc->selfpainting) continue; if (snd_blocked > 0) { if (!sc->inactive_sound) continue; } S_LockMixer(); S_Update_(sc); S_UnlockMixer(); } #endif } #ifdef HAVE_MIXER static void S_Update_(soundcardinfo_t *sc) { int soundtime; /*in pairs*/ unsigned endtime; int samps; // Updates DMA time soundtime = S_GetMixerTime(sc); if (sc->samplequeue > 0) { /*device uses a write-once queue*/ endtime = soundtime + sc->samplequeue/sc->sn.numchannels; soundtime = sc->paintedtime; samps = sc->samplequeue / sc->sn.numchannels; } else if (sc->samplequeue < 0) { /*device is telling us the exact point that we should be mixing to*/ endtime = soundtime; soundtime = sc->paintedtime; samps = sc->sn.samples / sc->sn.numchannels; } else { /*device uses memory-mapped output*/ // check to make sure that we haven't overshot if (sc->paintedtime < soundtime) { //Con_Printf ("S_Update_ : overflow\n"); sc->paintedtime = soundtime; } // mix ahead of current position endtime = soundtime + (int)(_snd_mixahead.value * sc->sn.speed); samps = sc->sn.samples / sc->sn.numchannels; } if (endtime - soundtime > samps) { endtime = soundtime + samps; } /*DirectSound may have killed us to give priority to another app, ask to restore it*/ if (sc->Restore) sc->Restore(sc); S_PaintChannels (sc, endtime); sc->Submit(sc, soundtime, endtime); } /* called periodically by dedicated mixer threads. do any blocking calls AFTER this returns. note that this means you can't use the Submit/unlock method to submit blocking audio. */ void S_MixerThread(soundcardinfo_t *sc) { S_LockMixer(); S_Update_(sc); S_UnlockMixer(); } #endif /* =============================================================================== console functions =============================================================================== */ void S_Play_f(void) { //plays a sound located around the player int i; char name[256]; sfx_t *sfx; const char *cmdname = Cmd_Argv(0); float vol, attenuation = 0; unsigned int flags = CF_NOSPACIALISE; int entnum = 0; float *origin = NULL; /* //Vanilla compat (breaks modern QW mods): if (!strcmp(cmdname, "play")) { flags = 0; attenuation = 1; origin = listener[0].origin; entnum = listener[0].entnum; } */ i = 1; while (iloadstate != SLS_LOADED) sc = NULL; else if (sfx->decoder.decodedata) { if (sfx->decoder.querydata) sc = (sfx->decoder.querydata(sfx, &scachebuf, NULL, 0) < 0)?NULL:&scachebuf; else sc = NULL; //don't bother trying to actually decode anything here. if (!sc) { Con_Printf("S( ) : %s\n", sfx->name); continue; } } else sc = sfx->decoder.buf; if (!sc) { Con_Printf("?( ) : %s\n", sfx->name); continue; } size = (sc->soundoffset+sc->length)*QAF_BYTES(sc->format)*(sc->numchannels); duration = (sc->soundoffset+sc->length) / sc->speed; total += size; if (sfx->loopstart >= 0) Con_Printf ("L"); else Con_Printf (" "); Con_Printf("(%2db%2ic) %6i %2is : %s\n",QAF_BYTES(sc->format)*8, sc->numchannels, size, duration, sfx->name); } Con_Printf ("Total resident: %i\n", total); S_UnlockMixer(); } void S_LocalSound2 (const char *sound, int channel, float volume) { sfx_t *sfx; if (nosound.ival) return; if (!sound_started) return; sfx = S_PrecacheSound (sound); if (!sfx) { Con_Printf ("S_LocalSound: can't cache %s\n", sound); return; } S_StartSound (0, channel, sfx, NULL, NULL, volume, 0, 0, 0, CF_CLI_INACTIVE|CF_NOSPACIALISE|CF_NOREVERB); } void S_LocalSound (const char *sound) { S_LocalSound2(sound, 256, 1); } typedef struct { sfxdecode_t decoder; qboolean inuse; int id; sfx_t *sfx; int numchannels; qaudiofmt_t format; int length; void *data; } streaming_t; #define MAX_RAW_SOURCES (MAX_CLIENTS+1) streaming_t s_streamers[MAX_RAW_SOURCES]; void S_ClearRaw(void) { memset(s_streamers, 0, sizeof(s_streamers)); } //returns an sfxcache_t stating where the data is sfxcache_t *QDECL S_Raw_Locate(sfx_t *sfx, sfxcache_t *buf, ssamplepos_t start, int length) { streaming_t *s = sfx->decoder.buf; if (buf) { buf->data = s->data; buf->length = s->length; buf->numchannels = s->numchannels; buf->soundoffset = 0; buf->speed = snd_speed; buf->format = s->format; } if (start >= s->length) return NULL; //eof... return buf; } void QDECL S_Raw_Ended(sfx_t *sfx) { //no longer playing anywhere... streaming_t *s = sfx->decoder.buf; s->inuse = false; //let it get reused now. } void QDECL S_Raw_Purge(sfx_t *sfx) { //flush all caches, will be re-read from disk (or not, because this is streamed) streaming_t *s = sfx->decoder.buf; s->length = 0; s->numchannels = 0; BZ_Free(s->data); s->data = NULL; s->inuse = false; memset(&sfx->decoder, 0, sizeof(sfx->decoder)); } //streaming audio. //this is useful when there is one source, and the sound is to be played with no attenuation void S_RawAudio(int sourceid, qbyte *data, int speed, int samples, int channels, qaudiofmt_t format, float volume) { soundcardinfo_t *si; int i; int prepadl; //this is the amount of data that was previously available, and will be removed from the buffer. int spare; //the amount of existing data that is still left to be played int outsamples; //the amount of data we're going to add (at the output rate) double speedfactor; qbyte *newcache; streaming_t *s, *free=NULL; if (!sound_started) return; for (s = s_streamers, i = 0; i < MAX_RAW_SOURCES; i++, s++) { if (!s->inuse) { if (!free) free = s; continue; } if (s->id == sourceid) break; } if (!data) { if (i == MAX_RAW_SOURCES) return; //wierd, it wasn't even playing. s->inuse = false; S_LockMixer(); for (si = sndcardinfo; si; si=si->next) for (i = 0; i < si->total_chans; i++) if (si->channel[i].sfx == s->sfx) { si->channel[i].sfx = NULL; break; } BZ_Free(s->data); s->data = NULL; S_UnlockMixer(); return; } if (i == MAX_RAW_SOURCES || !s->inuse) //whoops. { if (i == MAX_RAW_SOURCES) { if (!free) { Con_Printf("No free audio streams\n"); return; } s = free; } if (!s->sfx) s->sfx = S_FindName(va("***stream_%i***", i), true, false); s->sfx->decoder.buf = s; s->sfx->decoder.decodedata = S_Raw_Locate; s->sfx->decoder.ended = S_Raw_Ended; s->sfx->decoder.purge = S_Raw_Purge; s->sfx->loopstart = -1; //non-looping... s->sfx->loadstate = SLS_LOADED; s->numchannels = channels; s->format = format; s->data = NULL; s->length = 0; s->id = sourceid; s->inuse = true; // Con_Printf("Added new raw stream\n"); } S_LockMixer(); if (s->format != format || s->numchannels != channels) { s->format = format; s->numchannels = channels; s->length = 0; Con_Printf("Restarting raw stream\n"); } speedfactor = (double)speed/snd_speed; outsamples = samples/speedfactor; prepadl = 0x7fffffff; for (si = sndcardinfo; si; si=si->next) //make sure all cards are playing, and that we still get a prepad if just one is. { for (i = 0; i < si->total_chans; i++) if (si->channel[i].sfx == s->sfx) { if (prepadl > (si->channel[i].pos>>PITCHSHIFT)) prepadl = (si->channel[i].pos>>PITCHSHIFT); break; } } if (prepadl == 0x7fffffff) { if (snd_show.ival) Con_Printf("Wasn't playing\n"); prepadl = 0; spare = 0; if (spare > snd_speed) { Con_DPrintf("Sacrificed raw sound stream\n"); spare = 0; //too far out. sacrifice it all } } else { if (prepadl < 0) prepadl = 0; spare = s->length - prepadl; if (spare < 0) //remaining samples since last time spare = 0; if (spare > snd_speed*2) // more than 2 seconds of sound. don't buffer more than 2 seconds. 1: its probably buggy if we need to. 2: takes too much memory, and we use malloc+copies. { Con_DPrintf("Sacrificed raw sound stream\n"); spare = 0; //too far out. sacrifice it all } } newcache = BZ_Malloc((spare+outsamples) * (s->numchannels) * QAF_BYTES(s->format)); memcpy(newcache, (qbyte*)s->data + prepadl * (s->numchannels) * QAF_BYTES(s->format), spare * (s->numchannels) * QAF_BYTES(s->format)); BZ_Free(s->data); s->data = newcache; s->length = spare + outsamples; { extern cvar_t snd_linearresample_stream; short *outpos = (short *)((char*)s->data + spare * (s->numchannels) * QAF_BYTES(s->format)); SND_ResampleStream(data, speed, format, channels, samples, outpos, snd_speed, s->format, s->numchannels, snd_linearresample_stream.ival); } for (si = sndcardinfo; si; si=si->next) { for (i = 0; i < si->total_chans; i++) if (si->channel[i].sfx == s->sfx) { si->channel[i].pos -= prepadl*si->channel[i].rate; if (si->channel[i].pos < 0) si->channel[i].pos = 0; si->channel[i].master_vol = 255 * volume; if (si->ChannelUpdate) si->ChannelUpdate(si, &si->channel[i], CUR_SPACIALISEONLY); break; } if (i == si->total_chans) //this one wasn't playing. { channel_t *c = SND_PickChannel(si, -1, 0); if (c) { c->flags = (sourceid>=0?CF_CLI_INACTIVE:0)|CF_CL_ABSVOLUME|CF_NOSPACIALISE; c->entnum = 0; c->entchannel = 0; c->dist_mult = 0; c->master_vol = 255 * volume; c->pos = 0; c->rate = 1<sfx = s->sfx; SND_Spatialize(si, c); if (si->ChannelUpdate) si->ChannelUpdate(si, c, CUR_EVERYTHING); } } } S_UnlockMixer(); }