chiark / gitweb /
Merge branch 'master' of git.distorted.org.uk:~mdw/publish/public-git/disorder
[disorder] / lib / uaudio-oss.c
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>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <errno.h>
31 #include <time.h>
32
33 #include "mem.h"
34 #include "log.h"
35 #include "uaudio.h"
36 #include "configuration.h"
37
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
45
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
54 static int oss_fd = -1;
55 static int oss_mixer_fd = -1;
56 static int oss_mixer_channel;
57
58 static const char *const oss_options[] = {
59   "device",
60   "mixer-device",
61   "mixer-channel",
62   NULL
63 };
64
65 /** @brief Open the OSS sound device */
66 static void oss_open(void) {
67   const char *device = uaudio_get("device", NULL);
68
69 #if EMPEG_HOST
70   if(!device || !*device || !strcmp(device, "default"))
71     device = "/dev/audio";
72 #else
73   if(!device || !*device || !strcmp(device, "default")) {
74     if(access("/dev/dsp", W_OK) == 0)
75       device = "/dev/dsp";
76     else
77       device = "/dev/audio";
78   }
79 #endif
80   if((oss_fd = open(device, O_WRONLY, 0)) < 0)
81     disorder_fatal(errno, "error opening %s", device);
82 #if !EMPEG_HOST
83   int stereo = (uaudio_channels == 2), format;
84   if(ioctl(oss_fd, SNDCTL_DSP_STEREO, &stereo) < 0)
85     disorder_fatal(errno, "error calling ioctl SNDCTL_DSP_STEREO %d", stereo);
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)
91     disorder_fatal(errno, "error calling ioctl SNDCTL_DSP_SETFMT %#x", format);
92   int rate = uaudio_rate;
93   if(ioctl(oss_fd, SNDCTL_DSP_SPEED, &rate) < 0)
94     disorder_fatal(errno, "error calling ioctl SNDCTL_DSP_SPEED %d", rate);
95   if(rate != uaudio_rate)
96     disorder_error(0, "asked for %dHz, got %dHz", uaudio_rate, rate);
97 #endif
98 }
99
100 /** @brief Close the OSS sound device */
101 static void oss_close(void) {
102   if(oss_fd != -1) {
103     close(oss_fd);
104     oss_fd = -1;
105   }
106 }
107
108 /** @brief Actually play sound via OSS */
109 static 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)
130     disorder_fatal(errno, "error writing to sound device");
131   return rc / uaudio_sample_size;
132 }
133
134 static void oss_start(uaudio_callback *callback,
135                       void *userdata) {
136   if(uaudio_channels != 1 && uaudio_channels != 2)
137     disorder_fatal(0, "asked for %d channels but only support 1 or 2",
138           uaudio_channels); 
139   if(uaudio_bits != 8 && uaudio_bits != 16)
140     disorder_fatal(0, "asked for %d bits/channel but only support 8 or 16",
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,
146                       4608 / uaudio_sample_size,
147                       0);
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,
153                       4096 / uaudio_sample_size,
154                       0);
155 #endif
156 }
157
158 static void oss_stop(void) {
159   uaudio_thread_stop();
160   oss_close();                          /* might not have been paused */
161 }
162
163 /** @brief Channel names */
164 static const char *oss_channels[] = SOUND_DEVICE_NAMES;
165
166 static int oss_mixer_find_channel(const char *channel) {
167   if(!channel[strspn(channel, "0123456789")])
168     return atoi(channel);
169   else {
170     for(unsigned n = 0; n < sizeof oss_channels / sizeof *oss_channels; ++n)
171       if(!strcmp(oss_channels[n], channel))
172         return n;
173     return -1;
174   }
175 }  
176
177 static 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)
181     disorder_fatal(errno, "error opening %s", mixer);
182   const char *channel = uaudio_get("mixer-channel", "pcm");
183   oss_mixer_channel = oss_mixer_find_channel(channel);
184   if(oss_mixer_channel < 0)
185     disorder_fatal(0, "no such channel as '%s'", channel);
186 }
187
188 static void oss_close_mixer(void) {
189   close(oss_mixer_fd);
190   oss_mixer_fd = -1;
191 }
192
193 static void oss_get_volume(int *left, int *right) {
194   int r;
195
196   *left = *right = 0;
197   if(ioctl(oss_mixer_fd, SOUND_MIXER_READ(oss_mixer_channel), &r) < 0)
198     disorder_error(errno, "error getting volume");
199   else {
200     *left = r & 0xff;
201     *right = (r >> 8) & 0xff;
202   }
203 }
204
205 static void oss_set_volume(int *left, int *right) {
206   int r =  (*left & 0xff) + (*right & 0xff) * 256;
207   if(ioctl(oss_mixer_fd, SOUND_MIXER_WRITE(oss_mixer_channel), &r) == -1)
208     disorder_error(errno, "error setting volume");
209   else if(ioctl(oss_mixer_fd, SOUND_MIXER_READ(oss_mixer_channel), &r) < 0)
210     disorder_error(errno, "error getting volume");
211   else {
212     *left = r & 0xff;
213     *right = (r >> 8) & 0xff;
214   }
215 }
216
217 static 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
223 const struct uaudio uaudio_oss = {
224   .name = "oss",
225   .options = oss_options,
226   .start = oss_start,
227   .stop = oss_stop,
228   .activate = uaudio_thread_activate,
229   .deactivate = uaudio_thread_deactivate,
230   .open_mixer = oss_open_mixer,
231   .close_mixer = oss_close_mixer,
232   .get_volume = oss_get_volume,
233   .set_volume = oss_set_volume,
234   .configure = oss_configure,
235   .flags = UAUDIO_API_CLIENT | UAUDIO_API_SERVER,
236 };
237
238 #endif
239
240 /*
241 Local Variables:
242 c-basic-offset:2
243 comment-column:40
244 fill-column:79
245 indent-tabs-mode:nil
246 End:
247 */