From: havoc Date: Sun, 26 May 2019 03:04:36 +0000 (+0000) Subject: UNMERGE! Implement SDL2 AUDIO_F32 format and use it as the default, since it has... X-Git-Tag: xonotic-v0.8.5~5 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=refs%2Fmerge-requests%2F29%2Fmerge;p=xonotic%2Fdarkplaces.git UNMERGE! Implement SDL2 AUDIO_F32 format and use it as the default, since it has become the preferred format of more platforms these days, and SDL2 uses it internally. Improved the fallback mechanism on SDL2 audio setup - by removing it; SDL2 will tell us what changes it makes for the format, and it will convert the rest automatically. git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@12476 d7cf8633-e32d-0410-b094-e92efae38249 ::stable-branch::unmerge=925dc3b341a087249ca6fa0be7c227e49d01b213 --- diff --git a/snd_main.c b/snd_main.c index bc874db6..62807eb0 100644 --- a/snd_main.c +++ b/snd_main.c @@ -239,10 +239,10 @@ static cvar_t ambient_fade = {0, "ambient_fade", "100", "rate of volume fading w 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"}; @@ -373,6 +373,77 @@ int S_GetSoundChannels(void) } +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) @@ -546,7 +617,7 @@ void S_Startup (void) chosen_fmt.speed = atoi (com_argv[i + 1]); fixed_speed = true; } -// COMMANDLINEOPTION: Sound: -sndbits chooses 8 bit or 16 bit or 32bit float sound output +// COMMANDLINEOPTION: Sound: -sndbits chooses 8 bit or 16 bit sound output i = COM_CheckParm ("-sndbits"); if (0 < i && i < com_argc - 1) { @@ -586,11 +657,6 @@ void S_Startup (void) 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; @@ -611,12 +677,39 @@ void S_Startup (void) // 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; diff --git a/snd_main.h b/snd_main.h index 5a07d72b..527ce49f 100644 --- a/snd_main.h +++ b/snd_main.h @@ -153,6 +153,9 @@ void S_MixToBuffer(void *stream, unsigned int frames); 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); @@ -162,9 +165,9 @@ snd_ringbuffer_t *Snd_CreateRingBuffer (const snd_format_t* format, unsigned int // 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); diff --git a/snd_mem.c b/snd_mem.c index 1098c2b6..f6f9c145 100644 --- a/snd_mem.c +++ b/snd_mem.c @@ -71,6 +71,229 @@ snd_ringbuffer_t *Snd_CreateRingBuffer (const snd_format_t* format, unsigned int } +/* +==================== +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; +} + + //============================================================================= /* diff --git a/snd_mix.c b/snd_mix.c index 4ed38a88..10da4004 100644 --- a/snd_mix.c +++ b/snd_mix.c @@ -29,7 +29,7 @@ static portable_sampleframe_t paintbuffer_unswapped[PAINTBUFFER_SIZE]; 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; @@ -42,7 +42,7 @@ static void S_CaptureAVISound(const portable_sampleframe_t *sampleframes, size_t { 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); @@ -127,67 +127,11 @@ static void S_SoftClipPaintBuffer(portable_sampleframe_t *painted_ptr, int nbfra 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 diff --git a/snd_sdl.c b/snd_sdl.c index f7e0f472..d5cca856 100644 --- a/snd_sdl.c +++ b/snd_sdl.c @@ -102,7 +102,7 @@ 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 (snd_format_t* fmt) +qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) { unsigned int buffersize; SDL_AudioSpec wantspec; @@ -118,15 +118,14 @@ qboolean SndSys_Init (snd_format_t* fmt) 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" @@ -136,7 +135,7 @@ qboolean SndSys_Init (snd_format_t* fmt) "\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; @@ -149,12 +148,28 @@ qboolean SndSys_Init (snd_format_t* fmt) "\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);