fteqw/engine/client/snd_alsa.c
Spoike 31506617f0 Implemented device enumeration for SDL+ALSA+OSS4 audio drivers. Back to using the 'default' alsa device by default, warts and all.
Fixed openal linux .so name, now usable in linux.
sdl audio code now uses sdl2 audio, and thus can support multiple devices simultaneously.
linux non-sdl builds now dynamically link to SDL2 for audio. This is now the default audio system in ALL non-android linux builds. This is the only real option to cope with the mess that is alsa.
Fix netgraph when running q2. No longer makes palette assumptions.
Fixed q2 ping values.
Tweaked a load of windows code to use wide chars, because microsoft do not support utf-8.
fixed an issue with winsspi where data from large packets could get lost.
now tries to read .lit2 files (although still refuses to read them for now).
Fixed motionblur. To make Shpuld happy... :P

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4871 fc73d0e0-1445-4013-8a0c-d673dee63da5
2015-05-14 03:06:58 +00:00

574 lines
17 KiB
C
Executable file

/*
snd_alsa.c
Support for the ALSA 1.0.1 sound driver
Copyright (C) 1999,2000 contributors of the QuakeForge project
Please see the file "AUTHORS" for a list of contributors
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
*/
//actually stolen from darkplaces.
//I guess noone can be arsed to write it themselves. :/
//
//This file is otherwise known as 'will the linux jokers please stop fucking over the open sound system please'
#include <alsa/asoundlib.h>
#include "quakedef.h"
#include <dlfcn.h>
static void *alsasharedobject;
int (*psnd_pcm_open) (snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode);
int (*psnd_pcm_close) (snd_pcm_t *pcm);
const char *(*psnd_strerror) (int errnum);
int (*psnd_pcm_hw_params_any) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
int (*psnd_pcm_hw_params_set_access) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access);
int (*psnd_pcm_hw_params_set_format) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
int (*psnd_pcm_hw_params_set_channels) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
int (*psnd_pcm_hw_params_set_rate_near) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
int (*psnd_pcm_hw_params_set_period_size_near) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
int (*psnd_pcm_hw_params) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
int (*psnd_pcm_sw_params_current) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
int (*psnd_pcm_sw_params_set_start_threshold) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
int (*psnd_pcm_sw_params_set_stop_threshold) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
int (*psnd_pcm_sw_params) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
int (*psnd_pcm_hw_params_get_buffer_size) (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
int (*psnd_pcm_hw_params_set_buffer_size_near) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
int (*psnd_pcm_set_params) (snd_pcm_t *pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int rate, int soft_resample, unsigned int latency);
snd_pcm_sframes_t (*psnd_pcm_avail_update) (snd_pcm_t *pcm);
snd_pcm_state_t (*psnd_pcm_state) (snd_pcm_t *pcm);
int (*psnd_pcm_start) (snd_pcm_t *pcm);
int (*psnd_pcm_recover) (snd_pcm_t *pcm, int err, int silent);
size_t (*psnd_pcm_hw_params_sizeof) (void);
size_t (*psnd_pcm_sw_params_sizeof) (void);
int (*psnd_pcm_mmap_begin) (snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames);
snd_pcm_sframes_t (*psnd_pcm_mmap_commit) (snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames);
snd_pcm_sframes_t (*psnd_pcm_writei) (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
int (*psnd_pcm_prepare) (snd_pcm_t *pcm);
int (*psnd_device_name_hint) (int card, const char *iface, void ***hints);
char * (*psnd_device_name_get_hint) (const void *hint, const char *id);
int (*psnd_device_name_free_hint) (void **hints);
static unsigned int ALSA_MMap_GetDMAPos (soundcardinfo_t *sc)
{
const snd_pcm_channel_area_t *areas;
snd_pcm_uframes_t offset;
snd_pcm_uframes_t nframes = sc->sn.samples / sc->sn.numchannels;
psnd_pcm_avail_update (sc->handle);
psnd_pcm_mmap_begin (sc->handle, &areas, &offset, &nframes);
offset *= sc->sn.numchannels;
nframes *= sc->sn.numchannels;
sc->sn.samplepos = offset;
sc->sn.buffer = areas->addr;
return sc->sn.samplepos;
}
static void ALSA_MMap_Submit (soundcardinfo_t *sc, int start, int end)
{
int state;
int count = end - start;
const snd_pcm_channel_area_t *areas;
snd_pcm_uframes_t nframes;
snd_pcm_uframes_t offset;
nframes = count / sc->sn.numchannels;
psnd_pcm_avail_update (sc->handle);
psnd_pcm_mmap_begin (sc->handle, &areas, &offset, &nframes);
state = psnd_pcm_state (sc->handle);
switch (state) {
case SND_PCM_STATE_PREPARED:
psnd_pcm_mmap_commit (sc->handle, offset, nframes);
psnd_pcm_start (sc->handle);
break;
case SND_PCM_STATE_RUNNING:
psnd_pcm_mmap_commit (sc->handle, offset, nframes);
break;
default:
break;
}
}
static unsigned int ALSA_RW_GetDMAPos (soundcardinfo_t *sc)
{
int frames;
frames = psnd_pcm_avail_update(sc->handle);
if (frames < 0)
{
psnd_pcm_start (sc->handle);
psnd_pcm_recover(sc->handle, frames, true);
frames = psnd_pcm_avail_update(sc->handle);
}
if (frames >= 0)
{
sc->sn.samplepos = (sc->snd_sent + frames) * sc->sn.numchannels;
}
return sc->sn.samplepos;
}
static void ALSA_RW_Submit (soundcardinfo_t *sc, int start, int end)
{
// int state;
unsigned int frames, offset, ringsize;
unsigned chunk;
int result;
int stride = sc->sn.numchannels * (sc->sn.samplebits/8);
while(1)
{
/*we can't change the data that was already written*/
frames = end - sc->snd_sent;
if (frames <= 0)
return;
// state = psnd_pcm_state (sc->handle);
ringsize = sc->sn.samples / sc->sn.numchannels;
chunk = frames;
offset = sc->snd_sent % ringsize;
if (offset + chunk >= ringsize)
chunk = ringsize - offset;
result = psnd_pcm_writei(sc->handle, sc->sn.buffer + offset*stride, chunk);
if (result < chunk)
{
if (result < 0)
return;
}
sc->snd_sent += chunk;
chunk = frames - chunk;
if (chunk)
{
result = psnd_pcm_writei(sc->handle, sc->sn.buffer, chunk);
if (result > 0)
sc->snd_sent += result;
}
// if (state == SND_PCM_STATE_PREPARED)
// psnd_pcm_start (sc->handle);
};
}
static void ALSA_Shutdown (soundcardinfo_t *sc)
{
psnd_pcm_close (sc->handle);
if (sc->Submit == ALSA_RW_Submit)
free(sc->sn.buffer);
Con_DPrintf("Alsa closed\n");
}
static void *ALSA_LockBuffer(soundcardinfo_t *sc, unsigned int *sampidx)
{
return sc->sn.buffer;
}
static void ALSA_UnlockBuffer(soundcardinfo_t *sc, void *buffer)
{
}
static void ALSA_SetUnderWater(soundcardinfo_t *sc, qboolean underwater)
{
}
static qboolean Alsa_InitAlsa(void)
{
static qboolean tried;
static qboolean alsaworks;
if (tried)
return alsaworks;
tried = true;
// Try alternative names of libasound, sometimes it is not linked correctly.
alsasharedobject = dlopen("libasound.so.2", RTLD_LAZY|RTLD_LOCAL);
if (!alsasharedobject)
{
alsasharedobject = dlopen("libasound.so", RTLD_LAZY|RTLD_LOCAL);
if (!alsasharedobject)
{
return false;
}
}
psnd_pcm_open = dlsym(alsasharedobject, "snd_pcm_open");
psnd_pcm_close = dlsym(alsasharedobject, "snd_pcm_close");
psnd_strerror = dlsym(alsasharedobject, "snd_strerror");
psnd_pcm_hw_params_any = dlsym(alsasharedobject, "snd_pcm_hw_params_any");
psnd_pcm_hw_params_set_access = dlsym(alsasharedobject, "snd_pcm_hw_params_set_access");
psnd_pcm_hw_params_set_format = dlsym(alsasharedobject, "snd_pcm_hw_params_set_format");
psnd_pcm_hw_params_set_channels = dlsym(alsasharedobject, "snd_pcm_hw_params_set_channels");
psnd_pcm_hw_params_set_rate_near = dlsym(alsasharedobject, "snd_pcm_hw_params_set_rate_near");
psnd_pcm_hw_params_set_period_size_near = dlsym(alsasharedobject, "snd_pcm_hw_params_set_period_size_near");
psnd_pcm_hw_params = dlsym(alsasharedobject, "snd_pcm_hw_params");
psnd_pcm_sw_params_current = dlsym(alsasharedobject, "snd_pcm_sw_params_current");
psnd_pcm_sw_params_set_start_threshold = dlsym(alsasharedobject, "snd_pcm_sw_params_set_start_threshold");
psnd_pcm_sw_params_set_stop_threshold = dlsym(alsasharedobject, "snd_pcm_sw_params_set_stop_threshold");
psnd_pcm_sw_params = dlsym(alsasharedobject, "snd_pcm_sw_params");
psnd_pcm_hw_params_get_buffer_size = dlsym(alsasharedobject, "snd_pcm_hw_params_get_buffer_size");
psnd_pcm_avail_update = dlsym(alsasharedobject, "snd_pcm_avail_update");
psnd_pcm_state = dlsym(alsasharedobject, "snd_pcm_state");
psnd_pcm_start = dlsym(alsasharedobject, "snd_pcm_start");
psnd_pcm_recover = dlsym(alsasharedobject, "snd_pcm_recover");
psnd_pcm_set_params = dlsym(alsasharedobject, "snd_pcm_set_params");
psnd_pcm_hw_params_sizeof = dlsym(alsasharedobject, "snd_pcm_hw_params_sizeof");
psnd_pcm_sw_params_sizeof = dlsym(alsasharedobject, "snd_pcm_sw_params_sizeof");
psnd_pcm_hw_params_set_buffer_size_near = dlsym(alsasharedobject, "snd_pcm_hw_params_set_buffer_size_near");
psnd_pcm_mmap_begin = dlsym(alsasharedobject, "snd_pcm_mmap_begin");
psnd_pcm_mmap_commit = dlsym(alsasharedobject, "snd_pcm_mmap_commit");
psnd_pcm_writei = dlsym(alsasharedobject, "snd_pcm_writei");
psnd_pcm_prepare = dlsym(alsasharedobject, "snd_pcm_prepare");
psnd_device_name_hint = dlsym(alsasharedobject, "snd_device_name_hint");
psnd_device_name_get_hint = dlsym(alsasharedobject, "snd_device_name_get_hint");
psnd_device_name_free_hint = dlsym(alsasharedobject, "snd_device_name_free_hint");
alsaworks = psnd_pcm_open
&& psnd_pcm_close
&& psnd_strerror
&& psnd_pcm_hw_params_any
&& psnd_pcm_hw_params_set_access
&& psnd_pcm_hw_params_set_format
&& psnd_pcm_hw_params_set_channels
&& psnd_pcm_hw_params_set_rate_near
&& psnd_pcm_hw_params_set_period_size_near
&& psnd_pcm_hw_params
&& psnd_pcm_sw_params_current
&& psnd_pcm_sw_params_set_start_threshold
&& psnd_pcm_sw_params_set_stop_threshold
&& psnd_pcm_sw_params
&& psnd_pcm_hw_params_get_buffer_size
&& psnd_pcm_avail_update
&& psnd_pcm_state
&& psnd_pcm_start
&& psnd_pcm_hw_params_sizeof
&& psnd_pcm_sw_params_sizeof
&& psnd_pcm_hw_params_set_buffer_size_near
&& psnd_pcm_mmap_begin
&& psnd_pcm_mmap_commit
&& psnd_pcm_writei && psnd_pcm_prepare
&& psnd_device_name_hint && psnd_device_name_get_hint && psnd_device_name_free_hint
;
return alsaworks;
}
static qboolean QDECL ALSA_InitCard (soundcardinfo_t *sc, const char *pcmname)
{
snd_pcm_t *pcm;
snd_pcm_uframes_t buffer_size;
int err;
snd_pcm_hw_params_t *hw;
snd_pcm_sw_params_t *sw;
#if 0
int bps, stereo;
unsigned int rate;
snd_pcm_uframes_t frag_size;
#endif
qboolean mmap = false;
if (!Alsa_InitAlsa())
{
Con_Printf(CON_ERROR "Alsa does not appear to be installed or compatible\n");
return false;
}
hw = alloca(psnd_pcm_hw_params_sizeof());
sw = alloca(psnd_pcm_sw_params_sizeof());
memset(sw, 0, psnd_pcm_sw_params_sizeof());
memset(hw, 0, psnd_pcm_hw_params_sizeof());
//WARNING: 'default' as the default sucks arse. it adds about a second's worth of lag.
if (!pcmname)
pcmname = "default";
sc->inactive_sound = true; //linux sound devices always play sound, even when we're not the active app...
Con_Printf("Initing ALSA sound device \"%s\"\n", pcmname);
err = psnd_pcm_open (&pcm, pcmname, SND_PCM_STREAM_PLAYBACK,
SND_PCM_NONBLOCK);
if (0 > err)
{
Con_Printf (CON_ERROR "ALSA Error: open error (%s): %s\n", pcmname, psnd_strerror (err));
return 0;
}
Con_Printf ("ALSA: Using PCM %s.\n", pcmname);
#if 1
err = psnd_pcm_set_params(pcm, ((sc->sn.samplebits==8)?SND_PCM_FORMAT_U8:SND_PCM_FORMAT_S16), (mmap?SND_PCM_ACCESS_MMAP_INTERLEAVED:SND_PCM_ACCESS_RW_INTERLEAVED), sc->sn.numchannels, sc->sn.speed, true, 0.04*1000000);
if (0 > err)
{
Con_Printf (CON_ERROR "ALSA: error setting params. %s\n", psnd_strerror (err));
goto error;
}
// sc->sn.numchannels = stereo;
// sc->sn.samplepos = 0;
// sc->sn.samplebits = bps;
sc->samplequeue = buffer_size = 2048;
#else
err = psnd_pcm_hw_params_any (pcm, hw);
if (0 > err)
{
Con_Printf (CON_ERROR "ALSA: error setting hw_params_any. %s\n", psnd_strerror (err));
goto error;
}
err = psnd_pcm_hw_params_set_access (pcm, hw, mmap?SND_PCM_ACCESS_MMAP_INTERLEAVED:SND_PCM_ACCESS_RW_INTERLEAVED);
if (0 > err)
{
Con_Printf (CON_ERROR "ALSA: Failure to set interleaved PCM access. %s\n", psnd_strerror (err));
goto error;
}
// get sample bit size
bps = sc->sn.samplebits;
{
snd_pcm_format_t spft;
if (bps == 16)
spft = SND_PCM_FORMAT_S16;
else
spft = SND_PCM_FORMAT_U8;
err = psnd_pcm_hw_params_set_format (pcm, hw, spft);
while (err < 0)
{
if (spft == SND_PCM_FORMAT_S16)
{
bps = 8;
spft = SND_PCM_FORMAT_U8;
}
else
{
Con_Printf (CON_ERROR "ALSA: no usable formats. %s\n", psnd_strerror (err));
goto error;
}
err = psnd_pcm_hw_params_set_format (pcm, hw, spft);
}
}
// get speaker channels
stereo = sc->sn.numchannels;
err = psnd_pcm_hw_params_set_channels (pcm, hw, stereo);
while (err < 0)
{
if (stereo > 2)
stereo = 2;
else if (stereo > 1)
stereo = 1;
else
{
Con_Printf (CON_ERROR "ALSA: no usable number of channels. %s\n", psnd_strerror (err));
goto error;
}
err = psnd_pcm_hw_params_set_channels (pcm, hw, stereo);
}
// get rate
rate = sc->sn.speed;
err = psnd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0);
while (err < 0)
{
if (rate > 48000)
rate = 48000;
else if (rate > 44100)
rate = 44100;
else if (rate > 22150)
rate = 22150;
else if (rate > 11025)
rate = 11025;
else if (rate > 800)
rate = 800;
else
{
Con_Printf (CON_ERROR "ALSA: no usable rates. %s\n", psnd_strerror (err));
goto error;
}
err = psnd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0);
}
if (rate > 11025)
frag_size = 8 * bps * rate / 11025;
else
frag_size = 8 * bps;
err = psnd_pcm_hw_params_set_period_size_near (pcm, hw, &frag_size, 0);
if (0 > err)
{
Con_Printf (CON_ERROR "ALSA: unable to set period size near %i. %s\n", (int) frag_size, psnd_strerror (err));
goto error;
}
err = psnd_pcm_hw_params (pcm, hw);
if (0 > err) {
Con_Printf (CON_ERROR "ALSA: unable to install hw params: %s\n",
psnd_strerror (err));
goto error;
}
err = psnd_pcm_sw_params_current (pcm, sw);
if (0 > err) {
Con_Printf (CON_ERROR "ALSA: unable to determine current sw params. %s\n", psnd_strerror (err));
goto error;
}
err = psnd_pcm_sw_params_set_start_threshold (pcm, sw, ~0U);
if (0 > err) {
Con_Printf (CON_ERROR "ALSA: unable to set playback threshold. %s\n", psnd_strerror (err));
goto error;
}
err = psnd_pcm_sw_params_set_stop_threshold (pcm, sw, ~0U);
if (0 > err) {
Con_Printf (CON_ERROR "ALSA: unable to set playback stop threshold. %s\n", psnd_strerror (err));
goto error;
}
err = psnd_pcm_sw_params (pcm, sw);
if (0 > err) {
Con_Printf (CON_ERROR "ALSA: unable to install sw params. %s\n", psnd_strerror (err));
goto error;
}
sc->sn.numchannels = stereo;
sc->sn.samplepos = 0;
sc->sn.samplebits = bps;
buffer_size = sc->sn.samples / stereo;
if (buffer_size)
{
err = psnd_pcm_hw_params_set_buffer_size_near(pcm, hw, &buffer_size);
if (err < 0)
{
Con_Printf (CON_ERROR "ALSA: unable to set buffer size. %s\n", psnd_strerror (err));
goto error;
}
}
err = psnd_pcm_hw_params_get_buffer_size (hw, &buffer_size);
if (0 > err) {
Con_Printf (CON_ERROR "ALSA: unable to get buffer size. %s\n", psnd_strerror (err));
goto error;
}
sc->sn.speed = rate;
#endif
sc->sn.samples = buffer_size * sc->sn.numchannels; // mono samples in buffer
sc->handle = pcm;
sc->Lock = ALSA_LockBuffer;
sc->Unlock = ALSA_UnlockBuffer;
sc->SetWaterDistortion = ALSA_SetUnderWater;
sc->Shutdown = ALSA_Shutdown;
if (mmap)
{
sc->GetDMAPos = ALSA_MMap_GetDMAPos;
sc->Submit = ALSA_MMap_Submit;
sc->GetDMAPos(sc); // sets shm->buffer
//alsa doesn't seem to like high mixahead values
//(maybe it tells us above somehow...)
//so force it lower
//quake's default of 0.2 was for 10fps rendering anyway
//so force it down to 0.1 which is the default for halflife at least, and should give better latency
{
extern cvar_t _snd_mixahead;
if (_snd_mixahead.value >= 0.2)
{
Con_Printf("Alsa Hack: _snd_mixahead forced lower\n");
_snd_mixahead.value = 0.1;
}
}
}
else
{
sc->GetDMAPos = ALSA_RW_GetDMAPos;
sc->Submit = ALSA_RW_Submit;
sc->samplequeue = sc->sn.samples;
sc->sn.buffer = malloc(sc->sn.samples * (sc->sn.samplebits/8));
err = psnd_pcm_prepare(pcm);
if (0 > err)
{
Con_Printf (CON_ERROR "ALSA: unable to prepare for use. %s\n", psnd_strerror (err));
goto error;
}
}
return true;
error:
psnd_pcm_close (pcm);
return false;
}
#define SDRVNAME "ALSA"
static qboolean QDECL ALSA_Enumerate(void (QDECL *cb) (const char *drivername, const char *devicecode, const char *readablename))
{
size_t i;
void **hints;
if (Alsa_InitAlsa())
{
if (!psnd_device_name_hint(-1, "pcm", &hints))
{
for (i = 0; hints[i]; i++)
{
char *n = psnd_device_name_get_hint(hints[i], "NAME");
if (n)
{
char *t = psnd_device_name_get_hint(hints[i], "IOID");
if (!t || strcasecmp(t, "Input"))
{
char *d = psnd_device_name_get_hint(hints[i], "DESC");
if (d)
cb(SDRVNAME, n, va("ALSA (%s)", d));
else
cb(SDRVNAME, n, n);
free(d);
}
free(t);
free(n); //dangerous to free things across boundaries.
}
}
psnd_device_name_free_hint(hints);
}
else
return false;
}
return true;
}
sounddriver_t ALSA_Output =
{
SDRVNAME,
ALSA_InitCard,
ALSA_Enumerate
};