forked from len0rd/rockbox
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@21070 a1c6a512-1295-4272-9138-f99709370657
845 lines
24 KiB
C
845 lines
24 KiB
C
/* 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. */
|
|
|
|
#ifdef USEAPI_OSS
|
|
|
|
#include <linux/soundcard.h>
|
|
|
|
#include "m_pd.h"
|
|
#include "s_stuff.h"
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <fcntl.h>
|
|
#include <sched.h>
|
|
#include <sys/mman.h>
|
|
|
|
|
|
/* 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);
|
|
*/
|
|
}
|
|
|
|
static int oss_ndev = 0;
|
|
|
|
/* find out how many OSS devices we have. Since this has to
|
|
open the devices to find out if they're there, we have
|
|
to be called before audio is actually started up. So we
|
|
cache the results, which in effect are the number of available
|
|
devices. */
|
|
void oss_init(void)
|
|
{
|
|
int fd, i;
|
|
static int countedthem = 0;
|
|
if (countedthem)
|
|
return;
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
char devname[100];
|
|
if (i == 0)
|
|
strcpy(devname, "/dev/dsp");
|
|
else sprintf(devname, "/dev/dsp%d", i);
|
|
if ( (fd = open(devname, O_WRONLY|O_NONBLOCK)) != -1)
|
|
{
|
|
oss_ndev++;
|
|
close(fd);
|
|
}
|
|
else break;
|
|
}
|
|
countedthem = 1;
|
|
}
|
|
|
|
|
|
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<linux_nindevs;i++)
|
|
close(linux_adcs[i].d_fd);
|
|
|
|
for (i=0;i<linux_noutdevs;i++)
|
|
close(linux_dacs[i].d_fd);
|
|
|
|
linux_nindevs = linux_noutdevs = 0;
|
|
}
|
|
|
|
static int linux_dacs_write(int fd,void* buf,long bytes)
|
|
{
|
|
return write(fd, buf, bytes);
|
|
}
|
|
|
|
static int linux_adcs_read(int fd,void* buf,long bytes)
|
|
{
|
|
return read(fd, buf, bytes);
|
|
}
|
|
|
|
/* query audio devices for "available" data size. */
|
|
static void oss_calcspace(void)
|
|
{
|
|
int dev;
|
|
audio_buf_info ainfo;
|
|
for (dev=0; dev < linux_noutdevs; dev++)
|
|
{
|
|
if (ioctl(linux_dacs[dev].d_fd, SOUND_PCM_GETOSPACE, &ainfo) < 0)
|
|
fprintf(stderr,"OSS: ioctl on output device %d failed",dev);
|
|
linux_dacs[dev].d_space = ainfo.bytes;
|
|
}
|
|
|
|
for (dev = 0; dev < linux_nindevs; dev++)
|
|
{
|
|
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);
|
|
linux_adcs[dev].d_space = ainfo.bytes;
|
|
}
|
|
}
|
|
|
|
void linux_audiostatus(void)
|
|
{
|
|
int dev;
|
|
if (!oss_blockmode)
|
|
{
|
|
oss_calcspace();
|
|
for (dev=0; dev < linux_noutdevs; dev++)
|
|
fprintf(stderr, "dac %d space %d\n", dev, linux_dacs[dev].d_space);
|
|
|
|
for (dev = 0; dev < linux_nindevs; dev++)
|
|
fprintf(stderr, "adc %d space %d\n", dev, linux_adcs[dev].d_space);
|
|
|
|
}
|
|
}
|
|
|
|
/* this call resyncs audio output and input which will cause discontinuities
|
|
in audio output and/or input. */
|
|
|
|
static void oss_doresync( void)
|
|
{
|
|
int dev, zeroed = 0, wantsize;
|
|
char buf[OSS_MAXSAMPLEWIDTH * DEFDACBLKSIZE * OSS_MAXCHPERDEV];
|
|
audio_buf_info ainfo;
|
|
|
|
/* 1. if any input devices are ahead (have more than 1 buffer stored),
|
|
drop one or more buffers worth */
|
|
for (dev = 0; dev < linux_nindevs; dev++)
|
|
{
|
|
if (linux_adcs[dev].d_space == 0)
|
|
{
|
|
linux_adcs_read(linux_adcs[dev].d_fd, buf,
|
|
OSS_XFERSIZE(linux_adcs[dev].d_nchannels,
|
|
linux_adcs[dev].d_bytespersamp));
|
|
}
|
|
else while (linux_adcs[dev].d_space >
|
|
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)
|
|
{
|
|
t_sample *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;
|
|
t_time 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++)
|
|
{
|
|
t_sample f = SCALE32(*fp1);
|
|
*lp = (f >= 2147483647 ? 2147483647 :
|
|
(f < -2147483647 ? -2147483647 : 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<nchannels; j++, fp2 += DEFDACBLKSIZE)
|
|
{
|
|
int s = SCALE16(*fp2);
|
|
if (s > 32767) s = 32767;
|
|
else if (s < -32767) s = -32767;
|
|
sp[j] = s;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#if 0
|
|
#define PR_S "%8d"
|
|
{
|
|
int nm = 64;
|
|
int* sp1 = buf;
|
|
post("dac:");
|
|
while (nm > 0)
|
|
{
|
|
post(PR_S PR_S PR_S PR_S PR_S PR_S PR_S PR_S,
|
|
sp1[0], sp1[1], sp1[2], sp1[3], sp1[4], sp1[5], sp1[6], sp1[7]);
|
|
nm -= 8;
|
|
sp1 += 8;
|
|
}
|
|
}
|
|
#endif
|
|
linux_dacs_write(linux_dacs[dev].d_fd, buf,
|
|
OSS_XFERSIZE(nchannels, linux_dacs[dev].d_bytespersamp));
|
|
|
|
#if 0
|
|
if ((timenow = sys_getrealtime()) - timeref > 200)
|
|
{
|
|
post("dacslept %d",sys_getrealtime() - timeref);
|
|
if (!oss_blockmode)
|
|
sys_log_error(ERR_DACSLEPT);
|
|
else rtnval = SENDDACS_SLEPT;
|
|
}
|
|
#endif
|
|
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 0
|
|
if ((timenow = sys_getrealtime()) - timeref > 200)
|
|
{
|
|
if (!oss_blockmode)
|
|
sys_log_error(ERR_ADCSLEPT);
|
|
else
|
|
rtnval = SENDDACS_SLEPT;
|
|
}
|
|
#endif
|
|
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 = ((t_sample)(*lp))*(t_sample)(1./2147483648.);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = DEFDACBLKSIZE,fp1 = sys_soundin + thischan*DEFDACBLKSIZE,
|
|
sp = (t_oss_int16 *)buf; i--; fp1++, sp += nchannels)
|
|
{
|
|
for (j=0;j<sys_inchannels;j++)
|
|
fp1[j*DEFDACBLKSIZE] = INVSCALE16(sp[j]);
|
|
}
|
|
}
|
|
thischan += nchannels;
|
|
}
|
|
if (thischan != sys_inchannels)
|
|
bug("inchannels");
|
|
return (rtnval);
|
|
}
|
|
|
|
void oss_listdevs( void)
|
|
{
|
|
post("device listing not implemented in OSS yet\n");
|
|
}
|
|
|
|
void oss_getdevs(char *indevlist, int *nindevs,
|
|
char *outdevlist, int *noutdevs, int *canmulti,
|
|
int maxndev, int devdescsize)
|
|
{
|
|
int i, ndev;
|
|
*canmulti = 2; /* supports multiple devices */
|
|
if ((ndev = oss_ndev) > maxndev)
|
|
ndev = maxndev;
|
|
for (i = 0; i < ndev; i++)
|
|
{
|
|
sprintf(indevlist + i * devdescsize, "OSS device #%d", i+1);
|
|
sprintf(outdevlist + i * devdescsize, "OSS device #%d", i+1);
|
|
}
|
|
*nindevs = *noutdevs = ndev;
|
|
}
|
|
|
|
#endif
|
|
|