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
22 /*
23 * !!! FIXME: streamline this a little by removing all the
24 * !!! FIXME: if (capture) {} else {} sections that are identical
25 * !!! FIXME: except for one flag.
26 */
27
28 /* !!! FIXME: can this target support hotplugging? */
29 /* !!! FIXME: ...does SDL2 even support QNX? */
30
31 #include "../../SDL_internal.h"
32
33 #if SDL_AUDIO_DRIVER_QSA
34
35 #include <errno.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <signal.h>
39 #include <sys/types.h>
40 #include <sys/time.h>
41 #include <sched.h>
42 #include <sys/select.h>
43 #include <sys/neutrino.h>
44 #include <sys/asoundlib.h>
45
46 #include "SDL_timer.h"
47 #include "SDL_audio.h"
48 #include "../SDL_audio_c.h"
49 #include "SDL_qsa_audio.h"
50
51 /* default channel communication parameters */
52 #define DEFAULT_CPARAMS_RATE 44100
53 #define DEFAULT_CPARAMS_VOICES 1
54
55 #define DEFAULT_CPARAMS_FRAG_SIZE 4096
56 #define DEFAULT_CPARAMS_FRAGS_MIN 1
57 #define DEFAULT_CPARAMS_FRAGS_MAX 1
58
59 #define QSA_NO_WORKAROUNDS 0x00000000
60 #define QSA_MMAP_WORKAROUND 0x00000001
61
62 struct BuggyCards
63 {
64 char *cardname;
65 unsigned long bugtype;
66 };
67
68 #define QSA_WA_CARDS 3
69 #define QSA_MAX_CARD_NAME_LENGTH 33
70
71 struct BuggyCards buggycards[QSA_WA_CARDS] = {
72 {"Sound Blaster Live!", QSA_MMAP_WORKAROUND},
73 {"Vortex 8820", QSA_MMAP_WORKAROUND},
74 {"Vortex 8830", QSA_MMAP_WORKAROUND},
75 };
76
77 /* List of found devices */
78 #define QSA_MAX_DEVICES 32
79 #define QSA_MAX_NAME_LENGTH 81+16 /* Hardcoded in QSA, can't be changed */
80
81 typedef struct _QSA_Device
82 {
83 char name[QSA_MAX_NAME_LENGTH]; /* Long audio device name for SDL */
84 int cardno;
85 int deviceno;
86 } QSA_Device;
87
88 QSA_Device qsa_playback_device[QSA_MAX_DEVICES];
89 uint32_t qsa_playback_devices;
90
91 QSA_Device qsa_capture_device[QSA_MAX_DEVICES];
92 uint32_t qsa_capture_devices;
93
94 static SDL_INLINE int
QSA_SetError(const char * fn,int status)95 QSA_SetError(const char *fn, int status)
96 {
97 return SDL_SetError("QSA: %s() failed: %s", fn, snd_strerror(status));
98 }
99
100 /* card names check to apply the workarounds */
101 static int
QSA_CheckBuggyCards(_THIS,unsigned long checkfor)102 QSA_CheckBuggyCards(_THIS, unsigned long checkfor)
103 {
104 char scardname[QSA_MAX_CARD_NAME_LENGTH];
105 int it;
106
107 if (snd_card_get_name
108 (this->hidden->cardno, scardname, QSA_MAX_CARD_NAME_LENGTH - 1) < 0) {
109 return 0;
110 }
111
112 for (it = 0; it < QSA_WA_CARDS; it++) {
113 if (SDL_strcmp(buggycards[it].cardname, scardname) == 0) {
114 if (buggycards[it].bugtype == checkfor) {
115 return 1;
116 }
117 }
118 }
119
120 return 0;
121 }
122
123 /* !!! FIXME: does this need to be here? Does the SDL version not work? */
124 static void
QSA_ThreadInit(_THIS)125 QSA_ThreadInit(_THIS)
126 {
127 struct sched_param param;
128 int status;
129
130 /* Increase default 10 priority to 25 to avoid jerky sound */
131 status = SchedGet(0, 0, ¶m);
132 param.sched_priority = param.sched_curpriority + 15;
133 status = SchedSet(0, 0, SCHED_NOCHANGE, ¶m);
134 }
135
136 /* PCM channel parameters initialize function */
137 static void
QSA_InitAudioParams(snd_pcm_channel_params_t * cpars)138 QSA_InitAudioParams(snd_pcm_channel_params_t * cpars)
139 {
140 SDL_zerop(cpars);
141 cpars->channel = SND_PCM_CHANNEL_PLAYBACK;
142 cpars->mode = SND_PCM_MODE_BLOCK;
143 cpars->start_mode = SND_PCM_START_DATA;
144 cpars->stop_mode = SND_PCM_STOP_STOP;
145 cpars->format.format = SND_PCM_SFMT_S16_LE;
146 cpars->format.interleave = 1;
147 cpars->format.rate = DEFAULT_CPARAMS_RATE;
148 cpars->format.voices = DEFAULT_CPARAMS_VOICES;
149 cpars->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE;
150 cpars->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN;
151 cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX;
152 }
153
154 /* This function waits until it is possible to write a full sound buffer */
155 static void
QSA_WaitDevice(_THIS)156 QSA_WaitDevice(_THIS)
157 {
158 fd_set wfds;
159 fd_set rfds;
160 int selectret;
161 struct timeval timeout;
162
163 if (!this->hidden->iscapture) {
164 FD_ZERO(&wfds);
165 FD_SET(this->hidden->audio_fd, &wfds);
166 } else {
167 FD_ZERO(&rfds);
168 FD_SET(this->hidden->audio_fd, &rfds);
169 }
170
171 do {
172 /* Setup timeout for playing one fragment equal to 2 seconds */
173 /* If timeout occured than something wrong with hardware or driver */
174 /* For example, Vortex 8820 audio driver stucks on second DAC because */
175 /* it doesn't exist ! */
176 timeout.tv_sec = 2;
177 timeout.tv_usec = 0;
178 this->hidden->timeout_on_wait = 0;
179
180 if (!this->hidden->iscapture) {
181 selectret =
182 select(this->hidden->audio_fd + 1, NULL, &wfds, NULL,
183 &timeout);
184 } else {
185 selectret =
186 select(this->hidden->audio_fd + 1, &rfds, NULL, NULL,
187 &timeout);
188 }
189
190 switch (selectret) {
191 case -1:
192 {
193 SDL_SetError("QSA: select() failed: %s", strerror(errno));
194 return;
195 }
196 break;
197 case 0:
198 {
199 SDL_SetError("QSA: timeout on buffer waiting occured");
200 this->hidden->timeout_on_wait = 1;
201 return;
202 }
203 break;
204 default:
205 {
206 if (!this->hidden->iscapture) {
207 if (FD_ISSET(this->hidden->audio_fd, &wfds)) {
208 return;
209 }
210 } else {
211 if (FD_ISSET(this->hidden->audio_fd, &rfds)) {
212 return;
213 }
214 }
215 }
216 break;
217 }
218 } while (1);
219 }
220
221 static void
QSA_PlayDevice(_THIS)222 QSA_PlayDevice(_THIS)
223 {
224 snd_pcm_channel_status_t cstatus;
225 int written;
226 int status;
227 int towrite;
228 void *pcmbuffer;
229
230 if (!SDL_AtomicGet(&this->enabled) || !this->hidden) {
231 return;
232 }
233
234 towrite = this->spec.size;
235 pcmbuffer = this->hidden->pcm_buf;
236
237 /* Write the audio data, checking for EAGAIN (buffer full) and underrun */
238 do {
239 written =
240 snd_pcm_plugin_write(this->hidden->audio_handle, pcmbuffer,
241 towrite);
242 if (written != towrite) {
243 /* Check if samples playback got stuck somewhere in hardware or in */
244 /* the audio device driver */
245 if ((errno == EAGAIN) && (written == 0)) {
246 if (this->hidden->timeout_on_wait != 0) {
247 SDL_SetError("QSA: buffer playback timeout");
248 return;
249 }
250 }
251
252 /* Check for errors or conditions */
253 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
254 /* Let a little CPU time go by and try to write again */
255 SDL_Delay(1);
256
257 /* if we wrote some data */
258 towrite -= written;
259 pcmbuffer += written * this->spec.channels;
260 continue;
261 } else {
262 if ((errno == EINVAL) || (errno == EIO)) {
263 SDL_zero(cstatus);
264 if (!this->hidden->iscapture) {
265 cstatus.channel = SND_PCM_CHANNEL_PLAYBACK;
266 } else {
267 cstatus.channel = SND_PCM_CHANNEL_CAPTURE;
268 }
269
270 status =
271 snd_pcm_plugin_status(this->hidden->audio_handle,
272 &cstatus);
273 if (status < 0) {
274 QSA_SetError("snd_pcm_plugin_status", status);
275 return;
276 }
277
278 if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) ||
279 (cstatus.status == SND_PCM_STATUS_READY)) {
280 if (!this->hidden->iscapture) {
281 status =
282 snd_pcm_plugin_prepare(this->hidden->
283 audio_handle,
284 SND_PCM_CHANNEL_PLAYBACK);
285 } else {
286 status =
287 snd_pcm_plugin_prepare(this->hidden->
288 audio_handle,
289 SND_PCM_CHANNEL_CAPTURE);
290 }
291 if (status < 0) {
292 QSA_SetError("snd_pcm_plugin_prepare", status);
293 return;
294 }
295 }
296 continue;
297 } else {
298 return;
299 }
300 }
301 } else {
302 /* we wrote all remaining data */
303 towrite -= written;
304 pcmbuffer += written * this->spec.channels;
305 }
306 } while ((towrite > 0) && SDL_AtomicGet(&this->enabled));
307
308 /* If we couldn't write, assume fatal error for now */
309 if (towrite != 0) {
310 SDL_OpenedAudioDeviceDisconnected(this);
311 }
312 }
313
314 static Uint8 *
QSA_GetDeviceBuf(_THIS)315 QSA_GetDeviceBuf(_THIS)
316 {
317 return this->hidden->pcm_buf;
318 }
319
320 static void
QSA_CloseDevice(_THIS)321 QSA_CloseDevice(_THIS)
322 {
323 if (this->hidden->audio_handle != NULL) {
324 if (!this->hidden->iscapture) {
325 /* Finish playing available samples */
326 snd_pcm_plugin_flush(this->hidden->audio_handle,
327 SND_PCM_CHANNEL_PLAYBACK);
328 } else {
329 /* Cancel unread samples during capture */
330 snd_pcm_plugin_flush(this->hidden->audio_handle,
331 SND_PCM_CHANNEL_CAPTURE);
332 }
333 snd_pcm_close(this->hidden->audio_handle);
334 }
335
336 SDL_free(this->hidden->pcm_buf);
337 SDL_free(this->hidden);
338 }
339
340 static int
QSA_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)341 QSA_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
342 {
343 const QSA_Device *device = (const QSA_Device *) handle;
344 int status = 0;
345 int format = 0;
346 SDL_AudioFormat test_format = 0;
347 int found = 0;
348 snd_pcm_channel_setup_t csetup;
349 snd_pcm_channel_params_t cparams;
350
351 /* Initialize all variables that we clean on shutdown */
352 this->hidden =
353 (struct SDL_PrivateAudioData *) SDL_calloc(1,
354 (sizeof
355 (struct
356 SDL_PrivateAudioData)));
357 if (this->hidden == NULL) {
358 return SDL_OutOfMemory();
359 }
360 SDL_zerop(this->hidden);
361
362 /* Initialize channel transfer parameters to default */
363 QSA_InitAudioParams(&cparams);
364
365 /* Initialize channel direction: capture or playback */
366 this->hidden->iscapture = iscapture ? SDL_TRUE : SDL_FALSE;
367
368 if (device != NULL) {
369 /* Open requested audio device */
370 this->hidden->deviceno = device->deviceno;
371 this->hidden->cardno = device->cardno;
372 status = snd_pcm_open(&this->hidden->audio_handle,
373 device->cardno, device->deviceno,
374 iscapture ? SND_PCM_OPEN_PLAYBACK : SND_PCM_OPEN_CAPTURE);
375 } else {
376 /* Open system default audio device */
377 status = snd_pcm_open_preferred(&this->hidden->audio_handle,
378 &this->hidden->cardno,
379 &this->hidden->deviceno,
380 iscapture ? SND_PCM_OPEN_PLAYBACK : SND_PCM_OPEN_CAPTURE);
381 }
382
383 /* Check if requested device is opened */
384 if (status < 0) {
385 this->hidden->audio_handle = NULL;
386 return QSA_SetError("snd_pcm_open", status);
387 }
388
389 if (!QSA_CheckBuggyCards(this, QSA_MMAP_WORKAROUND)) {
390 /* Disable QSA MMAP plugin for buggy audio drivers */
391 status =
392 snd_pcm_plugin_set_disable(this->hidden->audio_handle,
393 PLUGIN_DISABLE_MMAP);
394 if (status < 0) {
395 return QSA_SetError("snd_pcm_plugin_set_disable", status);
396 }
397 }
398
399 /* Try for a closest match on audio format */
400 format = 0;
401 /* can't use format as SND_PCM_SFMT_U8 = 0 in qsa */
402 found = 0;
403
404 for (test_format = SDL_FirstAudioFormat(this->spec.format); !found;) {
405 /* if match found set format to equivalent QSA format */
406 switch (test_format) {
407 case AUDIO_U8:
408 {
409 format = SND_PCM_SFMT_U8;
410 found = 1;
411 }
412 break;
413 case AUDIO_S8:
414 {
415 format = SND_PCM_SFMT_S8;
416 found = 1;
417 }
418 break;
419 case AUDIO_S16LSB:
420 {
421 format = SND_PCM_SFMT_S16_LE;
422 found = 1;
423 }
424 break;
425 case AUDIO_S16MSB:
426 {
427 format = SND_PCM_SFMT_S16_BE;
428 found = 1;
429 }
430 break;
431 case AUDIO_U16LSB:
432 {
433 format = SND_PCM_SFMT_U16_LE;
434 found = 1;
435 }
436 break;
437 case AUDIO_U16MSB:
438 {
439 format = SND_PCM_SFMT_U16_BE;
440 found = 1;
441 }
442 break;
443 case AUDIO_S32LSB:
444 {
445 format = SND_PCM_SFMT_S32_LE;
446 found = 1;
447 }
448 break;
449 case AUDIO_S32MSB:
450 {
451 format = SND_PCM_SFMT_S32_BE;
452 found = 1;
453 }
454 break;
455 case AUDIO_F32LSB:
456 {
457 format = SND_PCM_SFMT_FLOAT_LE;
458 found = 1;
459 }
460 break;
461 case AUDIO_F32MSB:
462 {
463 format = SND_PCM_SFMT_FLOAT_BE;
464 found = 1;
465 }
466 break;
467 default:
468 {
469 break;
470 }
471 }
472
473 if (!found) {
474 test_format = SDL_NextAudioFormat();
475 }
476 }
477
478 /* assumes test_format not 0 on success */
479 if (test_format == 0) {
480 return SDL_SetError("QSA: Couldn't find any hardware audio formats");
481 }
482
483 this->spec.format = test_format;
484
485 /* Set the audio format */
486 cparams.format.format = format;
487
488 /* Set mono/stereo/4ch/6ch/8ch audio */
489 cparams.format.voices = this->spec.channels;
490
491 /* Set rate */
492 cparams.format.rate = this->spec.freq;
493
494 /* Setup the transfer parameters according to cparams */
495 status = snd_pcm_plugin_params(this->hidden->audio_handle, &cparams);
496 if (status < 0) {
497 return QSA_SetError("snd_pcm_channel_params", status);
498 }
499
500 /* Make sure channel is setup right one last time */
501 SDL_zero(csetup);
502 if (!this->hidden->iscapture) {
503 csetup.channel = SND_PCM_CHANNEL_PLAYBACK;
504 } else {
505 csetup.channel = SND_PCM_CHANNEL_CAPTURE;
506 }
507
508 /* Setup an audio channel */
509 if (snd_pcm_plugin_setup(this->hidden->audio_handle, &csetup) < 0) {
510 return SDL_SetError("QSA: Unable to setup channel");
511 }
512
513 /* Calculate the final parameters for this audio specification */
514 SDL_CalculateAudioSpec(&this->spec);
515
516 this->hidden->pcm_len = this->spec.size;
517
518 if (this->hidden->pcm_len == 0) {
519 this->hidden->pcm_len =
520 csetup.buf.block.frag_size * this->spec.channels *
521 (snd_pcm_format_width(format) / 8);
522 }
523
524 /*
525 * Allocate memory to the audio buffer and initialize with silence
526 * (Note that buffer size must be a multiple of fragment size, so find
527 * closest multiple)
528 */
529 this->hidden->pcm_buf =
530 (Uint8 *) SDL_malloc(this->hidden->pcm_len);
531 if (this->hidden->pcm_buf == NULL) {
532 return SDL_OutOfMemory();
533 }
534 SDL_memset(this->hidden->pcm_buf, this->spec.silence,
535 this->hidden->pcm_len);
536
537 /* get the file descriptor */
538 if (!this->hidden->iscapture) {
539 this->hidden->audio_fd =
540 snd_pcm_file_descriptor(this->hidden->audio_handle,
541 SND_PCM_CHANNEL_PLAYBACK);
542 } else {
543 this->hidden->audio_fd =
544 snd_pcm_file_descriptor(this->hidden->audio_handle,
545 SND_PCM_CHANNEL_CAPTURE);
546 }
547
548 if (this->hidden->audio_fd < 0) {
549 return QSA_SetError("snd_pcm_file_descriptor", status);
550 }
551
552 /* Prepare an audio channel */
553 if (!this->hidden->iscapture) {
554 /* Prepare audio playback */
555 status =
556 snd_pcm_plugin_prepare(this->hidden->audio_handle,
557 SND_PCM_CHANNEL_PLAYBACK);
558 } else {
559 /* Prepare audio capture */
560 status =
561 snd_pcm_plugin_prepare(this->hidden->audio_handle,
562 SND_PCM_CHANNEL_CAPTURE);
563 }
564
565 if (status < 0) {
566 return QSA_SetError("snd_pcm_plugin_prepare", status);
567 }
568
569 /* We're really ready to rock and roll. :-) */
570 return 0;
571 }
572
573 static void
QSA_DetectDevices(void)574 QSA_DetectDevices(void)
575 {
576 uint32_t it;
577 uint32_t cards;
578 uint32_t devices;
579 int32_t status;
580
581 /* Detect amount of available devices */
582 /* this value can be changed in the runtime */
583 cards = snd_cards();
584
585 /* If io-audio manager is not running we will get 0 as number */
586 /* of available audio devices */
587 if (cards == 0) {
588 /* We have no any available audio devices */
589 return;
590 }
591
592 /* !!! FIXME: code duplication */
593 /* Find requested devices by type */
594 { /* output devices */
595 /* Playback devices enumeration requested */
596 for (it = 0; it < cards; it++) {
597 devices = 0;
598 do {
599 status =
600 snd_card_get_longname(it,
601 qsa_playback_device
602 [qsa_playback_devices].name,
603 QSA_MAX_NAME_LENGTH);
604 if (status == EOK) {
605 snd_pcm_t *handle;
606
607 /* Add device number to device name */
608 sprintf(qsa_playback_device[qsa_playback_devices].name +
609 SDL_strlen(qsa_playback_device
610 [qsa_playback_devices].name), " d%d",
611 devices);
612
613 /* Store associated card number id */
614 qsa_playback_device[qsa_playback_devices].cardno = it;
615
616 /* Check if this device id could play anything */
617 status =
618 snd_pcm_open(&handle, it, devices,
619 SND_PCM_OPEN_PLAYBACK);
620 if (status == EOK) {
621 qsa_playback_device[qsa_playback_devices].deviceno =
622 devices;
623 status = snd_pcm_close(handle);
624 if (status == EOK) {
625 SDL_AddAudioDevice(SDL_FALSE, qsa_playback_device[qsa_playback_devices].name, &qsa_playback_device[qsa_playback_devices]);
626 qsa_playback_devices++;
627 }
628 } else {
629 /* Check if we got end of devices list */
630 if (status == -ENOENT) {
631 break;
632 }
633 }
634 } else {
635 break;
636 }
637
638 /* Check if we reached maximum devices count */
639 if (qsa_playback_devices >= QSA_MAX_DEVICES) {
640 break;
641 }
642 devices++;
643 } while (1);
644
645 /* Check if we reached maximum devices count */
646 if (qsa_playback_devices >= QSA_MAX_DEVICES) {
647 break;
648 }
649 }
650 }
651
652 { /* capture devices */
653 /* Capture devices enumeration requested */
654 for (it = 0; it < cards; it++) {
655 devices = 0;
656 do {
657 status =
658 snd_card_get_longname(it,
659 qsa_capture_device
660 [qsa_capture_devices].name,
661 QSA_MAX_NAME_LENGTH);
662 if (status == EOK) {
663 snd_pcm_t *handle;
664
665 /* Add device number to device name */
666 sprintf(qsa_capture_device[qsa_capture_devices].name +
667 SDL_strlen(qsa_capture_device
668 [qsa_capture_devices].name), " d%d",
669 devices);
670
671 /* Store associated card number id */
672 qsa_capture_device[qsa_capture_devices].cardno = it;
673
674 /* Check if this device id could play anything */
675 status =
676 snd_pcm_open(&handle, it, devices,
677 SND_PCM_OPEN_CAPTURE);
678 if (status == EOK) {
679 qsa_capture_device[qsa_capture_devices].deviceno =
680 devices;
681 status = snd_pcm_close(handle);
682 if (status == EOK) {
683 SDL_AddAudioDevice(SDL_TRUE, qsa_capture_device[qsa_capture_devices].name, &qsa_capture_device[qsa_capture_devices]);
684 qsa_capture_devices++;
685 }
686 } else {
687 /* Check if we got end of devices list */
688 if (status == -ENOENT) {
689 break;
690 }
691 }
692
693 /* Check if we reached maximum devices count */
694 if (qsa_capture_devices >= QSA_MAX_DEVICES) {
695 break;
696 }
697 } else {
698 break;
699 }
700 devices++;
701 } while (1);
702
703 /* Check if we reached maximum devices count */
704 if (qsa_capture_devices >= QSA_MAX_DEVICES) {
705 break;
706 }
707 }
708 }
709 }
710
711 static void
QSA_Deinitialize(void)712 QSA_Deinitialize(void)
713 {
714 /* Clear devices array on shutdown */
715 /* !!! FIXME: we zero these on init...any reason to do it here? */
716 SDL_zero(qsa_playback_device);
717 SDL_zero(qsa_capture_device);
718 qsa_playback_devices = 0;
719 qsa_capture_devices = 0;
720 }
721
722 static int
QSA_Init(SDL_AudioDriverImpl * impl)723 QSA_Init(SDL_AudioDriverImpl * impl)
724 {
725 snd_pcm_t *handle = NULL;
726 int32_t status = 0;
727
728 /* Clear devices array */
729 SDL_zero(qsa_playback_device);
730 SDL_zero(qsa_capture_device);
731 qsa_playback_devices = 0;
732 qsa_capture_devices = 0;
733
734 /* Set function pointers */
735 /* DeviceLock and DeviceUnlock functions are used default, */
736 /* provided by SDL, which uses pthread_mutex for lock/unlock */
737 impl->DetectDevices = QSA_DetectDevices;
738 impl->OpenDevice = QSA_OpenDevice;
739 impl->ThreadInit = QSA_ThreadInit;
740 impl->WaitDevice = QSA_WaitDevice;
741 impl->PlayDevice = QSA_PlayDevice;
742 impl->GetDeviceBuf = QSA_GetDeviceBuf;
743 impl->CloseDevice = QSA_CloseDevice;
744 impl->Deinitialize = QSA_Deinitialize;
745 impl->LockDevice = NULL;
746 impl->UnlockDevice = NULL;
747
748 impl->OnlyHasDefaultOutputDevice = 0;
749 impl->ProvidesOwnCallbackThread = 0;
750 impl->SkipMixerLock = 0;
751 impl->HasCaptureSupport = 1;
752 impl->OnlyHasDefaultOutputDevice = 0;
753 impl->OnlyHasDefaultCaptureDevice = 0;
754
755 /* Check if io-audio manager is running or not */
756 status = snd_cards();
757 if (status == 0) {
758 /* if no, return immediately */
759 return 1;
760 }
761
762 return 1; /* this audio target is available. */
763 }
764
765 AudioBootStrap QSAAUDIO_bootstrap = {
766 "qsa", "QNX QSA Audio", QSA_Init, 0
767 };
768
769 #endif /* SDL_AUDIO_DRIVER_QSA */
770
771 /* vi: set ts=4 sw=4 expandtab: */
772