From 9c0e19a3be2288db79e2502e5fa450c3e20a668d Mon Sep 17 00:00:00 2001 From: Guenter Geiger Date: Fri, 9 May 2003 16:04:00 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r610, which included commits to RCS files with non-trunk default branches. svn path=/trunk/; revision=611 --- pd/src/s_audio_oss.c | 772 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 772 insertions(+) create mode 100644 pd/src/s_audio_oss.c (limited to 'pd/src/s_audio_oss.c') diff --git a/pd/src/s_audio_oss.c b/pd/src/s_audio_oss.c new file mode 100644 index 00000000..382e6a75 --- /dev/null +++ b/pd/src/s_audio_oss.c @@ -0,0 +1,772 @@ +/* Copyright (c) 1997-2003 Guenter Geiger, Miller Puckette, Larry Troxler, +* Winfried Ritsch, Karl MacMillan, and others. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* this file inputs and outputs audio using the OSS API available on linux. */ + +#include + +#include "m_pd.h" +#include "s_stuff.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Defines */ +#define DEBUG(x) x +#define DEBUG2(x) {x;} + +#define OSS_MAXCHPERDEV 32 /* max channels per OSS device */ +#define OSS_MAXDEV 4 /* maximum number of input or output devices */ +#define OSS_DEFFRAGSIZE 256 /* default log fragment size (frames) */ +#define OSS_DEFAUDIOBUF 40000 /* default audiobuffer, microseconds */ +#define OSS_DEFAULTCH 2 +#define RME_DEFAULTCH 8 /* need this even if RME undefined */ +typedef int16_t t_oss_int16; +typedef int32_t t_oss_int32; +#define OSS_MAXSAMPLEWIDTH sizeof(t_oss_int32) +#define OSS_BYTESPERCHAN(width) (DEFDACBLKSIZE * (width)) +#define OSS_XFERSAMPS(chans) (DEFDACBLKSIZE* (chans)) +#define OSS_XFERSIZE(chans, width) (DEFDACBLKSIZE * (chans) * (width)) + +/* GLOBALS */ +static int linux_meters; /* true if we're metering */ +static float linux_inmax; /* max input amplitude */ +static float linux_outmax; /* max output amplitude */ +static int linux_fragsize = 0; /* for block mode; block size (sample frames) */ + +/* our device handles */ + +typedef struct _oss_dev +{ + int d_fd; + unsigned int d_space; /* bytes available for writing/reading */ + int d_bufsize; /* total buffer size in blocks for this device */ + int d_dropcount; /* # of buffers to drop for resync (output only) */ + unsigned int d_nchannels; /* number of channels for this device */ + unsigned int d_bytespersamp; /* bytes per sample (2 for 16 bit, 4 for 32) */ +} t_oss_dev; + +static t_oss_dev linux_dacs[OSS_MAXDEV]; +static t_oss_dev linux_adcs[OSS_MAXDEV]; +static int linux_noutdevs = 0; +static int linux_nindevs = 0; + + /* exported variables */ +float sys_dacsr; +t_sample *sys_soundout; +t_sample *sys_soundin; + + /* OSS-specific private variables */ +static int oss_blockmode = 1; /* flag to use "blockmode" */ +static int oss_32bit = 0; /* allow 23 bit transfers in OSS */ +static char ossdsp[] = "/dev/dsp%d"; + + /* don't assume we can turn all 31 bits when doing float-to-fix; + otherwise some audio drivers (e.g. Midiman/ALSA) wrap around. */ +#define FMAX 0x7ffff000 +#define CLIP32(x) (((x)>FMAX)?FMAX:((x) < -FMAX)?-FMAX:(x)) + + +/* ------------- private routines for all APIS ------------------- */ + +static void linux_flush_all_underflows_to_zero(void) +{ +/* + TODO: Implement similar thing for linux (GGeiger) + + One day we will figure this out, I hope, because it + costs CPU time dearly on Intel - LT + */ + /* union fpc_csr f; + f.fc_word = get_fpc_csr(); + f.fc_struct.flush = 1; + set_fpc_csr(f.fc_word); + */ +} + + +void oss_set32bit( void) +{ + oss_32bit = 1; +} + + +typedef struct _multidev { + int fd; + int channels; + int format; +} t_multidev; + +int oss_reset(int fd) { + int err; + if ((err = ioctl(fd,SNDCTL_DSP_RESET)) < 0) + error("OSS: Could not reset"); + return err; +} + + /* The AFMT_S32_BLOCKED format is not defined in standard linux kernels + but is proposed by Guenter Geiger to support extending OSS to handle + 32 bit sample. This is user in Geiger's OSS driver for RME Hammerfall. + I'm not clear why this isn't called AFMT_S32_[SLN]E... */ + +#ifndef AFMT_S32_BLOCKED +#define AFMT_S32_BLOCKED 0x0000400 +#endif + +void oss_configure(t_oss_dev *dev, int srate, int dac, int skipblocksize) +{ /* IOhannes */ + int orig, param, nblk, fd = dev->d_fd, wantformat; + int nchannels = dev->d_nchannels; + int advwas = sys_schedadvance; + + audio_buf_info ainfo; + + /* IOhannes : + * pd is very likely to crash if different formats are used on + multiple soundcards + */ + + /* set resolution - first try 4 byte samples */ + if (oss_32bit && (ioctl(fd,SNDCTL_DSP_GETFMTS,¶m) >= 0) && + (param & AFMT_S32_BLOCKED)) + { + wantformat = AFMT_S32_BLOCKED; + dev->d_bytespersamp = 4; + } + else + { + wantformat = AFMT_S16_NE; + dev->d_bytespersamp = 2; + } + param = wantformat; + + if (sys_verbose) + post("bytes per sample = %d", dev->d_bytespersamp); + if (ioctl(fd, SNDCTL_DSP_SETFMT, ¶m) == -1) + fprintf(stderr,"OSS: Could not set DSP format\n"); + else if (wantformat != param) + fprintf(stderr,"OSS: DSP format: wanted %d, got %d\n", + wantformat, param); + + /* sample rate */ + orig = param = srate; + if (ioctl(fd, SNDCTL_DSP_SPEED, ¶m) == -1) + fprintf(stderr,"OSS: Could not set sampling rate for device\n"); + else if( orig != param ) + fprintf(stderr,"OSS: sampling rate: wanted %d, got %d\n", + orig, param ); + + if (oss_blockmode && !skipblocksize) + { + int fragbytes, logfragsize, nfragment; + /* setting fragment count and size. */ + if (!linux_fragsize) + { + linux_fragsize = OSS_DEFFRAGSIZE; + while (linux_fragsize > DEFDACBLKSIZE + && linux_fragsize * 4 > sys_advance_samples) + linux_fragsize = linux_fragsize/2; + } + /* post("adv_samples %d", sys_advance_samples); */ + nfragment = (sys_schedadvance * (44100. * 1.e-6)) / linux_fragsize; + + fragbytes = linux_fragsize * (dev->d_bytespersamp * nchannels); + logfragsize = ilog2(fragbytes); + + if (fragbytes != (1 << logfragsize)) + post("warning: OSS takes only power of 2 blocksize; using %d", + (1 << logfragsize)/(dev->d_bytespersamp * nchannels)); + if (sys_verbose) + post("setting nfrags = %d, fragsize %d\n", nfragment, fragbytes); + + param = orig = (nfragment<<16) + logfragsize; + if (ioctl(fd,SNDCTL_DSP_SETFRAGMENT, ¶m) == -1) + error("OSS: Could not set or read fragment size\n"); + if (param != orig) + { + nfragment = ((param >> 16) & 0xffff); + logfragsize = (param & 0xffff); + post("warning: actual fragments %d, blocksize %d", + nfragment, (1 << logfragsize)); + } + if (sys_verbose) + post("audiobuffer set to %d msec", (int)(0.001 * sys_schedadvance)); + } + + if (dac) + { + /* use "free space" to learn the buffer size. Normally you + should set this to your own desired value; but this seems not + to be implemented uniformly across different sound cards. LATER + we should figure out what to do if the requested scheduler advance + is greater than this buffer size; for now, we just print something + out. */ + + int defect; + if (ioctl(fd, SOUND_PCM_GETOSPACE,&ainfo) < 0) + fprintf(stderr,"OSS: ioctl on output device failed"); + dev->d_bufsize = ainfo.bytes; + + defect = sys_advance_samples * (dev->d_bytespersamp * nchannels) + - dev->d_bufsize - OSS_XFERSIZE(nchannels, dev->d_bytespersamp); + if (defect > 0) + { + if (sys_verbose || defect > (dev->d_bufsize >> 2)) + fprintf(stderr, + "OSS: requested audio buffer size %d limited to %d\n", + sys_advance_samples * (dev->d_bytespersamp * nchannels), + dev->d_bufsize); + sys_advance_samples = + (dev->d_bufsize - OSS_XFERSAMPS(nchannels)) / + (dev->d_bytespersamp *nchannels); + } + } +} + +static int oss_setchannels(int fd, int wantchannels, char *devname) +{ /* IOhannes */ + int param = wantchannels; + + while (param>1) { + int save = param; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, ¶m) == -1) { + error("OSS: SNDCTL_DSP_CHANNELS failed %s",devname); + } else { + if (param == save) return (param); + } + param=save-1; + } + + return (0); +} + +#define O_AUDIOFLAG 0 /* O_NDELAY */ + +int oss_open_audio(int nindev, int *indev, int nchin, int *chin, + int noutdev, int *outdev, int nchout, int *chout, int rate) +{ /* IOhannes */ + int capabilities = 0; + int inchannels = 0, outchannels = 0; + char devname[20]; + int n, i, fd; + char buf[OSS_MAXSAMPLEWIDTH * DEFDACBLKSIZE * OSS_MAXCHPERDEV]; + int num_devs = 0; + int wantmore=0; + int spread = 0; + audio_buf_info ainfo; + + linux_nindevs = linux_noutdevs = 0; + + + /* mark input devices unopened */ + for (i = 0; i < OSS_MAXDEV; i++) + linux_adcs[i].d_fd = -1; + + /* open output devices */ + wantmore=0; + if (noutdev < 0 || nindev < 0) + bug("linux_open_audio"); + + for (n = 0; n < noutdev; n++) + { + int gotchans, j, inindex = -1; + int thisdevice = (outdev[n] >= 0 ? outdev[n] : n-1); + int wantchannels = (nchout>n) ? chout[n] : wantmore; + fd = -1; + if (!wantchannels) + goto end_out_loop; + + if (thisdevice > 1) + sprintf(devname, "/dev/dsp%d", thisdevice-1); + else sprintf(devname, "/dev/dsp"); + + /* search for input request for same device. Succeed only + if the number of channels matches. */ + for (j = 0; j < nindev; j++) + if (indev[j] == thisdevice && chin[j] == wantchannels) + inindex = j; + + /* if the same device is requested for input and output, + try to open it read/write */ + if (inindex >= 0) + { + sys_setalarm(1000000); + if ((fd = open(devname, O_RDWR | O_AUDIOFLAG)) == -1) + { + post("%s (read/write): %s", devname, strerror(errno)); + post("(now will try write-only...)"); + } + else + { + if (sys_verbose) + post("opened %s for reading and writing\n", devname); + linux_adcs[inindex].d_fd = fd; + } + } + /* if that didn't happen or if it failed, try write-only */ + if (fd == -1) + { + sys_setalarm(1000000); + if ((fd = open(devname, O_WRONLY | O_AUDIOFLAG)) == -1) + { + post("%s (writeonly): %s", + devname, strerror(errno)); + break; + } + if (sys_verbose) + post("opened %s for writing only\n", devname); + } + if (ioctl(fd, SNDCTL_DSP_GETCAPS, &capabilities) == -1) + error("OSS: SNDCTL_DSP_GETCAPS failed %s", devname); + + gotchans = oss_setchannels(fd, + (wantchannels>OSS_MAXCHPERDEV)?OSS_MAXCHPERDEV:wantchannels, + devname); + + if (sys_verbose) + post("opened audio output on %s; got %d channels", + devname, gotchans); + + if (gotchans < 2) + { + /* can't even do stereo? just give up. */ + close(fd); + } + else + { + linux_dacs[linux_noutdevs].d_nchannels = gotchans; + linux_dacs[linux_noutdevs].d_fd = fd; + oss_configure(linux_dacs+linux_noutdevs, rate, 1, 0); + + linux_noutdevs++; + outchannels += gotchans; + if (inindex >= 0) + { + linux_adcs[inindex].d_nchannels = gotchans; + chin[inindex] = gotchans; + } + } + /* LATER think about spreading large numbers of channels over + various dsp's and vice-versa */ + wantmore = wantchannels - gotchans; + end_out_loop: ; + } + + /* open input devices */ + wantmore = 0; + for (n = 0; n < nindev; n++) + { + int gotchans=0; + int thisdevice = (indev[n] >= 0 ? indev[n] : n-1); + int wantchannels = (nchin>n)?chin[n]:wantmore; + int alreadyopened = 0; + if (!wantchannels) + goto end_in_loop; + + if (thisdevice > 1) + sprintf(devname, "/dev/dsp%d", thisdevice - 1); + else sprintf(devname, "/dev/dsp"); + + sys_setalarm(1000000); + + /* perhaps it's already open from the above? */ + if (linux_dacs[n].d_fd >= 0) + { + fd = linux_dacs[n].d_fd; + alreadyopened = 1; + } + else + { + /* otherwise try to open it here. */ + if ((fd = open(devname, O_RDONLY | O_AUDIOFLAG)) == -1) + { + post("%s (readonly): %s", devname, strerror(errno)); + goto end_in_loop; + } + if (sys_verbose) + post("opened %s for reading only\n", devname); + } + linux_adcs[linux_nindevs].d_fd = fd; + gotchans = oss_setchannels(fd, + (wantchannels>OSS_MAXCHPERDEV)?OSS_MAXCHPERDEV:wantchannels, + devname); + if (sys_verbose) + post("opened audio input device %s; got %d channels", + devname, gotchans); + + if (gotchans < 1) + { + close(fd); + goto end_in_loop; + } + + linux_adcs[linux_nindevs].d_nchannels = gotchans; + + oss_configure(linux_adcs+linux_nindevs, rate, 0, alreadyopened); + + inchannels += gotchans; + linux_nindevs++; + + wantmore = wantchannels-gotchans; + /* LATER think about spreading large numbers of channels over + various dsp's and vice-versa */ + end_in_loop: ; + } + + /* We have to do a read to start the engine. This is + necessary because sys_send_dacs waits until the input + buffer is filled and only reads on a filled buffer. + This is good, because it's a way to make sure that we + will not block. But I wonder why we only have to read + from one of the devices and not all of them??? */ + + if (linux_nindevs) + { + if (sys_verbose) + fprintf(stderr,("OSS: issuing first ADC 'read' ... ")); + read(linux_adcs[0].d_fd, buf, + linux_adcs[0].d_bytespersamp * + linux_adcs[0].d_nchannels * DEFDACBLKSIZE); + if (sys_verbose) + fprintf(stderr, "...done.\n"); + } + sys_setalarm(0); + return (0); +} + +void oss_close_audio( void) +{ + int i; + for (i=0;i + OSS_XFERSIZE(linux_adcs[dev].d_nchannels, + linux_adcs[dev].d_bytespersamp)) + { + linux_adcs_read(linux_adcs[dev].d_fd, buf, + OSS_XFERSIZE(linux_adcs[dev].d_nchannels, + linux_adcs[dev].d_bytespersamp)); + if (ioctl(linux_adcs[dev].d_fd, SOUND_PCM_GETISPACE, &ainfo) < 0) + { + fprintf(stderr, "OSS: ioctl on input device %d, fd %d failed", + dev, linux_adcs[dev].d_fd); + break; + } + linux_adcs[dev].d_space = ainfo.bytes; + } + } + + /* 2. if any output devices are behind, feed them zeros to catch them + up */ + for (dev = 0; dev < linux_noutdevs; dev++) + { + while (linux_dacs[dev].d_space > linux_dacs[dev].d_bufsize - + sys_advance_samples * (linux_dacs[dev].d_nchannels * + linux_dacs[dev].d_bytespersamp)) + { + if (!zeroed) + { + unsigned int i; + for (i = 0; i < OSS_XFERSAMPS(linux_dacs[dev].d_nchannels); + i++) + buf[i] = 0; + zeroed = 1; + } + linux_dacs_write(linux_dacs[dev].d_fd, buf, + OSS_XFERSIZE(linux_dacs[dev].d_nchannels, + linux_dacs[dev].d_bytespersamp)); + if (ioctl(linux_dacs[dev].d_fd, SOUND_PCM_GETOSPACE, &ainfo) < 0) + { + fprintf(stderr, "OSS: ioctl on output device %d, fd %d failed", + dev, linux_dacs[dev].d_fd); + break; + } + linux_dacs[dev].d_space = ainfo.bytes; + } + } + /* 3. if any DAC devices are too far ahead, plan to drop the + number of frames which will let the others catch up. */ + for (dev = 0; dev < linux_noutdevs; dev++) + { + if (linux_dacs[dev].d_space > linux_dacs[dev].d_bufsize - + (sys_advance_samples - 1) * linux_dacs[dev].d_nchannels * + linux_dacs[dev].d_bytespersamp) + { + linux_dacs[dev].d_dropcount = sys_advance_samples - 1 - + (linux_dacs[dev].d_space - linux_dacs[dev].d_bufsize) / + (linux_dacs[dev].d_nchannels * + linux_dacs[dev].d_bytespersamp) ; + } + else linux_dacs[dev].d_dropcount = 0; + } +} + +int oss_send_dacs(void) +{ + float *fp1, *fp2; + long fill; + int i, j, dev, rtnval = SENDDACS_YES; + char buf[OSS_MAXSAMPLEWIDTH * DEFDACBLKSIZE * OSS_MAXCHPERDEV]; + t_oss_int16 *sp; + t_oss_int32 *lp; + /* the maximum number of samples we should have in the ADC buffer */ + int idle = 0; + int thischan; + double timeref, timenow; + + if (!linux_nindevs && !linux_noutdevs) + return (SENDDACS_NO); + + if (!oss_blockmode) + { + /* determine whether we're idle. This is true if either (1) + some input device has less than one buffer to read or (2) some + output device has fewer than (sys_advance_samples) blocks buffered + already. */ + oss_calcspace(); + + for (dev=0; dev < linux_noutdevs; dev++) + if (linux_dacs[dev].d_dropcount || + (linux_dacs[dev].d_bufsize - linux_dacs[dev].d_space > + sys_advance_samples * linux_dacs[dev].d_bytespersamp * + linux_dacs[dev].d_nchannels)) + idle = 1; + for (dev=0; dev < linux_nindevs; dev++) + if (linux_adcs[dev].d_space < + OSS_XFERSIZE(linux_adcs[dev].d_nchannels, + linux_adcs[dev].d_bytespersamp)) + idle = 1; + } + + if (idle && !oss_blockmode) + { + /* sometimes---rarely---when the ADC available-byte-count is + zero, it's genuine, but usually it's because we're so + late that the ADC has overrun its entire kernel buffer. We + distinguish between the two by waiting 2 msec and asking again. + There should be an error flag we could check instead; look for this + someday... */ + for (dev = 0;dev < linux_nindevs; dev++) + if (linux_adcs[dev].d_space == 0) + { + audio_buf_info ainfo; + sys_microsleep(2000); + oss_calcspace(); + if (linux_adcs[dev].d_space != 0) continue; + + /* here's the bad case. Give up and resync. */ + sys_log_error(ERR_DATALATE); + oss_doresync(); + return (SENDDACS_NO); + } + /* check for slippage between devices, either because + data got lost in the driver from a previous late condition, or + because the devices aren't synced. When we're idle, no + input device should have more than one buffer readable and + no output device should have less than sys_advance_samples-1 + */ + + for (dev=0; dev < linux_noutdevs; dev++) + if (!linux_dacs[dev].d_dropcount && + (linux_dacs[dev].d_bufsize - linux_dacs[dev].d_space < + (sys_advance_samples - 2) * + (linux_dacs[dev].d_bytespersamp * + linux_dacs[dev].d_nchannels))) + goto badsync; + for (dev=0; dev < linux_nindevs; dev++) + if (linux_adcs[dev].d_space > 3 * + OSS_XFERSIZE(linux_adcs[dev].d_nchannels, + linux_adcs[dev].d_bytespersamp)) + goto badsync; + + /* return zero to tell the scheduler we're idle. */ + return (SENDDACS_NO); + badsync: + sys_log_error(ERR_RESYNC); + oss_doresync(); + return (SENDDACS_NO); + + } + + /* do output */ + + timeref = sys_getrealtime(); + for (dev=0, thischan = 0; dev < linux_noutdevs; dev++) + { + int nchannels = linux_dacs[dev].d_nchannels; + if (linux_dacs[dev].d_dropcount) + linux_dacs[dev].d_dropcount--; + else + { + if (linux_dacs[dev].d_bytespersamp == 4) + { + for (i = DEFDACBLKSIZE * nchannels, fp1 = sys_soundout + + DEFDACBLKSIZE*thischan, + lp = (t_oss_int32 *)buf; i--; fp1++, lp++) + { + float f = *fp1 * 2147483648.; + *lp = (f >= 2147483647. ? 2147483647. : + (f < -2147483648. ? -2147483648. : f)); + } + } + else + { + for (i = DEFDACBLKSIZE, fp1 = sys_soundout + + DEFDACBLKSIZE*thischan, + sp = (t_oss_int16 *)buf; i--; fp1++, sp += nchannels) + { + for (j=0, fp2 = fp1; j 32767) s = 32767; + else if (s < -32767) s = -32767; + sp[j] = s; + } + } + } + linux_dacs_write(linux_dacs[dev].d_fd, buf, + OSS_XFERSIZE(nchannels, linux_dacs[dev].d_bytespersamp)); + if ((timenow = sys_getrealtime()) - timeref > 0.002) + { + if (!oss_blockmode) + sys_log_error(ERR_DACSLEPT); + else rtnval = SENDDACS_SLEPT; + } + timeref = timenow; + } + thischan += nchannels; + } + memset(sys_soundout, 0, + sys_outchannels * (sizeof(float) * DEFDACBLKSIZE)); + + /* do input */ + + for (dev = 0, thischan = 0; dev < linux_nindevs; dev++) + { + int nchannels = linux_adcs[dev].d_nchannels; + linux_adcs_read(linux_adcs[dev].d_fd, buf, + OSS_XFERSIZE(nchannels, linux_adcs[dev].d_bytespersamp)); + + if ((timenow = sys_getrealtime()) - timeref > 0.002) + { + if (!oss_blockmode) + sys_log_error(ERR_ADCSLEPT); + else + rtnval = SENDDACS_SLEPT; + } + timeref = timenow; + + if (linux_adcs[dev].d_bytespersamp == 4) + { + for (i = DEFDACBLKSIZE*nchannels, + fp1 = sys_soundin + thischan*DEFDACBLKSIZE, + lp = (t_oss_int32 *)buf; i--; fp1++, lp++) + { + *fp1 = ((float)(*lp))*(float)(1./2147483648.); + } + } + else + { + for (i = DEFDACBLKSIZE,fp1 = sys_soundin + thischan*DEFDACBLKSIZE, + sp = (t_oss_int16 *)buf; i--; fp1++, sp += nchannels) + { + for (j=0;j