chiark / gitweb /
Synchronize from trunk
[disorder] / lib / uaudio-oss.c
CommitLineData
7a2c7068
RK
1/*
2 * This file is part of DisOrder.
3 * Copyright (C) 2009 Richard Kettlewell
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18/** @file lib/uaudio-oss.c
19 * @brief Support for OSS backend */
20#include "common.h"
21
22#if HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
23
24#if HAVE_SYS_SOUNDCARD_H
25# include <sys/soundcard.h>
26#endif
27#include <sys/ioctl.h>
7a2c7068
RK
28#include <fcntl.h>
29#include <unistd.h>
30#include <errno.h>
b1f6ca8c 31#include <time.h>
7a2c7068
RK
32
33#include "mem.h"
34#include "log.h"
b60e20be 35#include "uaudio.h"
ba70caca 36#include "configuration.h"
7a2c7068 37
4fd38868
RK
38#ifndef AFMT_U16_NE
39# if BYTE_ORDER == BIG_ENDIAN
40# define AFMT_U16_NE AFMT_U16_BE
41# else
42# define AFMT_U16_NE AFMT_U16_LE
43# endif
44#endif
b60e20be 45
b50cfb8a
RK
46/* documentation does not match implementation! */
47#ifndef SOUND_MIXER_READ
48# define SOUND_MIXER_READ(x) MIXER_READ(x)
49#endif
50#ifndef SOUND_MIXER_WRITE
51# define SOUND_MIXER_WRITE(x) MIXER_WRITE(x)
52#endif
53
4fd38868 54static int oss_fd = -1;
b50cfb8a
RK
55static int oss_mixer_fd = -1;
56static int oss_mixer_channel;
7a2c7068
RK
57
58static const char *const oss_options[] = {
59 "device",
b50cfb8a
RK
60 "mixer-device",
61 "mixer-channel",
7a2c7068
RK
62 NULL
63};
64
4fd38868
RK
65/** @brief Open the OSS sound device */
66static void oss_open(void) {
b50cfb8a 67 const char *device = uaudio_get("device", NULL);
7a2c7068
RK
68
69#if EMPEG_HOST
4fd38868 70 if(!device || !*device || !strcmp(device, "default"))
9c6d5d95 71 device = "/dev/audio";
7a2c7068 72#else
4fd38868
RK
73 if(!device || !*device || !strcmp(device, "default")) {
74 if(access("/dev/dsp", W_OK) == 0)
75 device = "/dev/dsp";
7a2c7068 76 else
4fd38868 77 device = "/dev/audio";
7a2c7068 78 }
4fd38868
RK
79#endif
80 if((oss_fd = open(device, O_WRONLY, 0)) < 0)
2e9ba080 81 disorder_fatal(errno, "error opening %s", device);
4fd38868
RK
82#if !EMPEG_HOST
83 int stereo = (uaudio_channels == 2), format;
84 if(ioctl(oss_fd, SNDCTL_DSP_STEREO, &stereo) < 0)
2e9ba080 85 disorder_fatal(errno, "error calling ioctl SNDCTL_DSP_STEREO %d", stereo);
4fd38868
RK
86 if(uaudio_bits == 16)
87 format = uaudio_signed ? AFMT_S16_NE : AFMT_U16_NE;
88 else
89 format = uaudio_signed ? AFMT_S8 : AFMT_U8;
90 if(ioctl(oss_fd, SNDCTL_DSP_SETFMT, &format) < 0)
2e9ba080 91 disorder_fatal(errno, "error calling ioctl SNDCTL_DSP_SETFMT %#x", format);
4fd38868
RK
92 int rate = uaudio_rate;
93 if(ioctl(oss_fd, SNDCTL_DSP_SPEED, &rate) < 0)
2e9ba080 94 disorder_fatal(errno, "error calling ioctl SNDCTL_DSP_SPEED %d", rate);
4fd38868 95 if(rate != uaudio_rate)
c04511f6 96 disorder_error(0, "asked for %dHz, got %dHz", uaudio_rate, rate);
4fd38868
RK
97#endif
98}
99
b1f6ca8c
RK
100/** @brief Close the OSS sound device */
101static void oss_close(void) {
102 if(oss_fd != -1) {
103 close(oss_fd);
104 oss_fd = -1;
105 }
7a2c7068
RK
106}
107
b1f6ca8c
RK
108/** @brief Actually play sound via OSS */
109static size_t oss_play(void *buffer, size_t samples, unsigned flags) {
110 /* cf uaudio-alsa.c:alsa-play() */
111 if(flags & UAUDIO_PAUSED) {
112 if(flags & UAUDIO_PAUSE)
113 oss_close();
114 if(samples > 64)
115 samples /= 2;
116 const uint64_t ns = ((uint64_t)samples * 1000000000
117 / (uaudio_rate * uaudio_channels));
118 struct timespec ts[1];
119 ts->tv_sec = ns / 1000000000;
120 ts->tv_nsec = ns % 1000000000;
121 while(nanosleep(ts, ts) < 0 && errno == EINTR)
122 ;
123 return samples;
124 }
125 if(flags & UAUDIO_RESUME)
126 oss_open();
127 const size_t bytes = samples * uaudio_sample_size;
128 int rc = write(oss_fd, buffer, bytes);
129 if(rc < 0)
2e9ba080 130 disorder_fatal(errno, "error writing to sound device");
b1f6ca8c 131 return rc / uaudio_sample_size;
7a2c7068 132}
b1f6ca8c 133
b60e20be
RK
134static void oss_start(uaudio_callback *callback,
135 void *userdata) {
4fd38868 136 if(uaudio_channels != 1 && uaudio_channels != 2)
2e9ba080 137 disorder_fatal(0, "asked for %d channels but only support 1 or 2",
4fd38868
RK
138 uaudio_channels);
139 if(uaudio_bits != 8 && uaudio_bits != 16)
2e9ba080 140 disorder_fatal(0, "asked for %d bits/channel but only support 8 or 16",
4fd38868
RK
141 uaudio_bits);
142#if EMPEG_HOST
143 /* Very specific buffer size requirements here apparently */
144 uaudio_thread_start(callback, userdata, oss_play,
145 4608 / uaudio_sample_size,
63761c19
RK
146 4608 / uaudio_sample_size,
147 0);
4fd38868
RK
148#else
149 /* We could SNDCTL_DSP_GETBLKSIZE but only when the device is already open,
150 * which is kind of inconvenient. We go with 1-4Kbyte for now. */
151 uaudio_thread_start(callback, userdata, oss_play,
152 32 / uaudio_sample_size,
63761c19
RK
153 4096 / uaudio_sample_size,
154 0);
4fd38868 155#endif
b60e20be
RK
156}
157
158static void oss_stop(void) {
4fd38868 159 uaudio_thread_stop();
b1f6ca8c 160 oss_close(); /* might not have been paused */
b60e20be 161}
7a2c7068 162
b50cfb8a
RK
163/** @brief Channel names */
164static const char *oss_channels[] = SOUND_DEVICE_NAMES;
165
166static int oss_mixer_find_channel(const char *channel) {
42f738c2 167 if(!channel[strspn(channel, "0123456789")])
b50cfb8a
RK
168 return atoi(channel);
169 else {
42f738c2
RK
170 for(unsigned n = 0; n < sizeof oss_channels / sizeof *oss_channels; ++n)
171 if(!strcmp(oss_channels[n], channel))
b50cfb8a
RK
172 return n;
173 return -1;
174 }
175}
176
177static void oss_open_mixer(void) {
178 const char *mixer = uaudio_get("mixer-device", "/dev/mixer");
179 /* TODO infer mixer-device from device */
180 if((oss_mixer_fd = open(mixer, O_RDWR, 0)) < 0)
2e9ba080 181 disorder_fatal(errno, "error opening %s", mixer);
b50cfb8a
RK
182 const char *channel = uaudio_get("mixer-channel", "pcm");
183 oss_mixer_channel = oss_mixer_find_channel(channel);
184 if(oss_mixer_channel < 0)
2e9ba080 185 disorder_fatal(0, "no such channel as '%s'", channel);
b50cfb8a
RK
186}
187
188static void oss_close_mixer(void) {
189 close(oss_mixer_fd);
190 oss_mixer_fd = -1;
191}
192
193static void oss_get_volume(int *left, int *right) {
194 int r;
195
196 *left = *right = 0;
42f738c2 197 if(ioctl(oss_mixer_fd, SOUND_MIXER_READ(oss_mixer_channel), &r) < 0)
2e9ba080 198 disorder_error(errno, "error getting volume");
b50cfb8a
RK
199 else {
200 *left = r & 0xff;
201 *right = (r >> 8) & 0xff;
202 }
203}
204
205static void oss_set_volume(int *left, int *right) {
206 int r = (*left & 0xff) + (*right & 0xff) * 256;
42f738c2 207 if(ioctl(oss_mixer_fd, SOUND_MIXER_WRITE(oss_mixer_channel), &r) == -1)
2e9ba080 208 disorder_error(errno, "error setting volume");
42f738c2 209 else if(ioctl(oss_mixer_fd, SOUND_MIXER_READ(oss_mixer_channel), &r) < 0)
2e9ba080 210 disorder_error(errno, "error getting volume");
b50cfb8a
RK
211 else {
212 *left = r & 0xff;
213 *right = (r >> 8) & 0xff;
214 }
215}
216
ba70caca
RK
217static void oss_configure(void) {
218 uaudio_set("device", config->device);
219 uaudio_set("mixer-device", config->mixer);
220 uaudio_set("mixer-channel", config->channel);
221}
222
7a2c7068
RK
223const struct uaudio uaudio_oss = {
224 .name = "oss",
225 .options = oss_options,
226 .start = oss_start,
227 .stop = oss_stop,
b1f6ca8c
RK
228 .activate = uaudio_thread_activate,
229 .deactivate = uaudio_thread_deactivate,
b50cfb8a
RK
230 .open_mixer = oss_open_mixer,
231 .close_mixer = oss_close_mixer,
232 .get_volume = oss_get_volume,
233 .set_volume = oss_set_volume,
ba70caca 234 .configure = oss_configure,
7a2c7068
RK
235};
236
237#endif
238
239/*
240Local Variables:
241c-basic-offset:2
242comment-column:40
243fill-column:79
244indent-tabs-mode:nil
245End:
246*/