sndio/libsndio/sio_oss.c

812 lines
19 KiB
C

/* $OpenBSD$ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
* Copyright (c) 2016 Tobias Kortkamp <t@tobik.me>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef USE_OSS
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "debug.h"
#include "sio_priv.h"
#include "bsd-compat.h"
#define DEVPATH_PREFIX "/dev/dsp"
#define DEVPATH_MAX (1 + \
sizeof(DEVPATH_PREFIX) - 1 + \
sizeof(int) * 3)
struct sio_oss_fmt {
int fmt;
unsigned int bits;
unsigned int bps;
unsigned int sig;
unsigned int le;
unsigned int msb;
};
static struct sio_oss_fmt formats[] = {
/* See http://manuals.opensound.com/developer/formats.html.
* AFMT_{S8,U16}_* are marked as obsolete so are missing here.
*/
/* le+msb not important */
{ AFMT_U8, 8, 1, 0, 0, 0 },
{ AFMT_U8, 8, 1, 0, 1, 0 },
{ AFMT_U8, 8, 1, 0, 0, 1 },
{ AFMT_U8, 8, 1, 0, 1, 1 },
/* msb not important */
{ AFMT_S16_BE, 16, 2, 1, 0, 0 },
{ AFMT_S16_BE, 16, 2, 1, 0, 1 },
{ AFMT_S16_LE, 16, 2, 1, 1, 0 },
{ AFMT_S16_LE, 16, 2, 1, 1, 1 },
{ AFMT_S24_BE, 24, 3, 1, 0, 0 },
{ AFMT_S24_BE, 24, 3, 1, 0, 1 },
{ AFMT_S24_LE, 24, 3, 1, 1, 0 },
{ AFMT_S24_LE, 24, 3, 1, 1, 1 },
{ AFMT_U24_BE, 24, 3, 0, 0, 0 },
{ AFMT_U24_BE, 24, 3, 0, 0, 1 },
{ AFMT_U24_LE, 24, 3, 0, 1, 0 },
{ AFMT_U24_LE, 24, 3, 0, 1, 1 },
{ AFMT_S32_BE, 32, 4, 1, 0, 1 },
{ AFMT_S32_LE, 32, 4, 1, 1, 1 },
{ AFMT_U32_BE, 32, 4, 0, 0, 1 },
{ AFMT_U32_LE, 32, 4, 0, 1, 1 },
};
struct sio_oss_hdl {
struct sio_hdl sio;
int fd;
int idelta, odelta;
int iused;
int oused;
int bpf;
int fmt;
unsigned int rate;
unsigned int chan;
unsigned int appbufsz;
unsigned int round;
int filling;
};
static struct sio_hdl *sio_oss_fdopen(const char *, int, unsigned int, int);
static int sio_oss_getcap(struct sio_hdl *, struct sio_cap *);
static int sio_oss_getfd(const char *, unsigned int, int);
static int sio_oss_getpar(struct sio_hdl *, struct sio_par *);
static int sio_oss_nfds(struct sio_hdl *);
static int sio_oss_pollfd(struct sio_hdl *, struct pollfd *, int);
static int sio_oss_revents(struct sio_hdl *, struct pollfd *);
static int sio_oss_setpar(struct sio_hdl *, struct sio_par *);
static int sio_oss_start(struct sio_hdl *);
static int sio_oss_stop(struct sio_hdl *);
static int sio_oss_xrun(struct sio_oss_hdl *);
static size_t sio_oss_read(struct sio_hdl *, void *, size_t);
static size_t sio_oss_write(struct sio_hdl *, const void *, size_t);
static void sio_oss_close(struct sio_hdl *);
static int sio_oss_setvol(struct sio_hdl *, unsigned int);
static void sio_oss_getvol(struct sio_hdl *);
static struct sio_ops sio_oss_ops = {
sio_oss_close,
sio_oss_setpar,
sio_oss_getpar,
sio_oss_getcap,
sio_oss_write,
sio_oss_read,
sio_oss_start,
sio_oss_stop,
sio_oss_nfds,
sio_oss_pollfd,
sio_oss_revents,
sio_oss_setvol,
sio_oss_getvol,
};
/*
* guess device capabilities
*/
static int
sio_oss_getcap(struct sio_hdl *sh, struct sio_cap *cap)
{
/* From sound(4):
* The FreeBSD multichannel matrix processor supports up to 18
* interleaved channels, but the limit is currently set to 8
* channels (as commonly used for 7.1 surround sound).
*/
static unsigned int chans[] = {
1, 2, 4, 6, 8
};
static unsigned int rates[] = {
8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100,
48000, 64000, 88200, 96000, 192000
};
static int afmts[] = {
AFMT_U8, AFMT_S16_LE, AFMT_S16_BE, AFMT_S24_LE, AFMT_U24_LE,
AFMT_S32_LE, AFMT_U32_LE
};
struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh;
unsigned int nconf = 0;
unsigned int enc_map = 0, rchan_map = 0, pchan_map = 0, rate_map;
unsigned int i, j, k, conf;
int fmts;
if (ioctl(hdl->fd, SNDCTL_DSP_GETFMTS, &fmts) == -1) {
DPERROR("sio_oss_getcap: GETFMTS");
hdl->sio.eof = 1;
return 0;
}
/*
* get a subset of supported encodings
*/
for (j = 0, i = 0; i < sizeof(afmts) / sizeof(afmts[0]); i++) {
if (fmts & afmts[i]) {
for (k = 0; k < sizeof(formats) / sizeof(formats[0]); k++) {
if (formats[k].fmt == afmts[i]) {
cap->enc[j].sig = formats[k].sig;
cap->enc[j].bits = formats[k].bits;
cap->enc[j].bps = formats[k].bps;
cap->enc[j].le = formats[k].le;
cap->enc[j].msb = formats[k].msb;
enc_map |= 1 << j;
j++;
break;
}
}
}
}
/*
* fill channels
*/
if (hdl->sio.mode & SIO_PLAY) {
for (i = 0; i < sizeof(chans) / sizeof(chans[0]); i++) {
cap->pchan[i] = chans[i];
pchan_map |= (1 << i);
}
}
if (hdl->sio.mode & SIO_REC) {
for (i = 0; i < sizeof(chans) / sizeof(chans[0]); i++) {
cap->rchan[i] = chans[i];
rchan_map |= (1 << i);
}
}
/*
* fill rates
*/
for (j = 0; j < sizeof(formats) / sizeof(formats[0]); j++) {
rate_map = 0;
if ((enc_map & (1 << j)) == 0)
continue;
for (i = 0; i < sizeof(rates) / sizeof(rates[0]); i++) {
cap->rate[i] = rates[i];
rate_map |= (1 << i);
}
for (conf = 0; conf < nconf; conf++) {
if (cap->confs[conf].rate == rate_map) {
cap->confs[conf].enc |= (1 << j);
break;
}
}
if (conf == nconf) {
if (nconf == SIO_NCONF)
break;
cap->confs[nconf].enc = (1 << j);
cap->confs[nconf].pchan = pchan_map;
cap->confs[nconf].rchan = rchan_map;
cap->confs[nconf].rate = rate_map;
nconf++;
}
}
cap->nconf = nconf;
return 1;
}
static int
sio_oss_getfd(const char *str, unsigned int mode, int nbio)
{
const char *p;
char path[DEVPATH_MAX];
unsigned int devnum;
int fd, flags, val;
audio_buf_info bi;
p = _sndio_parsetype(str, "rsnd");
if (p == NULL) {
DPRINTF("sio_oss_getfd: %s: \"rsnd\" expected\n", str);
return -1;
}
switch (*p) {
case '/':
p++;
break;
default:
DPRINTF("sio_oss_getfd: %s: '/' expected\n", str);
return -1;
}
if (strcmp(p, "default") == 0) {
strlcpy(path, DEVPATH_PREFIX, sizeof(path));
} else {
p = _sndio_parsenum(p, &devnum, 255);
if (p == NULL || *p != '\0') {
DPRINTF("sio_sun_getfd: %s: number expected after '/'\n", str);
return -1;
}
snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum);
}
if (mode == (SIO_PLAY | SIO_REC))
flags = O_RDWR;
else
flags = (mode & SIO_PLAY) ? O_WRONLY : O_RDONLY;
while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) == -1) {
if (errno == EINTR)
continue;
DPERROR(path);
return -1;
}
/*
* Check if the device supports playing/recording.
* Unfortunately, it's possible for devices to be opened RDWR
* even when they don't support playing/recording.
*/
if (mode & SIO_PLAY && ioctl(fd, SNDCTL_DSP_GETOSPACE, &bi) == -1) {
close(fd);
return -1;
}
if (mode & SIO_REC && ioctl(fd, SNDCTL_DSP_GETISPACE, &bi) == -1) {
close(fd);
return -1;
}
val = 1;
if (ioctl(fd, SNDCTL_DSP_LOW_WATER, &val) == -1) {
DPERROR("sio_oss_start: LOW_WATER");
close(fd);
return -1;
}
return fd;
}
static struct sio_hdl *
sio_oss_fdopen(const char *str, int fd, unsigned int mode, int nbio)
{
struct sio_oss_hdl *hdl;
hdl = malloc(sizeof(struct sio_oss_hdl));
if (hdl == NULL)
return NULL;
_sio_create(&hdl->sio, &sio_oss_ops, mode, nbio);
/* Set default device parameters */
hdl->fmt = AFMT_S16_LE;
hdl->rate = 48000;
hdl->chan = 2;
hdl->round = 960;
hdl->appbufsz = 8 * 960;
hdl->filling = 0;
hdl->fd = fd;
return (struct sio_hdl *)hdl;
}
struct sio_hdl *
_sio_oss_open(const char *str, unsigned int mode, int nbio)
{
struct sio_oss_hdl *hdl;
int fd;
fd = sio_oss_getfd(str, mode, nbio);
if (fd == -1)
return NULL;
hdl = (struct sio_oss_hdl *)sio_oss_fdopen(str, fd, mode, nbio);
if (hdl != NULL)
return (struct sio_hdl*)hdl;
while (close(fd) == -1 && errno == EINTR)
; /* retry */
return NULL;
}
static void
sio_oss_close(struct sio_hdl *sh)
{
struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh;
while (close(hdl->fd) == -1 && errno == EINTR)
; /* retry */
free(hdl);
}
static int
sio_oss_start(struct sio_hdl *sh)
{
struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh;
int trig;
hdl->iused = 0;
hdl->oused = 0;
hdl->idelta = 0;
hdl->odelta = 0;
if (hdl->sio.mode & SIO_PLAY) {
/*
* keep the device paused and let sio_oss_pollfd() trigger the
* start later, to avoid buffer underruns
*/
hdl->filling = 1;
trig = 0;
} else {
/*
* no play buffers to fill, start now!
*/
trig = PCM_ENABLE_INPUT;
_sio_onmove_cb(&hdl->sio, 0);
}
if (ioctl(hdl->fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) {
DPERROR("sio_oss_start: SETTRIGGER");
hdl->sio.eof = 1;
return 0;
}
return 1;
}
static int
sio_oss_stop(struct sio_hdl *sh)
{
struct sio_oss_hdl *hdl = (struct sio_oss_hdl*)sh;
int trig;
if (hdl->filling) {
hdl->filling = 0;
return 1;
}
trig = 0;
if (ioctl(hdl->fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) {
DPERROR("sio_oss_stop: SETTRIGGER");
hdl->sio.eof = 1;
return 0;
}
return 1;
}
static int
sio_oss_setpar(struct sio_hdl *sh, struct sio_par *par)
{
struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh;
unsigned int i, round, bufsz;
int frag_max, frag_shift, frag_count, frag;
unsigned int le, sig, msb;
le = par->le;
sig = par->sig;
msb = par->msb;
if (le == ~0U)
le = 0;
if (sig == ~0U)
sig = 0;
if (msb == ~0U)
msb = 0;
hdl->fmt = AFMT_S16_LE;
for (i = 0; i < sizeof(formats)/sizeof(formats[0]); i++) {
if (formats[i].bits == par->bits &&
formats[i].le == le &&
formats[i].sig == sig &&
formats[i].msb == msb) {
hdl->fmt = formats[i].fmt;
break;
}
}
if (par->rate != ~0U)
hdl->rate = par->rate;
if (hdl->rate < 8000)
hdl->rate = 8000;
if (hdl->rate > 192000)
hdl->rate = 192000;
if ((hdl->sio.mode & SIO_PLAY) && par->pchan != ~0U)
hdl->chan = par->pchan;
else if ((hdl->sio.mode & SIO_REC) && par->rchan != ~0U)
hdl->chan = par->rchan;
if (ioctl(hdl->fd, SNDCTL_DSP_SETFMT, &hdl->fmt) == -1) {
DPERROR("sio_oss_setpar: SETFMT");
hdl->sio.eof = 1;
return 0;
}
for (i = 0; ; i++) {
if (i == sizeof(formats) / sizeof(formats[0])) {
DPRINTF("sio_oss_setpar: unknown fmt %d\n", hdl->fmt);
hdl->sio.eof = 1;
return 0;
}
if (formats[i].fmt == hdl->fmt)
break;
}
if (ioctl(hdl->fd, SNDCTL_DSP_SPEED, &hdl->rate) == -1) {
DPERROR("sio_oss_setpar: SPEED");
hdl->sio.eof = 1;
return 0;
}
if (ioctl(hdl->fd, SNDCTL_DSP_CHANNELS, &hdl->chan) == -1) {
DPERROR("sio_oss_setpar: CHANNELS");
hdl->sio.eof = 1;
return 0;
}
hdl->bpf = formats[i].bps * hdl->chan;
if (par->round != ~0U && par->appbufsz != ~0U) {
round = par->round;
bufsz = par->appbufsz;
} else if (par->round != ~0U) {
round = par->round;
bufsz = 2 * par->round;
} else if (par->appbufsz != ~0U) {
round = par->appbufsz / 2;
bufsz = par->appbufsz;
} else {
/*
* even if it's not specified, we have to set the
* block size to ensure that both play and record
* direction get the same block size. Pick an
* arbitrary value that would work for most players at
* 48kHz, stereo, 16-bit.
*/
round = 512;
bufsz = 1024;
}
frag_max = round * hdl->chan * formats[i].bps;
frag_shift = 8;
while (1 << (frag_shift + 1) <= frag_max)
frag_shift++;
frag_count = bufsz / round;
if (frag_count < 2)
frag_count = 2;
frag = frag_count << 16 | frag_shift;
if (ioctl(hdl->fd, SNDCTL_DSP_SETFRAGMENT, &frag) == -1) {
DPERROR("sio_oss_setpar: SETFRAGMENT");
hdl->sio.eof = 1;
return 0;
}
return 1;
}
static int
sio_oss_getpar(struct sio_hdl *sh, struct sio_par *par)
{
struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh;
unsigned int i, found = 0;
audio_buf_info pbi, rbi;
for (i = 0; i < sizeof(formats)/sizeof(formats[0]); i++) {
if (formats[i].fmt == hdl->fmt) {
par->sig = formats[i].sig;
par->le = formats[i].le;
par->bits = formats[i].bits;
par->bps = formats[i].bps;
par->msb = formats[i].msb;
found = 1;
break;
}
}
if (!found) {
DPRINTF("sio_oss_getpar: unknown format %d\n", hdl->fmt);
hdl->sio.eof = 1;
return 0;
}
par->rate = hdl->rate;
par->pchan = hdl->chan;
par->rchan = hdl->chan;
par->xrun = SIO_IGNORE;
if (hdl->sio.mode & SIO_PLAY) {
if (ioctl(hdl->fd, SNDCTL_DSP_GETOSPACE, &pbi) == -1) {
DPERROR("sio_oss_getpar: SNDCTL_DSP_GETOSPACE");
hdl->sio.eof = 1;
return 0;
}
par->round = pbi.fragsize / (par->pchan * par->bps);
par->bufsz = pbi.fragstotal * par->round;
}
if (hdl->sio.mode & SIO_REC) {
if (ioctl(hdl->fd, SNDCTL_DSP_GETISPACE, &rbi) == -1) {
DPERROR("sio_oss_getpar: SNDCTL_DSP_GETISPACE");
hdl->sio.eof = 1;
return 0;
}
if (!(hdl->sio.mode & SIO_PLAY)) {
par->round = rbi.fragsize / (par->rchan * par->bps);
par->bufsz = rbi.fragstotal * par->round;
}
}
par->appbufsz = par->bufsz;
#ifdef DEBUG
if ((hdl->sio.mode & (SIO_REC | SIO_PLAY)) == (SIO_REC | SIO_PLAY)) {
if (pbi.fragsize != rbi.fragsize) {
DPRINTF("sio_oss_getpar: frag size/count mismatch\n"
"play: count = %d, size = %d\n"
"rec: count = %d, size = %d\n",
pbi.fragstotal, pbi.fragsize,
rbi.fragstotal, rbi.fragsize);
hdl->sio.eof = 1;
return 0;
}
}
#endif
return 1;
}
static size_t
sio_oss_read(struct sio_hdl *sh, void *buf, size_t len)
{
struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh;
ssize_t n;
while ((n = read(hdl->fd, buf, len)) == -1) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
DPERROR("sio_oss_read: read");
hdl->sio.eof = 1;
}
return 0;
}
if (n == 0) {
DPRINTF("sio_oss_read: eof\n");
hdl->sio.eof = 1;
return 0;
}
hdl->idelta += n;
return n;
}
static size_t
sio_oss_write(struct sio_hdl *sh, const void *buf, size_t len)
{
struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh;
const unsigned char *data = buf;
ssize_t n, todo;
todo = len;
while ((n = write(hdl->fd, data, todo)) == -1) {
if (errno == EINTR)
continue;
if (errno != EAGAIN) {
DPERROR("sio_oss_write: write");
hdl->sio.eof = 1;
}
return 0;
}
hdl->odelta += n;
return n;
}
static int
sio_oss_nfds(struct sio_hdl *hdl)
{
return 1;
}
static int
sio_oss_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events)
{
struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh;
int trig;
pfd->fd = hdl->fd;
pfd->events = events;
if (hdl->filling && hdl->sio.wused == hdl->sio.par.bufsz *
hdl->sio.par.pchan * hdl->sio.par.bps) {
hdl->filling = 0;
trig = 0;
if (hdl->sio.mode & SIO_PLAY)
trig |= PCM_ENABLE_OUTPUT;
if (hdl->sio.mode & SIO_REC)
trig |= PCM_ENABLE_INPUT;
if (ioctl(hdl->fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) {
DPERROR("sio_oss_pollfd: SETTRIGGER");
hdl->sio.eof = 1;
return 0;
}
_sio_onmove_cb(&hdl->sio, 0);
}
return 1;
}
static int
sio_oss_xrun(struct sio_oss_hdl *hdl)
{
int clk;
int wsil, rdrop, cmove;
int rbpf, rround;
int wbpf;
DPRINTFN(2, "sio_oss_xrun:\n");
#ifdef DEBUG
if (_sndio_debug >= 2)
_sio_printpos(&hdl->sio);
#endif
/*
* we assume rused/wused are zero if rec/play modes are not
* selected. This allows us to keep the same formula for all
* modes, provided we set rbpf/wbpf to 1 to avoid division by
* zero.
*
* to understand the formula, draw a picture :)
*/
rbpf = (hdl->sio.mode & SIO_REC) ?
hdl->sio.par.bps * hdl->sio.par.rchan : 1;
wbpf = (hdl->sio.mode & SIO_PLAY) ?
hdl->sio.par.bps * hdl->sio.par.pchan : 1;
rround = hdl->sio.par.round * rbpf;
clk = hdl->sio.cpos % hdl->sio.par.round;
rdrop = (clk * rbpf - hdl->sio.rused) % rround;
if (rdrop < 0)
rdrop += rround;
cmove = (rdrop + hdl->sio.rused) / rbpf;
wsil = cmove * wbpf + hdl->sio.wused;
DPRINTFN(2, "wsil = %d, cmove = %d, rdrop = %d\n", wsil, cmove, rdrop);
if (!sio_oss_stop(&hdl->sio))
return 0;
if (!sio_oss_start(&hdl->sio))
return 0;
if (hdl->sio.mode & SIO_PLAY) {
hdl->odelta -= cmove * hdl->bpf;
hdl->sio.wsil = wsil;
}
if (hdl->sio.mode & SIO_REC) {
hdl->idelta -= cmove * hdl->bpf;
hdl->sio.rdrop = rdrop;
}
DPRINTFN(2, "xrun: corrected\n");
DPRINTFN(2, "wsil = %d, rdrop = %d, odelta = %d, idelta = %d\n",
wsil, rdrop, hdl->odelta, hdl->idelta);
return 1;
}
static int
sio_oss_revents(struct sio_hdl *sh, struct pollfd *pfd)
{
struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh;
audio_errinfo ei;
int delta, iused, oused;
int revents = pfd->revents;
oss_count_t optr, iptr;
if ((pfd->revents & POLLHUP) ||
(pfd->revents & (POLLIN | POLLOUT)) == 0)
return pfd->revents;
/* Hide xruns from clients */
if (ioctl(hdl->fd, SNDCTL_DSP_GETERROR, &ei) == -1) {
DPERROR("sio_oss_revents: GETERROR");
hdl->sio.eof = 1;
return POLLHUP;
}
if (ei.play_underruns > 0 || ei.rec_overruns > 0) {
if (!sio_oss_xrun(hdl))
return POLLHUP;
return 0;
}
if (hdl->sio.mode & SIO_PLAY) {
if (ioctl(hdl->fd, SNDCTL_DSP_CURRENT_OPTR, &optr) == -1) {
DPERROR("sio_oss_revents: CURRENT_OPTR");
hdl->sio.eof = 1;
return POLLHUP;
}
oused = optr.fifo_samples * hdl->bpf;
hdl->odelta -= oused - hdl->oused;
hdl->oused = oused;
if (!(hdl->sio.mode & SIO_REC)) {
hdl->idelta = hdl->odelta;
}
}
if (hdl->sio.mode & SIO_REC) {
if (ioctl(hdl->fd, SNDCTL_DSP_CURRENT_IPTR, &iptr) == -1) {
DPERROR("sio_oss_revents: CURRENT_IPTR");
hdl->sio.eof = 1;
return POLLHUP;
}
iused = iptr.fifo_samples * hdl->bpf;
hdl->idelta += iused - hdl->iused;
hdl->iused = iused;
if (!(hdl->sio.mode & SIO_PLAY)) {
hdl->odelta = hdl->idelta;
}
}
delta = (hdl->idelta > hdl->odelta) ? hdl->idelta : hdl->odelta;
if (delta > 0) {
_sio_onmove_cb(&hdl->sio, delta / hdl->bpf);
hdl->idelta -= delta;
hdl->odelta -= delta;
}
return revents;
}
static int
sio_oss_setvol(struct sio_hdl *sh, unsigned int vol)
{
struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh;
int newvol;
/* Scale to 0..100 */
newvol = (100 * vol + SIO_MAXVOL / 2) / SIO_MAXVOL;
newvol = newvol | (newvol << 8);
if (ioctl(hdl->fd, SNDCTL_DSP_SETPLAYVOL, &newvol) == -1) {
DPERROR("sio_oss_setvol");
hdl->sio.eof = 1;
return 0;
}
return 1;
}
static void
sio_oss_getvol(struct sio_hdl *sh)
{
struct sio_oss_hdl *hdl = (struct sio_oss_hdl *)sh;
int vol;
if (ioctl(hdl->fd, SNDCTL_DSP_GETPLAYVOL, &vol) == -1) {
DPERROR("sio_oss_getvol");
hdl->sio.eof = 1;
return;
}
/* Use left channel volume and scale to SIO_MAXVOL */
vol = (SIO_MAXVOL * (vol & 0x7f) + 50) / 100;
_sio_onvol_cb(&hdl->sio, vol);
}
#endif /* defined USE_OSS */