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 | |
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 |
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 |
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 |
116 | UnloadSNDIOLibrary(void) |
117 | { |
118 | if (sndio_handle != NULL) { |
119 | SDL_UnloadObject(sndio_handle); |
120 | sndio_handle = NULL; |
121 | } |
122 | } |
123 | |
124 | static int |
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 |
146 | UnloadSNDIOLibrary(void) |
147 | { |
148 | } |
149 | |
150 | static int |
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 |
163 | SNDIO_WaitDevice(_THIS) |
164 | { |
165 | /* no-op; SNDIO_sio_write() blocks if necessary. */ |
166 | } |
167 | |
168 | static void |
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 |
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 |
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 * |
220 | SNDIO_GetDeviceBuf(_THIS) |
221 | { |
222 | return this->hidden->mixbuf; |
223 | } |
224 | |
225 | static void |
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 |
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 |
348 | SNDIO_Deinitialize(void) |
349 | { |
350 | UnloadSNDIOLibrary(); |
351 | } |
352 | |
353 | static int |
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 | |