• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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