1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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#ifdef SDL_AUDIO_DRIVER_SNDIO
25
26// OpenBSD sndio target
27
28#ifdef 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_sysaudio.h"
40#include "SDL_sndioaudio.h"
41
42#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
43#endif
44
45#ifndef INFTIM
46#define INFTIM -1
47#endif
48
49#ifndef SIO_DEVANY
50#define SIO_DEVANY "default"
51#endif
52
53static struct sio_hdl *(*SNDIO_sio_open)(const char *, unsigned int, int);
54static void (*SNDIO_sio_close)(struct sio_hdl *);
55static int (*SNDIO_sio_setpar)(struct sio_hdl *, struct sio_par *);
56static int (*SNDIO_sio_getpar)(struct sio_hdl *, struct sio_par *);
57static int (*SNDIO_sio_start)(struct sio_hdl *);
58static int (*SNDIO_sio_stop)(struct sio_hdl *);
59static size_t (*SNDIO_sio_read)(struct sio_hdl *, void *, size_t);
60static size_t (*SNDIO_sio_write)(struct sio_hdl *, const void *, size_t);
61static int (*SNDIO_sio_nfds)(struct sio_hdl *);
62static int (*SNDIO_sio_pollfd)(struct sio_hdl *, struct pollfd *, int);
63static int (*SNDIO_sio_revents)(struct sio_hdl *, struct pollfd *);
64static int (*SNDIO_sio_eof)(struct sio_hdl *);
65static void (*SNDIO_sio_initpar)(struct sio_par *);
66
67#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
68static const char *sndio_library = SDL_AUDIO_DRIVER_SNDIO_DYNAMIC;
69static SDL_SharedObject *sndio_handle = NULL;
70
71static bool load_sndio_sym(const char *fn, void **addr)
72{
73 *addr = SDL_LoadFunction(sndio_handle, fn);
74 if (!*addr) {
75 return false; // Don't call SDL_SetError(): SDL_LoadFunction already did.
76 }
77
78 return true;
79}
80
81// cast funcs to char* first, to please GCC's strict aliasing rules.
82#define SDL_SNDIO_SYM(x) \
83 if (!load_sndio_sym(#x, (void **)(char *)&SNDIO_##x)) \
84 return false
85#else
86#define SDL_SNDIO_SYM(x) SNDIO_##x = x
87#endif
88
89static bool load_sndio_syms(void)
90{
91 SDL_SNDIO_SYM(sio_open);
92 SDL_SNDIO_SYM(sio_close);
93 SDL_SNDIO_SYM(sio_setpar);
94 SDL_SNDIO_SYM(sio_getpar);
95 SDL_SNDIO_SYM(sio_start);
96 SDL_SNDIO_SYM(sio_stop);
97 SDL_SNDIO_SYM(sio_read);
98 SDL_SNDIO_SYM(sio_write);
99 SDL_SNDIO_SYM(sio_nfds);
100 SDL_SNDIO_SYM(sio_pollfd);
101 SDL_SNDIO_SYM(sio_revents);
102 SDL_SNDIO_SYM(sio_eof);
103 SDL_SNDIO_SYM(sio_initpar);
104 return true;
105}
106
107#undef SDL_SNDIO_SYM
108
109#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
110
111static void UnloadSNDIOLibrary(void)
112{
113 if (sndio_handle) {
114 SDL_UnloadObject(sndio_handle);
115 sndio_handle = NULL;
116 }
117}
118
119static bool LoadSNDIOLibrary(void)
120{
121 bool result = true;
122 if (!sndio_handle) {
123 sndio_handle = SDL_LoadObject(sndio_library);
124 if (!sndio_handle) {
125 result = false; // Don't call SDL_SetError(): SDL_LoadObject already did.
126 } else {
127 result = load_sndio_syms();
128 if (!result) {
129 UnloadSNDIOLibrary();
130 }
131 }
132 }
133 return result;
134}
135
136#else
137
138static void UnloadSNDIOLibrary(void)
139{
140}
141
142static bool LoadSNDIOLibrary(void)
143{
144 load_sndio_syms();
145 return true;
146}
147
148#endif // SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
149
150static bool SNDIO_WaitDevice(SDL_AudioDevice *device)
151{
152 const bool recording = device->recording;
153
154 while (!SDL_GetAtomicInt(&device->shutdown)) {
155 if (SNDIO_sio_eof(device->hidden->dev)) {
156 return false;
157 }
158
159 const int nfds = SNDIO_sio_pollfd(device->hidden->dev, device->hidden->pfd, recording ? POLLIN : POLLOUT);
160 if (nfds <= 0 || poll(device->hidden->pfd, nfds, 10) < 0) {
161 return false;
162 }
163
164 const int revents = SNDIO_sio_revents(device->hidden->dev, device->hidden->pfd);
165 if (recording && (revents & POLLIN)) {
166 break;
167 } else if (!recording && (revents & POLLOUT)) {
168 break;
169 } else if (revents & POLLHUP) {
170 return false;
171 }
172 }
173
174 return true;
175}
176
177static bool SNDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
178{
179 // !!! FIXME: this should be non-blocking so we can check device->shutdown.
180 // this is set to blocking, because we _have_ to send the entire buffer down, but hopefully WaitDevice took most of the delay time.
181 if (SNDIO_sio_write(device->hidden->dev, buffer, buflen) != buflen) {
182 return false; // If we couldn't write, assume fatal error for now
183 }
184#ifdef DEBUG_AUDIO
185 fprintf(stderr, "Wrote %d bytes of audio data\n", written);
186#endif
187 return true;
188}
189
190static int SNDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
191{
192 // We set recording devices non-blocking; this can safely return 0 in SDL3, but we'll check for EOF to cause a device disconnect.
193 const size_t br = SNDIO_sio_read(device->hidden->dev, buffer, buflen);
194 if ((br == 0) && SNDIO_sio_eof(device->hidden->dev)) {
195 return -1;
196 }
197 return (int) br;
198}
199
200static void SNDIO_FlushRecording(SDL_AudioDevice *device)
201{
202 char buf[512];
203 while (!SDL_GetAtomicInt(&device->shutdown) && (SNDIO_sio_read(device->hidden->dev, buf, sizeof(buf)) > 0)) {
204 // do nothing
205 }
206}
207
208static Uint8 *SNDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
209{
210 return device->hidden->mixbuf;
211}
212
213static void SNDIO_CloseDevice(SDL_AudioDevice *device)
214{
215 if (device->hidden) {
216 if (device->hidden->dev) {
217 SNDIO_sio_stop(device->hidden->dev);
218 SNDIO_sio_close(device->hidden->dev);
219 }
220 SDL_free(device->hidden->pfd);
221 SDL_free(device->hidden->mixbuf);
222 SDL_free(device->hidden);
223 device->hidden = NULL;
224 }
225}
226
227static bool SNDIO_OpenDevice(SDL_AudioDevice *device)
228{
229 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
230 if (!device->hidden) {
231 return false;
232 }
233
234 // Recording devices must be non-blocking for SNDIO_FlushRecording
235 device->hidden->dev = SNDIO_sio_open(SIO_DEVANY,
236 device->recording ? SIO_REC : SIO_PLAY, device->recording);
237 if (!device->hidden->dev) {
238 return SDL_SetError("sio_open() failed");
239 }
240
241 device->hidden->pfd = SDL_malloc(sizeof(struct pollfd) * SNDIO_sio_nfds(device->hidden->dev));
242 if (!device->hidden->pfd) {
243 return false;
244 }
245
246 struct sio_par par;
247 SNDIO_sio_initpar(&par);
248
249 par.rate = device->spec.freq;
250 par.pchan = device->spec.channels;
251 par.round = device->sample_frames;
252 par.appbufsz = par.round * 2;
253
254 // Try for a closest match on audio format
255 SDL_AudioFormat test_format;
256 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
257 while ((test_format = *(closefmts++)) != 0) {
258 if (!SDL_AUDIO_ISFLOAT(test_format)) {
259 par.le = SDL_AUDIO_ISLITTLEENDIAN(test_format) ? 1 : 0;
260 par.sig = SDL_AUDIO_ISSIGNED(test_format) ? 1 : 0;
261 par.bits = SDL_AUDIO_BITSIZE(test_format);
262
263 if (SNDIO_sio_setpar(device->hidden->dev, &par) == 0) {
264 continue;
265 }
266 if (SNDIO_sio_getpar(device->hidden->dev, &par) == 0) {
267 return SDL_SetError("sio_getpar() failed");
268 }
269 if (par.bps != SIO_BPS(par.bits)) {
270 continue;
271 }
272 if ((par.bits == 8 * par.bps) || (par.msb)) {
273 break;
274 }
275 }
276 }
277
278 if (!test_format) {
279 return SDL_SetError("sndio: Unsupported audio format");
280 }
281
282 if ((par.bps == 4) && (par.sig) && (par.le)) {
283 device->spec.format = SDL_AUDIO_S32LE;
284 } else if ((par.bps == 4) && (par.sig) && (!par.le)) {
285 device->spec.format = SDL_AUDIO_S32BE;
286 } else if ((par.bps == 2) && (par.sig) && (par.le)) {
287 device->spec.format = SDL_AUDIO_S16LE;
288 } else if ((par.bps == 2) && (par.sig) && (!par.le)) {
289 device->spec.format = SDL_AUDIO_S16BE;
290 } else if ((par.bps == 1) && (par.sig)) {
291 device->spec.format = SDL_AUDIO_S8;
292 } else if ((par.bps == 1) && (!par.sig)) {
293 device->spec.format = SDL_AUDIO_U8;
294 } else {
295 return SDL_SetError("sndio: Got unsupported hardware audio format.");
296 }
297
298 device->spec.freq = par.rate;
299 device->spec.channels = par.pchan;
300 device->sample_frames = par.round;
301
302 // Calculate the final parameters for this audio specification
303 SDL_UpdatedAudioDeviceFormat(device);
304
305 // Allocate mixing buffer
306 device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
307 if (!device->hidden->mixbuf) {
308 return false;
309 }
310 SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
311
312 if (!SNDIO_sio_start(device->hidden->dev)) {
313 return SDL_SetError("sio_start() failed");
314 }
315
316 return true; // We're ready to rock and roll. :-)
317}
318
319static void SNDIO_Deinitialize(void)
320{
321 UnloadSNDIOLibrary();
322}
323
324static void SNDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
325{
326 *default_playback = SDL_AddAudioDevice(false, DEFAULT_PLAYBACK_DEVNAME, NULL, (void *)0x1);
327 *default_recording = SDL_AddAudioDevice(true, DEFAULT_RECORDING_DEVNAME, NULL, (void *)0x2);
328}
329
330static bool SNDIO_Init(SDL_AudioDriverImpl *impl)
331{
332 if (!LoadSNDIOLibrary()) {
333 return false;
334 }
335
336 impl->OpenDevice = SNDIO_OpenDevice;
337 impl->WaitDevice = SNDIO_WaitDevice;
338 impl->PlayDevice = SNDIO_PlayDevice;
339 impl->GetDeviceBuf = SNDIO_GetDeviceBuf;
340 impl->CloseDevice = SNDIO_CloseDevice;
341 impl->WaitRecordingDevice = SNDIO_WaitDevice;
342 impl->RecordDevice = SNDIO_RecordDevice;
343 impl->FlushRecording = SNDIO_FlushRecording;
344 impl->Deinitialize = SNDIO_Deinitialize;
345 impl->DetectDevices = SNDIO_DetectDevices;
346
347 impl->HasRecordingSupport = true;
348
349 return true;
350}
351
352AudioBootStrap SNDIO_bootstrap = {
353 "sndio", "OpenBSD sndio", SNDIO_Init, false, false
354};
355
356#endif // SDL_AUDIO_DRIVER_SNDIO
357