1 /* Sysdep Open Sound System sound dsp driver
2 
3    Copyright 2000 Hans de Goede
4 
5    This file and the acompanying files in this directory are free software;
6    you can redistribute them and/or modify them under the terms of the GNU
7    Library General Public License as published by the Free Software Foundation;
8    either version 2 of the License, or (at your option) any later version.
9 
10    These files are distributed in the hope that they will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public
16    License along with these files; see the file COPYING.LIB.  If not,
17    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18    Boston, MA 02111-1307, USA.
19 */
20 /* Changelog
21 Version 0.1, January 2000
22 -initial release, based on the xmame driver done by Mike Oliphant
23  (oliphant@ling.ed.ac.uk), amongst others (Hans de Goede)
24 */
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/ioctl.h>
32 #if defined (__ARCH_openbsd)
33 #include <soundcard.h>
34 #else
35 #include <sys/soundcard.h>
36 #endif
37 #include "sysdep/sysdep_dsp.h"
38 #include "sysdep/sysdep_dsp_priv.h"
39 #include "sysdep/plugin_manager.h"
40 
41 #ifdef __ARCH_openbsd
42 #define AUDIO_DEVICE   "/dev/audio"
43 #else
44 #define AUDIO_DEVICE   "/dev/dsp"
45 #endif
46 
47 #if 1		/* QUASI88 */
48 extern int verbose_proc;
49 #define fprintf     if (verbose_proc) fprintf
50 #endif		/* QUASI88 */
51 
52 /* our per instance private data struct */
53 struct oss_dsp_priv_data {
54    int fd;
55 };
56 
57 /* public methods prototypes (static but exported through the sysdep_dsp or
58    plugin struct) */
59 static void *oss_dsp_create(const void *flags);
60 static void oss_dsp_destroy(struct sysdep_dsp_struct *dsp);
61 static int oss_dsp_get_freespace(struct sysdep_dsp_struct *dsp);
62 static int oss_dsp_write(struct sysdep_dsp_struct *dsp, unsigned char *data,
63    int count);
64 
65 /* public variables */
66 const struct plugin_struct sysdep_dsp_oss = {
67    "oss",
68    "sysdep_dsp",
69    "Open Sound System DSP plugin",
70    NULL, /* no options */
71    NULL, /* no init */
72    NULL, /* no exit */
73    oss_dsp_create,
74    3     /* high priority */
75 };
76 
77 /* private variables */
78 static int oss_dsp_bytes_per_sample[4] = SYSDEP_DSP_BYTES_PER_SAMPLE;
79 
80 /* public methods (static but exported through the sysdep_dsp or plugin
81    struct) */
oss_dsp_create(const void * flags)82 static void *oss_dsp_create(const void *flags)
83 {
84    int i, j;
85    audio_buf_info info;
86    struct oss_dsp_priv_data *priv = NULL;
87    struct sysdep_dsp_struct *dsp = NULL;
88    const struct sysdep_dsp_create_params *params = flags;
89    const char *device = params->device;
90 
91    /* allocate the dsp struct */
92    if (!(dsp = calloc(1, sizeof(struct sysdep_dsp_struct))))
93    {
94       fprintf(stderr,
95          "error malloc failed for struct sysdep_dsp_struct\n");
96       return NULL;
97    }
98 
99    /* alloc private data */
100    if (!(priv = calloc(1, sizeof(struct oss_dsp_priv_data))))
101    {
102       fprintf(stderr,
103          "error malloc failed for struct oss_dsp_priv_data\n");
104       oss_dsp_destroy(dsp);
105       return NULL;
106    }
107 
108    /* fill in the functions and some data */
109    priv->fd = -1;
110    dsp->_priv = priv;
111    dsp->get_freespace = oss_dsp_get_freespace;
112    dsp->write = oss_dsp_write;
113    dsp->destroy = oss_dsp_destroy;
114    dsp->hw_info.type = params->type;
115    dsp->hw_info.samplerate = params->samplerate;
116 
117    /* open the sound device */
118    if (!device)
119       device = AUDIO_DEVICE;
120 
121    /* always open in non-blocking mode, otherwise the open itself may
122       block, hanging the entire application */
123    if ((priv->fd = open(device, O_WRONLY|O_NONBLOCK, 0)) < 0) {
124       perror("error: " AUDIO_DEVICE);
125       oss_dsp_destroy(dsp);
126       return NULL;
127    }
128 
129    /* set back to blocking mode, unless non-blocking mode was selected */
130    if (!(params->flags & SYSDEP_DSP_O_NONBLOCK))
131    {
132       if (fcntl(priv->fd, F_SETFL, O_WRONLY) < 0)
133       {
134          perror("OSS-driver, error: fnctl");
135          oss_dsp_destroy(dsp);
136          return NULL;
137       }
138    }
139 
140    /* set the number of bits */
141 #ifdef LSB_FIRST
142    i = j = (dsp->hw_info.type & SYSDEP_DSP_16BIT) ? AFMT_S16_LE : AFMT_U8;
143 #else
144    i = j = (dsp->hw_info.type & SYSDEP_DSP_16BIT) ? AFMT_S16_BE : AFMT_U8;
145 #endif
146 
147    if (ioctl(priv->fd, SNDCTL_DSP_SETFMT, &i) < 0)
148    {
149       perror("error: SNDCTL_DSP_SETFMT");
150       oss_dsp_destroy(dsp);
151       return NULL;
152    }
153 
154    if (i != j)
155    {
156       if (dsp->hw_info.type & SYSDEP_DSP_16BIT)
157       {
158          fprintf(stderr, "warning: couldn't set sound to 16 bits,\n"
159             "   trying again with 8 bits: ");
160       }
161       else
162       {
163          fprintf(stderr, "error: couldn't set sound to 8 bits,\n");
164          oss_dsp_destroy(dsp);
165          return NULL;
166       }
167 
168       dsp->hw_info.type &= ~SYSDEP_DSP_16BIT;
169       i = AFMT_U8;
170       if (ioctl(priv->fd, SNDCTL_DSP_SETFMT, &i) < 0)
171       {
172          perror("error: SNDCTL_DSP_SETFMT");
173          oss_dsp_destroy(dsp);
174          return NULL;
175       }
176 
177       if (i != AFMT_U8)
178       {
179          fprintf(stderr, "failed\n");
180          oss_dsp_destroy(dsp);
181          return NULL;
182       }
183       fprintf(stderr, "success\n");
184    }
185 
186    /* set the number of channels */
187    i = (dsp->hw_info.type & SYSDEP_DSP_STEREO) ? 1 : 0;
188    if (ioctl(priv->fd, SNDCTL_DSP_STEREO, &i) < 0)
189    {
190       perror("error: SNDCTL_DSP_STEREO");
191       oss_dsp_destroy(dsp);
192       return NULL;
193    }
194 
195    if (i)
196       dsp->hw_info.type |= SYSDEP_DSP_STEREO;
197    else
198       dsp->hw_info.type &= ~SYSDEP_DSP_STEREO;
199 
200    /* set the samplerate */
201    if (ioctl(priv->fd, SNDCTL_DSP_SPEED, &dsp->hw_info.samplerate) < 0)
202    {
203       perror("error: SNDCTL_DSP_SPEED");
204       oss_dsp_destroy(dsp);
205       return NULL;
206    }
207 
208    /* calculate and set the fragsize & number of frags */
209    /* fragsize (as power of 2) */
210    i = 7;
211    if (dsp->hw_info.type & SYSDEP_DSP_16BIT) i++;
212    if (dsp->hw_info.type & SYSDEP_DSP_STEREO) i++;
213    i += dsp->hw_info.samplerate / 22000;
214 
215    /* number of frags */
216    j = ((dsp->hw_info.samplerate * oss_dsp_bytes_per_sample[dsp->hw_info.type] *
217       params->bufsize) / (0x01 << i)) + 1;
218 
219    /* set the fraginfo */
220    i = j = i | (j << 16);
221    fprintf(stderr, "info: setting fragsize to %d, numfrags to %d\n",
222    	1 << (i & 0x0000FFFF), i >> 16);
223    if (ioctl(priv->fd, SNDCTL_DSP_SETFRAGMENT, &i) < 0)
224    {
225       perror("error: SNDCTL_DSP_SETFRAGMENT");
226       oss_dsp_destroy(dsp);
227       return NULL;
228    }
229 
230    if (ioctl(priv->fd, SNDCTL_DSP_GETOSPACE, &info) < 0)
231    {
232       perror("warning: SNDCTL_DSP_GETOSPACE");
233       fprintf(stderr, "   falling back to timer based-audio\n");
234       dsp->get_freespace = NULL;
235    }
236    else
237    {
238       fprintf(stderr, "info: fragsize = %d, numfrags = %d\n", info.fragsize,
239          info.fragstotal);
240       /* i = requested fragsize, j = requested numfrags */
241       i = 1 << (j & 0x0000FFFF);
242       j = j >> 16;
243       if ((info.fragsize < (i / 2)) || (info.fragsize > (i * 2)) ||
244          (info.fragstotal < (j - 2)) || (info.fragstotal > (j + 2)))
245       {
246          fprintf(stderr, "warning: obtained fragsize/numfrags differs too much from requested\n"
247             "   you may wish to adjust the bufsize setting in your xmamerc file, or try\n"
248 	    "   timer-based audio by adding -timer to your command line\n");
249       }
250       else if (info.bytes > (info.fragsize * info.fragstotal))
251       {
252          fprintf(stderr, "warning: freespace > (fragsize * numfrags) assuming buggy FreeBSD PCM driver,\n"
253             "   falling back to timer based-audio\n");
254          dsp->get_freespace = NULL;
255       }
256       else
257          /* info.fragstotal + 1 to work around yet more OSS bugs ;( */
258          dsp->hw_info.bufsize = (info.fragsize * (info.fragstotal + 1)) /
259             oss_dsp_bytes_per_sample[dsp->hw_info.type];
260    }
261 
262    fprintf(stderr, "info: audiodevice %s set to %dbit linear %s %dHz\n",
263       device, (dsp->hw_info.type & SYSDEP_DSP_16BIT)? 16:8,
264       (dsp->hw_info.type & SYSDEP_DSP_STEREO)? "stereo":"mono",
265       dsp->hw_info.samplerate);
266 
267    return dsp;
268 }
269 
oss_dsp_destroy(struct sysdep_dsp_struct * dsp)270 static void oss_dsp_destroy(struct sysdep_dsp_struct *dsp)
271 {
272    struct oss_dsp_priv_data *priv = dsp->_priv;
273 
274    if (priv)
275    {
276       if (priv->fd >= 0)
277          close(priv->fd);
278 
279       free(priv);
280    }
281    free(dsp);
282 }
283 
oss_dsp_get_freespace(struct sysdep_dsp_struct * dsp)284 static int oss_dsp_get_freespace(struct sysdep_dsp_struct *dsp)
285 {
286    audio_buf_info info;
287    struct oss_dsp_priv_data *priv = dsp->_priv;
288 
289    if (ioctl(priv->fd, SNDCTL_DSP_GETOSPACE, &info) < 0)
290    {
291       perror("error: SNDCTL_DSP_GETOSPACE");
292       return -1;
293    }
294    return info.bytes / oss_dsp_bytes_per_sample[dsp->hw_info.type];
295 }
296 
oss_dsp_write(struct sysdep_dsp_struct * dsp,unsigned char * data,int count)297 static int oss_dsp_write(struct sysdep_dsp_struct *dsp, unsigned char *data,
298    int count)
299 {
300    int result;
301    struct oss_dsp_priv_data *priv = dsp->_priv;
302 
303    result = write(priv->fd, data, count *
304       oss_dsp_bytes_per_sample[dsp->hw_info.type]);
305 
306    if (result < 0)
307       return -1;
308 
309    return result / oss_dsp_bytes_per_sample[dsp->hw_info.type];
310 }
311