1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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
55static struct sio_hdl * (*SNDIO_sio_open)(const char *, unsigned int, int);
56static void (*SNDIO_sio_close)(struct sio_hdl *);
57static int (*SNDIO_sio_setpar)(struct sio_hdl *, struct sio_par *);
58static int (*SNDIO_sio_getpar)(struct sio_hdl *, struct sio_par *);
59static int (*SNDIO_sio_start)(struct sio_hdl *);
60static int (*SNDIO_sio_stop)(struct sio_hdl *);
61static size_t (*SNDIO_sio_read)(struct sio_hdl *, void *, size_t);
62static size_t (*SNDIO_sio_write)(struct sio_hdl *, const void *, size_t);
63static int (*SNDIO_sio_nfds)(struct sio_hdl *);
64static int (*SNDIO_sio_pollfd)(struct sio_hdl *, struct pollfd *, int);
65static int (*SNDIO_sio_revents)(struct sio_hdl *, struct pollfd *);
66static int (*SNDIO_sio_eof)(struct sio_hdl *);
67static void (*SNDIO_sio_initpar)(struct sio_par *);
68
69#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
70static const char *sndio_library = SDL_AUDIO_DRIVER_SNDIO_DYNAMIC;
71static void *sndio_handle = NULL;
72
73static int
74load_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
92static int
93load_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
115static void
116UnloadSNDIOLibrary(void)
117{
118 if (sndio_handle != NULL) {
119 SDL_UnloadObject(sndio_handle);
120 sndio_handle = NULL;
121 }
122}
123
124static int
125LoadSNDIOLibrary(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
145static void
146UnloadSNDIOLibrary(void)
147{
148}
149
150static int
151LoadSNDIOLibrary(void)
152{
153 load_sndio_syms();
154 return 0;
155}
156
157#endif /* SDL_AUDIO_DRIVER_SNDIO_DYNAMIC */
158
159
160
161
162static void
163SNDIO_WaitDevice(_THIS)
164{
165 /* no-op; SNDIO_sio_write() blocks if necessary. */
166}
167
168static void
169SNDIO_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
184static int
185SNDIO_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
209static void
210SNDIO_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
219static Uint8 *
220SNDIO_GetDeviceBuf(_THIS)
221{
222 return this->hidden->mixbuf;
223}
224
225static void
226SNDIO_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
239static int
240SNDIO_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
347static void
348SNDIO_Deinitialize(void)
349{
350 UnloadSNDIOLibrary();
351}
352
353static int
354SNDIO_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
376AudioBootStrap 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