• 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_BSD
24 
25 /*
26  * Driver for native OpenBSD/NetBSD audio(4).
27  * vedge@vedge.com.ar.
28  */
29 
30 #include <errno.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <sys/time.h>
34 #include <sys/ioctl.h>
35 #include <sys/stat.h>
36 #include <sys/types.h>
37 #include <sys/audioio.h>
38 
39 #include "SDL_timer.h"
40 #include "SDL_audio.h"
41 #include "../SDL_audio_c.h"
42 #include "../SDL_audiodev_c.h"
43 #include "SDL_bsdaudio.h"
44 
45 /* Use timer for synchronization */
46 /* #define USE_TIMER_SYNC */
47 
48 /* #define DEBUG_AUDIO */
49 /* #define DEBUG_AUDIO_STREAM */
50 
51 
52 static void
BSDAUDIO_DetectDevices(void)53 BSDAUDIO_DetectDevices(void)
54 {
55     SDL_EnumUnixAudioDevices(0, NULL);
56 }
57 
58 
59 static void
BSDAUDIO_Status(_THIS)60 BSDAUDIO_Status(_THIS)
61 {
62 #ifdef DEBUG_AUDIO
63     /* *INDENT-OFF* */
64     audio_info_t info;
65     const audio_prinfo *prinfo;
66 
67     if (ioctl(this->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
68         fprintf(stderr, "AUDIO_GETINFO failed.\n");
69         return;
70     }
71 
72     prinfo = this->iscapture ? &info.play : &info.record;
73 
74     fprintf(stderr, "\n"
75             "[%s info]\n"
76             "buffer size	:   %d bytes\n"
77             "sample rate	:   %i Hz\n"
78             "channels	:   %i\n"
79             "precision	:   %i-bit\n"
80             "encoding	:   0x%x\n"
81             "seek		:   %i\n"
82             "sample count	:   %i\n"
83             "EOF count	:   %i\n"
84             "paused		:   %s\n"
85             "error occured	:   %s\n"
86             "waiting		:   %s\n"
87             "active		:   %s\n"
88             "",
89             this->iscapture ? "record" : "play",
90             prinfo->buffer_size,
91             prinfo->sample_rate,
92             prinfo->channels,
93             prinfo->precision,
94             prinfo->encoding,
95             prinfo->seek,
96             prinfo->samples,
97             prinfo->eof,
98             prinfo->pause ? "yes" : "no",
99             prinfo->error ? "yes" : "no",
100             prinfo->waiting ? "yes" : "no",
101             prinfo->active ? "yes" : "no");
102 
103     fprintf(stderr, "\n"
104             "[audio info]\n"
105             "monitor_gain	:   %i\n"
106             "hw block size	:   %d bytes\n"
107             "hi watermark	:   %i\n"
108             "lo watermark	:   %i\n"
109             "audio mode	:   %s\n"
110             "",
111             info.monitor_gain,
112             info.blocksize,
113             info.hiwat, info.lowat,
114             (info.mode == AUMODE_PLAY) ? "PLAY"
115             : (info.mode = AUMODE_RECORD) ? "RECORD"
116             : (info.mode == AUMODE_PLAY_ALL ? "PLAY_ALL" : "?"));
117     /* *INDENT-ON* */
118 #endif /* DEBUG_AUDIO */
119 }
120 
121 
122 /* This function waits until it is possible to write a full sound buffer */
123 static void
BSDAUDIO_WaitDevice(_THIS)124 BSDAUDIO_WaitDevice(_THIS)
125 {
126 #ifndef USE_BLOCKING_WRITES     /* Not necessary when using blocking writes */
127     /* See if we need to use timed audio synchronization */
128     if (this->hidden->frame_ticks) {
129         /* Use timer for general audio synchronization */
130         Sint32 ticks;
131 
132         ticks = ((Sint32) (this->hidden->next_frame - SDL_GetTicks())) - FUDGE_TICKS;
133         if (ticks > 0) {
134             SDL_Delay(ticks);
135         }
136     } else {
137         /* Use select() for audio synchronization */
138         fd_set fdset;
139         struct timeval timeout;
140 
141         FD_ZERO(&fdset);
142         FD_SET(this->hidden->audio_fd, &fdset);
143         timeout.tv_sec = 10;
144         timeout.tv_usec = 0;
145 #ifdef DEBUG_AUDIO
146         fprintf(stderr, "Waiting for audio to get ready\n");
147 #endif
148         if (select(this->hidden->audio_fd + 1, NULL, &fdset, NULL, &timeout)
149             <= 0) {
150             const char *message =
151                 "Audio timeout - buggy audio driver? (disabled)";
152             /* In general we should never print to the screen,
153                but in this case we have no other way of letting
154                the user know what happened.
155              */
156             fprintf(stderr, "SDL: %s\n", message);
157             SDL_OpenedAudioDeviceDisconnected(this);
158             /* Don't try to close - may hang */
159             this->hidden->audio_fd = -1;
160 #ifdef DEBUG_AUDIO
161             fprintf(stderr, "Done disabling audio\n");
162 #endif
163         }
164 #ifdef DEBUG_AUDIO
165         fprintf(stderr, "Ready!\n");
166 #endif
167     }
168 #endif /* !USE_BLOCKING_WRITES */
169 }
170 
171 static void
BSDAUDIO_PlayDevice(_THIS)172 BSDAUDIO_PlayDevice(_THIS)
173 {
174     int written, p = 0;
175 
176     /* Write the audio data, checking for EAGAIN on broken audio drivers */
177     do {
178         written = write(this->hidden->audio_fd,
179                         &this->hidden->mixbuf[p], this->hidden->mixlen - p);
180 
181         if (written > 0)
182             p += written;
183         if (written == -1 && errno != 0 && errno != EAGAIN && errno != EINTR) {
184             /* Non recoverable error has occurred. It should be reported!!! */
185             perror("audio");
186             break;
187         }
188 
189 #ifdef DEBUG_AUDIO
190         fprintf(stderr, "Wrote %d bytes of audio data\n", written);
191 #endif
192 
193         if (p < this->hidden->mixlen
194             || ((written < 0) && ((errno == 0) || (errno == EAGAIN)))) {
195             SDL_Delay(1);       /* Let a little CPU time go by */
196         }
197     } while (p < this->hidden->mixlen);
198 
199     /* If timer synchronization is enabled, set the next write frame */
200     if (this->hidden->frame_ticks) {
201         this->hidden->next_frame += this->hidden->frame_ticks;
202     }
203 
204     /* If we couldn't write, assume fatal error for now */
205     if (written < 0) {
206         SDL_OpenedAudioDeviceDisconnected(this);
207     }
208 }
209 
210 static Uint8 *
BSDAUDIO_GetDeviceBuf(_THIS)211 BSDAUDIO_GetDeviceBuf(_THIS)
212 {
213     return (this->hidden->mixbuf);
214 }
215 
216 
217 static int
BSDAUDIO_CaptureFromDevice(_THIS,void * _buffer,int buflen)218 BSDAUDIO_CaptureFromDevice(_THIS, void *_buffer, int buflen)
219 {
220     Uint8 *buffer = (Uint8 *) _buffer;
221     int br, p = 0;
222 
223     /* Write the audio data, checking for EAGAIN on broken audio drivers */
224     do {
225         br = read(this->hidden->audio_fd, buffer + p, buflen - p);
226         if (br > 0)
227             p += br;
228         if (br == -1 && errno != 0 && errno != EAGAIN && errno != EINTR) {
229             /* Non recoverable error has occurred. It should be reported!!! */
230             perror("audio");
231             return p ? p : -1;
232         }
233 
234 #ifdef DEBUG_AUDIO
235         fprintf(stderr, "Captured %d bytes of audio data\n", br);
236 #endif
237 
238         if (p < buflen
239             || ((br < 0) && ((errno == 0) || (errno == EAGAIN)))) {
240             SDL_Delay(1);       /* Let a little CPU time go by */
241         }
242     } while (p < buflen);
243 }
244 
245 static void
BSDAUDIO_FlushCapture(_THIS)246 BSDAUDIO_FlushCapture(_THIS)
247 {
248     audio_info_t info;
249     size_t remain;
250     Uint8 buf[512];
251 
252     if (ioctl(this->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
253         return;  /* oh well. */
254     }
255 
256     remain = (size_t) (info.record.samples * (SDL_AUDIO_BITSIZE(this->spec.format) / 8));
257     while (remain > 0) {
258         const size_t len = SDL_min(sizeof (buf), remain);
259         const int br = read(this->hidden->audio_fd, buf, len);
260         if (br <= 0) {
261             return;  /* oh well. */
262         }
263         remain -= br;
264     }
265 }
266 
267 static void
BSDAUDIO_CloseDevice(_THIS)268 BSDAUDIO_CloseDevice(_THIS)
269 {
270     if (this->hidden->audio_fd >= 0) {
271         close(this->hidden->audio_fd);
272     }
273     SDL_free(this->hidden->mixbuf);
274     SDL_free(this->hidden);
275 }
276 
277 static int
BSDAUDIO_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)278 BSDAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
279 {
280     const int flags = iscapture ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT;
281     SDL_AudioFormat format = 0;
282     audio_info_t info;
283     audio_prinfo *prinfo = iscapture ? &info.play : &info.record;
284 
285     /* We don't care what the devname is...we'll try to open anything. */
286     /*  ...but default to first name in the list... */
287     if (devname == NULL) {
288         devname = SDL_GetAudioDeviceName(0, iscapture);
289         if (devname == NULL) {
290             return SDL_SetError("No such audio device");
291         }
292     }
293 
294     /* Initialize all variables that we clean on shutdown */
295     this->hidden = (struct SDL_PrivateAudioData *)
296         SDL_malloc((sizeof *this->hidden));
297     if (this->hidden == NULL) {
298         return SDL_OutOfMemory();
299     }
300     SDL_zerop(this->hidden);
301 
302     /* Open the audio device */
303     this->hidden->audio_fd = open(devname, flags, 0);
304     if (this->hidden->audio_fd < 0) {
305         return SDL_SetError("Couldn't open %s: %s", devname, strerror(errno));
306     }
307 
308     AUDIO_INITINFO(&info);
309 
310     /* Calculate the final parameters for this audio specification */
311     SDL_CalculateAudioSpec(&this->spec);
312 
313     /* Set to play mode */
314     info.mode = iscapture ? AUMODE_RECORD : AUMODE_PLAY;
315     if (ioctl(this->hidden->audio_fd, AUDIO_SETINFO, &info) < 0) {
316         return SDL_SetError("Couldn't put device into play mode");
317     }
318 
319     AUDIO_INITINFO(&info);
320     for (format = SDL_FirstAudioFormat(this->spec.format);
321          format; format = SDL_NextAudioFormat()) {
322         switch (format) {
323         case AUDIO_U8:
324             prinfo->encoding = AUDIO_ENCODING_ULINEAR;
325             prinfo->precision = 8;
326             break;
327         case AUDIO_S8:
328             prinfo->encoding = AUDIO_ENCODING_SLINEAR;
329             prinfo->precision = 8;
330             break;
331         case AUDIO_S16LSB:
332             prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
333             prinfo->precision = 16;
334             break;
335         case AUDIO_S16MSB:
336             prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
337             prinfo->precision = 16;
338             break;
339         case AUDIO_U16LSB:
340             prinfo->encoding = AUDIO_ENCODING_ULINEAR_LE;
341             prinfo->precision = 16;
342             break;
343         case AUDIO_U16MSB:
344             prinfo->encoding = AUDIO_ENCODING_ULINEAR_BE;
345             prinfo->precision = 16;
346             break;
347         default:
348             continue;
349         }
350 
351         if (ioctl(this->hidden->audio_fd, AUDIO_SETINFO, &info) == 0) {
352             break;
353         }
354     }
355 
356     if (!format) {
357         return SDL_SetError("No supported encoding for 0x%x", this->spec.format);
358     }
359 
360     this->spec.format = format;
361 
362     AUDIO_INITINFO(&info);
363     prinfo->channels = this->spec.channels;
364     if (ioctl(this->hidden->audio_fd, AUDIO_SETINFO, &info) == -1) {
365         this->spec.channels = 1;
366     }
367     AUDIO_INITINFO(&info);
368     prinfo->sample_rate = this->spec.freq;
369     info.blocksize = this->spec.size;
370     info.hiwat = 5;
371     info.lowat = 3;
372     (void) ioctl(this->hidden->audio_fd, AUDIO_SETINFO, &info);
373     (void) ioctl(this->hidden->audio_fd, AUDIO_GETINFO, &info);
374     this->spec.freq = prinfo->sample_rate;
375 
376     if (!iscapture) {
377         /* Allocate mixing buffer */
378         this->hidden->mixlen = this->spec.size;
379         this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->hidden->mixlen);
380         if (this->hidden->mixbuf == NULL) {
381             return SDL_OutOfMemory();
382         }
383         SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
384     }
385 
386     BSDAUDIO_Status(this);
387 
388     /* We're ready to rock and roll. :-) */
389     return 0;
390 }
391 
392 static int
BSDAUDIO_Init(SDL_AudioDriverImpl * impl)393 BSDAUDIO_Init(SDL_AudioDriverImpl * impl)
394 {
395     /* Set the function pointers */
396     impl->DetectDevices = BSDAUDIO_DetectDevices;
397     impl->OpenDevice = BSDAUDIO_OpenDevice;
398     impl->PlayDevice = BSDAUDIO_PlayDevice;
399     impl->WaitDevice = BSDAUDIO_WaitDevice;
400     impl->GetDeviceBuf = BSDAUDIO_GetDeviceBuf;
401     impl->CloseDevice = BSDAUDIO_CloseDevice;
402     impl->CaptureFromDevice = BSDAUDIO_CaptureFromDevice;
403     impl->FlushCapture = BSDAUDIO_FlushCapture;
404 
405     impl->HasCaptureSupport = SDL_TRUE;
406     impl->AllowsArbitraryDeviceNames = 1;
407 
408     return 1;   /* this audio target is available. */
409 }
410 
411 
412 AudioBootStrap BSD_AUDIO_bootstrap = {
413     "bsd", "BSD audio", BSDAUDIO_Init, 0
414 };
415 
416 #endif /* SDL_AUDIO_DRIVER_BSD */
417 
418 /* vi: set ts=4 sw=4 expandtab: */
419