4c2066601a
Fixed up the -netquake / -spasm / -fitz args slightly, should actually be usable now. sv_mintic 0 is now treated as 0.013 when using nqplayerphysics, to try to make it smoother for nq clients. Preparing for astc's volume formats. Mostly for completeness, I was bored. Disabled for now because nothing supports them anyway. Fix broken mousewheel in SDL2 builds. Fix configs not getting loaded following initial downloads in the web port/etc. Make the near-cloud layer of q1 scrolling sky fully opaque by default (like vanilla). Sky fog now ignores depth, treating it as an infinite distance. Fix turbs not responding to fog. r_fullbright no longer needs vid_reload to take effect (and more efficient now). Tweaked the audio code to use an format enum instead of byte width, just with the same values still, primarily to clean up loaders that deal with S32 vs F32, or U8 vs S8. Added a cvar to control whether to use threads for the qcgc. Still disabled by default but no longer requires engine recompiles to enable! git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5683 fc73d0e0-1445-4013-8a0c-d673dee63da5
543 lines
14 KiB
C
543 lines
14 KiB
C
#include "quakedef.h"
|
|
|
|
#ifdef AVAIL_OGGVORBIS
|
|
#define OV_EXCLUDE_STATIC_CALLBACKS
|
|
|
|
#ifdef __MORPHOS__
|
|
#include <exec/exec.h>
|
|
#include <libraries/vorbisfile.h>
|
|
|
|
#include <proto/exec.h>
|
|
#include <proto/vorbisfile.h>
|
|
#else
|
|
#include <vorbis/vorbisfile.h>
|
|
#endif
|
|
|
|
|
|
#ifdef LIBVORBISFILE_STATIC
|
|
#define p_ov_open_callbacks ov_open_callbacks
|
|
#define p_ov_clear ov_clear
|
|
#define p_ov_info ov_info
|
|
#define p_ov_comment ov_comment
|
|
#define p_ov_pcm_total ov_pcm_total
|
|
#define p_ov_time_total ov_time_total
|
|
#define p_ov_read ov_read
|
|
#define p_ov_pcm_seek ov_pcm_seek
|
|
#else
|
|
#if defined(__MORPHOS__)
|
|
|
|
#define oggvorbislibrary VorbisFileBase
|
|
struct Library *VorbisFileBase;
|
|
|
|
#else
|
|
dllhandle_t *oggvorbislibrary;
|
|
#endif
|
|
|
|
#ifdef __MORPHOS__
|
|
#define p_ov_open_callbacks(a, b, c, d, e) ov_open_callbacks(a, b, c, d, &e)
|
|
#define p_ov_clear ov_clear
|
|
#define p_ov_info ov_info
|
|
#define p_ov_comment ov_comment
|
|
#define p_ov_pcm_total ov_pcm_total
|
|
#define p_ov_time_total ov_time_total
|
|
#define p_ov_read ov_read
|
|
#define p_ov_pcm_seek ov_pcm_seek
|
|
#else
|
|
int (VARGS *p_ov_open_callbacks) (void *datasource, OggVorbis_File *vf, char *initial, long ibytes, ov_callbacks callbacks);
|
|
int (VARGS *p_ov_clear)(OggVorbis_File *vf);
|
|
vorbis_info *(VARGS *p_ov_info)(OggVorbis_File *vf,int link);
|
|
vorbis_comment *(VARGS *p_ov_comment) (OggVorbis_File *vf,int link);
|
|
ogg_int64_t (VARGS *p_ov_pcm_total)(OggVorbis_File *vf,int i);
|
|
double (VARGS *p_ov_time_total)(OggVorbis_File *vf,int i);
|
|
long (VARGS *p_ov_read)(OggVorbis_File *vf,char *buffer,int length,int bigendianp,int word,int sgned,int *bitstream);
|
|
int (VARGS *p_ov_pcm_seek)(OggVorbis_File *vf,ogg_int64_t pos);
|
|
#endif
|
|
#endif
|
|
|
|
|
|
typedef struct {
|
|
unsigned char *start; //file positions
|
|
unsigned long length;
|
|
unsigned long pos;
|
|
int srcspeed;
|
|
int srcchannels;
|
|
|
|
qboolean nopurge;
|
|
qboolean failed;
|
|
|
|
char *tempbuffer;
|
|
int tempbufferbytes;
|
|
|
|
char *decodedbuffer;
|
|
int decodedbufferbytes;
|
|
int decodedbytestart;
|
|
int decodedbytecount;
|
|
|
|
quintptr_t pcmtotal;
|
|
float timetotal;
|
|
OggVorbis_File vf;
|
|
|
|
sfx_t *s;
|
|
} ovdecoderbuffer_t;
|
|
|
|
static float QDECL OV_Query(struct sfx_s *sfx, struct sfxcache_s *buf, char *name, size_t namesize)
|
|
{
|
|
ovdecoderbuffer_t *dec = sfx->decoder.buf;
|
|
if (!dec)
|
|
return -1;
|
|
|
|
if (dec->timetotal < 0)
|
|
{
|
|
dec->pcmtotal = p_ov_pcm_total(&dec->vf, -1);
|
|
dec->timetotal = p_ov_time_total(&dec->vf, -1);
|
|
}
|
|
|
|
if (buf)
|
|
{
|
|
buf->data = NULL; //you're not meant to actually be using the data here
|
|
buf->soundoffset = 0;
|
|
buf->length = dec->pcmtotal;
|
|
buf->numchannels = dec->srcchannels;
|
|
buf->speed = dec->srcspeed;
|
|
buf->format = QAF_S16;
|
|
}
|
|
if (name)
|
|
{
|
|
vorbis_comment *c = p_ov_comment(&dec->vf, -1);
|
|
int i;
|
|
const char *artist = NULL;
|
|
const char *title = NULL;
|
|
for (i = 0; i < c->comments; i++)
|
|
{
|
|
if (!strncmp(c->user_comments[i], "ARTIST=", 7))
|
|
artist = c->user_comments[i]+7;
|
|
else if (!strncmp(c->user_comments[i], "TITLE=", 6))
|
|
title = c->user_comments[i]+6;
|
|
}
|
|
|
|
if (artist && title)
|
|
Q_snprintfz(name, namesize, "%s - %s", artist, title);
|
|
else if (title)
|
|
Q_snprintfz(name, namesize, "%s", title);
|
|
}
|
|
return dec->timetotal;
|
|
}
|
|
|
|
static sfxcache_t *QDECL OV_DecodeSome(struct sfx_s *sfx, struct sfxcache_s *buf, ssamplepos_t start, int length)
|
|
{
|
|
extern int snd_speed;
|
|
extern cvar_t snd_linearresample_stream;
|
|
int bigendianp = bigendian;
|
|
int current_section = 0;
|
|
|
|
ovdecoderbuffer_t *dec = sfx->decoder.buf;
|
|
int bytesread;
|
|
|
|
int outspeed = snd_speed;
|
|
|
|
// Con_Printf("Minlength = %03i ", minlength);
|
|
|
|
start *= 2*dec->srcchannels;
|
|
length *= 2*dec->srcchannels;
|
|
|
|
if (length)
|
|
{
|
|
if (start < dec->decodedbytestart)
|
|
{
|
|
// Con_Printf("Rewound to %i\n", start);
|
|
dec->failed = false;
|
|
|
|
//check pos
|
|
if (p_ov_pcm_seek(&dec->vf, start * (dec->srcspeed/(2.0*dec->srcchannels*outspeed))) == 0)
|
|
{
|
|
/*something rewound, purge clear the buffer*/
|
|
dec->decodedbytecount = 0;
|
|
dec->decodedbytestart = start;
|
|
}
|
|
}
|
|
|
|
/* if (start > dec->decodedbytestart + dec->decodedbytecount)
|
|
{
|
|
dec->decodedbytestart = start;
|
|
p_ov_pcm_seek(&dec->vf, (dec->decodedbytestart * dec->srcspeed) / outspeed);
|
|
}
|
|
*/
|
|
if (dec->decodedbytecount > outspeed*8 && !dec->nopurge)
|
|
{
|
|
/*everything is okay, but our buffer is getting needlessly large.
|
|
keep anything after the 'new' position, but discard all before that
|
|
trim shouldn't be able to go negative
|
|
*/
|
|
int trim = start - dec->decodedbytestart;
|
|
if (trim < 0)
|
|
{
|
|
dec->decodedbytecount = 0;
|
|
dec->decodedbytestart = start;
|
|
// Con_Printf("trim < 0\n");
|
|
}
|
|
else if (trim > dec->decodedbytecount)
|
|
{
|
|
if (0==p_ov_pcm_seek(&dec->vf, start * (dec->srcspeed/(2.0*dec->srcchannels*outspeed))))
|
|
{
|
|
dec->decodedbytecount = 0;
|
|
dec->decodedbytestart = start;
|
|
}
|
|
// Con_Printf("trim > count\n");
|
|
}
|
|
else
|
|
{
|
|
// Con_Printf("trim retain\n");
|
|
//FIXME: retain an extra half-second for dual+ sound devices running slightly out of sync
|
|
memmove(dec->decodedbuffer, dec->decodedbuffer + trim, dec->decodedbytecount - trim);
|
|
dec->decodedbytecount -= trim;
|
|
dec->decodedbytestart += trim;
|
|
}
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
if (dec->failed || start+length <= dec->decodedbytestart + dec->decodedbytecount)
|
|
break;
|
|
|
|
if (dec->decodedbufferbytes < start+length - dec->decodedbytestart + 4096) //expand if needed. 4096 seems to be the recommended size.
|
|
{
|
|
// Con_Printf("Expand buffer\n");
|
|
dec->decodedbufferbytes = (start+length - dec->decodedbytestart) + max(outspeed, 4096); //over allocate, because we can.
|
|
dec->decodedbuffer = BZ_Realloc(dec->decodedbuffer, dec->decodedbufferbytes);
|
|
}
|
|
|
|
if (outspeed == dec->srcspeed)
|
|
{
|
|
bytesread = p_ov_read(&dec->vf, dec->decodedbuffer+dec->decodedbytecount, (start+length) - (dec->decodedbytestart+dec->decodedbytecount), bigendianp, 2, 1, ¤t_section);
|
|
if (bytesread <= 0)
|
|
{
|
|
if (bytesread != 0) //0==eof
|
|
{
|
|
dec->failed = true;
|
|
Con_Printf("ogg decoding failed %i\n", bytesread);
|
|
break;
|
|
}
|
|
if (start >= dec->decodedbytestart+dec->decodedbytecount)
|
|
return NULL; //let the mixer know that we hit the end
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
double scale = dec->srcspeed / (double)outspeed;
|
|
int decodesize = dec->decodedbufferbytes-dec->decodedbytecount; //bytes available
|
|
decodesize /= 2*dec->srcchannels; //convert bytes to frames
|
|
decodesize = floor(decodesize * scale); //round down, so that the SND_ResampleStream won't overflow the target buffer.
|
|
decodesize *= 2*dec->srcchannels; //convert from frames back to bytes
|
|
if (decodesize > dec->tempbufferbytes)
|
|
{
|
|
dec->tempbuffer = BZ_Realloc(dec->tempbuffer, decodesize);
|
|
dec->tempbufferbytes = decodesize;
|
|
}
|
|
|
|
bytesread = p_ov_read(&dec->vf, dec->tempbuffer, decodesize, bigendianp, 2, 1, ¤t_section);
|
|
|
|
if (bytesread <= 0)
|
|
{
|
|
if (bytesread != 0) //0==eof
|
|
{
|
|
dec->failed = true;
|
|
Con_Printf("ogg decoding failed %i\n", bytesread);
|
|
return NULL;
|
|
}
|
|
if (start >= dec->decodedbytestart+dec->decodedbytecount)
|
|
return NULL; //let the mixer know that we hit the end
|
|
break;
|
|
}
|
|
|
|
SND_ResampleStream(dec->tempbuffer,
|
|
dec->srcspeed,
|
|
2,
|
|
dec->srcchannels,
|
|
bytesread / (2 * dec->srcchannels),
|
|
dec->decodedbuffer+dec->decodedbytecount,
|
|
outspeed,
|
|
2,
|
|
dec->srcchannels,
|
|
snd_linearresample_stream.ival);
|
|
bytesread /= 2*dec->srcchannels; //convert bytes to frames
|
|
bytesread = bytesread / scale; //calculate the same ammount that SND_ResampleStream will have splurged (we should probably make the output count explicit).
|
|
bytesread *= 2*dec->srcchannels; //convert frames to bytes
|
|
}
|
|
|
|
dec->decodedbytecount += bytesread;
|
|
}
|
|
}
|
|
|
|
if (buf)
|
|
{
|
|
buf->data = dec->decodedbuffer;
|
|
buf->soundoffset = dec->decodedbytestart / (2 * dec->srcchannels);
|
|
buf->length = dec->decodedbytecount / (2 * dec->srcchannels);
|
|
buf->numchannels = dec->srcchannels;
|
|
buf->speed = snd_speed;
|
|
buf->format = QAF_S16;
|
|
}
|
|
return buf;
|
|
}
|
|
/*static void OV_CanceledDecoder(void *ctx, void *data, size_t a, size_t b)
|
|
{
|
|
sfx_t *s = ctx;
|
|
if (s->loadstate != SLS_LOADING)
|
|
s->loadstate = SLS_NOTLOADED;
|
|
}*/
|
|
static void QDECL OV_CancelDecoder(sfx_t *s)
|
|
{ //called when the sound is unloaded. the entire thing is going away.
|
|
ovdecoderbuffer_t *dec;
|
|
s->loadstate = SLS_FAILED;
|
|
|
|
dec = s->decoder.buf;
|
|
s->decoder.buf = NULL;
|
|
s->decoder.purge = NULL;
|
|
s->decoder.ended = NULL;
|
|
s->decoder.querydata = NULL;
|
|
s->decoder.decodedata = NULL;
|
|
p_ov_clear (&dec->vf); //close the decoder
|
|
|
|
if (dec->tempbuffer)
|
|
{
|
|
BZ_Free(dec->tempbuffer);
|
|
dec->tempbufferbytes = 0;
|
|
}
|
|
|
|
BZ_Free(dec->decodedbuffer);
|
|
dec->decodedbuffer = NULL;
|
|
|
|
BZ_Free(dec);
|
|
|
|
//due to the nature of message passing, we can get into a state where the main thread is going to flag us as loaded when we have already failed.
|
|
//that is bad.
|
|
//so post a message to the main thread to override it, just in case.
|
|
// COM_AddWork(WG_MAIN, OV_CanceledDecoder, s, NULL, SLS_NOTLOADED, 0);
|
|
s->loadstate = SLS_NOTLOADED;
|
|
}
|
|
static void QDECL OV_ClearDecoder(sfx_t *s)
|
|
{ //called when the sound is no longer playing.
|
|
ovdecoderbuffer_t *dec;
|
|
dec = s->decoder.buf;
|
|
if (dec->nopurge)
|
|
{
|
|
/* BZ_Free(dec->tempbuffer);
|
|
dec->tempbuffer = NULL;
|
|
dec->tempbufferbytes = 0;
|
|
|
|
BZ_Free(dec->decodedbuffer);
|
|
dec->decodedbuffer = NULL;
|
|
dec->decodedbufferbytes = 0;
|
|
dec->decodedbytestart = 0;
|
|
dec->decodedbytecount = 0;
|
|
*/
|
|
return;
|
|
}
|
|
OV_CancelDecoder(s);
|
|
}
|
|
|
|
static size_t VARGS read_func (void *ptr, size_t size, size_t nmemb, void *datasource)
|
|
{
|
|
ovdecoderbuffer_t *buffer = datasource;
|
|
int spare = buffer->length - buffer->pos;
|
|
|
|
if (size*nmemb > spare)
|
|
nmemb = spare / size;
|
|
memcpy(ptr, &buffer->start[buffer->pos], size*nmemb);
|
|
buffer->pos += size*nmemb;
|
|
return nmemb;
|
|
}
|
|
|
|
static int VARGS seek_func (void *datasource, ogg_int64_t offset, int whence)
|
|
{
|
|
ovdecoderbuffer_t *buffer = datasource;
|
|
switch(whence)
|
|
{
|
|
case SEEK_SET:
|
|
buffer->pos = offset;
|
|
break;
|
|
case SEEK_END:
|
|
buffer->pos = buffer->length+offset;
|
|
break;
|
|
case SEEK_CUR:
|
|
buffer->pos+=offset;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int VARGS close_func (void *datasource)
|
|
{
|
|
ovdecoderbuffer_t *buffer = datasource;
|
|
BZ_Free(buffer->start);
|
|
buffer->start=0;
|
|
return 0;
|
|
}
|
|
|
|
static long VARGS tell_func (void *datasource)
|
|
{
|
|
ovdecoderbuffer_t *buffer = datasource;
|
|
return buffer->pos;
|
|
}
|
|
static ov_callbacks callbacks = {
|
|
read_func,
|
|
seek_func,
|
|
close_func,
|
|
tell_func,
|
|
};
|
|
static qboolean OV_StartDecode(unsigned char *start, unsigned long length, ovdecoderbuffer_t *buffer)
|
|
{
|
|
#ifndef LIBVORBISFILE_STATIC
|
|
static qboolean tried;
|
|
#ifndef __MORPHOS__
|
|
static dllfunction_t funcs[] =
|
|
{
|
|
{(void*)&p_ov_open_callbacks, "ov_open_callbacks"},
|
|
{(void*)&p_ov_comment, "ov_comment"},
|
|
{(void*)&p_ov_pcm_total, "ov_pcm_total"},
|
|
{(void*)&p_ov_time_total, "ov_time_total"},
|
|
{(void*)&p_ov_clear, "ov_clear"},
|
|
{(void*)&p_ov_info, "ov_info"},
|
|
{(void*)&p_ov_read, "ov_read"},
|
|
{(void*)&p_ov_pcm_seek, "ov_pcm_seek"},
|
|
{NULL}
|
|
};
|
|
#endif
|
|
|
|
if (!oggvorbislibrary && !tried)
|
|
#if defined(__MORPHOS__)
|
|
{
|
|
VorbisFileBase = OpenLibrary("vorbisfile.library", 2);
|
|
if (!VorbisFileBase)
|
|
{
|
|
Con_Printf("Unable to open vorbisfile.library version 2\n");
|
|
}
|
|
}
|
|
#elif defined(_WIN32)
|
|
{
|
|
oggvorbislibrary = Sys_LoadLibrary("vorbisfile", funcs);
|
|
if (!oggvorbislibrary)
|
|
oggvorbislibrary = Sys_LoadLibrary("libvorbisfile", funcs);
|
|
|
|
if (!oggvorbislibrary)
|
|
{
|
|
oggvorbislibrary = Sys_LoadLibrary("libvorbisfile-3", funcs);
|
|
if (!oggvorbislibrary)
|
|
oggvorbislibrary = Sys_LoadLibrary("libvorbisfile", funcs);
|
|
}
|
|
|
|
if (!oggvorbislibrary)
|
|
Con_Printf("Couldn't load DLL: \"vorbisfile.dll\" or \"libvorbisfile-3\".\n");
|
|
}
|
|
#else
|
|
{
|
|
oggvorbislibrary = Sys_LoadLibrary("libvorbisfile.so.3", funcs);
|
|
if (!oggvorbislibrary)
|
|
oggvorbislibrary = Sys_LoadLibrary("libvorbisfile", funcs);
|
|
if (!oggvorbislibrary)
|
|
Con_Printf("Couldn't load library: \"libvorbisfile\".\n");
|
|
}
|
|
#endif
|
|
|
|
tried = true;
|
|
|
|
if (!oggvorbislibrary)
|
|
{
|
|
Con_Printf("ogg vorbis library is not loaded.\n");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
buffer->start = start;
|
|
buffer->length = length;
|
|
buffer->pos = 0;
|
|
if (p_ov_open_callbacks(buffer, &buffer->vf, NULL, 0, callbacks))
|
|
{
|
|
Con_Printf("Input %s does not appear to be an Ogg Vorbis bitstream.\n", buffer->s->name);
|
|
return false;
|
|
}
|
|
|
|
/* Print the comments plus a few lines about the bitstream we're
|
|
decoding */
|
|
{
|
|
// char **ptr=p_ov_comment(&buffer->vf,-1)->user_comments;
|
|
vorbis_info *vi=p_ov_info(&buffer->vf,-1);
|
|
|
|
if (vi->channels < 1 || vi->channels > 2)
|
|
{
|
|
p_ov_clear (&buffer->vf);
|
|
Con_Printf("Input %s has %i channels.\n", buffer->s->name, vi->channels);
|
|
return false;
|
|
}
|
|
|
|
buffer->srcchannels = vi->channels;
|
|
buffer->srcspeed = vi->rate;
|
|
/*
|
|
while(*ptr){
|
|
Con_Printf("%s\n",*ptr);
|
|
ptr++;
|
|
}
|
|
Con_Printf("\nBitstream is %d channel, %ldHz\n",vi->channels,vi->rate);
|
|
Con_Printf("\nDecoded length: %ld samples\n",
|
|
(long)p_ov_pcm_total(&buffer->vf,-1));
|
|
Con_Printf("Encoded by: %s\n\n",p_ov_comment(&buffer->vf,-1)->vendor);
|
|
*/ }
|
|
buffer->tempbuffer = NULL;
|
|
buffer->tempbufferbytes = 0;
|
|
|
|
buffer->start = BZ_Malloc(length);
|
|
memcpy(buffer->start, start, length);
|
|
|
|
buffer->timetotal = -1;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
qboolean QDECL S_LoadOVSound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed, qboolean forcedecode)
|
|
{
|
|
ovdecoderbuffer_t *buffer;
|
|
|
|
if (datalen < 4 || strncmp(data, "OggS", 4))
|
|
return false;
|
|
|
|
buffer = Z_Malloc(sizeof(ovdecoderbuffer_t));
|
|
|
|
buffer->decodedbytestart = 0;
|
|
buffer->decodedbytecount = 0;
|
|
buffer->nopurge = forcedecode;
|
|
buffer->s = s;
|
|
s->decoder.buf = buffer;
|
|
s->loopstart = -1;
|
|
|
|
if (!OV_StartDecode(data, datalen, buffer))
|
|
{
|
|
if (buffer->decodedbuffer)
|
|
{
|
|
BZ_Free(buffer->decodedbuffer);
|
|
buffer->decodedbuffer = NULL;
|
|
}
|
|
buffer->decodedbufferbytes = 0;
|
|
buffer->decodedbytestart = 0;
|
|
buffer->decodedbytecount = 0;
|
|
Z_Free(s->decoder.buf);
|
|
s->decoder.buf = NULL;
|
|
s->loadstate = SLS_FAILED; //failed!
|
|
return false;
|
|
}
|
|
s->decoder.decodedata = OV_DecodeSome;
|
|
s->decoder.querydata = OV_Query;
|
|
s->decoder.purge = OV_CancelDecoder;
|
|
s->decoder.ended = OV_ClearDecoder;
|
|
|
|
s->decoder.decodedata(s, NULL, 0, 100);
|
|
|
|
if (p_ov_time_total(&buffer->vf, -1) < 5) //short sounds might as well remain cached.
|
|
buffer->nopurge = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|