1/***
2  This file is part of PulseAudio.
3
4  Copyright 2004-2006 Lennart Poettering
5  Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
6
7  PulseAudio is free software; you can redistribute it and/or modify
8  it under the terms of the GNU Lesser General Public License as published
9  by the Free Software Foundation; either version 2.1 of the License,
10  or (at your option) any later version.
11
12  PulseAudio is distributed in the hope that it will be useful, but
13  WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  General Public License for more details.
16
17  You should have received a copy of the GNU Lesser General Public License
18  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19***/
20
21#ifdef HAVE_CONFIG_H
22#include <config.h>
23#endif
24
25#include <sys/soundcard.h>
26#include <sys/ioctl.h>
27#include <stdio.h>
28#include <errno.h>
29#include <string.h>
30#include <unistd.h>
31#include <sys/types.h>
32#include <fcntl.h>
33
34#include <pulse/xmalloc.h>
35#include <pulsecore/core-error.h>
36#include <pulsecore/core-util.h>
37#include <pulsecore/log.h>
38#include <pulsecore/macro.h>
39
40#include "oss-util.h"
41
42int pa_oss_open(const char *device, int *mode, int* pcaps) {
43    static const int nonblock_io = 1;
44    int fd = -1;
45    int caps;
46    char *t;
47
48    pa_assert(device);
49    pa_assert(mode);
50    pa_assert(*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY);
51
52    if (!pcaps)
53        pcaps = &caps;
54
55    if (*mode == O_RDWR) {
56        if ((fd = pa_open_cloexec(device, O_RDWR|O_NDELAY, 0)) >= 0) {
57            ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0);
58
59            if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) {
60                pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno));
61                goto fail;
62            }
63
64            if (*pcaps & DSP_CAP_DUPLEX)
65                goto success;
66
67            pa_log_warn("'%s' doesn't support full duplex", device);
68
69            pa_close(fd);
70        }
71
72        if ((fd = pa_open_cloexec(device, (*mode = O_WRONLY)|O_NDELAY, 0)) < 0) {
73            if ((fd = pa_open_cloexec(device, (*mode = O_RDONLY)|O_NDELAY, 0)) < 0) {
74                pa_log("open('%s'): %s", device, pa_cstrerror(errno));
75                goto fail;
76            }
77        }
78    } else {
79        if ((fd = pa_open_cloexec(device, *mode|O_NDELAY, 0)) < 0) {
80            pa_log("open('%s'): %s", device, pa_cstrerror(errno));
81            goto fail;
82        }
83    }
84
85    *pcaps = 0;
86
87    if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) {
88        pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno));
89        goto fail;
90    }
91
92success:
93    if (ioctl(fd, FIONBIO, &nonblock_io) < 0) {
94        pa_log("FIONBIO: %s", pa_cstrerror(errno));
95        goto fail;
96    }
97
98    t = pa_sprintf_malloc(
99            "%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
100                 *pcaps & DSP_CAP_BATCH ? " BATCH" : "",
101#ifdef DSP_CAP_BIND
102                 *pcaps & DSP_CAP_BIND ? " BIND" : "",
103#else
104                 "",
105#endif
106                 *pcaps & DSP_CAP_COPROC ? " COPROC" : "",
107                 *pcaps & DSP_CAP_DUPLEX ? " DUPLEX" : "",
108#ifdef DSP_CAP_FREERATE
109                 *pcaps & DSP_CAP_FREERATE ? " FREERATE" : "",
110#else
111                 "",
112#endif
113#ifdef DSP_CAP_INPUT
114                 *pcaps & DSP_CAP_INPUT ? " INPUT" : "",
115#else
116                 "",
117#endif
118                 *pcaps & DSP_CAP_MMAP ? " MMAP" : "",
119#ifdef DSP_CAP_MODEM
120                 *pcaps & DSP_CAP_MODEM ? " MODEM" : "",
121#else
122                 "",
123#endif
124#ifdef DSP_CAP_MULTI
125                 *pcaps & DSP_CAP_MULTI ? " MULTI" : "",
126#else
127                 "",
128#endif
129#ifdef DSP_CAP_OUTPUT
130                 *pcaps & DSP_CAP_OUTPUT ? " OUTPUT" : "",
131#else
132                 "",
133#endif
134                 *pcaps & DSP_CAP_REALTIME ? " REALTIME" : "",
135#ifdef DSP_CAP_SHADOW
136                 *pcaps & DSP_CAP_SHADOW ? " SHADOW" : "",
137#else
138                 "",
139#endif
140#ifdef DSP_CAP_VIRTUAL
141                 *pcaps & DSP_CAP_VIRTUAL ? " VIRTUAL" : "",
142#else
143                 "",
144#endif
145                 *pcaps & DSP_CAP_TRIGGER ? " TRIGGER" : "");
146
147    pa_log_debug("capabilities:%s", t);
148    pa_xfree(t);
149
150    return fd;
151
152fail:
153    if (fd >= 0)
154        pa_close(fd);
155    return -1;
156}
157
158int pa_oss_auto_format(int fd, pa_sample_spec *ss) {
159    int format, channels, speed, reqformat;
160    pa_sample_format_t orig_format;
161
162    static const int format_trans[PA_SAMPLE_MAX] = {
163        [PA_SAMPLE_U8] = AFMT_U8,
164        [PA_SAMPLE_ALAW] = AFMT_A_LAW,
165        [PA_SAMPLE_ULAW] = AFMT_MU_LAW,
166        [PA_SAMPLE_S16LE] = AFMT_S16_LE,
167        [PA_SAMPLE_S16BE] = AFMT_S16_BE,
168        [PA_SAMPLE_FLOAT32LE] = AFMT_QUERY, /* not supported */
169        [PA_SAMPLE_FLOAT32BE] = AFMT_QUERY, /* not supported */
170        [PA_SAMPLE_S32LE] = AFMT_QUERY, /* not supported */
171        [PA_SAMPLE_S32BE] = AFMT_QUERY, /* not supported */
172#if defined(AFMT_S24_LE) && defined(AFMT_S24_BE)
173        [PA_SAMPLE_S24LE] = AFMT_S24_LE,
174        [PA_SAMPLE_S24BE] = AFMT_S24_BE,
175#else
176        [PA_SAMPLE_S24LE] = AFMT_QUERY, /* not supported */
177        [PA_SAMPLE_S24BE] = AFMT_QUERY, /* not supported */
178#endif
179        [PA_SAMPLE_S24_32LE] = AFMT_QUERY, /* not supported */
180        [PA_SAMPLE_S24_32BE] = AFMT_QUERY, /* not supported */
181    };
182
183    pa_assert(fd >= 0);
184    pa_assert(ss);
185
186    orig_format = ss->format;
187
188    reqformat = format = format_trans[ss->format];
189    if (reqformat == AFMT_QUERY || ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != reqformat) {
190        format = AFMT_S16_NE;
191        if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_S16_NE) {
192            int f = AFMT_S16_NE == AFMT_S16_LE ? AFMT_S16_BE : AFMT_S16_LE;
193            format = f;
194            if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != f) {
195                format = AFMT_U8;
196                if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_U8) {
197                    pa_log("SNDCTL_DSP_SETFMT: %s", format != AFMT_U8 ? "No supported sample format" : pa_cstrerror(errno));
198                    return -1;
199                } else
200                    ss->format = PA_SAMPLE_U8;
201            } else
202                ss->format = f == AFMT_S16_LE ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE;
203        } else
204            ss->format = PA_SAMPLE_S16NE;
205    }
206
207    if (orig_format != ss->format)
208        pa_log_warn("device doesn't support sample format %s, changed to %s.",
209               pa_sample_format_to_string(orig_format),
210               pa_sample_format_to_string(ss->format));
211
212    channels = ss->channels;
213    if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0) {
214        pa_log("SNDCTL_DSP_CHANNELS: %s", pa_cstrerror(errno));
215        return -1;
216    }
217    pa_assert(channels > 0);
218
219    if (ss->channels != channels) {
220        pa_log_warn("device doesn't support %i channels, using %i channels.", ss->channels, channels);
221        ss->channels = (uint8_t) channels;
222    }
223
224    speed = (int) ss->rate;
225    if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0) {
226        pa_log("SNDCTL_DSP_SPEED: %s", pa_cstrerror(errno));
227        return -1;
228    }
229    pa_assert(speed > 0);
230
231    if (ss->rate != (unsigned) speed) {
232        pa_log_warn("device doesn't support %i Hz, changed to %i Hz.", ss->rate, speed);
233
234        /* If the sample rate deviates too much, we need to resample */
235        if (speed < ss->rate*.95 || speed > ss->rate*1.05)
236            ss->rate = (uint32_t) speed;
237    }
238
239    return 0;
240}
241
242int pa_oss_set_fragments(int fd, int nfrags, int frag_size) {
243    int arg;
244
245    pa_assert(frag_size >= 0);
246
247    arg = ((int) nfrags << 16) | pa_ulog2(frag_size);
248
249    pa_log_debug("Asking for %i fragments of size %i (requested %i)", nfrags, 1 << pa_ulog2(frag_size), frag_size);
250
251    if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg) < 0) {
252        pa_log("SNDCTL_DSP_SETFRAGMENT: %s", pa_cstrerror(errno));
253        return -1;
254    }
255
256    return 0;
257}
258
259int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_cvolume *volume) {
260    char cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
261    unsigned vol;
262
263    pa_assert(fd >= 0);
264    pa_assert(ss);
265    pa_assert(volume);
266
267    if (ioctl(fd, mixer, &vol) < 0)
268        return -1;
269
270    pa_cvolume_reset(volume, ss->channels);
271
272    volume->values[0] = PA_CLAMP_VOLUME(((vol & 0xFF) * PA_VOLUME_NORM) / 100);
273
274    if (volume->channels >= 2)
275        volume->values[1] = PA_CLAMP_VOLUME((((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100);
276
277    pa_log_debug("Read mixer settings: %s", pa_cvolume_snprint_verbose(cv, sizeof(cv), volume, NULL, false));
278    return 0;
279}
280
281int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) {
282    char cv[PA_CVOLUME_SNPRINT_MAX];
283    unsigned vol;
284    pa_volume_t l, r;
285
286    l = volume->values[0] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[0];
287
288    vol = (l*100)/PA_VOLUME_NORM;
289
290    if (ss->channels >= 2) {
291        r = volume->values[1] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[1];
292        vol |= ((r*100)/PA_VOLUME_NORM) << 8;
293    }
294
295    if (ioctl(fd, mixer, &vol) < 0)
296        return -1;
297
298    pa_log_debug("Wrote mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume));
299    return 0;
300}
301
302static int get_device_number(const char *dev) {
303    const char *p;
304    const char *e;
305    char *rp = NULL;
306    int r = -1;
307
308    if (!(p = rp = pa_readlink(dev))) {
309        if (errno != EINVAL && errno != ENOLINK)
310            return -2;
311        p = dev;
312    }
313
314    /* find the last forward slash */
315    while ((e = strrchr(p, '/')))
316        p = e + 1;
317
318    /* collect unit number at end, if any */
319    while (*p) {
320        if (*p >= '0' && *p <= '9') {
321            if (r < 0)
322                r = 0;
323            else
324                r *= 10;
325            r += *p - '0';
326        } else {
327            r = -1;
328        }
329        p++;
330    }
331
332    pa_xfree(rp);
333    return r;
334}
335
336int pa_oss_get_hw_description(const char *dev, char *name, size_t l) {
337    FILE *f;
338    int n, r = -1;
339    int b = 0;
340
341    if ((n = get_device_number(dev)) == -2)
342        return -1;
343
344    if (!(f = pa_fopen_cloexec("/dev/sndstat", "r")) &&
345        !(f = pa_fopen_cloexec("/proc/sndstat", "r")) &&
346        !(f = pa_fopen_cloexec("/proc/asound/oss/sndstat", "r"))) {
347
348        if (errno != ENOENT)
349            pa_log_warn("failed to open OSS sndstat device: %s", pa_cstrerror(errno));
350
351        return -1;
352    }
353
354    while (!feof(f)) {
355        char line[1024] = { 0 };
356        unsigned device;
357
358        if (!fgets(line, sizeof(line), f))
359            break;
360
361        line[strcspn(line, "\r\n")] = 0;
362
363        if (!b) {
364            b = pa_streq(line, "Audio devices:") || pa_streq(line, "Installed devices:");
365            continue;
366        }
367
368        if (line[0] == 0)
369            break;
370
371        if (sscanf(line, "%u: ", &device) != 1 && sscanf(line, "pcm%u: ", &device) != 1)
372            continue;
373
374        if (device == n) {
375            char *k = strchr(line, ':');
376            pa_assert(k);
377            k++;
378            k += strspn(k, " <");
379
380            if (pa_endswith(k, " (DUPLEX)"))
381                k[strlen(k)-9] = 0;
382
383            k[strcspn(k, ">")] = 0;
384
385            // Include the number to disambiguate devices with the same name
386            pa_snprintf(name, l, "%u - %s", device, k);
387            r = 0;
388            break;
389        }
390    }
391
392    fclose(f);
393    return r;
394}
395
396static int open_mixer(const char *mixer) {
397    int fd;
398
399    if ((fd = pa_open_cloexec(mixer, O_RDWR|O_NDELAY, 0)) >= 0)
400        return fd;
401
402    return -1;
403}
404
405int pa_oss_open_mixer_for_device(const char *device) {
406    int n;
407    char *fn;
408    int fd;
409
410    if ((n = get_device_number(device)) == -2)
411        return -1;
412
413    if (n == -1)
414        if ((fd = open_mixer("/dev/mixer")) >= 0)
415            return fd;
416
417    fn = pa_sprintf_malloc("/dev/mixer%i", n);
418    fd = open_mixer(fn);
419    pa_xfree(fn);
420
421    if (fd < 0)
422        pa_log_warn("Failed to open mixer '%s': %s", device, pa_cstrerror(errno));
423
424    return fd;
425}
426