New qc builtin to allow menuqc+csqc to stream their own audio.

This commit is contained in:
Shpoike 2025-01-22 08:13:39 +00:00
parent 3837beb84e
commit edda391be5
10 changed files with 114 additions and 12 deletions

View file

@ -336,7 +336,7 @@ static qbyte *CIN_ReadNextFrame (cinematics_t *cin)
else if (cin->s_width == 2)
COM_SwapLittleShortBlock((short *)samples, count*cin->s_channels);
S_RawAudio (0, samples, cin->s_rate, count, cin->s_channels, cin->s_width, volume.value );
S_RawAudio (SOURCEID_CINEMATIC, samples, cin->s_rate, count, cin->s_channels, cin->s_width, volume.value );
in.data = compressed;
in.count = size;

View file

@ -1801,10 +1801,10 @@ static qboolean Media_WinAvi_DecodeFrame (cin_t *cin, qboolean nosound, qboolean
qacmStreamConvert(cin->avi.audiodecoder, &strhdr, ACM_STREAMCONVERTF_BLOCKALIGN);
qacmStreamUnprepareHeader(cin->avi.audiodecoder, &strhdr, 0);
S_RawAudio(-1, strhdr.pbDst, cin->avi.pWaveFormat->nSamplesPerSec, strhdr.cbDstLengthUsed/4, cin->avi.pWaveFormat->nChannels, 2, volume.value);
S_RawAudio(SOURCEID_CINEMATIC, strhdr.pbDst, cin->avi.pWaveFormat->nSamplesPerSec, strhdr.cbDstLengthUsed/4, cin->avi.pWaveFormat->nChannels, 2, volume.value);
}
else
S_RawAudio(-1, pBuffer, cin->avi.pWaveFormat->nSamplesPerSec, samples, cin->avi.pWaveFormat->nChannels, 2, volume.value);
S_RawAudio(SOURCEID_CINEMATIC, pBuffer, cin->avi.pWaveFormat->nSamplesPerSec, samples, cin->avi.pWaveFormat->nChannels, 2, volume.value);
}
return true;
}
@ -2223,7 +2223,7 @@ static qboolean Media_Roq_DecodeFrame (cin_t *cin, qboolean nosound, qboolean fo
while (cin->roq.roqfilm->audio_channels && S_HaveOutput() && cin->roq.roqfilm->aud_pos < cin->roq.roqfilm->vid_pos)
{
if (roq_read_audio(cin->roq.roqfilm)>0)
S_RawAudio(-1, cin->roq.roqfilm->audio, 22050, cin->roq.roqfilm->audio_size/cin->roq.roqfilm->audio_channels, cin->roq.roqfilm->audio_channels, 2, volume.value );
S_RawAudio(SOURCEID_CINEMATIC, cin->roq.roqfilm->audio, 22050, cin->roq.roqfilm->audio_size/cin->roq.roqfilm->audio_channels, cin->roq.roqfilm->audio_channels, 2, volume.value );
else
break;
}
@ -2589,7 +2589,7 @@ qboolean Media_StopFilm(qboolean all)
R_UnloadShader(videoshader);
videoshader = NULL;
S_RawAudio(-1, NULL, 0, 0, 0, 0, 0);
S_RawAudio(SOURCEID_CINEMATIC, NULL, 0, 0, 0, 0, 0);
}
while (pendingfilms && !videoshader)

View file

@ -1050,6 +1050,55 @@ void QCBUILTIN PF_cl_localsound(pubprogfuncs_t *prinst, struct globalvars_s *pr_
S_LocalSound2(s, chan, vol);
}
void QCBUILTIN PF_cl_queueaudio(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
world_t *world = prinst->parms->user;
int sourceid = (world->keydestmask == kdm_menu)?SOURCEID_MENUQC:SOURCEID_CSQC;
int hz = G_INT(OFS_PARM0);
int channels = G_INT(OFS_PARM1);
int type = G_INT(OFS_PARM2);
int qcptr = G_INT(OFS_PARM3);
int numframes = G_INT(OFS_PARM4);
int i;
const float volume = 1;
qaudiofmt_t fmt;
const void *data;
void *ndata;
if (hz < 1 || (channels != 1 && channels != 2) || numframes <= 0)
{ //dumb arg.
G_FLOAT(OFS_RETURN) = -1;
return;
}
switch(type)
{
case 8: fmt = QAF_S8; data = PR_GetReadQCPtr(prinst, qcptr, numframes*channels*QAF_BYTES(fmt));
ndata = alloca(sizeof(char)*numframes*channels);
for (i = numframes*channels; i --> 0; )
((char*)ndata)[i] = ((const qbyte*)data)[i]-128;
data = ndata; break;
case -8: fmt = QAF_S8; data = PR_GetReadQCPtr(prinst, qcptr, numframes*channels*QAF_BYTES(fmt)); break;
case -16: fmt = QAF_S16; data = PR_GetReadQCPtr(prinst, qcptr, numframes*channels*QAF_BYTES(fmt)); break;
// case 16: fmt = QAF_U16: data = PR_GetReadQCPtr(prinst, qcptr, numframes*channels*QAF_BYTES(fmt)); break;
#ifdef MIXER_F32
case 256|32: fmt = QAF_F32; data = PR_GetReadQCPtr(prinst, qcptr, numframes*channels*QAF_BYTES(fmt)); break;
#endif
default:
G_FLOAT(OFS_RETURN) = -2; //unsupported width.
return;
}
S_RawAudio(sourceid, data, hz, numframes*channels, channels, fmt, volume);
G_FLOAT(OFS_RETURN) = 1;
}
void QCBUILTIN PF_cl_getqueuedaudiotime(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
world_t *world = prinst->parms->user;
int sourceid = (world->keydestmask == kdm_menu)?SOURCEID_MENUQC:SOURCEID_CSQC;
G_FLOAT(OFS_RETURN) = S_RawAudioQueued(sourceid);
}
void QCBUILTIN PF_cl_getlocaluserinfoblob (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{

View file

@ -7035,6 +7035,8 @@ static struct {
{"SetListener", PF_cs_setlistener, 351}, // #351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC)
{"setup_reverb", PF_cs_setupreverb, 0},
{"queueaudio", PF_cl_queueaudio, 0},
{"getqueuedaudiotime", PF_cl_getqueuedaudiotime, 0},
{"registercommand", PF_cs_registercommand, 352}, // #352 void(string cmdname) registercommand (EXT_CSQC)
{"wasfreed", PF_WasFreed, 353}, // #353 float(entity ent) wasfreed (EXT_CSQC) (should be availabe on server too)

View file

@ -2655,6 +2655,8 @@ static struct {
{"isdemo", PF_isdemo, 349},
// {NULL, PF_Fixme, 350},
// {NULL, PF_Fixme, 351},
{"queueaudio", PF_cl_queueaudio, 0},
{"getqueuedaudiotime", PF_cl_getqueuedaudiotime, 0},
{"registercommand", PF_menu_registercommand, 352},
{"wasfreed", PF_WasFreed, 353},
{"serverkey", PF_cl_serverkey, 354}, // #354 string(string key) serverkey;

View file

@ -4214,7 +4214,7 @@ typedef struct {
int length;
void *data;
} streaming_t;
#define MAX_RAW_SOURCES (MAX_CLIENTS+1)
#define MAX_RAW_SOURCES (MAX_CLIENTS+3)
streaming_t s_streamers[MAX_RAW_SOURCES];
void S_ClearRaw(void)
@ -4256,8 +4256,47 @@ void QDECL S_Raw_Purge(sfx_t *sfx)
memset(&sfx->decoder, 0, sizeof(sfx->decoder));
}
float S_RawAudioQueued(int sourceid) //returns in seconds. we don't know what the original sample count was.
{
soundcardinfo_t *si;
streaming_t *s;
int i;
float r;
ssamplepos_t highest, pos;
for (s = s_streamers, i = 0; i < MAX_RAW_SOURCES; i++, s++)
{
if (s->inuse && s->id == sourceid)
{
S_LockMixer();
highest = ((~(usamplepos_t)0)>>1);
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 (si->GetChannelPos)
pos = si->GetChannelPos(si, &si->channel[i]);
else
pos = si->channel[i].pos>>PITCHSHIFT;
if (highest > pos)
highest = pos;
break;
}
}
if (highest == ((~(usamplepos_t)0)>>1))
r = 0; //nothing playing it... needs to be woken up. pretend nothing is there so it gets poked a bit.
else
r = (s->length - highest) / (float)snd_speed;
S_UnlockMixer();
return r;
}
}
return 0; //not found
}
//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)
void S_RawAudio(int sourceid, const qbyte *data, int speed, int samples, int channels, qaudiofmt_t format, float volume)
{
soundcardinfo_t *si;
int i;

View file

@ -291,11 +291,11 @@ static wavinfo_t GetWavinfo (char *name, qbyte *wav, int wavlength);
// SND_ResampleStream: takes a sound stream and converts with given parameters. Limited to
// 8-16-bit signed conversions and mono-to-mono/stereo-to-stereo conversions.
// Not an in-place algorithm.
void SND_ResampleStream (void *in, int inrate, qaudiofmt_t informat, int inchannels, int insamps, void *out, int outrate, qaudiofmt_t outformat, int outchannels, int resampstyle)
void SND_ResampleStream (const void *in, int inrate, qaudiofmt_t informat, int inchannels, int insamps, void *out, int outrate, qaudiofmt_t outformat, int outchannels, int resampstyle)
{
double scale;
signed char *in8 = (signed char *)in;
short *in16 = (short *)in;
const signed char *in8 = (const signed char *)in;
const short *in16 = (const short *)in;
signed char *out8 = (signed char *)out;
short *out16 = (short *)out;
int outsamps, outnlsamps, outsampleft, outsampright;

View file

@ -262,7 +262,7 @@ qboolean S_IsPlayingSomewhere(sfx_t *s);
// picks a channel based on priorities, empty slots, number of channels
channel_t *SND_PickChannel(soundcardinfo_t *sc, int entnum, int entchannel);
void SND_ResampleStream (void *in, int inrate, qaudiofmt_t inwidth, int inchannels, int insamps, void *out, int outrate, qaudiofmt_t outwidth, int outchannels, int resampstyle);
void SND_ResampleStream (const void *in, int inrate, qaudiofmt_t inwidth, int inchannels, int insamps, void *out, int outrate, qaudiofmt_t outwidth, int outchannels, int resampstyle);
// restart entire sound subsystem (doesn't flush old sounds, so make sure that happens)
void S_DoRestart (qboolean onlyifneeded);
@ -270,7 +270,13 @@ void S_DoRestart (qboolean onlyifneeded);
void S_Restart_f (void);
//plays streaming audio
void S_RawAudio(int sourceid, qbyte *data, int speed, int samples, int channels, qaudiofmt_t width, float volume);
#define SOURCEID_MENUQC -3
#define SOURCEID_CSQC -2
#define SOURCEID_CINEMATIC -1
#define SOURCEID_VOIP_FIRST 0
#define SOURCEID_VOIP_MAX MAX_CLIENTS-1
void S_RawAudio(int sourceid, const qbyte *data, int speed, int samples, int channels, qaudiofmt_t width, float volume);
float S_RawAudioQueued(int sourceid);
void CLVC_Poll (void);

View file

@ -541,6 +541,8 @@ void QCBUILTIN PF_cl_sprint (pubprogfuncs_t *prinst, struct globalvars_s *pr_glo
void QCBUILTIN PF_cl_bprint (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_cl_clientcount (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_cl_localsound(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_cl_queueaudio(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_cl_getqueuedaudiotime(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_cl_SendPacket(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_cl_getlocaluserinfoblob (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_cl_getlocaluserinfostring (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);

View file

@ -11850,6 +11850,8 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs
{"isserver", PF_Fixme, 0, 0, 0, 350, D("float()", "Returns non-zero whenever the local console can directly affect the server (ie: listen servers or single-player). Compat note: DP returns 0 for single-player.")},//(EXT_CSQC)
{"SetListener", PF_Fixme, 0, 0, 0, 351, D("void(vector origin, vector forward, vector right, vector up, optional float reverbtype)", "Sets the position of the view, as far as the audio subsystem is concerned. This should be called once per CSQC_UpdateView as it will otherwise revert to default. For reverbtype, see setup_reverb or treat as 'underwater'.")},// (EXT_CSQC)
{"setup_reverb", PF_Fixme, 0, 0, 0, 0, D("typedef struct {\n\tfloat flDensity;\n\tfloat flDiffusion;\n\tfloat flGain;\n\tfloat flGainHF;\n\tfloat flGainLF;\n\tfloat flDecayTime;\n\tfloat flDecayHFRatio;\n\tfloat flDecayLFRatio;\n\tfloat flReflectionsGain;\n\tfloat flReflectionsDelay;\n\tvector flReflectionsPan;\n\tfloat flLateReverbGain;\n\tfloat flLateReverbDelay;\n\tvector flLateReverbPan;\n\tfloat flEchoTime;\n\tfloat flEchoDepth;\n\tfloat flModulationTime;\n\tfloat flModulationDepth;\n\tfloat flAirAbsorptionGainHF;\n\tfloat flHFReference;\n\tfloat flLFReference;\n\tfloat flRoomRolloffFactor;\n\tint iDecayHFLimit;\n} reverbinfo_t;\nvoid(float reverbslot, reverbinfo_t *reverbinfo, int sizeofreverinfo_t)", "Reconfigures a reverb slot for weird effects. Slot 0 is reserved for no effects. Slot 1 is reserved for underwater effects. Reserved slots will be reinitialised on snd_restart, but can otherwise be changed. These reverb slots can be activated with SetListener. Note that reverb will currently only work when using OpenAL.")},
{"queueaudio", PF_Fixme, 0, 0, 0, 0, D("float(int hz, int channels, int type, void *data, unsigned int frames)", "Queues raw audio. For best results do not vary the hz/channels values. type: -8=__int8"/*"8=__uint8"*/", -16==__int16"/*", 16=__uint16"*//*", -32=__int32"*//*", 32=__uint32"*//*", 256|32==float"*/)},
{"getqueuedaudio",PF_Fixme, 0, 0, 0, 0, D("float()", "Returns the number of seconds of audio that is still pending.")},
{"registercommand", PF_sv_registercommand,0,0, 0, 352, D("void(string cmdname, optional string desc)", "Register the given console command, for easy console use.\nConsole commands that are later used will invoke CSQC_ConsoleCommand/m_consolecommand/ConsoleCmd according to module.")},//(EXT_CSQC)
{"wasfreed", PF_WasFreed,0, 0, 0, 353, D("float(entity ent)", "Quickly check to see if the entity is currently free. This function is only valid during the half-second non-reuse window, after that it may give bad results. Try one second to make it more robust.")},//(EXT_CSQC) (should be availabe on server too)
{"serverkey", PF_sv_serverkeystring,0,0, 0, 354, D("string(string key)", "Look up a key in the server's public serverinfo string. If the key contains binary data then it will be truncated at the first null.")},//