| /*** | |
| This file is part of PulseAudio. | |
| Copyright 2004-2006 Lennart Poettering | |
| Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB | |
| PulseAudio is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU Lesser General Public License as published | |
| by the Free Software Foundation; either version 2.1 of the License, | |
| or (at your option) any later version. | |
| PulseAudio is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU Lesser General Public License | |
| along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. | |
| ***/ | |
| #ifdef HAVE_CONFIG_H | |
| #include <config.h> | |
| #endif | |
| #include <sys/soundcard.h> | |
| #include <sys/ioctl.h> | |
| #include <stdio.h> | |
| #include <errno.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <sys/types.h> | |
| #include <fcntl.h> | |
| #include <pulse/xmalloc.h> | |
| #include <pulsecore/core-error.h> | |
| #include <pulsecore/core-util.h> | |
| #include <pulsecore/log.h> | |
| #include <pulsecore/macro.h> | |
| #include "oss-util.h" | |
| int pa_oss_open(const char *device, int *mode, int* pcaps) { | |
| static const int nonblock_io = 1; | |
| int fd = -1; | |
| int caps; | |
| char *t; | |
| pa_assert(device); | |
| pa_assert(mode); | |
| pa_assert(*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY); | |
| if (!pcaps) | |
| pcaps = ∩︀ | |
| if (*mode == O_RDWR) { | |
| if ((fd = pa_open_cloexec(device, O_RDWR|O_NDELAY, 0)) >= 0) { | |
| ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); | |
| if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { | |
| pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno)); | |
| goto fail; | |
| } | |
| if (*pcaps & DSP_CAP_DUPLEX) | |
| goto success; | |
| pa_log_warn("'%s' doesn't support full duplex", device); | |
| pa_close(fd); | |
| } | |
| if ((fd = pa_open_cloexec(device, (*mode = O_WRONLY)|O_NDELAY, 0)) < 0) { | |
| if ((fd = pa_open_cloexec(device, (*mode = O_RDONLY)|O_NDELAY, 0)) < 0) { | |
| pa_log("open('%s'): %s", device, pa_cstrerror(errno)); | |
| goto fail; | |
| } | |
| } | |
| } else { | |
| if ((fd = pa_open_cloexec(device, *mode|O_NDELAY, 0)) < 0) { | |
| pa_log("open('%s'): %s", device, pa_cstrerror(errno)); | |
| goto fail; | |
| } | |
| } | |
| *pcaps = 0; | |
| if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { | |
| pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno)); | |
| goto fail; | |
| } | |
| success: | |
| if (ioctl(fd, FIONBIO, &nonblock_io) < 0) { | |
| pa_log("FIONBIO: %s", pa_cstrerror(errno)); | |
| goto fail; | |
| } | |
| t = pa_sprintf_malloc( | |
| "%s%s%s%s%s%s%s%s%s%s%s%s%s%s", | |
| *pcaps & DSP_CAP_BATCH ? " BATCH" : "", | |
| #ifdef DSP_CAP_BIND | |
| *pcaps & DSP_CAP_BIND ? " BIND" : "", | |
| #else | |
| "", | |
| #endif | |
| *pcaps & DSP_CAP_COPROC ? " COPROC" : "", | |
| *pcaps & DSP_CAP_DUPLEX ? " DUPLEX" : "", | |
| #ifdef DSP_CAP_FREERATE | |
| *pcaps & DSP_CAP_FREERATE ? " FREERATE" : "", | |
| #else | |
| "", | |
| #endif | |
| #ifdef DSP_CAP_INPUT | |
| *pcaps & DSP_CAP_INPUT ? " INPUT" : "", | |
| #else | |
| "", | |
| #endif | |
| *pcaps & DSP_CAP_MMAP ? " MMAP" : "", | |
| #ifdef DSP_CAP_MODEM | |
| *pcaps & DSP_CAP_MODEM ? " MODEM" : "", | |
| #else | |
| "", | |
| #endif | |
| #ifdef DSP_CAP_MULTI | |
| *pcaps & DSP_CAP_MULTI ? " MULTI" : "", | |
| #else | |
| "", | |
| #endif | |
| #ifdef DSP_CAP_OUTPUT | |
| *pcaps & DSP_CAP_OUTPUT ? " OUTPUT" : "", | |
| #else | |
| "", | |
| #endif | |
| *pcaps & DSP_CAP_REALTIME ? " REALTIME" : "", | |
| #ifdef DSP_CAP_SHADOW | |
| *pcaps & DSP_CAP_SHADOW ? " SHADOW" : "", | |
| #else | |
| "", | |
| #endif | |
| #ifdef DSP_CAP_VIRTUAL | |
| *pcaps & DSP_CAP_VIRTUAL ? " VIRTUAL" : "", | |
| #else | |
| "", | |
| #endif | |
| *pcaps & DSP_CAP_TRIGGER ? " TRIGGER" : ""); | |
| pa_log_debug("capabilities:%s", t); | |
| pa_xfree(t); | |
| return fd; | |
| fail: | |
| if (fd >= 0) | |
| pa_close(fd); | |
| return -1; | |
| } | |
| int pa_oss_auto_format(int fd, pa_sample_spec *ss) { | |
| int format, channels, speed, reqformat; | |
| pa_sample_format_t orig_format; | |
| static const int format_trans[PA_SAMPLE_MAX] = { | |
| [PA_SAMPLE_U8] = AFMT_U8, | |
| [PA_SAMPLE_ALAW] = AFMT_A_LAW, | |
| [PA_SAMPLE_ULAW] = AFMT_MU_LAW, | |
| [PA_SAMPLE_S16LE] = AFMT_S16_LE, | |
| [PA_SAMPLE_S16BE] = AFMT_S16_BE, | |
| [PA_SAMPLE_FLOAT32LE] = AFMT_QUERY, /* not supported */ | |
| [PA_SAMPLE_FLOAT32BE] = AFMT_QUERY, /* not supported */ | |
| [PA_SAMPLE_S32LE] = AFMT_QUERY, /* not supported */ | |
| [PA_SAMPLE_S32BE] = AFMT_QUERY, /* not supported */ | |
| #if defined(AFMT_S24_LE) && defined(AFMT_S24_BE) | |
| [PA_SAMPLE_S24LE] = AFMT_S24_LE, | |
| [PA_SAMPLE_S24BE] = AFMT_S24_BE, | |
| #else | |
| [PA_SAMPLE_S24LE] = AFMT_QUERY, /* not supported */ | |
| [PA_SAMPLE_S24BE] = AFMT_QUERY, /* not supported */ | |
| #endif | |
| [PA_SAMPLE_S24_32LE] = AFMT_QUERY, /* not supported */ | |
| [PA_SAMPLE_S24_32BE] = AFMT_QUERY, /* not supported */ | |
| }; | |
| pa_assert(fd >= 0); | |
| pa_assert(ss); | |
| orig_format = ss->format; | |
| reqformat = format = format_trans[ss->format]; | |
| if (reqformat == AFMT_QUERY || ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != reqformat) { | |
| format = AFMT_S16_NE; | |
| if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_S16_NE) { | |
| int f = AFMT_S16_NE == AFMT_S16_LE ? AFMT_S16_BE : AFMT_S16_LE; | |
| format = f; | |
| if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != f) { | |
| format = AFMT_U8; | |
| if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_U8) { | |
| pa_log("SNDCTL_DSP_SETFMT: %s", format != AFMT_U8 ? "No supported sample format" : pa_cstrerror(errno)); | |
| return -1; | |
| } else | |
| ss->format = PA_SAMPLE_U8; | |
| } else | |
| ss->format = f == AFMT_S16_LE ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE; | |
| } else | |
| ss->format = PA_SAMPLE_S16NE; | |
| } | |
| if (orig_format != ss->format) | |
| pa_log_warn("device doesn't support sample format %s, changed to %s.", | |
| pa_sample_format_to_string(orig_format), | |
| pa_sample_format_to_string(ss->format)); | |
| channels = ss->channels; | |
| if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0) { | |
| pa_log("SNDCTL_DSP_CHANNELS: %s", pa_cstrerror(errno)); | |
| return -1; | |
| } | |
| pa_assert(channels > 0); | |
| if (ss->channels != channels) { | |
| pa_log_warn("device doesn't support %i channels, using %i channels.", ss->channels, channels); | |
| ss->channels = (uint8_t) channels; | |
| } | |
| speed = (int) ss->rate; | |
| if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0) { | |
| pa_log("SNDCTL_DSP_SPEED: %s", pa_cstrerror(errno)); | |
| return -1; | |
| } | |
| pa_assert(speed > 0); | |
| if (ss->rate != (unsigned) speed) { | |
| pa_log_warn("device doesn't support %i Hz, changed to %i Hz.", ss->rate, speed); | |
| /* If the sample rate deviates too much, we need to resample */ | |
| if (speed < ss->rate*.95 || speed > ss->rate*1.05) | |
| ss->rate = (uint32_t) speed; | |
| } | |
| return 0; | |
| } | |
| int pa_oss_set_fragments(int fd, int nfrags, int frag_size) { | |
| int arg; | |
| pa_assert(frag_size >= 0); | |
| arg = ((int) nfrags << 16) | pa_ulog2(frag_size); | |
| pa_log_debug("Asking for %i fragments of size %i (requested %i)", nfrags, 1 << pa_ulog2(frag_size), frag_size); | |
| if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg) < 0) { | |
| pa_log("SNDCTL_DSP_SETFRAGMENT: %s", pa_cstrerror(errno)); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_cvolume *volume) { | |
| char cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; | |
| unsigned vol; | |
| pa_assert(fd >= 0); | |
| pa_assert(ss); | |
| pa_assert(volume); | |
| if (ioctl(fd, mixer, &vol) < 0) | |
| return -1; | |
| pa_cvolume_reset(volume, ss->channels); | |
| volume->values[0] = PA_CLAMP_VOLUME(((vol & 0xFF) * PA_VOLUME_NORM) / 100); | |
| if (volume->channels >= 2) | |
| volume->values[1] = PA_CLAMP_VOLUME((((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100); | |
| pa_log_debug("Read mixer settings: %s", pa_cvolume_snprint_verbose(cv, sizeof(cv), volume, NULL, false)); | |
| return 0; | |
| } | |
| int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) { | |
| char cv[PA_CVOLUME_SNPRINT_MAX]; | |
| unsigned vol; | |
| pa_volume_t l, r; | |
| l = volume->values[0] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[0]; | |
| vol = (l*100)/PA_VOLUME_NORM; | |
| if (ss->channels >= 2) { | |
| r = volume->values[1] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[1]; | |
| vol |= ((r*100)/PA_VOLUME_NORM) << 8; | |
| } | |
| if (ioctl(fd, mixer, &vol) < 0) | |
| return -1; | |
| pa_log_debug("Wrote mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume)); | |
| return 0; | |
| } | |
| static int get_device_number(const char *dev) { | |
| const char *p; | |
| const char *e; | |
| char *rp = NULL; | |
| int r = -1; | |
| if (!(p = rp = pa_readlink(dev))) { | |
| if (errno != EINVAL && errno != ENOLINK) | |
| return -2; | |
| p = dev; | |
| } | |
| /* find the last forward slash */ | |
| while ((e = strrchr(p, '/'))) | |
| p = e + 1; | |
| /* collect unit number at end, if any */ | |
| while (*p) { | |
| if (*p >= '0' && *p <= '9') { | |
| if (r < 0) | |
| r = 0; | |
| else | |
| r *= 10; | |
| r += *p - '0'; | |
| } else { | |
| r = -1; | |
| } | |
| p++; | |
| } | |
| pa_xfree(rp); | |
| return r; | |
| } | |
| int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { | |
| FILE *f; | |
| int n, r = -1; | |
| int b = 0; | |
| if ((n = get_device_number(dev)) == -2) | |
| return -1; | |
| if (!(f = pa_fopen_cloexec("/dev/sndstat", "r")) && | |
| !(f = pa_fopen_cloexec("/proc/sndstat", "r")) && | |
| !(f = pa_fopen_cloexec("/proc/asound/oss/sndstat", "r"))) { | |
| if (errno != ENOENT) | |
| pa_log_warn("failed to open OSS sndstat device: %s", pa_cstrerror(errno)); | |
| return -1; | |
| } | |
| while (!feof(f)) { | |
| char line[1024] = { 0 }; | |
| unsigned device; | |
| if (!fgets(line, sizeof(line), f)) | |
| break; | |
| line[strcspn(line, "\r\n")] = 0; | |
| if (!b) { | |
| b = pa_streq(line, "Audio devices:") || pa_streq(line, "Installed devices:"); | |
| continue; | |
| } | |
| if (line[0] == 0) | |
| break; | |
| if (sscanf(line, "%u: ", &device) != 1 && sscanf(line, "pcm%u: ", &device) != 1) | |
| continue; | |
| if (device == n) { | |
| char *k = strchr(line, ':'); | |
| pa_assert(k); | |
| k++; | |
| k += strspn(k, " <"); | |
| if (pa_endswith(k, " (DUPLEX)")) | |
| k[strlen(k)-9] = 0; | |
| k[strcspn(k, ">")] = 0; | |
| // Include the number to disambiguate devices with the same name | |
| pa_snprintf(name, l, "%u - %s", device, k); | |
| r = 0; | |
| break; | |
| } | |
| } | |
| fclose(f); | |
| return r; | |
| } | |
| static int open_mixer(const char *mixer) { | |
| int fd; | |
| if ((fd = pa_open_cloexec(mixer, O_RDWR|O_NDELAY, 0)) >= 0) | |
| return fd; | |
| return -1; | |
| } | |
| int pa_oss_open_mixer_for_device(const char *device) { | |
| int n; | |
| char *fn; | |
| int fd; | |
| if ((n = get_device_number(device)) == -2) | |
| return -1; | |
| if (n == -1) | |
| if ((fd = open_mixer("/dev/mixer")) >= 0) | |
| return fd; | |
| fn = pa_sprintf_malloc("/dev/mixer%i", n); | |
| fd = open_mixer(fn); | |
| pa_xfree(fn); | |
| if (fd < 0) | |
| pa_log_warn("Failed to open mixer '%s': %s", device, pa_cstrerror(errno)); | |
| return fd; | |
| } |