1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 
22 #include "../../SDL_internal.h"
23 
24 #if SDL_AUDIO_DRIVER_SNDIO
25 
26 /* OpenBSD sndio target */
27 
28 #if HAVE_STDIO_H
29 #include <stdio.h>
30 #endif
31 
32 #ifdef HAVE_SIGNAL_H
33 #include <signal.h>
34 #endif
35 
36 #include <poll.h>
37 #include <unistd.h>
38 
39 #include "SDL_audio.h"
40 #include "../SDL_audio_c.h"
41 #include "SDL_sndioaudio.h"
42 
43 #ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
44 #include "SDL_loadso.h"
45 #endif
46 
47 #ifndef INFTIM
48 #define INFTIM -1
49 #endif
50 
51 #ifndef SIO_DEVANY
52 #define SIO_DEVANY "default"
53 #endif
54 
55 static struct sio_hdl * (*SNDIO_sio_open)(const char *, unsigned int, int);
56 static void (*SNDIO_sio_close)(struct sio_hdl *);
57 static int (*SNDIO_sio_setpar)(struct sio_hdl *, struct sio_par *);
58 static int (*SNDIO_sio_getpar)(struct sio_hdl *, struct sio_par *);
59 static int (*SNDIO_sio_start)(struct sio_hdl *);
60 static int (*SNDIO_sio_stop)(struct sio_hdl *);
61 static size_t (*SNDIO_sio_read)(struct sio_hdl *, void *, size_t);
62 static size_t (*SNDIO_sio_write)(struct sio_hdl *, const void *, size_t);
63 static int (*SNDIO_sio_nfds)(struct sio_hdl *);
64 static int (*SNDIO_sio_pollfd)(struct sio_hdl *, struct pollfd *, int);
65 static int (*SNDIO_sio_revents)(struct sio_hdl *, struct pollfd *);
66 static int (*SNDIO_sio_eof)(struct sio_hdl *);
67 static void (*SNDIO_sio_initpar)(struct sio_par *);
68 
69 #ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
70 static const char *sndio_library = SDL_AUDIO_DRIVER_SNDIO_DYNAMIC;
71 static void *sndio_handle = NULL;
72 
73 static int
load_sndio_sym(const char * fn,void ** addr)74 load_sndio_sym(const char *fn, void **addr)
75 {
76     *addr = SDL_LoadFunction(sndio_handle, fn);
77     if (*addr == NULL) {
78         /* Don't call SDL_SetError(): SDL_LoadFunction already did. */
79         return 0;
80     }
81 
82     return 1;
83 }
84 
85 /* cast funcs to char* first, to please GCC's strict aliasing rules. */
86 #define SDL_SNDIO_SYM(x) \
87     if (!load_sndio_sym(#x, (void **) (char *) &SNDIO_##x)) return -1
88 #else
89 #define SDL_SNDIO_SYM(x) SNDIO_##x = x
90 #endif
91 
92 static int
load_sndio_syms(void)93 load_sndio_syms(void)
94 {
95     SDL_SNDIO_SYM(sio_open);
96     SDL_SNDIO_SYM(sio_close);
97     SDL_SNDIO_SYM(sio_setpar);
98     SDL_SNDIO_SYM(sio_getpar);
99     SDL_SNDIO_SYM(sio_start);
100     SDL_SNDIO_SYM(sio_stop);
101     SDL_SNDIO_SYM(sio_read);
102     SDL_SNDIO_SYM(sio_write);
103     SDL_SNDIO_SYM(sio_nfds);
104     SDL_SNDIO_SYM(sio_pollfd);
105     SDL_SNDIO_SYM(sio_revents);
106     SDL_SNDIO_SYM(sio_eof);
107     SDL_SNDIO_SYM(sio_initpar);
108     return 0;
109 }
110 
111 #undef SDL_SNDIO_SYM
112 
113 #ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
114 
115 static void
UnloadSNDIOLibrary(void)116 UnloadSNDIOLibrary(void)
117 {
118     if (sndio_handle != NULL) {
119         SDL_UnloadObject(sndio_handle);
120         sndio_handle = NULL;
121     }
122 }
123 
124 static int
LoadSNDIOLibrary(void)125 LoadSNDIOLibrary(void)
126 {
127     int retval = 0;
128     if (sndio_handle == NULL) {
129         sndio_handle = SDL_LoadObject(sndio_library);
130         if (sndio_handle == NULL) {
131             retval = -1;
132             /* Don't call SDL_SetError(): SDL_LoadObject already did. */
133         } else {
134             retval = load_sndio_syms();
135             if (retval < 0) {
136                 UnloadSNDIOLibrary();
137             }
138         }
139     }
140     return retval;
141 }
142 
143 #else
144 
145 static void
UnloadSNDIOLibrary(void)146 UnloadSNDIOLibrary(void)
147 {
148 }
149 
150 static int
LoadSNDIOLibrary(void)151 LoadSNDIOLibrary(void)
152 {
153     load_sndio_syms();
154     return 0;
155 }
156 
157 #endif /* SDL_AUDIO_DRIVER_SNDIO_DYNAMIC */
158 
159 
160 
161 
162 static void
SNDIO_WaitDevice(_THIS)163 SNDIO_WaitDevice(_THIS)
164 {
165     /* no-op; SNDIO_sio_write() blocks if necessary. */
166 }
167 
168 static void
SNDIO_PlayDevice(_THIS)169 SNDIO_PlayDevice(_THIS)
170 {
171     const int written = SNDIO_sio_write(this->hidden->dev,
172                                         this->hidden->mixbuf,
173                                         this->hidden->mixlen);
174 
175     /* If we couldn't write, assume fatal error for now */
176     if ( written == 0 ) {
177         SDL_OpenedAudioDeviceDisconnected(this);
178     }
179 #ifdef DEBUG_AUDIO
180     fprintf(stderr, "Wrote %d bytes of audio data\n", written);
181 #endif
182 }
183 
184 static int
SNDIO_CaptureFromDevice(_THIS,void * buffer,int buflen)185 SNDIO_CaptureFromDevice(_THIS, void *buffer, int buflen)
186 {
187     size_t r;
188     int revents;
189     int nfds;
190 
191     /* Emulate a blocking read */
192     r = SNDIO_sio_read(this->hidden->dev, buffer, buflen);
193     while (r == 0 && !SNDIO_sio_eof(this->hidden->dev)) {
194         if ((nfds = SNDIO_sio_pollfd(this->hidden->dev, this->hidden->pfd, POLLIN)) <= 0
195             || poll(this->hidden->pfd, nfds, INFTIM) < 0) {
196             return -1;
197         }
198         revents = SNDIO_sio_revents(this->hidden->dev, this->hidden->pfd);
199         if (revents & POLLIN) {
200             r = SNDIO_sio_read(this->hidden->dev, buffer, buflen);
201         }
202         if (revents & POLLHUP) {
203             break;
204         }
205     }
206     return (int) r;
207 }
208 
209 static void
SNDIO_FlushCapture(_THIS)210 SNDIO_FlushCapture(_THIS)
211 {
212     char buf[512];
213 
214     while (SNDIO_sio_read(this->hidden->dev, buf, sizeof(buf)) != 0) {
215         /* do nothing */;
216     }
217 }
218 
219 static Uint8 *
SNDIO_GetDeviceBuf(_THIS)220 SNDIO_GetDeviceBuf(_THIS)
221 {
222     return this->hidden->mixbuf;
223 }
224 
225 static void
SNDIO_CloseDevice(_THIS)226 SNDIO_CloseDevice(_THIS)
227 {
228     if ( this->hidden->pfd != NULL ) {
229         SDL_free(this->hidden->pfd);
230     }
231     if ( this->hidden->dev != NULL ) {
232         SNDIO_sio_stop(this->hidden->dev);
233         SNDIO_sio_close(this->hidden->dev);
234     }
235     SDL_free(this->hidden->mixbuf);
236     SDL_free(this->hidden);
237 }
238 
239 static int
SNDIO_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)240 SNDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
241 {
242     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
243     struct sio_par par;
244     int status;
245 
246     this->hidden = (struct SDL_PrivateAudioData *)
247         SDL_malloc(sizeof(*this->hidden));
248     if (this->hidden == NULL) {
249         return SDL_OutOfMemory();
250     }
251     SDL_zerop(this->hidden);
252 
253     this->hidden->mixlen = this->spec.size;
254 
255     /* Capture devices must be non-blocking for SNDIO_FlushCapture */
256     if ((this->hidden->dev =
257         SNDIO_sio_open(devname != NULL ? devname : SIO_DEVANY,
258                        iscapture ? SIO_REC : SIO_PLAY, iscapture)) == NULL) {
259         return SDL_SetError("sio_open() failed");
260     }
261 
262     /* Allocate the pollfd array for capture devices */
263     if (iscapture && (this->hidden->pfd =
264         SDL_malloc(sizeof(struct pollfd) * SNDIO_sio_nfds(this->hidden->dev))) == NULL) {
265         return SDL_OutOfMemory();
266     }
267 
268     SNDIO_sio_initpar(&par);
269 
270     par.rate = this->spec.freq;
271     par.pchan = this->spec.channels;
272     par.round = this->spec.samples;
273     par.appbufsz = par.round * 2;
274 
275     /* Try for a closest match on audio format */
276     status = -1;
277     while (test_format && (status < 0)) {
278         if (!SDL_AUDIO_ISFLOAT(test_format)) {
279             par.le = SDL_AUDIO_ISLITTLEENDIAN(test_format) ? 1 : 0;
280             par.sig = SDL_AUDIO_ISSIGNED(test_format) ? 1 : 0;
281             par.bits = SDL_AUDIO_BITSIZE(test_format);
282 
283             if (SNDIO_sio_setpar(this->hidden->dev, &par) == 0) {
284                 continue;
285             }
286             if (SNDIO_sio_getpar(this->hidden->dev, &par) == 0) {
287                 return SDL_SetError("sio_getpar() failed");
288             }
289             if (par.bps != SIO_BPS(par.bits)) {
290                 continue;
291             }
292             if ((par.bits == 8 * par.bps) || (par.msb)) {
293                 status = 0;
294                 break;
295             }
296         }
297         test_format = SDL_NextAudioFormat();
298     }
299 
300     if (status < 0) {
301         return SDL_SetError("sndio: Couldn't find any hardware audio formats");
302     }
303 
304     if ((par.bps == 4) && (par.sig) && (par.le))
305         this->spec.format = AUDIO_S32LSB;
306     else if ((par.bps == 4) && (par.sig) && (!par.le))
307         this->spec.format = AUDIO_S32MSB;
308     else if ((par.bps == 2) && (par.sig) && (par.le))
309         this->spec.format = AUDIO_S16LSB;
310     else if ((par.bps == 2) && (par.sig) && (!par.le))
311         this->spec.format = AUDIO_S16MSB;
312     else if ((par.bps == 2) && (!par.sig) && (par.le))
313         this->spec.format = AUDIO_U16LSB;
314     else if ((par.bps == 2) && (!par.sig) && (!par.le))
315         this->spec.format = AUDIO_U16MSB;
316     else if ((par.bps == 1) && (par.sig))
317         this->spec.format = AUDIO_S8;
318     else if ((par.bps == 1) && (!par.sig))
319         this->spec.format = AUDIO_U8;
320     else {
321         return SDL_SetError("sndio: Got unsupported hardware audio format.");
322     }
323 
324     this->spec.freq = par.rate;
325     this->spec.channels = par.pchan;
326     this->spec.samples = par.round;
327 
328     /* Calculate the final parameters for this audio specification */
329     SDL_CalculateAudioSpec(&this->spec);
330 
331     /* Allocate mixing buffer */
332     this->hidden->mixlen = this->spec.size;
333     this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->hidden->mixlen);
334     if (this->hidden->mixbuf == NULL) {
335         return SDL_OutOfMemory();
336     }
337     SDL_memset(this->hidden->mixbuf, this->spec.silence, this->hidden->mixlen);
338 
339     if (!SNDIO_sio_start(this->hidden->dev)) {
340         return SDL_SetError("sio_start() failed");
341     }
342 
343     /* We're ready to rock and roll. :-) */
344     return 0;
345 }
346 
347 static void
SNDIO_Deinitialize(void)348 SNDIO_Deinitialize(void)
349 {
350     UnloadSNDIOLibrary();
351 }
352 
353 static int
SNDIO_Init(SDL_AudioDriverImpl * impl)354 SNDIO_Init(SDL_AudioDriverImpl * impl)
355 {
356     if (LoadSNDIOLibrary() < 0) {
357         return 0;
358     }
359 
360     /* Set the function pointers */
361     impl->OpenDevice = SNDIO_OpenDevice;
362     impl->WaitDevice = SNDIO_WaitDevice;
363     impl->PlayDevice = SNDIO_PlayDevice;
364     impl->GetDeviceBuf = SNDIO_GetDeviceBuf;
365     impl->CloseDevice = SNDIO_CloseDevice;
366     impl->CaptureFromDevice = SNDIO_CaptureFromDevice;
367     impl->FlushCapture = SNDIO_FlushCapture;
368     impl->Deinitialize = SNDIO_Deinitialize;
369 
370     impl->AllowsArbitraryDeviceNames = 1;
371     impl->HasCaptureSupport = SDL_TRUE;
372 
373     return 1;   /* this audio target is available. */
374 }
375 
376 AudioBootStrap SNDIO_bootstrap = {
377     "sndio", "OpenBSD sndio", SNDIO_Init, 0
378 };
379 
380 #endif /* SDL_AUDIO_DRIVER_SNDIO */
381 
382 /* vi: set ts=4 sw=4 expandtab: */
383