1
0
Fork 0
forked from fte/fteqw
fteqw/engine/client/snd_linux.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

552 lines
14 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>
#include <sys/soundcard.h>
#include <stdio.h>
#include "quakedef.h"
#ifdef __linux__
#include <sys/stat.h>
#endif
static int tryrates[] = { 11025, 22051, 44100, 8000, 48000 };
static void OSS_SetUnderWater(soundcardinfo_t *sc, qboolean underwater) //simply a stub. Any ideas how to actually implement this properly?
{
}
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->samplebits / 8)) & (shm->samples-1);
// fprintf(stderr, "%d \r", count.ptr);
sc->sn.samplepos = count.ptr / (sc->sn.samplebits / 8);
}
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.samplebits / 8);
}
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.samplebits/8);
bytes -= sc->snd_sent;
if (!bytes)
return;
ringsize = sc->sn.samples * (sc->sn.samplebits/8);
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.samplebits/8));
}
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 (!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
//choose bits
// ask the device what it supports
ioctl(sc->audio_fd, SNDCTL_DSP_GETFMTS, &fmt);
if (!(fmt & AFMT_S16_LE) && sc->sn.samplebits > 8)
sc->sn.samplebits = 8; // they asked for 16bit (the default) but their card does not support it
if (!(fmt & AFMT_U8) && sc->sn.samplebits == 8)
{ //their card doesn't support 8bit which we're trying to use.
Con_Printf(CON_ERROR "OSS: No needed sample formats supported\n");
OSS_Shutdown(sc);
return false;
}
if (sc->sn.samplebits == 16)
{
rc = AFMT_S16_LE;
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.samplebits == 8)
{
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
{
perror(snddev);
Con_Printf(CON_ERROR "OSS: %d-bit sound not supported.\n", sc->sn.samplebits);
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.samplebits/8);
/*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.samplebits/8), 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.samplebits/8);
sc->sn.samples*=2;
sc->sn.buffer = malloc(sc->sn.samples*(sc->sn.samplebits/8));
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->SetWaterDistortion = OSS_SetUnderWater;
sc->Shutdown = OSS_Shutdown;
return true;
}
#define SDRVNAME "OSS"
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)
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)
static qboolean QDECL OSS_Enumerate(void (QDECL *cb) (const char *drivername, const char *devicecode, const char *readablename))
{
int i;
int fd = open("/dev/mixer", O_RDWR, 0);
oss_sysinfo si;
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);
return false; //enumeration failed.
}
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))
{
//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;
}
void *OSS_Capture_Init(int rate, const char *snddev)
{
int tmp;
intptr_t fd;
if (!snddev || !*snddev)
snddev = "/dev/dsp";
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;
}
void OSS_Capture_Start(void *ctx)
{
/*oss will automagically restart it when we next read*/
}
void OSS_Capture_Stop(void *ctx)
{
intptr_t fd = ((intptr_t)ctx)-1;
ioctl(fd, SNDCTL_DSP_RESET, NULL);
}
void OSS_Capture_Shutdown(void *ctx)
{
intptr_t fd = ((intptr_t)ctx)-1;
close(fd);
}
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