Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F149898073
D45951.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
23 KB
Referenced Files
None
Subscribers
None
D45951.diff
View Options
diff --git a/lib/libaudio/Makefile b/lib/libaudio/Makefile
new file mode 100644
--- /dev/null
+++ b/lib/libaudio/Makefile
@@ -0,0 +1,22 @@
+LIB= audio
+SRCS= ${LIB}.c
+INCS= ${LIB}.h
+MAN= ${LIB}.3
+VERSION_DEF= ${LIBCSRCDIR}/Versions.def
+SYMBOL_MAPS= ${.CURDIR}/Symbol.map
+LDFLAGS+= -lmixer -lnv
+
+MLINKS+= audio.3 midi.3
+
+MLINKS+= audio.3 audio_open.3
+MLINKS+= audio.3 audio_close.3
+MLINKS+= audio.3 audio_read.3
+MLINKS+= audio.3 audio_write.3
+MLINKS+= audio.3 audio_set_vol.3
+MLINKS+= audio.3 audio_get_vol.3
+MLINKS+= audio.3 midi_open.3
+MLINKS+= audio.3 midi_close.3
+MLINKS+= audio.3 midi_read.3
+MLINKS+= audio.3 midi_write.3
+
+.include <bsd.lib.mk>
diff --git a/lib/libaudio/Symbol.map b/lib/libaudio/Symbol.map
new file mode 100644
--- /dev/null
+++ b/lib/libaudio/Symbol.map
@@ -0,0 +1,13 @@
+FBSD_1.8 {
+ audio_open;
+ audio_close;
+ audio_read;
+ audio_write;
+ audio_set_vol;
+ audio_get_vol;
+
+ midi_open;
+ midi_close;
+ midi_read;
+ midi_write;
+};
diff --git a/lib/libaudio/audio.h b/lib/libaudio/audio.h
new file mode 100644
--- /dev/null
+++ b/lib/libaudio/audio.h
@@ -0,0 +1,113 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 The FreeBSD Foundation
+ *
+ * This software was developed by Christos Margiolis <christos@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _AUDIO_H_
+#define _AUDIO_H_
+
+#include <sys/cdefs.h>
+#include <sys/soundcard.h>
+
+#include <limits.h>
+#include <mixer.h>
+#include <stdbool.h>
+
+#define AUDIO_DEFAULT_FORMAT AFMT_S16_NE
+#define AUDIO_DEFAULT_CHANNELS 1
+#define AUDIO_DEFAULT_RATE 44100
+
+#define AUDIO_VOLMIN 0
+#define AUDIO_VOLMAX 100
+
+struct audio_channel {
+ char name[NAME_MAX];
+ int unit;
+ int caps;
+ int format;
+ int channels;
+ int rate;
+ int min_rate;
+ int max_rate;
+ size_t bufsz;
+ size_t sample_size;
+ size_t frame_size;
+ /* TODO bps */
+ int frame_total;
+ char *map;
+ struct audio_device *device;
+};
+
+struct audio_device {
+#define AUDIO_REC 0x0001
+#define AUDIO_PLAY 0x0002
+#define AUDIO_NBIO 0x0004
+#define AUDIO_EXCL 0x0008
+#define AUDIO_MMAP 0x0010
+ int open_mode;
+ int fd;
+ int unit;
+ char name[NAME_MAX];
+ char desc[NAME_MAX];
+ char devnode[NAME_MAX];
+ bool is_default;
+ int caps;
+ int format; /* XXX Do we need it here as well? */
+ int rate; /* XXX Do we need it here as well? */
+ int channels; /* XXX Do we need it here as well? */
+ struct mixer *mixer;
+ struct audio_channel *chan_in;
+ struct audio_channel *chan_out;
+};
+
+struct midi_device {
+#define MIDI_IN 0x0001
+#define MIDI_OUT 0x0002
+#define MIDI_NBIO 0x0004
+ int open_mode;
+ int fd;
+ /* TODO Add more once we have better support. */
+};
+
+__BEGIN_DECLS
+
+struct audio_device *audio_open(const char *, int, int, int, int);
+int audio_close(struct audio_device *);
+ssize_t audio_read(struct audio_device *, void *, size_t);
+ssize_t audio_write(struct audio_device *, void *, size_t);
+int audio_set_vol(struct audio_channel *, int);
+int audio_get_vol(struct audio_channel *);
+
+struct midi_device *midi_open(const char *, int);
+int midi_close(struct midi_device *);
+ssize_t midi_read(struct midi_device *, void *, size_t);
+ssize_t midi_write(struct midi_device *, void *, size_t);
+
+__END_DECLS
+
+#endif /* _AUDIO_H_ */
diff --git a/lib/libaudio/audio.3 b/lib/libaudio/audio.3
new file mode 100644
--- /dev/null
+++ b/lib/libaudio/audio.3
@@ -0,0 +1,54 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2024 The FreeBSD Foundation
+.\"
+.\" Portions of this software were developed by Christos Margiolis
+.\" <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd June 19, 2024
+.Dt AUDIO 3
+.Os
+.Sh NAME
+.Nm audio_open ,
+.Nm audio_close ,
+.Nm audio_read ,
+.Nm audio_write ,
+.Nm audio_set_vol ,
+.Nm audio_get_vol ,
+.Nm midi_open ,
+.Nm midi_close ,
+.Nm midi_read ,
+.Nm midi_write
+.Nd interface to the audio and MIDI subsystem
+.Sh LIBRARY
+Audio and MIDI library (libaudio, -laudio)
+.Sh SYNOPSIS
+.In audio.h
+.Sh AUTHORS
+The
+.Nm
+library was implemented by
+.An Christos Margiolis Aq Mt christos@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/lib/libaudio/audio.c b/lib/libaudio/audio.c
new file mode 100644
--- /dev/null
+++ b/lib/libaudio/audio.c
@@ -0,0 +1,704 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 The FreeBSD Foundation
+ *
+ * This software was developed by Christos Margiolis <christos@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/mman.h>
+#include <sys/nv.h>
+#include <sys/sndstat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "audio.h"
+
+#define DSP_BASEPATH "/dev/dsp"
+
+#define LOG_HELPER(fmt, ...) \
+ syslog(LOG_ERR, "%s:%d: " fmt "%s\n", __func__, __LINE__, __VA_ARGS__)
+#define LOG(...) \
+ LOG_HELPER(__VA_ARGS__, "")
+
+/* TODO Change name? */
+static int
+_pollwait(int fd, int events)
+{
+ struct pollfd pfd;
+
+ pfd.fd = fd;
+ pfd.events = events;
+
+ for (;;) {
+ while (poll(&pfd, 1, -1) < 0) {
+ if (errno == EINTR)
+ continue;
+ LOG("poll() failed");
+ return (-1);
+ }
+ if (pfd.revents & POLLHUP) {
+ errno = ENODEV;
+ return (-1);
+ }
+ if ((pfd.revents & events) != 0)
+ break;
+ }
+
+ return (0);
+}
+
+/* TODO Expose it in header file? */
+static int
+audio_fmt2bytes(int fmt)
+{
+ if (fmt & (AFMT_S8 | AFMT_U8))
+ return (1);
+ else if (fmt & (AFMT_S16_NE | AFMT_U16_NE))
+ return (2);
+ else if (fmt & (AFMT_S24_NE | AFMT_U24_NE))
+ return (3);
+ else if (fmt & (AFMT_S32_NE | AFMT_U32_NE))
+ return (4);
+ else {
+ LOG("unknown format: %#08x", fmt);
+ return (0);
+ }
+}
+
+struct audio_device *
+audio_open(const char *path, int open_mode, int format, int channels, int rate)
+{
+ struct audio_device *d;
+ struct audio_channel *c;
+ struct sndstioc_nv_arg arg = {0};
+ const nvlist_t * const *di;
+ const nvlist_t * const *cdi;
+ nvlist_t *nvl = NULL;
+ oss_audioinfo ai;
+ oss_sysinfo si;
+ audio_buf_info bi;
+ char buf[NAME_MAX];
+ size_t i, n;
+ int stfd = -1, tmp;
+
+ if ((d = calloc(1, sizeof(struct audio_device))) == NULL) {
+ LOG("cannot allocate audio_device");
+ goto fail;
+ }
+
+ open_mode &= AUDIO_REC | AUDIO_PLAY | AUDIO_NBIO |
+ AUDIO_EXCL | AUDIO_MMAP;
+ if (!open_mode) {
+ LOG("no valid open mode specified");
+ errno = EINVAL;
+ goto fail;
+ }
+
+ tmp = 0;
+ if ((open_mode & (AUDIO_REC | AUDIO_PLAY)) == (AUDIO_REC | AUDIO_PLAY))
+ tmp |= O_RDWR;
+ else if (open_mode & AUDIO_REC)
+ tmp |= O_RDONLY;
+ else if (open_mode & AUDIO_PLAY)
+ tmp |= O_WRONLY;
+ else {
+ LOG("please specify at least one audio direction");
+ errno = EINVAL;
+ goto fail;
+ }
+ if (open_mode & AUDIO_NBIO)
+ tmp |= O_NONBLOCK;
+ if (open_mode & AUDIO_EXCL)
+ tmp |= O_EXCL;
+
+ if (path == NULL || strcmp(path, DSP_BASEPATH) == 0) {
+ d->unit = -1;
+ path = DSP_BASEPATH;
+ } else {
+ /* FIXME Handle relative and userdev paths */
+ d->unit = strtol(path + strlen(DSP_BASEPATH), NULL, 10);
+ if (errno == EINVAL || errno == ERANGE) {
+ LOG("strtol(%s) failed", path + strlen(DSP_BASEPATH));
+ goto fail;
+ }
+ }
+
+ if ((d->fd = open(path, tmp)) < 0) {
+ LOG("open(%s, %#04x) failed", path, tmp);
+ goto fail;
+ }
+
+ ai.dev = d->unit;
+ if (ioctl(d->fd,
+ open_mode & AUDIO_EXCL ? SNDCTL_AUDIOINFO_EX : SNDCTL_AUDIOINFO,
+ &ai) < 0) {
+ LOG("ioctl(SNDCTL_AUDIOINFO%s, %d) failed",
+ d->open_mode & AUDIO_EXCL ? "_EX" : "", d->unit);
+ goto fail;
+ }
+ d->unit = ai.dev;
+ d->caps = ai.caps;
+ d->is_default = d->unit == mixer_get_dunit();
+ d->open_mode = open_mode;
+ strlcpy(d->desc, ai.name, sizeof(d->desc));
+ strlcpy(d->devnode, ai.devnode, sizeof(d->devnode));
+
+ mixer_get_path(buf, sizeof(buf), d->unit);
+ d->mixer = mixer_open(buf);
+
+ if (ioctl(d->fd, OSS_SYSINFO, &si) < 0) {
+ LOG("ioctl(OSS_SYSINFO) failed");
+ goto fail;
+ }
+
+ /* Memory map prior to FreeBSD 13.2 may use wrong buffer sizes. */
+ if (strncmp(si.version, "1302000", 7) < 0) {
+ /* XXX Fail or just disable mmap silently? */
+ if (d->open_mode & AUDIO_MMAP) {
+ LOG("memory map not working properly prior to 13.2");
+ errno = ENODEV;
+ goto fail;
+ }
+ d->caps &= ~PCM_CAP_MMAP;
+ }
+
+ if ((d->open_mode & (AUDIO_NBIO | AUDIO_MMAP)) &&
+ (d->caps & PCM_CAP_TRIGGER) == 0) {
+ LOG("triggering not supported");
+ errno = ENODEV;
+ goto fail;
+ }
+
+ if ((d->open_mode & AUDIO_MMAP) && (d->caps & PCM_CAP_MMAP) == 0) {
+ LOG("memory mapping not supported");
+ errno = ENODEV;
+ goto fail;
+ }
+
+ /*
+ * Disable format conversions if we are in exclusive or memory map
+ * mode.
+ */
+ if (d->open_mode & (AUDIO_EXCL | AUDIO_MMAP)) {
+ /*
+ * The OSS docs advise against checking the return value of
+ * this IOCTL.
+ */
+ n = 0;
+ ioctl(d->fd, SNDCTL_DSP_COOKEDMODE, &n);
+ }
+
+ /* Set sample format. */
+ if (format <= 0)
+ format = AUDIO_DEFAULT_FORMAT;
+ tmp = format;
+ if (ioctl(d->fd, SNDCTL_DSP_SETFMT, &tmp) < 0) {
+ LOG("ioctl(SNDCTL_DSP_SETFMT, %#08x) failed", format);
+ goto fail;
+ }
+ if (tmp != format)
+ LOG("driver forced sample format: %#08x -> %#08x", format, tmp);
+ d->format = tmp;
+
+ /* Set sample channels. */
+ if (channels <= 0)
+ channels = AUDIO_DEFAULT_CHANNELS;
+ tmp = channels;
+ if (ioctl(d->fd, SNDCTL_DSP_CHANNELS, &tmp) < 0) {
+ LOG("ioctl(SNDCTL_DSP_CHANNELS, %d) failed", channels);
+ goto fail;
+ }
+ if (tmp != channels)
+ LOG("driver forced sample channels: %d -> %d", channels, tmp);
+ d->channels = tmp;
+
+ /* Set sample rate. */
+ if (rate <= 0)
+ rate = AUDIO_DEFAULT_RATE;
+ tmp = rate;
+ if (ioctl(d->fd, SNDCTL_DSP_SPEED, &tmp) < 0) {
+ LOG("ioctl(SNDCTL_DSP_SPEED, %d) failed", rate);
+ goto fail;
+ }
+ if (tmp != rate)
+ LOG("driver forced sample rate: %d -> %d", rate, tmp);
+ d->rate = tmp;
+
+ /*
+ * Read /dev/sndstat nvlist for some contents we cannot fetch from the
+ * AUDIOINFO/ENGINEINFO IOCTLs.
+ */
+ if ((stfd = open("/dev/sndstat", O_RDONLY)) < 0) {
+ LOG("open(/dev/sndstat) failed");
+ goto fail;
+ }
+ if (ioctl(stfd, SNDSTIOC_REFRESH_DEVS, NULL) < 0) {
+ LOG("ioctl(SNDSTIOC_REFRESH_DEVS) failed");
+ goto fail;
+ }
+ arg.nbytes = 0;
+ arg.buf = NULL;
+ if (ioctl(stfd, SNDSTIOC_GET_DEVS, &arg) < 0) {
+ LOG("ioctl(SNDSTIOC_GET_DEVS#1) failed");
+ goto fail;
+ }
+ if ((arg.buf = malloc(arg.nbytes)) == NULL) {
+ LOG("cannot allocate sndstat buffer");
+ goto fail;
+ }
+ if (ioctl(stfd, SNDSTIOC_GET_DEVS, &arg) < 0) {
+ LOG("ioctl(SNDSTIOC_GET_DEVS#2) failed");
+ goto fail;
+ }
+ nvl = nvlist_unpack(arg.buf, arg.nbytes, 0);
+ di = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &n);
+
+ /* Search for our device. */
+ for (i = 0; i < n; i++)
+ if (i == (size_t)d->unit)
+ break;
+ if (i == n) {
+ LOG("cannot find device %d in sndstat nvlist", d->unit);
+ errno = ENODEV;
+ goto fail;
+ }
+
+ strlcpy(d->name, nvlist_get_string(di[i], SNDST_DSPS_NAMEUNIT),
+ sizeof(d->name));
+
+ if (!nvlist_exists(di[i], SNDST_DSPS_PROVIDER_INFO)) {
+ LOG("%s: provider info nvlist not found", d->name);
+ errno = ENODEV;
+ goto fail;
+ }
+
+ cdi = nvlist_get_nvlist_array(
+ nvlist_get_nvlist(di[i], SNDST_DSPS_PROVIDER_INFO),
+ SNDST_DSPS_SOUND4_CHAN_INFO, &n);
+
+ for (i = 0; i < n; i++) {
+ ai.dev = i;
+ if (ioctl(d->fd, SNDCTL_ENGINEINFO, &ai) < 0) {
+ LOG("%s: ioctl(SNDCTL_ENGINEINFO, %zu) failed",
+ d->name, i);
+ goto fail;
+ }
+ /* We only care about our process' channel(s). */
+ if (getpid() != ai.pid)
+ continue;
+ if ((c = calloc(1, sizeof(struct audio_channel))) == NULL) {
+ LOG("%s: cannot allocate audio_channel", d->name);
+ goto fail;
+ }
+ c->device = d;
+ strlcpy(c->name, ai.name, sizeof(c->name));
+ c->unit = ai.dev;
+ c->caps = ai.caps;
+ c->min_rate = ai.min_rate;
+ c->max_rate = ai.max_rate;
+ c->channels = d->channels;
+#define NV(type, id) \
+ nvlist_get_ ## type (cdi[i], SNDST_DSPS_SOUND4_CHAN_ ## id)
+ c->format = NV(number, FORMAT);
+ c->rate = NV(number, RATE);
+#undef NV
+ /* TODO Handle buffer size assignment */
+ if (c->caps & PCM_CAP_INPUT) {
+ if (ioctl(d->fd, SNDCTL_DSP_GETISPACE, &bi) < 0) {
+ LOG("%s: ioctl(SNDCTL_DSP_GETISPACE)", c->name);
+ goto fail;
+ }
+ tmp = PROT_READ;
+ d->chan_in = c;
+ } else if (c->caps & PCM_CAP_OUTPUT) {
+ if (ioctl(d->fd, SNDCTL_DSP_GETOSPACE, &bi) < 0) {
+ LOG("%s: ioctl(SNDCTL_DSP_GETOSPACE)", c->name);
+ goto fail;
+ }
+ tmp = PROT_WRITE;
+ d->chan_out = c;
+ }
+
+ c->bufsz = bi.fragstotal * bi.fragsize;
+ c->sample_size = audio_fmt2bytes(c->format);
+ c->frame_size = c->sample_size * c->channels;
+ c->frame_total = c->bufsz / c->frame_size;
+
+ /* Memory map buffer */
+ if (d->open_mode & AUDIO_MMAP) {
+ c->map = mmap(NULL, c->bufsz, tmp,
+ MAP_FILE | MAP_SHARED, d->fd, 0);
+ if (c->map == MAP_FAILED) {
+ LOG("%s: cannot mmap %s buffer",
+ c->name, tmp == PROT_READ ?
+ "recording" : "playback");
+ goto fail;
+ }
+ for (i = 0; i < c->bufsz; i++)
+ c->map[i] = 0;
+ }
+ }
+ if (d->chan_in == NULL && d->chan_out == NULL) {
+ LOG("%s: no channels allocated", d->name);
+ errno = ENODEV;
+ goto fail;
+ }
+
+ /* Trigger channel start */
+ if (d->open_mode & (AUDIO_NBIO | AUDIO_MMAP)) {
+ tmp = 0;
+ if (ioctl(d->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0) {
+ LOG("%s: ioctl(SNDCTL_DSP_SETTRIGGER#1) failed",
+ d->name);
+ goto fail;
+ }
+
+ tmp = 0;
+ if (d->chan_in != NULL)
+ tmp |= PCM_ENABLE_INPUT;
+ if (d->chan_out != NULL)
+ tmp |= PCM_ENABLE_OUTPUT;
+ if (ioctl(d->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0) {
+ LOG("%s: ioctl(SNDCTL_DSP_SETTRIGGER#2) failed",
+ d->name);
+ goto fail;
+ }
+ }
+
+ close(stfd);
+ free(arg.buf);
+ nvlist_destroy(nvl);
+
+ return (d);
+
+fail:
+ close(stfd);
+ nvlist_destroy(nvl);
+ if (arg.buf != NULL)
+ free(arg.buf);
+ if (d != NULL)
+ audio_close(d);
+
+ return (NULL);
+}
+
+int
+audio_close(struct audio_device *d)
+{
+ struct audio_channel *c;
+ int rc;
+
+ if (d == NULL)
+ return (0);
+
+ rc = close(d->fd);
+ if (d->chan_in != NULL) {
+ c = d->chan_in;
+ if (d->open_mode & AUDIO_MMAP)
+ munmap(c->map, c->bufsz);
+ free(c);
+ }
+ if (d->chan_out != NULL) {
+ c = d->chan_out;
+ if (d->open_mode & AUDIO_MMAP)
+ munmap(c->map, c->bufsz);
+ free(c);
+ }
+ free(d);
+
+ return (rc);
+}
+
+static ssize_t
+audio_io(struct audio_device *d, void *buf, size_t nbytes, bool rd)
+{
+ struct audio_channel *c;
+ count_info ci;
+ char *data;
+ size_t todo;
+ ssize_t n;
+
+ if (d == NULL) {
+ LOG("device is null");
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if ((rd && d->chan_in == NULL) || (!rd && d->chan_out == NULL)) {
+ LOG("%s: no %s channel", d->name, rd ? "recording" : "playback");
+ errno = EINVAL;
+ return (-1);
+ }
+
+ c = rd ? d->chan_in : d->chan_out;
+
+ if (nbytes == 0)
+ return (0);
+ if (nbytes > c->bufsz && rd)
+ nbytes = c->bufsz;
+
+ for (data = buf, todo = nbytes; todo > 0;) {
+ /* Wait for events. */
+ if (d->open_mode & (AUDIO_NBIO | AUDIO_MMAP) &&
+ _pollwait(d->fd, rd ? POLLIN : POLLOUT) < 0)
+ break;
+
+ /* FIXME */
+ /* Calculate payload in case of mmap. */
+ if (d->open_mode & AUDIO_MMAP) {
+ if (ioctl(d->fd, rd ? SNDCTL_DSP_GETIPTR :
+ SNDCTL_DSP_GETOPTR, &ci) < 0) {
+ LOG("%s: ioctl(SNDCTL_DSP_GET%sPTR) failed",
+ c->name, rd ? "I" : "O");
+ break;
+ }
+ if (!ci.bytes)
+ continue;
+ ci.ptr %= c->bufsz;
+ n = c->bufsz - ci.ptr;
+ if ((size_t)n > todo)
+ n = todo;
+ LOG("todo=%ld, ptr=%d, n=%ld", todo, ci.ptr, n);
+ }
+
+ /* Do the actual reading/writing. */
+ if (rd) {
+ if (d->open_mode & AUDIO_MMAP)
+ memcpy(data, c->map + ci.ptr, n);
+ else
+ n = read(d->fd, data, todo);
+ } else {
+ if (d->open_mode & AUDIO_MMAP)
+ memcpy(c->map + ci.ptr, data, n);
+ else
+ n = write(d->fd, data, todo);
+ }
+ if ((d->open_mode & AUDIO_MMAP) == 0 && errno == EAGAIN)
+ continue;
+ if (n >= 0) {
+ data += n;
+ todo -= n;
+ } else {
+ LOG("%s: failed to %s: %s", rd ? "read" : "write",
+ c->name, strerror(errno));
+ break;
+ }
+ }
+
+ return (nbytes - todo);
+}
+
+ssize_t
+audio_read(struct audio_device *d, void *buf, size_t nbytes)
+{
+ return (audio_io(d, buf, nbytes, true));
+}
+
+ssize_t
+audio_write(struct audio_device *d, void *buf, size_t nbytes)
+{
+ return (audio_io(d, buf, nbytes, false));
+}
+
+int
+audio_set_vol(struct audio_channel *c, int vol)
+{
+ if (c == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /*
+ * We could disallow volumes outside the [AUDIO_VOLMIN, AUDIO_VOLMAX]
+ * range, but sound(4) will cap them anyway.
+ */
+ if (c->caps & PCM_CAP_INPUT) {
+ if (ioctl(c->device->fd, SNDCTL_DSP_SETRECVOL, &vol) < 0) {
+ LOG("%s: ioctl(SNDCTL_DSP_SETRECVOL) failed", c->name);
+ return (-1);
+ }
+ } else if (c->caps & PCM_CAP_OUTPUT) {
+ if (ioctl(c->device->fd, SNDCTL_DSP_SETPLAYVOL, &vol) < 0) {
+ LOG("%s: ioctl(SNDCTL_DSP_SETPLAYVOL) failed", c->name);
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+int
+audio_get_vol(struct audio_channel *c)
+{
+ int vol = 0;
+
+ if (c == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if (c->caps & PCM_CAP_INPUT) {
+ if (ioctl(c->device->fd, SNDCTL_DSP_GETRECVOL, &vol) < 0) {
+ LOG("%s: ioctl(SNDCTL_DSP_SETRECVOL) failed", c->name);
+ return (-1);
+ }
+ } else if (c->caps & PCM_CAP_OUTPUT) {
+ if (ioctl(c->device->fd, SNDCTL_DSP_GETPLAYVOL, &vol) < 0) {
+ LOG("%s: ioctl(SNDCTL_DSP_GETPLAYVOL) failed", c->name);
+ return (-1);
+ }
+ }
+
+ return (vol);
+}
+
+struct midi_device *
+midi_open(const char *path, int open_mode)
+{
+ struct midi_device *d = NULL;
+ int tmp;
+
+ if (path == NULL) {
+ LOG("path not specified");
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if ((d = calloc(1, sizeof(struct midi_device))) == NULL) {
+ LOG("cannot allocate midi_device");
+ goto fail;
+ }
+
+ open_mode &= MIDI_IN | MIDI_OUT | MIDI_NBIO;
+ if (!open_mode) {
+ LOG("no valid open mode specified");
+ errno = EINVAL;
+ goto fail;
+ }
+
+ tmp = 0;
+ if ((open_mode & (MIDI_IN | MIDI_OUT)) == (MIDI_IN | MIDI_OUT))
+ tmp |= O_RDWR;
+ else if (open_mode & MIDI_IN)
+ tmp |= O_RDONLY;
+ else if (open_mode & MIDI_OUT)
+ tmp |= O_WRONLY;
+ else {
+ LOG("please specify at least one midi direction");
+ errno = EINVAL;
+ goto fail;
+ }
+ if (open_mode & MIDI_NBIO)
+ tmp |= O_NONBLOCK;
+
+ /* XXX Path checks like in audio_open()? */
+ /* TODO Somehow verify this is indeed a MIDI device */
+
+ if ((d->fd = open(path, tmp)) < 0) {
+ LOG("open(%s, %#04x) failed", path, tmp);
+ goto fail;
+ }
+
+ d->open_mode = open_mode;
+
+ return (d);
+
+fail:
+ if (d != NULL)
+ midi_close(d);
+
+ return (NULL);
+}
+
+int
+midi_close(struct midi_device *d)
+{
+ int rc;
+
+ rc = close(d->fd);
+ free(d);
+
+ return (rc);
+}
+
+static ssize_t
+midi_io(struct midi_device *d, void *buf, size_t nbytes, bool rd)
+{
+ char *data;
+ size_t todo;
+ ssize_t n;
+
+ if (d == NULL) {
+ LOG("device is null");
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if (nbytes == 0)
+ return (0);
+
+ /*
+ * XXX Do we need this loop if we usually read a handful of bytes only?
+ */
+ for (data = buf, todo = nbytes; todo > 0;) {
+ if (_pollwait(d->fd, rd ? POLLIN : POLLOUT) < 0)
+ break;
+
+ while ((n = rd ? read(d->fd, data, todo) :
+ write(d->fd, data, todo)) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN) {
+ LOG("failed to %s: %s",
+ rd ? "read" : "write", strerror(errno));
+ goto out;
+ }
+ }
+ data += n;
+ todo -= n;
+ break;
+ }
+out:
+ return (nbytes - todo);
+}
+
+ssize_t
+midi_read(struct midi_device *d, void *buf, size_t nbytes)
+{
+ return (midi_io(d, buf, nbytes, true));
+}
+
+ssize_t
+midi_write(struct midi_device *d, void *buf, size_t nbytes)
+{
+ return (midi_io(d, buf, nbytes, false));
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Mar 28, 11:36 PM (18 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
30498217
Default Alt Text
D45951.diff (23 KB)
Attached To
Mode
D45951: [WIP] audio(3): New OSS audio and MIDI library
Attached
Detach File
Event Timeline
Log In to Comment