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