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 #include "../../SDL_internal.h"
22
23 #if SDL_AUDIO_DRIVER_SUNAUDIO
24
25 /* Allow access to a raw mixing buffer */
26
27 #include <fcntl.h>
28 #include <errno.h>
29 #ifdef __NETBSD__
30 #include <sys/ioctl.h>
31 #include <sys/audioio.h>
32 #endif
33 #ifdef __SVR4
34 #include <sys/audioio.h>
35 #else
36 #include <sys/time.h>
37 #include <sys/types.h>
38 #endif
39 #include <unistd.h>
40
41 #include "SDL_timer.h"
42 #include "SDL_audio.h"
43 #include "../../core/unix/SDL_poll.h"
44 #include "../SDL_audio_c.h"
45 #include "../SDL_audiodev_c.h"
46 #include "SDL_sunaudio.h"
47
48 /* Open the audio device for playback, and don't block if busy */
49
50 #if defined(AUDIO_GETINFO) && !defined(AUDIO_GETBUFINFO)
51 #define AUDIO_GETBUFINFO AUDIO_GETINFO
52 #endif
53
54 /* Audio driver functions */
55 static Uint8 snd2au(int sample);
56
57 /* Audio driver bootstrap functions */
58 static void
SUNAUDIO_DetectDevices(void)59 SUNAUDIO_DetectDevices(void)
60 {
61 SDL_EnumUnixAudioDevices(1, (int (*)(int)) NULL);
62 }
63
64 #ifdef DEBUG_AUDIO
65 void
CheckUnderflow(_THIS)66 CheckUnderflow(_THIS)
67 {
68 #ifdef AUDIO_GETBUFINFO
69 audio_info_t info;
70 int left;
71
72 ioctl(this->hidden->audio_fd, AUDIO_GETBUFINFO, &info);
73 left = (this->hidden->written - info.play.samples);
74 if (this->hidden->written && (left == 0)) {
75 fprintf(stderr, "audio underflow!\n");
76 }
77 #endif
78 }
79 #endif
80
81 static void
SUNAUDIO_WaitDevice(_THIS)82 SUNAUDIO_WaitDevice(_THIS)
83 {
84 #ifdef AUDIO_GETBUFINFO
85 #define SLEEP_FUDGE 10 /* 10 ms scheduling fudge factor */
86 audio_info_t info;
87 Sint32 left;
88
89 ioctl(this->hidden->audio_fd, AUDIO_GETBUFINFO, &info);
90 left = (this->hidden->written - info.play.samples);
91 if (left > this->hidden->fragsize) {
92 Sint32 sleepy;
93
94 sleepy = ((left - this->hidden->fragsize) / this->hidden->frequency);
95 sleepy -= SLEEP_FUDGE;
96 if (sleepy > 0) {
97 SDL_Delay(sleepy);
98 }
99 }
100 #else
101 SDL_IOReady(this->hidden->audio_fd, SDL_TRUE, -1);
102 #endif
103 }
104
105 static void
SUNAUDIO_PlayDevice(_THIS)106 SUNAUDIO_PlayDevice(_THIS)
107 {
108 /* Write the audio data */
109 if (this->hidden->ulaw_only) {
110 /* Assuming that this->spec.freq >= 8000 Hz */
111 int accum, incr, pos;
112 Uint8 *aubuf;
113
114 accum = 0;
115 incr = this->spec.freq / 8;
116 aubuf = this->hidden->ulaw_buf;
117 switch (this->hidden->audio_fmt & 0xFF) {
118 case 8:
119 {
120 Uint8 *sndbuf;
121
122 sndbuf = this->hidden->mixbuf;
123 for (pos = 0; pos < this->hidden->fragsize; ++pos) {
124 *aubuf = snd2au((0x80 - *sndbuf) * 64);
125 accum += incr;
126 while (accum > 0) {
127 accum -= 1000;
128 sndbuf += 1;
129 }
130 aubuf += 1;
131 }
132 }
133 break;
134 case 16:
135 {
136 Sint16 *sndbuf;
137
138 sndbuf = (Sint16 *) this->hidden->mixbuf;
139 for (pos = 0; pos < this->hidden->fragsize; ++pos) {
140 *aubuf = snd2au(*sndbuf / 4);
141 accum += incr;
142 while (accum > 0) {
143 accum -= 1000;
144 sndbuf += 1;
145 }
146 aubuf += 1;
147 }
148 }
149 break;
150 }
151 #ifdef DEBUG_AUDIO
152 CheckUnderflow(this);
153 #endif
154 if (write(this->hidden->audio_fd, this->hidden->ulaw_buf,
155 this->hidden->fragsize) < 0) {
156 /* Assume fatal error, for now */
157 SDL_OpenedAudioDeviceDisconnected(this);
158 }
159 this->hidden->written += this->hidden->fragsize;
160 } else {
161 #ifdef DEBUG_AUDIO
162 CheckUnderflow(this);
163 #endif
164 if (write(this->hidden->audio_fd, this->hidden->mixbuf,
165 this->spec.size) < 0) {
166 /* Assume fatal error, for now */
167 SDL_OpenedAudioDeviceDisconnected(this);
168 }
169 this->hidden->written += this->hidden->fragsize;
170 }
171 }
172
173 static Uint8 *
SUNAUDIO_GetDeviceBuf(_THIS)174 SUNAUDIO_GetDeviceBuf(_THIS)
175 {
176 return (this->hidden->mixbuf);
177 }
178
179 static void
SUNAUDIO_CloseDevice(_THIS)180 SUNAUDIO_CloseDevice(_THIS)
181 {
182 SDL_free(this->hidden->ulaw_buf);
183 if (this->hidden->audio_fd >= 0) {
184 close(this->hidden->audio_fd);
185 }
186 SDL_free(this->hidden->mixbuf);
187 SDL_free(this->hidden);
188 }
189
190 static int
SUNAUDIO_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)191 SUNAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
192 {
193 #ifdef AUDIO_SETINFO
194 int enc;
195 #endif
196 int desired_freq = 0;
197 const int flags = ((iscapture) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT);
198 SDL_AudioFormat format = 0;
199 audio_info_t info;
200
201 /* We don't care what the devname is...we'll try to open anything. */
202 /* ...but default to first name in the list... */
203 if (devname == NULL) {
204 devname = SDL_GetAudioDeviceName(0, iscapture);
205 if (devname == NULL) {
206 return SDL_SetError("No such audio device");
207 }
208 }
209
210 /* Initialize all variables that we clean on shutdown */
211 this->hidden = (struct SDL_PrivateAudioData *)
212 SDL_malloc((sizeof *this->hidden));
213 if (this->hidden == NULL) {
214 return SDL_OutOfMemory();
215 }
216 SDL_zerop(this->hidden);
217
218 /* Open the audio device */
219 this->hidden->audio_fd = open(devname, flags, 0);
220 if (this->hidden->audio_fd < 0) {
221 return SDL_SetError("Couldn't open %s: %s", devname, strerror(errno));
222 }
223
224 desired_freq = this->spec.freq;
225
226 /* Determine the audio parameters from the AudioSpec */
227 switch (SDL_AUDIO_BITSIZE(this->spec.format)) {
228
229 case 8:
230 { /* Unsigned 8 bit audio data */
231 this->spec.format = AUDIO_U8;
232 #ifdef AUDIO_SETINFO
233 enc = AUDIO_ENCODING_LINEAR8;
234 #endif
235 }
236 break;
237
238 case 16:
239 { /* Signed 16 bit audio data */
240 this->spec.format = AUDIO_S16SYS;
241 #ifdef AUDIO_SETINFO
242 enc = AUDIO_ENCODING_LINEAR;
243 #endif
244 }
245 break;
246
247 default:
248 {
249 /* !!! FIXME: fallback to conversion on unsupported types! */
250 return SDL_SetError("Unsupported audio format");
251 }
252 }
253 this->hidden->audio_fmt = this->spec.format;
254
255 this->hidden->ulaw_only = 0; /* modern Suns do support linear audio */
256 #ifdef AUDIO_SETINFO
257 for (;;) {
258 audio_info_t info;
259 AUDIO_INITINFO(&info); /* init all fields to "no change" */
260
261 /* Try to set the requested settings */
262 info.play.sample_rate = this->spec.freq;
263 info.play.channels = this->spec.channels;
264 info.play.precision = (enc == AUDIO_ENCODING_ULAW)
265 ? 8 : this->spec.format & 0xff;
266 info.play.encoding = enc;
267 if (ioctl(this->hidden->audio_fd, AUDIO_SETINFO, &info) == 0) {
268
269 /* Check to be sure we got what we wanted */
270 if (ioctl(this->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
271 return SDL_SetError("Error getting audio parameters: %s",
272 strerror(errno));
273 }
274 if (info.play.encoding == enc
275 && info.play.precision == (this->spec.format & 0xff)
276 && info.play.channels == this->spec.channels) {
277 /* Yow! All seems to be well! */
278 this->spec.freq = info.play.sample_rate;
279 break;
280 }
281 }
282
283 switch (enc) {
284 case AUDIO_ENCODING_LINEAR8:
285 /* unsigned 8bit apparently not supported here */
286 enc = AUDIO_ENCODING_LINEAR;
287 this->spec.format = AUDIO_S16SYS;
288 break; /* try again */
289
290 case AUDIO_ENCODING_LINEAR:
291 /* linear 16bit didn't work either, resort to �-law */
292 enc = AUDIO_ENCODING_ULAW;
293 this->spec.channels = 1;
294 this->spec.freq = 8000;
295 this->spec.format = AUDIO_U8;
296 this->hidden->ulaw_only = 1;
297 break;
298
299 default:
300 /* oh well... */
301 return SDL_SetError("Error setting audio parameters: %s",
302 strerror(errno));
303 }
304 }
305 #endif /* AUDIO_SETINFO */
306 this->hidden->written = 0;
307
308 /* We can actually convert on-the-fly to U-Law */
309 if (this->hidden->ulaw_only) {
310 this->spec.freq = desired_freq;
311 this->hidden->fragsize = (this->spec.samples * 1000) /
312 (this->spec.freq / 8);
313 this->hidden->frequency = 8;
314 this->hidden->ulaw_buf = (Uint8 *) SDL_malloc(this->hidden->fragsize);
315 if (this->hidden->ulaw_buf == NULL) {
316 return SDL_OutOfMemory();
317 }
318 this->spec.channels = 1;
319 } else {
320 this->hidden->fragsize = this->spec.samples;
321 this->hidden->frequency = this->spec.freq / 1000;
322 }
323 #ifdef DEBUG_AUDIO
324 fprintf(stderr, "Audio device %s U-Law only\n",
325 this->hidden->ulaw_only ? "is" : "is not");
326 fprintf(stderr, "format=0x%x chan=%d freq=%d\n",
327 this->spec.format, this->spec.channels, this->spec.freq);
328 #endif
329
330 /* Update the fragment size as size in bytes */
331 SDL_CalculateAudioSpec(&this->spec);
332
333 /* Allocate mixing buffer */
334 this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->spec.size);
335 if (this->hidden->mixbuf == NULL) {
336 return SDL_OutOfMemory();
337 }
338 SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
339
340 /* We're ready to rock and roll. :-) */
341 return 0;
342 }
343
344 /************************************************************************/
345 /* This function (snd2au()) copyrighted: */
346 /************************************************************************/
347 /* Copyright 1989 by Rich Gopstein and Harris Corporation */
348 /* */
349 /* Permission to use, copy, modify, and distribute this software */
350 /* and its documentation for any purpose and without fee is */
351 /* hereby granted, provided that the above copyright notice */
352 /* appears in all copies and that both that copyright notice and */
353 /* this permission notice appear in supporting documentation, and */
354 /* that the name of Rich Gopstein and Harris Corporation not be */
355 /* used in advertising or publicity pertaining to distribution */
356 /* of the software without specific, written prior permission. */
357 /* Rich Gopstein and Harris Corporation make no representations */
358 /* about the suitability of this software for any purpose. It */
359 /* provided "as is" without express or implied warranty. */
360 /************************************************************************/
361
362 static Uint8
snd2au(int sample)363 snd2au(int sample)
364 {
365
366 int mask;
367
368 if (sample < 0) {
369 sample = -sample;
370 mask = 0x7f;
371 } else {
372 mask = 0xff;
373 }
374
375 if (sample < 32) {
376 sample = 0xF0 | (15 - sample / 2);
377 } else if (sample < 96) {
378 sample = 0xE0 | (15 - (sample - 32) / 4);
379 } else if (sample < 224) {
380 sample = 0xD0 | (15 - (sample - 96) / 8);
381 } else if (sample < 480) {
382 sample = 0xC0 | (15 - (sample - 224) / 16);
383 } else if (sample < 992) {
384 sample = 0xB0 | (15 - (sample - 480) / 32);
385 } else if (sample < 2016) {
386 sample = 0xA0 | (15 - (sample - 992) / 64);
387 } else if (sample < 4064) {
388 sample = 0x90 | (15 - (sample - 2016) / 128);
389 } else if (sample < 8160) {
390 sample = 0x80 | (15 - (sample - 4064) / 256);
391 } else {
392 sample = 0x80;
393 }
394 return (mask & sample);
395 }
396
397 static int
SUNAUDIO_Init(SDL_AudioDriverImpl * impl)398 SUNAUDIO_Init(SDL_AudioDriverImpl * impl)
399 {
400 /* Set the function pointers */
401 impl->DetectDevices = SUNAUDIO_DetectDevices;
402 impl->OpenDevice = SUNAUDIO_OpenDevice;
403 impl->PlayDevice = SUNAUDIO_PlayDevice;
404 impl->WaitDevice = SUNAUDIO_WaitDevice;
405 impl->GetDeviceBuf = SUNAUDIO_GetDeviceBuf;
406 impl->CloseDevice = SUNAUDIO_CloseDevice;
407
408 impl->AllowsArbitraryDeviceNames = 1;
409
410 return 1; /* this audio target is available. */
411 }
412
413 AudioBootStrap SUNAUDIO_bootstrap = {
414 "audio", "UNIX /dev/audio interface", SUNAUDIO_Init, 0
415 };
416
417 #endif /* SDL_AUDIO_DRIVER_SUNAUDIO */
418
419 /* vi: set ts=4 sw=4 expandtab: */
420