mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-17 18:30:58 +00:00
05bf637466
Thanks to "Sander van Dijk" <a.h.vandijk@gmail.com>, we now have much better SDL sound support. Here's the promised cleaned up version of the "double buffer" approach patch for "snd_sdl.c". I've taken some more time to re-read and test it this time, and it seems to behave well. All memory that is used by both the main thread and the SDL audio thread is prefixed with "shm_", and locking is used to ensure that only one thread accesses it at the same time.
312 lines
7.3 KiB
C
312 lines
7.3 KiB
C
/*
|
|
snd_sdl.c
|
|
|
|
(description)
|
|
|
|
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:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
static __attribute__ ((used)) const char rcsid[] =
|
|
"$Id$";
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#endif
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_audio.h>
|
|
#include <SDL_byteorder.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "QF/cmd.h"
|
|
#include "QF/cvar.h"
|
|
#include "QF/plugin.h"
|
|
#include "QF/qargs.h"
|
|
#include "QF/sys.h"
|
|
|
|
#include "snd_internal.h"
|
|
|
|
static dma_t sn;
|
|
static int snd_inited;
|
|
static int snd_blocked;
|
|
|
|
static unsigned shm_buflen;
|
|
static unsigned char *shm_buf;
|
|
static unsigned shm_rpos;
|
|
|
|
static unsigned wpos;
|
|
|
|
static cvar_t *snd_bits;
|
|
static cvar_t *snd_rate;
|
|
static cvar_t *snd_stereo;
|
|
|
|
static plugin_t plugin_info;
|
|
static plugin_data_t plugin_info_data;
|
|
static plugin_funcs_t plugin_info_funcs;
|
|
static general_data_t plugin_info_general_data;
|
|
static general_funcs_t plugin_info_general_funcs;
|
|
static snd_output_data_t plugin_info_snd_output_data;
|
|
static snd_output_funcs_t plugin_info_snd_output_funcs;
|
|
|
|
|
|
static void
|
|
paint_audio (void *unused, Uint8 * stream, int len)
|
|
{
|
|
while (shm_rpos + len > shm_buflen) {
|
|
memcpy (stream, shm_buf + shm_rpos, shm_buflen - shm_rpos);
|
|
stream += shm_buflen - shm_rpos;
|
|
len -= shm_buflen - shm_rpos;
|
|
shm_rpos = 0;
|
|
}
|
|
if (len) {
|
|
memcpy (stream, shm_buf + shm_rpos, len);
|
|
shm_rpos += len;
|
|
}
|
|
}
|
|
|
|
static void
|
|
SNDDMA_Init_Cvars (void)
|
|
{
|
|
snd_stereo = Cvar_Get ("snd_stereo", "1", CVAR_ROM, NULL,
|
|
"sound stereo output");
|
|
snd_rate = Cvar_Get ("snd_rate", "0", CVAR_ROM, NULL,
|
|
"sound playback rate. 0 is system default");
|
|
snd_bits = Cvar_Get ("snd_bits", "0", CVAR_ROM, NULL,
|
|
"sound sample depth. 0 is system default");
|
|
}
|
|
|
|
static volatile dma_t *
|
|
SNDDMA_Init (void)
|
|
{
|
|
SDL_AudioSpec desired, obtained;
|
|
|
|
if (snd_inited)
|
|
return &sn;
|
|
|
|
if (SDL_Init (SDL_INIT_AUDIO) < 0) {
|
|
Sys_Printf ("Couldn't initialize SDL AUDIO: %s\n", SDL_GetError ());
|
|
return 0;
|
|
};
|
|
|
|
/* Set up the desired format */
|
|
desired.freq = 22050;
|
|
if (snd_rate->int_val)
|
|
desired.freq = snd_rate->int_val;
|
|
switch (snd_bits->int_val) {
|
|
case 8:
|
|
desired.format = AUDIO_U8;
|
|
break;
|
|
case 0: // default is 16 bits per sample
|
|
case 16:
|
|
if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
|
|
desired.format = AUDIO_S16MSB;
|
|
else
|
|
desired.format = AUDIO_S16LSB;
|
|
break;
|
|
default:
|
|
Sys_Printf ("Unknown number of audio bits: %d\n",
|
|
snd_bits->int_val);
|
|
return 0;
|
|
}
|
|
desired.channels = snd_stereo->int_val ? 2 : 1;
|
|
desired.samples = 1024;
|
|
desired.callback = paint_audio;
|
|
|
|
/* Open the audio device */
|
|
if (SDL_OpenAudio (&desired, &obtained) < 0) {
|
|
Sys_Printf ("Couldn't open SDL audio: %s\n", SDL_GetError ());
|
|
return 0;
|
|
}
|
|
|
|
/* Make sure we can support the audio format */
|
|
switch (obtained.format) {
|
|
case AUDIO_U8:
|
|
/* Supported */
|
|
break;
|
|
case AUDIO_S16LSB:
|
|
case AUDIO_S16MSB:
|
|
if (((obtained.format == AUDIO_S16LSB) &&
|
|
(SDL_BYTEORDER == SDL_LIL_ENDIAN)) ||
|
|
((obtained.format == AUDIO_S16MSB) &&
|
|
(SDL_BYTEORDER == SDL_BIG_ENDIAN))) {
|
|
/* Supported */
|
|
break;
|
|
}
|
|
/* Unsupported, fall through */ ;
|
|
default:
|
|
/* Not supported -- force SDL to do our bidding */
|
|
SDL_CloseAudio ();
|
|
if (SDL_OpenAudio (&desired, NULL) < 0) {
|
|
Sys_Printf ("Couldn't open SDL audio: %s\n", SDL_GetError ());
|
|
return 0;
|
|
}
|
|
memcpy (&obtained, &desired, sizeof (desired));
|
|
break;
|
|
}
|
|
|
|
/* Fill the audio DMA information block */
|
|
sn.samplebits = (obtained.format & 0xFF);
|
|
sn.speed = obtained.freq;
|
|
sn.channels = obtained.channels;
|
|
sn.frames = obtained.samples * 8; // 8 chunks in the buffer
|
|
sn.framepos = 0;
|
|
sn.submission_chunk = 1;
|
|
|
|
shm_buflen = sn.frames * sn.channels * (sn.samplebits / 8);
|
|
|
|
sn.buffer = calloc (shm_buflen, 1);
|
|
if (!sn.buffer)
|
|
Sys_Error ("Failed to allocate buffer for sound!");
|
|
|
|
shm_buf = calloc (shm_buflen, 1);
|
|
if (!shm_buf)
|
|
Sys_Error ("Failed to allocate buffer for sound!");
|
|
|
|
shm_rpos = wpos = 0;
|
|
|
|
if (!snd_blocked)
|
|
SDL_PauseAudio (0);
|
|
|
|
snd_inited = 1;
|
|
|
|
return &sn;
|
|
}
|
|
|
|
static int
|
|
SNDDMA_GetDMAPos (void)
|
|
{
|
|
if (!snd_inited)
|
|
return 0;
|
|
|
|
SDL_LockAudio ();
|
|
sn.framepos = shm_rpos / (sn.channels * (sn.samplebits / 8));
|
|
SDL_UnlockAudio ();
|
|
|
|
return sn.framepos;
|
|
}
|
|
|
|
static void
|
|
SNDDMA_Shutdown (void)
|
|
{
|
|
if (snd_inited) {
|
|
SDL_CloseAudio ();
|
|
free (sn.buffer);
|
|
free (shm_buf);
|
|
snd_inited = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
SNDDMA_Submit
|
|
|
|
Send sound to device if buffer isn't really the dma buffer
|
|
*/
|
|
static void
|
|
SNDDMA_Submit (void)
|
|
{
|
|
static unsigned old_paintedtime;
|
|
unsigned len;
|
|
|
|
if (!snd_inited || snd_blocked)
|
|
return;
|
|
|
|
SDL_LockAudio ();
|
|
|
|
if (*plugin_info_snd_output_data.paintedtime < old_paintedtime)
|
|
old_paintedtime = 0;
|
|
|
|
len = (*plugin_info_snd_output_data.paintedtime - old_paintedtime) *
|
|
sn.channels * (sn.samplebits / 8);
|
|
|
|
old_paintedtime = *plugin_info_snd_output_data.paintedtime;
|
|
|
|
while (wpos + len > shm_buflen) {
|
|
memcpy (shm_buf + wpos, sn.buffer + wpos, shm_buflen - wpos);
|
|
len -= shm_buflen - wpos;
|
|
wpos = 0;
|
|
}
|
|
if (len) {
|
|
memcpy (shm_buf + wpos, sn.buffer + wpos, len);
|
|
wpos += len;
|
|
}
|
|
|
|
SDL_UnlockAudio ();
|
|
}
|
|
|
|
static void
|
|
SNDDMA_BlockSound (void)
|
|
{
|
|
if (!snd_inited)
|
|
return;
|
|
|
|
if (++snd_blocked == 1)
|
|
SDL_PauseAudio (1);
|
|
}
|
|
|
|
static void
|
|
SNDDMA_UnblockSound (void)
|
|
{
|
|
if (!snd_inited || !snd_blocked)
|
|
return;
|
|
|
|
if (!--snd_blocked)
|
|
SDL_PauseAudio (0);
|
|
}
|
|
|
|
PLUGIN_INFO(snd_output, sdl)
|
|
{
|
|
plugin_info.type = qfp_snd_output;
|
|
plugin_info.api_version = QFPLUGIN_VERSION;
|
|
plugin_info.plugin_version = "0.1";
|
|
plugin_info.description = "SDL digital output";
|
|
plugin_info.copyright = "Copyright (C) 1996-1997 id Software, Inc.\n"
|
|
"Copyright (C) 1999,2000,2001,2012 contributors of the QuakeForge "
|
|
"project\n"
|
|
"Please see the file \"AUTHORS\" for a list of contributors";
|
|
plugin_info.functions = &plugin_info_funcs;
|
|
plugin_info.data = &plugin_info_data;
|
|
|
|
plugin_info_data.general = &plugin_info_general_data;
|
|
plugin_info_data.input = NULL;
|
|
plugin_info_data.snd_output = &plugin_info_snd_output_data;
|
|
|
|
plugin_info_funcs.general = &plugin_info_general_funcs;
|
|
plugin_info_funcs.input = NULL;
|
|
plugin_info_funcs.snd_output = &plugin_info_snd_output_funcs;
|
|
|
|
plugin_info_general_funcs.p_Init = SNDDMA_Init_Cvars;
|
|
plugin_info_general_funcs.p_Shutdown = NULL;
|
|
plugin_info_snd_output_funcs.pS_O_Init = SNDDMA_Init;
|
|
plugin_info_snd_output_funcs.pS_O_Shutdown = SNDDMA_Shutdown;
|
|
plugin_info_snd_output_funcs.pS_O_GetDMAPos = SNDDMA_GetDMAPos;
|
|
plugin_info_snd_output_funcs.pS_O_Submit = SNDDMA_Submit;
|
|
plugin_info_snd_output_funcs.pS_O_BlockSound = SNDDMA_BlockSound;
|
|
plugin_info_snd_output_funcs.pS_O_UnblockSound = SNDDMA_UnblockSound;
|
|
|
|
return &plugin_info;
|
|
}
|