static cvar_t snd_noextraupdate = {0, "snd_noextraupdate", "0", "disables extra sound mixer calls that are meant to reduce the chance of sound breakup at very low framerates"};
static cvar_t snd_show = {0, "snd_show", "0", "shows some statistics about sound mixing"};
-// Default sound format is 48KHz, 32bit float, stereo
+// Default sound format is 48KHz, 16-bit, stereo
// (48KHz because a lot of onboard sound cards sucks at any other speed)
static cvar_t snd_speed = {CVAR_SAVE, "snd_speed", "48000", "sound output frequency, in hertz"};
-static cvar_t snd_width = {CVAR_SAVE, "snd_width", "4", "sound output precision, in bytes - 1 = 8bit, 2 = 16bit, 4 = 32bit float"};
+static cvar_t snd_width = {CVAR_SAVE, "snd_width", "2", "sound output precision, in bytes (1 and 2 supported)"};
static cvar_t snd_channels = {CVAR_SAVE, "snd_channels", "2", "number of channels for the sound output (2 for stereo; up to 8 supported for 3D sound)"};
static cvar_t snd_startloopingsounds = {0, "snd_startloopingsounds", "1", "whether to start sounds that would loop (you want this to be 1); existing sounds are not affected"};
}
+static qboolean S_ChooseCheaperFormat (snd_format_t* format, qboolean fixed_speed, qboolean fixed_width, qboolean fixed_channels)
+{
+ static const snd_format_t thresholds [] =
+ {
+ // speed width channels
+ { SND_MIN_SPEED, SND_MIN_WIDTH, SND_MIN_CHANNELS },
+ { 11025, 1, 2 },
+ { 22050, 2, 2 },
+ { 44100, 2, 2 },
+ { 48000, 2, 6 },
+ { 96000, 2, 6 },
+ { SND_MAX_SPEED, SND_MAX_WIDTH, SND_MAX_CHANNELS },
+ };
+ const unsigned int nb_thresholds = sizeof(thresholds) / sizeof(thresholds[0]);
+ unsigned int speed_level, width_level, channels_level;
+
+ // If we have reached the minimum values, there's nothing more we can do
+ if ((format->speed == thresholds[0].speed || fixed_speed) &&
+ (format->width == thresholds[0].width || fixed_width) &&
+ (format->channels == thresholds[0].channels || fixed_channels))
+ return false;
+
+ // Check the min and max values
+ #define CHECK_BOUNDARIES(param) \
+ if (format->param < thresholds[0].param) \
+ { \
+ format->param = thresholds[0].param; \
+ return true; \
+ } \
+ if (format->param > thresholds[nb_thresholds - 1].param) \
+ { \
+ format->param = thresholds[nb_thresholds - 1].param; \
+ return true; \
+ }
+ CHECK_BOUNDARIES(speed);
+ CHECK_BOUNDARIES(width);
+ CHECK_BOUNDARIES(channels);
+ #undef CHECK_BOUNDARIES
+
+ // Find the level of each parameter
+ #define FIND_LEVEL(param) \
+ param##_level = 0; \
+ while (param##_level < nb_thresholds - 1) \
+ { \
+ if (format->param <= thresholds[param##_level].param) \
+ break; \
+ \
+ param##_level++; \
+ }
+ FIND_LEVEL(speed);
+ FIND_LEVEL(width);
+ FIND_LEVEL(channels);
+ #undef FIND_LEVEL
+
+ // Decrease the parameter with the highest level to the previous level
+ if (channels_level >= speed_level && channels_level >= width_level && !fixed_channels)
+ {
+ format->channels = thresholds[channels_level - 1].channels;
+ return true;
+ }
+ if (speed_level >= width_level && !fixed_speed)
+ {
+ format->speed = thresholds[speed_level - 1].speed;
+ return true;
+ }
+
+ format->width = thresholds[width_level - 1].width;
+ return true;
+}
+
+
#define SWAP_LISTENERS(l1, l2, tmpl) { tmpl = (l1); (l1) = (l2); (l2) = tmpl; }
static void S_SetChannelLayout (void)
chosen_fmt.speed = atoi (com_argv[i + 1]);
fixed_speed = true;
}
-// COMMANDLINEOPTION: Sound: -sndbits <bits> chooses 8 bit or 16 bit or 32bit float sound output
+// COMMANDLINEOPTION: Sound: -sndbits <bits> chooses 8 bit or 16 bit sound output
i = COM_CheckParm ("-sndbits");
if (0 < i && i < com_argc - 1)
{
chosen_fmt.width = SND_MIN_WIDTH;
fixed_width = false;
}
- else if (chosen_fmt.width == 3)
- {
- chosen_fmt.width = 4;
- fixed_width = false;
- }
else if (chosen_fmt.width > SND_MAX_WIDTH)
{
chosen_fmt.width = SND_MAX_WIDTH;
// create the sound buffer used for sumitting the samples to the plaform-dependent module
if (!simsound)
{
- Con_Printf("S_Startup: initializing sound output format: %dHz, %d bit, %d channels...\n",
- chosen_fmt.speed,
- chosen_fmt.width,
- chosen_fmt.channels);
+ snd_format_t suggest_fmt;
+ qboolean accepted;
+
+ accepted = false;
+ do
+ {
+ Con_Printf("S_Startup: initializing sound output format: %dHz, %d bit, %d channels...\n",
+ chosen_fmt.speed, chosen_fmt.width * 8,
+ chosen_fmt.channels);
+
+ memset(&suggest_fmt, 0, sizeof(suggest_fmt));
+ accepted = SndSys_Init(&chosen_fmt, &suggest_fmt);
+
+ if (!accepted)
+ {
+ Con_Printf("S_Startup: sound output initialization FAILED\n");
+
+ // If the module is suggesting another one
+ if (suggest_fmt.speed != 0)
+ {
+ memcpy(&chosen_fmt, &suggest_fmt, sizeof(chosen_fmt));
+ Con_Printf (" Driver has suggested %dHz, %d bit, %d channels. Retrying...\n",
+ suggest_fmt.speed, suggest_fmt.width * 8,
+ suggest_fmt.channels);
+ }
+ // Else, try to find a less resource-demanding format
+ else if (!S_ChooseCheaperFormat (&chosen_fmt, fixed_speed, fixed_width, fixed_channels))
+ break;
+ }
+ } while (!accepted);
- if (!SndSys_Init(&chosen_fmt))
+ // If we haven't found a suitable format
+ if (!accepted)
{
Con_Print("S_Startup: SndSys_Init failed.\n");
sound_spatialized = false;
qboolean S_LoadSound (sfx_t *sfx, qboolean complain);
+snd_buffer_t *Snd_CreateSndBuffer (const unsigned char *samples, unsigned int sampleframes, const snd_format_t* in_format, unsigned int sb_speed);
+qboolean Snd_AppendToSndBuffer (snd_buffer_t* sb, const unsigned char *samples, unsigned int sampleframes, const snd_format_t* format);
+
// If "buffer" is NULL, the function allocates one buffer of "sampleframes" sample frames itself
// (if "sampleframes" is 0, the function chooses the size).
snd_ringbuffer_t *Snd_CreateRingBuffer (const snd_format_t* format, unsigned int sampleframes, void* buffer);
// Architecture-dependent functions
// ====================================================================
-// Create "snd_renderbuffer", attempting to use the chosen sound format, but accepting if the driver wants to change it (e.g. 7.1 to stereo or lowering the speed)
-// Note: SDL automatically converts all formats, so this only fails if there is no audio
-qboolean SndSys_Init (snd_format_t* fmt);
+// Create "snd_renderbuffer" with the proper sound format if the call is successful
+// May return a suggested format if the requested format isn't available
+qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested);
// Stop the sound card, delete "snd_renderbuffer" and free its other resources
void SndSys_Shutdown (void);
}
+/*
+====================
+Snd_CreateSndBuffer
+====================
+*/
+snd_buffer_t *Snd_CreateSndBuffer (const unsigned char *samples, unsigned int sampleframes, const snd_format_t* in_format, unsigned int sb_speed)
+{
+ size_t newsampleframes, memsize;
+ snd_buffer_t* sb;
+
+ newsampleframes = (size_t) ceil((double)sampleframes * (double)sb_speed / (double)in_format->speed);
+
+ memsize = newsampleframes * in_format->channels * in_format->width;
+ memsize += sizeof (*sb) - sizeof (sb->samples);
+
+ sb = (snd_buffer_t*)Mem_Alloc (snd_mempool, memsize);
+ sb->format.channels = in_format->channels;
+ sb->format.width = in_format->width;
+ sb->format.speed = sb_speed;
+ sb->maxframes = (unsigned int)newsampleframes;
+ sb->nbframes = 0;
+
+ if (!Snd_AppendToSndBuffer (sb, samples, sampleframes, in_format))
+ {
+ Mem_Free (sb);
+ return NULL;
+ }
+
+ return sb;
+}
+
+
+/*
+====================
+Snd_AppendToSndBuffer
+====================
+*/
+qboolean Snd_AppendToSndBuffer (snd_buffer_t* sb, const unsigned char *samples, unsigned int sampleframes, const snd_format_t* format)
+{
+ size_t srclength, outcount;
+ unsigned char *out_data;
+
+ //Con_DPrintf("ResampleSfx: %d samples @ %dHz -> %d samples @ %dHz\n",
+ // sampleframes, format->speed, outcount, sb->format.speed);
+
+ // If the formats are incompatible
+ if (sb->format.channels != format->channels || sb->format.width != format->width)
+ {
+ Con_Print("AppendToSndBuffer: incompatible sound formats!\n");
+ return false;
+ }
+
+ outcount = (size_t) ((double)sampleframes * (double)sb->format.speed / (double)format->speed);
+
+ // If the sound buffer is too short
+ if (outcount > sb->maxframes - sb->nbframes)
+ {
+ Con_Print("AppendToSndBuffer: sound buffer too short!\n");
+ return false;
+ }
+
+ out_data = &sb->samples[sb->nbframes * sb->format.width * sb->format.channels];
+ srclength = sampleframes * format->channels;
+
+ // Trivial case (direct transfer)
+ if (format->speed == sb->format.speed)
+ {
+ if (format->width == 1)
+ {
+ size_t i;
+
+ for (i = 0; i < srclength; i++)
+ ((signed char*)out_data)[i] = samples[i] - 128;
+ }
+ else // if (format->width == 2)
+ memcpy (out_data, samples, srclength * format->width);
+ }
+
+ // General case (linear interpolation with a fixed-point fractional
+ // step, 18-bit integer part and 14-bit fractional part)
+ // Can handle up to 2^18 (262144) samples per second (> 96KHz stereo)
+# define FRACTIONAL_BITS 14
+# define FRACTIONAL_MASK ((1 << FRACTIONAL_BITS) - 1)
+# define INTEGER_BITS (sizeof(samplefrac)*8 - FRACTIONAL_BITS)
+ else
+ {
+ const unsigned int fracstep = (unsigned int)((double)format->speed / sb->format.speed * (1 << FRACTIONAL_BITS));
+ size_t remain_in = srclength, total_out = 0;
+ unsigned int samplefrac;
+ const unsigned char *in_ptr = samples;
+ unsigned char *out_ptr = out_data;
+
+ // Check that we can handle one second of that sound
+ if (format->speed * format->channels > (1 << INTEGER_BITS))
+ {
+ Con_Printf ("ResampleSfx: sound quality too high for resampling (%uHz, %u channel(s))\n",
+ format->speed, format->channels);
+ return 0;
+ }
+
+ // We work 1 sec at a time to make sure we don't accumulate any
+ // significant error when adding "fracstep" over several seconds, and
+ // also to be able to handle very long sounds.
+ while (total_out < outcount)
+ {
+ size_t tmpcount, interpolation_limit, i, j;
+ unsigned int srcsample;
+
+ samplefrac = 0;
+
+ // If more than 1 sec of sound remains to be converted
+ if (outcount - total_out > sb->format.speed)
+ {
+ tmpcount = sb->format.speed;
+ interpolation_limit = tmpcount; // all samples can be interpolated
+ }
+ else
+ {
+ tmpcount = outcount - total_out;
+ interpolation_limit = (int)ceil((double)(((remain_in / format->channels) - 1) << FRACTIONAL_BITS) / fracstep);
+ if (interpolation_limit > tmpcount)
+ interpolation_limit = tmpcount;
+ }
+
+ // 16 bit samples
+ if (format->width == 2)
+ {
+ const short* in_ptr_short;
+
+ // Interpolated part
+ for (i = 0; i < interpolation_limit; i++)
+ {
+ srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels;
+ in_ptr_short = &((const short*)in_ptr)[srcsample];
+
+ for (j = 0; j < format->channels; j++)
+ {
+ int a, b;
+
+ a = *in_ptr_short;
+ b = *(in_ptr_short + format->channels);
+ *((short*)out_ptr) = (((b - a) * (samplefrac & FRACTIONAL_MASK)) >> FRACTIONAL_BITS) + a;
+
+ in_ptr_short++;
+ out_ptr += sizeof (short);
+ }
+
+ samplefrac += fracstep;
+ }
+
+ // Non-interpolated part
+ for (/* nothing */; i < tmpcount; i++)
+ {
+ srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels;
+ in_ptr_short = &((const short*)in_ptr)[srcsample];
+
+ for (j = 0; j < format->channels; j++)
+ {
+ *((short*)out_ptr) = *in_ptr_short;
+
+ in_ptr_short++;
+ out_ptr += sizeof (short);
+ }
+
+ samplefrac += fracstep;
+ }
+ }
+ // 8 bit samples
+ else // if (format->width == 1)
+ {
+ const unsigned char* in_ptr_byte;
+
+ // Convert up to 1 sec of sound
+ for (i = 0; i < interpolation_limit; i++)
+ {
+ srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels;
+ in_ptr_byte = &((const unsigned char*)in_ptr)[srcsample];
+
+ for (j = 0; j < format->channels; j++)
+ {
+ int a, b;
+
+ a = *in_ptr_byte - 128;
+ b = *(in_ptr_byte + format->channels) - 128;
+ *((signed char*)out_ptr) = (((b - a) * (samplefrac & FRACTIONAL_MASK)) >> FRACTIONAL_BITS) + a;
+
+ in_ptr_byte++;
+ out_ptr += sizeof (signed char);
+ }
+
+ samplefrac += fracstep;
+ }
+
+ // Non-interpolated part
+ for (/* nothing */; i < tmpcount; i++)
+ {
+ srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels;
+ in_ptr_byte = &((const unsigned char*)in_ptr)[srcsample];
+
+ for (j = 0; j < format->channels; j++)
+ {
+ *((signed char*)out_ptr) = *in_ptr_byte - 128;
+
+ in_ptr_byte++;
+ out_ptr += sizeof (signed char);
+ }
+
+ samplefrac += fracstep;
+ }
+ }
+
+ // Update the counters and the buffer position
+ remain_in -= format->speed * format->channels;
+ in_ptr += format->speed * format->channels * format->width;
+ total_out += tmpcount;
+ }
+ }
+
+ sb->nbframes += (unsigned int)outcount;
+ return true;
+}
+
+
//=============================================================================
/*
extern speakerlayout_t snd_speakerlayout; // for querying the listeners
#ifdef CONFIG_VIDEO_CAPTURE
-static void S_CaptureAVISound(const portable_sampleframe_t *sampleframes, size_t length)
+static void S_CaptureAVISound(const portable_sampleframe_t *paintbuffer, size_t length)
{
size_t i;
unsigned int j;
{
unsigned int j0 = snd_speakerlayout.listeners[j].channel_unswapped;
for(i = 0; i < length; ++i)
- paintbuffer_unswapped[i].sample[j0] = sampleframes[i].sample[j];
+ paintbuffer_unswapped[i].sample[j0] = paintbuffer[i].sample[j];
}
SCR_CaptureVideo_SoundFrame(paintbuffer_unswapped, length);
static void S_ConvertPaintBuffer(portable_sampleframe_t *painted_ptr, void *rb_ptr, int nbframes, int width, int nchannels)
{
- int i;
- if (width == 4) // 32bit float
- {
- float *snd_out = (float*)rb_ptr;
- if (nchannels == 8) // 7.1 surround
- {
- for (i = 0; i < nbframes; i++, painted_ptr++)
- {
- *snd_out++ = painted_ptr->sample[0];
- *snd_out++ = painted_ptr->sample[1];
- *snd_out++ = painted_ptr->sample[2];
- *snd_out++ = painted_ptr->sample[3];
- *snd_out++ = painted_ptr->sample[4];
- *snd_out++ = painted_ptr->sample[5];
- *snd_out++ = painted_ptr->sample[6];
- *snd_out++ = painted_ptr->sample[7];
- }
- }
- else if (nchannels == 6) // 5.1 surround
- {
- for (i = 0; i < nbframes; i++, painted_ptr++)
- {
- *snd_out++ = painted_ptr->sample[0];
- *snd_out++ = painted_ptr->sample[1];
- *snd_out++ = painted_ptr->sample[2];
- *snd_out++ = painted_ptr->sample[3];
- *snd_out++ = painted_ptr->sample[4];
- *snd_out++ = painted_ptr->sample[5];
- }
- }
- else if (nchannels == 4) // 4.0 surround
- {
- for (i = 0; i < nbframes; i++, painted_ptr++)
- {
- *snd_out++ = painted_ptr->sample[0];
- *snd_out++ = painted_ptr->sample[1];
- *snd_out++ = painted_ptr->sample[2];
- *snd_out++ = painted_ptr->sample[3];
- }
- }
- else if (nchannels == 2) // 2.0 stereo
- {
- for (i = 0; i < nbframes; i++, painted_ptr++)
- {
- *snd_out++ = painted_ptr->sample[0];
- *snd_out++ = painted_ptr->sample[1];
- }
- }
- else if (nchannels == 1) // 1.0 mono
- {
- for (i = 0; i < nbframes; i++, painted_ptr++)
- {
- *snd_out++ = painted_ptr->sample[0];
- }
- }
+ int i, val;
- // noise is really really annoying
- if (cls.timedemo)
- memset(rb_ptr, 0, nbframes * nchannels * width);
- }
- else if (width == 2) // 16bit
+ // FIXME: add 24bit and 32bit float formats
+ // FIXME: optimize with SSE intrinsics?
+ if (width == 2) // 16bit
{
short *snd_out = (short*)rb_ptr;
if (nchannels == 8) // 7.1 surround
May return a suggested format if the requested format isn't available
====================
*/
-qboolean SndSys_Init (snd_format_t* fmt)
+qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
{
unsigned int buffersize;
SDL_AudioSpec wantspec;
return false;
}
- buffersize = (unsigned int)ceil((double)fmt->speed / 25.0); // 2048 bytes on 24kHz to 48kHz
+ buffersize = (unsigned int)ceil((double)requested->speed / 25.0); // 2048 bytes on 24kHz to 48kHz
// Init the SDL Audio subsystem
- memset(&wantspec, 0, sizeof(wantspec));
wantspec.callback = Buffer_Callback;
wantspec.userdata = NULL;
- wantspec.freq = fmt->speed;
- wantspec.format = fmt->width == 1 ? AUDIO_U8 : (fmt->width == 2 ? AUDIO_S16SYS : AUDIO_F32);
- wantspec.channels = fmt->channels;
+ wantspec.freq = requested->speed;
+ wantspec.format = ((requested->width == 1) ? AUDIO_U8 : AUDIO_S16SYS);
+ wantspec.channels = requested->channels;
wantspec.samples = CeilPowerOf2(buffersize); // needs to be a power of 2 on some platforms.
Con_Printf("Wanted audio Specification:\n"
"\tSamples : %i\n",
wantspec.channels, wantspec.format, wantspec.freq, wantspec.samples);
- if ((audio_device = SDL_OpenAudioDevice(NULL, 0, &wantspec, &obtainspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE)) == 0)
+ if ((audio_device = SDL_OpenAudioDevice(NULL, 0, &wantspec, &obtainspec, 0)) == 0)
{
Con_Printf( "Failed to open the audio device! (%s)\n", SDL_GetError() );
return false;
"\tSamples : %i\n",
obtainspec.channels, obtainspec.format, obtainspec.freq, obtainspec.samples);
- fmt->speed = obtainspec.freq;
- fmt->channels = obtainspec.channels;
+ // If we haven't obtained what we wanted
+ if (wantspec.freq != obtainspec.freq ||
+ wantspec.format != obtainspec.format ||
+ wantspec.channels != obtainspec.channels)
+ {
+ SDL_CloseAudioDevice(audio_device);
+
+ // Pass the obtained format as a suggested format
+ if (suggested != NULL)
+ {
+ suggested->speed = obtainspec.freq;
+ // FIXME: check the format more carefully. There are plenty of unsupported cases
+ suggested->width = ((obtainspec.format == AUDIO_U8) ? 1 : 2);
+ suggested->channels = obtainspec.channels;
+ }
+
+ return false;
+ }
snd_threaded = true;
- snd_renderbuffer = Snd_CreateRingBuffer(fmt, 0, NULL);
+ snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL);
if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO)
Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_STANDARD);