1
0
Fork 0
forked from fte/fteqw
fteqw/engine/client/snd_linux.c
Spoike 49ae9573b8 reworked clipboard handling to avoid stalls when pasting in linux.
made a load of functions static (just code style stuff).
downloads menu now deselects any autoselected items, to avoid confusion.
csqc traces can now hit networked ents. I probably need to make some more tweaks to this.
arg completion for flocate+dir+modelviewer commands.
fix autosave not cycling saves when using the vanilla save format.
fix patch collisions with q3bsp submodels.
md3 now supports framegroups too.
added some more buttons to make xonotic happy.
gl_polyblend 2 shows the screen flashes at the edge of the screen instead of the middle.
colormod no longer applies to fullbrights (matching DP). this fixes an issue with xonotic.
fix uninitialised local that was causing issues with bones used as tags.
rewrote qc search_* to use a dynamic array instead of a linked list. this should make it faster to read when there's many handles open at a time.
fte's saved games can now save buffers properly.
fix issue with raster freetype fonts (read: coloured emoji).
initial support for struct-based classes (instead of entity-based classes). still has issues.
execing configs in untrusted packages will no longer run out of sync (fixing a number of afterquake issues).
fix stupid bug with the te_gunshot/etc builtins.


git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5317 fc73d0e0-1445-4013-8a0c-d673dee63da5
2018-10-11 10:31:23 +00:00

600 lines
15 KiB
C

#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/wait.h>
#if defined(__OpenBSD__)
#include <soundcard.h> //OpenBSD emulates this, so its no longer sys/.
#else
#include <sys/soundcard.h>
#endif
#include <stdio.h>
#include "quakedef.h"
#ifdef __linux__
#include <sys/stat.h>
#endif
#ifndef AFMT_FLOAT
#define AFMT_FLOAT 0x00004000 //OSS4 supports it, but linux is too shit to define it.
#endif
static int tryrates[] = { 11025, 22051, 44100, 8000, 48000 };
static unsigned int OSS_MMap_GetDMAPos(soundcardinfo_t *sc)
{
struct count_info count;
if (sc->audio_fd != -1)
{
if (ioctl(sc->audio_fd, SNDCTL_DSP_GETOPTR, &count)==-1)
{
perror("/dev/dsp");
Con_Printf("Uh, sound dead.\n");
close(sc->audio_fd);
sc->audio_fd = -1;
return 0;
}
// shm->samplepos = (count.bytes / shm->samplebytes) & (shm->samples-1);
// fprintf(stderr, "%d \r", count.ptr);
sc->sn.samplepos = count.ptr / sc->sn.samplebytes;
}
return sc->sn.samplepos;
}
static void OSS_MMap_Submit(soundcardinfo_t *sc, int start, int end)
{
}
static unsigned int OSS_Alsa_GetDMAPos(soundcardinfo_t *sc)
{
struct audio_buf_info info;
unsigned int bytes;
if (ioctl (sc->audio_fd, SNDCTL_DSP_GETOSPACE, &info) != -1)
{
bytes = sc->snd_sent + info.bytes;
sc->sn.samplepos = bytes / sc->sn.samplebytes;
}
return sc->sn.samplepos;
}
static void OSS_Alsa_Submit(soundcardinfo_t *sc, int start, int end)
{
unsigned int bytes, offset, ringsize;
unsigned chunk;
int result;
/*we can't change the data that was already written*/
bytes = end * sc->sn.numchannels * sc->sn.samplebytes;
bytes -= sc->snd_sent;
if (!bytes)
return;
ringsize = sc->sn.samples * sc->sn.samplebytes;
chunk = bytes;
offset = sc->snd_sent % ringsize;
if (offset + chunk >= ringsize)
chunk = ringsize - offset;
result = write(sc->audio_fd, sc->sn.buffer + offset, chunk);
if (result < chunk)
{
if (result >= 0)
sc->snd_sent += result;
// printf("full?\n");
return;
}
sc->snd_sent += chunk;
chunk = bytes - chunk;
if (chunk)
{
result = write(sc->audio_fd, sc->sn.buffer, chunk);
if (result > 0)
sc->snd_sent += result;
}
}
static void OSS_Shutdown(soundcardinfo_t *sc)
{
if (sc->sn.buffer) //close it properly, so we can go and restart it later.
{
if (sc->Submit == OSS_Alsa_Submit)
free(sc->sn.buffer); /*if using alsa-compat, just free the buffer*/
else
munmap(sc->sn.buffer, sc->sn.samples * sc->sn.samplebytes);
}
if (sc->audio_fd != -1)
close(sc->audio_fd);
*sc->name = '\0';
}
static void *OSS_Lock(soundcardinfo_t *sc, unsigned int *sampidx)
{
return sc->sn.buffer;
}
static void OSS_Unlock(soundcardinfo_t *sc, void *buffer)
{
}
static qboolean OSS_InitCard(soundcardinfo_t *sc, const char *snddev)
{ //FIXME: implement snd_multipledevices somehow.
int rc;
int fmt;
int tmp;
int i;
struct audio_buf_info info;
int caps;
qboolean alsadetected = false;
#ifdef __linux__
struct stat sb;
if (stat("/proc/asound", &sb) != -1)
alsadetected = true;
#endif
if (COM_CheckParm("-nooss"))
return false;
if (!snddev || !*snddev)
snddev = "/dev/dsp";
else if (strncmp(snddev, "/dev/dsp", 8))
{
Con_Printf("Refusing to use non-dsp device\n");
return false;
}
sc->inactive_sound = true; //linux sound devices always play sound, even when we're not the active app...
// open the sound device, confirm capability to mmap, and get size of dma buffer
Con_Printf("Initing OSS sound device %s\n", snddev);
#ifdef __linux__
//linux is a pile of shit.
//nonblock is needed to get around issues with the old/buggy linux oss3 clone implementation, as well as because this code is too lame to thread audio.
sc->audio_fd = open(snddev, O_RDWR | O_NONBLOCK); //try the primary device
//fixme: the following is desired once threading is supported.
//int flags = fcntl(fd, F_GETFL, 0);
//fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
#else
//FIXME: remove non-block if we're using threads.
sc->audio_fd = open(snddev, O_WRONLY | O_NONBLOCK); //try the primary device
#endif
if (sc->audio_fd < 0)
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Could not open %s\n", snddev);
OSS_Shutdown(sc);
return false;
}
Q_strncpyz(sc->name, snddev, sizeof(sc->name));
//reset it
rc = ioctl(sc->audio_fd, SNDCTL_DSP_RESET, 0);
if (rc < 0)
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Could not reset %s\n", snddev);
OSS_Shutdown(sc);
return false;
}
//check its general capabilities, we need trigger+mmap
if (ioctl(sc->audio_fd, SNDCTL_DSP_GETCAPS, &caps)==-1)
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Sound driver too old\n");
OSS_Shutdown(sc);
return false;
}
//choose channels
#ifdef SNDCTL_DSP_CHANNELS /*I'm paranoid, okay?*/
tmp = sc->sn.numchannels;
rc = ioctl(sc->audio_fd, SNDCTL_DSP_CHANNELS, &tmp);
if (rc < 0)
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Could not set %s to channels=%d\n", snddev, sc->sn.numchannels);
OSS_Shutdown(sc);
return false;
}
sc->sn.numchannels = tmp;
#else
tmp = 0;
if (sc->sn.numchannels == 2)
tmp = 1;
rc = ioctl(sc->audio_fd, SNDCTL_DSP_STEREO, &tmp);
if (rc < 0)
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Could not set %s to stereo=%d\n", snddev, sc->sn.numchannels);
OSS_Shutdown(sc);
return false;
}
if (tmp)
sc->sn.numchannels = 2;
else
sc->sn.numchannels = 1;
#endif
// ask the device what it supports
ioctl(sc->audio_fd, SNDCTL_DSP_GETFMTS, &fmt);
//choose a format
if (sc->sn.samplebytes >= 4 && (fmt & AFMT_FLOAT))
{
sc->sn.samplebytes = 4;
sc->sn.sampleformat = QSF_F32;
rc = AFMT_FLOAT;
rc = ioctl(sc->audio_fd, SNDCTL_DSP_SETFMT, &rc);
if (rc < 0)
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Could not support 16-bit data. Try 8-bit.\n");
OSS_Shutdown(sc);
return false;
}
}
else if (sc->sn.samplebytes >= 2 && (fmt & AFMT_S16_NE))
{
sc->sn.samplebytes = 2;
sc->sn.sampleformat = QSF_S16;
rc = AFMT_S16_NE;
rc = ioctl(sc->audio_fd, SNDCTL_DSP_SETFMT, &rc);
if (rc < 0)
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Could not support 16-bit data. Try 8-bit.\n");
OSS_Shutdown(sc);
return false;
}
}
else if (/*sc->sn.samplebytes == 1 && */(fmt & AFMT_U8))
{
sc->sn.samplebytes = 1;
sc->sn.sampleformat = QSF_U8;
rc = AFMT_U8;
rc = ioctl(sc->audio_fd, SNDCTL_DSP_SETFMT, &rc);
if (rc < 0)
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Could not support 8-bit data.\n");
OSS_Shutdown(sc);
return false;
}
}
else if (/*sc->sn.samplebytes == 1 && */(fmt & AFMT_S8))
{
sc->sn.samplebytes = 1;
sc->sn.sampleformat = QSF_S8;
rc = AFMT_S8;
rc = ioctl(sc->audio_fd, SNDCTL_DSP_SETFMT, &rc);
if (rc < 0)
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Could not support 8-bit data.\n");
OSS_Shutdown(sc);
return false;
}
}
else
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: %d-bit sound not supported.\n", sc->sn.samplebytes*8);
OSS_Shutdown(sc);
return false;
}
//choose speed
//use the default - menu set value.
tmp = sc->sn.speed;
if (ioctl(sc->audio_fd, SNDCTL_DSP_SPEED, &tmp) != 0)
{ //humph, default didn't work. Go for random preset ones that should work.
for (i=0 ; i<sizeof(tryrates)/4 ; i++)
{
tmp = tryrates[i];
if (!ioctl(sc->audio_fd, SNDCTL_DSP_SPEED, &tmp)) break;
}
if (i == (sizeof(tryrates)/4))
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Failed to obtain a suitable rate\n");
OSS_Shutdown(sc);
return false;
}
}
sc->sn.speed = tmp;
//figure out buffer size
if (ioctl(sc->audio_fd, SNDCTL_DSP_GETOSPACE, &info)==-1)
{
perror("GETOSPACE");
Con_Printf(CON_ERROR "OSS: Um, can't do GETOSPACE?\n");
OSS_Shutdown(sc);
return false;
}
sc->sn.samples = info.fragstotal * info.fragsize;
sc->sn.samples /= sc->sn.samplebytes;
/*samples is the number of samples*channels */
// memory map the dma buffer
sc->sn.buffer = MAP_FAILED;
if (alsadetected)
{
Con_Printf("Refusing to mmap oss device in case alsa's oss emulation crashes.\n");
}
else if ((caps & DSP_CAP_TRIGGER) && (caps & DSP_CAP_MMAP))
{
sc->sn.buffer = (unsigned char *) mmap(NULL, sc->sn.samples*sc->sn.samplebytes, PROT_WRITE, MAP_FILE|MAP_SHARED, sc->audio_fd, 0);
if (sc->sn.buffer == MAP_FAILED)
{
Con_Printf("%s: device reported mmap capability, but mmap failed.\n", snddev);
if (alsadetected)
{
char *f, *n;
f = (char *)com_argv[0];
while((n = strchr(f, '/')))
f = n + 1;
Con_Printf("Your system is running alsa.\nTry: sudo echo \"%s 0 0 direct\" > /proc/asound/card0/pcm0p/oss\n", f);
}
}
}
if (sc->sn.buffer == MAP_FAILED)
{
sc->sn.buffer = NULL;
sc->samplequeue = info.bytes / sc->sn.samplebytes;
sc->sn.samples*=2;
sc->sn.buffer = malloc(sc->sn.samples*sc->sn.samplebytes);
sc->Submit = OSS_Alsa_Submit;
sc->GetDMAPos = OSS_Alsa_GetDMAPos;
}
else
{
// toggle the trigger & start her up
tmp = 0;
rc = ioctl(sc->audio_fd, SNDCTL_DSP_SETTRIGGER, &tmp);
if (rc < 0)
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Could not toggle.\n");
OSS_Shutdown(sc);
return false;
}
tmp = PCM_ENABLE_OUTPUT;
rc = ioctl(sc->audio_fd, SNDCTL_DSP_SETTRIGGER, &tmp);
if (rc < 0)
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: Could not toggle.\n");
OSS_Shutdown(sc);
return false;
}
sc->Submit = OSS_MMap_Submit;
sc->GetDMAPos = OSS_MMap_GetDMAPos;
}
sc->sn.samplepos = 0;
sc->Lock = OSS_Lock;
sc->Unlock = OSS_Unlock;
sc->Shutdown = OSS_Shutdown;
return true;
}
#define SDRVNAME "OSS"
#if defined(__linux__) && !defined(SNDCTL_SYSINFO)
typedef struct oss_sysinfo {
char product[32]; /* E.g. SunOS Audio */
char version[32]; /* E.g. 4.0a */
int versionnum; /* See OSS_GETVERSION */
char options[128]; /* NOT SUPPORTED */
int numaudios; /* # of audio/dsp devices */
int openedaudio[8]; /* Reserved, always 0 */
int numsynths; /* NOT SUPPORTED, always 0 */
int nummidis; /* NOT SUPPORTED, always 0 */
int numtimers; /* NOT SUPPORTED, always 0 */
int nummixers; /* # of mixer devices */
int openedmidi[8]; /* Mask of midi devices are busy */
int numcards; /* Number of sound cards in the system */
int numaudioengines; /* Number of audio engines in the system */
char license[16]; /* E.g. "GPL" or "CDDL" */
char revision_info[256]; /* Reserved */
int filler[172]; /* Reserved */
} oss_sysinfo;
#define SNDCTL_SYSINFO _IOR ('X', 1, oss_sysinfo)
#endif
#if defined(__linux__) && !defined(SNDCTL_AUDIOINFO)
typedef struct oss_audioinfo {
int dev; /* Device to query */
char name[64]; /* Human readable name */
int busy; /* reserved */
int pid; /* reserved */
int caps; /* PCM_CAP_INPUT, PCM_CAP_OUTPUT */
int iformats; /* Supported input formats */
int oformats; /* Supported output formats */
int magic; /* reserved */
char cmd[64]; /* reserved */
int card_number;
int port_number; /* reserved */
int mixer_dev;
int legacy_device; /* Obsolete field. Replaced by devnode */
int enabled; /* reserved */
int flags; /* reserved */
int min_rate; /* Minimum sample rate */
int max_rate; /* Maximum sample rate */
int min_channels; /* Minimum number of channels */
int max_channels; /* Maximum number of channels */
int binding; /* reserved */
int rate_source; /* reserved */
char handle[32]; /* reserved */
unsigned int nrates; /* reserved */
unsigned int rates[20]; /* reserved */
char song_name[64]; /* reserved */
char label[16]; /* reserved */
int latency; /* reserved */
char devnode[32]; /* Device special file name (absolute path) */
int next_play_engine; /* reserved */
int next_rec_engine; /* reserved */
int filler[184]; /* reserved */
} oss_audioinfo;
#define SNDCTL_AUDIOINFO _IOWR('X', 7, oss_audioinfo)
#endif
static qboolean QDECL OSS_Enumerate(void (QDECL *cb) (const char *drivername, const char *devicecode, const char *readablename))
{
#if defined(SNDCTL_SYSINFO) && defined(SNDCTL_AUDIOINFO)
int i;
int fd;
oss_sysinfo si;
if (COM_CheckParm("-nooss"))
return true;
fd = open("/dev/mixer", O_RDWR, 0);
if (fd == -1)
return true; //oss not supported. don't list any devices.
if (ioctl(fd, SNDCTL_SYSINFO, &si) != -1)
{
if ((si.versionnum>>16) >= 4)
{ //only trust all the fields if its recent enough
for(i = 0; i < si.numaudios; i++)
{
oss_audioinfo ai;
ai.dev = i;
if (ioctl(fd, SNDCTL_AUDIOINFO, &ai) != -1)
cb(SDRVNAME, ai.devnode, ai.name);
}
close(fd);
return true;
}
else
printf("Not enumerating OSS %u.%u.%u devices.\n", (si.versionnum>>16)&0xffff, (si.versionnum>>8)&0xff, si.versionnum&0xff);
}
else
printf("OSS driver is too old to support device enumeration.\n");
close(fd);
#endif
return false; //enumeration failed, will show only a default device.
}
sounddriver_t OSS_Output =
{
SDRVNAME,
OSS_InitCard,
OSS_Enumerate
};
#ifdef VOICECHAT //this does apparently work after all.
#include <stdint.h>
static qboolean QDECL OSS_Capture_Enumerate (void (QDECL *callback) (const char *drivername, const char *devicecode, const char *readablename))
{
if (COM_CheckParm("-nooss"))
return true; //no default devices or anything
//open /dev/dsp or /dev/mixer or env("OSS_MIXERDEV") or something
//SNDCTL_SYSINFO to get sysinfo.numcards
//for i=0; i<sysinfo.numcards
//SNDCTL_CARDINFO
return false;
}
static void *OSS_Capture_Init(int rate, const char *snddev)
{
int tmp;
intptr_t fd;
if (!snddev || !*snddev)
snddev = "/dev/dsp";
if (COM_CheckParm("-nooss"))
return NULL;
fd = open(snddev, O_RDONLY | O_NONBLOCK); //try the primary device
if (fd == -1)
return NULL;
#ifdef SNDCTL_DSP_CHANNELS
tmp = 1;
if (ioctl(fd, SNDCTL_DSP_CHANNELS, &tmp) != 0)
#else
tmp = 0;
if (ioctl(fd, SNDCTL_DSP_STEREO, &tmp) != 0)
#endif
{
Con_Printf("Couldn't set mono\n");
perror(snddev);
}
tmp = AFMT_S16_LE;
if (ioctl(fd, SNDCTL_DSP_SETFMT, &tmp) != 0)
{
Con_Printf("Couldn't set sample bits\n");
perror(snddev);
}
tmp = rate;
if (ioctl(fd, SNDCTL_DSP_SPEED, &tmp) != 0)
{
Con_Printf("Couldn't set capture rate\n");
perror(snddev);
}
fd++;
return (void*)fd;
}
static void OSS_Capture_Start(void *ctx)
{
/*oss will automagically restart it when we next read*/
}
static void OSS_Capture_Stop(void *ctx)
{
intptr_t fd = ((intptr_t)ctx)-1;
ioctl(fd, SNDCTL_DSP_RESET, NULL);
}
static void OSS_Capture_Shutdown(void *ctx)
{
intptr_t fd = ((intptr_t)ctx)-1;
close(fd);
}
static unsigned int OSS_Capture_Update(void *ctx, unsigned char *buffer, unsigned int minbytes, unsigned int maxbytes)
{
intptr_t fd = ((intptr_t)ctx)-1;
ssize_t res;
res = read(fd, buffer, maxbytes);
if (res < 0)
return 0;
return res;
}
snd_capture_driver_t OSS_Capture =
{
1,
"OSS",
OSS_Capture_Enumerate,
OSS_Capture_Init,
OSS_Capture_Start,
OSS_Capture_Update,
OSS_Capture_Stop,
OSS_Capture_Shutdown
};
#endif