mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-22 12:01:25 +00:00
4443 lines
118 KiB
C
4443 lines
118 KiB
C
/*
|
|
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|CVAR_NOSAVE, "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|CVAR_NOSAVE, "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|CVAR_NOTFROMSERVER, "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 (j<MAXSOUNDCHANNELS)
|
|
Con_Printf(" %s (%i %i, %g %g %g, active)\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]);
|
|
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 <speex/speex.h>
|
|
#include <speex/speex_preprocess.h>
|
|
#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)
|
|
{
|
|
if (Cmd_IsInsecure())
|
|
return;
|
|
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;
|
|
extern sounddriver_t OPENAL_Output_Lame;
|
|
#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
|
|
|
|
#ifdef AVAIL_OPENAL
|
|
&OPENAL_Output_Lame,//streaming quake's audio via openal instead of using openal properly. used in our browser port to work around issues with webaudio (at least in chromium).
|
|
#endif
|
|
#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, 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;
|
|
qboolean safe = COM_CheckParm("-noenumerate") || COM_CheckParm("-safe");
|
|
|
|
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 (safe || !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 (safe || !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 ; i<MAX_PRECACHE_SOUNDS ; i++)
|
|
{
|
|
if (!cl.sound_name[i][0])
|
|
break;
|
|
cl.sound_precache[i] = S_FindName (cl.sound_name[i], true, false);
|
|
}
|
|
}
|
|
|
|
void S_Restart_f (void)
|
|
{
|
|
S_EnumerateDevices();
|
|
|
|
S_DoRestart(false);
|
|
}
|
|
|
|
void S_Control_f (void)
|
|
{
|
|
int i;
|
|
char *command;
|
|
|
|
command = Cmd_Argv (1);
|
|
|
|
if (!Q_strcasecmp(command, "off"))
|
|
{
|
|
Cache_Flush();//forget the old sounds.
|
|
|
|
S_StopAllSounds (true);
|
|
|
|
S_Shutdown(false);
|
|
sound_started = 0;
|
|
}
|
|
|
|
if (!Q_strcasecmp(command, "rate") || !Q_strcasecmp(command, "speed"))
|
|
{
|
|
Cvar_SetValue(&snd_khz, atof(Cmd_Argv (2))/1000);
|
|
S_Restart_f();
|
|
return;
|
|
}
|
|
|
|
//individual device control
|
|
if (!Q_strncasecmp(command, "card", 4))
|
|
{
|
|
int card;
|
|
soundcardinfo_t *sc;
|
|
card = atoi(command+4);
|
|
|
|
for (i = 0, sc = sndcardinfo; i < card && sc; i++,sc=sc->next)
|
|
;
|
|
|
|
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 oldest;
|
|
|
|
// Check for replacement sound, or an idle channel
|
|
oldest = -1;
|
|
for (ch_idx=DYNAMIC_FIRST; ch_idx < sc->max_chans ; 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;
|
|
}
|
|
|
|
if (!sc->channel[ch_idx].sfx)
|
|
oldest = ch_idx;
|
|
}
|
|
|
|
if (oldest == -1)
|
|
{
|
|
oldest = sc->max_chans;
|
|
Z_ReallocElements((void**)&sc->channel, &sc->max_chans, oldest+1, sizeof(*sc->channel));
|
|
}
|
|
|
|
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<<PITCHSHIFT) * scale + 0.5;
|
|
if (ch->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)<<PITCHSHIFT: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 (timeoffset != 0.0)
|
|
chanupdatetype |= CUR_OFFSET;
|
|
|
|
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<<PITCHSHIFT) * ratemul); //*sfx->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 < sc->total_chans; 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)
|
|
{
|
|
if (flags & CF_NOREPLACE)
|
|
{
|
|
int i;
|
|
for (i = 0; i < sc->total_chans; i++)
|
|
if (sc->channel[i].entnum == entnum && sc->channel[i].entchannel == entchannel)
|
|
break;
|
|
if (i < sc->total_chans)
|
|
continue;
|
|
}
|
|
#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 ; i<sc->total_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 = 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<<PITCHSHIFT;
|
|
VectorCopy (origin, ss->origin);
|
|
ss->master_vol = vol*255;
|
|
ss->dist_mult = attenuation / snd_nominaldistance.value;
|
|
ss->pos = 0;
|
|
ss->flags = CF_FORCELOOP|CF_CLI_STATIC;
|
|
|
|
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<<PITCHSHIFT;
|
|
chan->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<<PITCHSHIFT;
|
|
|
|
VectorClear(chan->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 && q3)
|
|
count = q3->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 < sc->total_chans ; 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 < sc->total_chans ; 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<<PITCHSHIFT; //q2 does weird stuff with the pos. we just forceloop and detect when it became irrelevant. this is required for stream decoding or openal
|
|
c->rate = 1<<PITCHSHIFT;
|
|
for (j = 0; j < countof(c->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 ; i<sc->total_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 (ch->flags & CF_CLI_STATIC)
|
|
{
|
|
// 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 ; j<i; j++, combine++)
|
|
if (combine->sfx == 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 ; i<sc->total_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;
|
|
RSpeedMark();
|
|
S_LockMixer();
|
|
for (sc = sndcardinfo; sc; sc = sc->next)
|
|
S_UpdateCard(sc);
|
|
S_UnlockMixer();
|
|
RSpeedEnd(RSPEED_AUDIO);
|
|
}
|
|
|
|
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 (i<Cmd_Argc())
|
|
{
|
|
if (!Q_strrchr(Cmd_Argv(i), '.'))
|
|
{
|
|
Q_strncpyz(name, Cmd_Argv(i), sizeof(name)-4);
|
|
Q_strcat(name, ".wav");
|
|
}
|
|
else
|
|
Q_strncpyz(name, Cmd_Argv(i), sizeof(name));
|
|
i++;
|
|
sfx = S_PrecacheSound(name);
|
|
|
|
if (!strcmp(cmdname, "playvol"))
|
|
vol = Q_atof(Cmd_Argv(i++));
|
|
else
|
|
vol = 1.0;
|
|
S_StartSound(entnum, 0, sfx, origin, NULL, vol, attenuation, 0, 0, flags);
|
|
}
|
|
}
|
|
|
|
void S_SoundList_f(void)
|
|
{
|
|
int i;
|
|
sfx_t *sfx;
|
|
sfxcache_t *sc;
|
|
sfxcache_t scachebuf;
|
|
int size, total;
|
|
int duration;
|
|
|
|
S_LockMixer();
|
|
|
|
|
|
total = 0;
|
|
for (sfx=known_sfx, i=0 ; i<num_sfx ; i++, sfx++)
|
|
{
|
|
if (sfx->loadstate != 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<<PITCHSHIFT;
|
|
c->sfx = s->sfx;
|
|
SND_Spatialize(si, c);
|
|
|
|
if (si->ChannelUpdate)
|
|
si->ChannelUpdate(si, c, CUR_EVERYTHING);
|
|
}
|
|
}
|
|
}
|
|
S_UnlockMixer();
|
|
}
|