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