1 /* 2 SDL - Simple DirectMedia Layer 3 Copyright (C) 1997-2012 Sam Lantinga 4 5 This library is free software; you can redistribute it and/or 6 modify it under the terms of the GNU Library General Public 7 License as published by the Free Software Foundation; either 8 version 2 of the License, or (at your option) any later version. 9 10 This library is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Library General Public License for more details. 14 15 You should have received a copy of the GNU Library General Public 16 License along with this library; if not, write to the Free 17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 19 Sam Lantinga 20 slouken (at) libsdl.org 21 */ 22 #include "SDL_config.h" 23 24 #include <errno.h> 25 #include <unistd.h> 26 #include <fcntl.h> 27 #include <signal.h> 28 #include <sys/types.h> 29 #include <sys/time.h> 30 #include <sched.h> 31 #include <sys/select.h> 32 #include <sys/neutrino.h> 33 #include <sys/asoundlib.h> 34 35 #include "SDL_timer.h" 36 #include "SDL_audio.h" 37 #include "../SDL_audiomem.h" 38 #include "../SDL_audio_c.h" 39 #include "SDL_nto_audio.h" 40 41 /* The tag name used by NTO audio */ 42 #define DRIVER_NAME "qsa-nto" 43 44 /* default channel communication parameters */ 45 #define DEFAULT_CPARAMS_RATE 22050 46 #define DEFAULT_CPARAMS_VOICES 1 47 /* FIXME: need to add in the near future flexible logic with frag_size and frags count */ 48 #define DEFAULT_CPARAMS_FRAG_SIZE 4096 49 #define DEFAULT_CPARAMS_FRAGS_MIN 1 50 #define DEFAULT_CPARAMS_FRAGS_MAX 1 51 52 /* Open the audio device for playback, and don't block if busy */ 53 #define OPEN_FLAGS SND_PCM_OPEN_PLAYBACK 54 55 #define QSA_NO_WORKAROUNDS 0x00000000 56 #define QSA_MMAP_WORKAROUND 0x00000001 57 58 struct BuggyCards 59 { 60 char* cardname; 61 unsigned long bugtype; 62 }; 63 64 #define QSA_WA_CARDS 3 65 66 struct BuggyCards buggycards[QSA_WA_CARDS]= 67 { 68 {"Sound Blaster Live!", QSA_MMAP_WORKAROUND}, 69 {"Vortex 8820", QSA_MMAP_WORKAROUND}, 70 {"Vortex 8830", QSA_MMAP_WORKAROUND}, 71 }; 72 73 /* Audio driver functions */ 74 static void NTO_ThreadInit(_THIS); 75 static int NTO_OpenAudio(_THIS, SDL_AudioSpec* spec); 76 static void NTO_WaitAudio(_THIS); 77 static void NTO_PlayAudio(_THIS); 78 static Uint8* NTO_GetAudioBuf(_THIS); 79 static void NTO_CloseAudio(_THIS); 80 81 /* card names check to apply the workarounds */ 82 static int NTO_CheckBuggyCards(_THIS, unsigned long checkfor) 83 { 84 char scardname[33]; 85 int it; 86 87 if (snd_card_get_name(cardno, scardname, 32)<0) 88 { 89 return 0; 90 } 91 92 for (it=0; it<QSA_WA_CARDS; it++) 93 { 94 if (SDL_strcmp(buggycards[it].cardname, scardname)==0) 95 { 96 if (buggycards[it].bugtype==checkfor) 97 { 98 return 1; 99 } 100 } 101 } 102 103 return 0; 104 } 105 106 static void NTO_ThreadInit(_THIS) 107 { 108 int status; 109 struct sched_param param; 110 111 /* increasing default 10 priority to 25 to avoid jerky sound */ 112 status=SchedGet(0, 0, ¶m); 113 param.sched_priority=param.sched_curpriority+15; 114 status=SchedSet(0, 0, SCHED_NOCHANGE, ¶m); 115 } 116 117 /* PCM transfer channel parameters initialize function */ 118 static void NTO_InitAudioParams(snd_pcm_channel_params_t* cpars) 119 { 120 SDL_memset(cpars, 0, sizeof(snd_pcm_channel_params_t)); 121 122 cpars->channel = SND_PCM_CHANNEL_PLAYBACK; 123 cpars->mode = SND_PCM_MODE_BLOCK; 124 cpars->start_mode = SND_PCM_START_DATA; 125 cpars->stop_mode = SND_PCM_STOP_STOP; 126 cpars->format.format = SND_PCM_SFMT_S16_LE; 127 cpars->format.interleave = 1; 128 cpars->format.rate = DEFAULT_CPARAMS_RATE; 129 cpars->format.voices = DEFAULT_CPARAMS_VOICES; 130 cpars->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE; 131 cpars->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN; 132 cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX; 133 } 134 135 static int NTO_AudioAvailable(void) 136 { 137 /* See if we can open a nonblocking channel. 138 Return value '1' means we can. 139 Return value '0' means we cannot. */ 140 141 int available; 142 int rval; 143 snd_pcm_t* handle; 144 145 available = 0; 146 handle = NULL; 147 148 rval = snd_pcm_open_preferred(&handle, NULL, NULL, OPEN_FLAGS); 149 150 if (rval >= 0) 151 { 152 available = 1; 153 154 if ((rval = snd_pcm_close(handle)) < 0) 155 { 156 SDL_SetError("NTO_AudioAvailable(): snd_pcm_close failed: %s\n", snd_strerror(rval)); 157 available = 0; 158 } 159 } 160 else 161 { 162 SDL_SetError("NTO_AudioAvailable(): there are no available audio devices.\n"); 163 } 164 165 return (available); 166 } 167 168 static void NTO_DeleteAudioDevice(SDL_AudioDevice *device) 169 { 170 if ((device)&&(device->hidden)) 171 { 172 SDL_free(device->hidden); 173 } 174 if (device) 175 { 176 SDL_free(device); 177 } 178 } 179 180 static SDL_AudioDevice* NTO_CreateAudioDevice(int devindex) 181 { 182 SDL_AudioDevice *this; 183 184 /* Initialize all variables that we clean on shutdown */ 185 this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice)); 186 if (this) 187 { 188 SDL_memset(this, 0, sizeof(SDL_AudioDevice)); 189 this->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(struct SDL_PrivateAudioData)); 190 } 191 if ((this == NULL) || (this->hidden == NULL)) 192 { 193 SDL_OutOfMemory(); 194 if (this) 195 { 196 SDL_free(this); 197 } 198 return (0); 199 } 200 SDL_memset(this->hidden, 0, sizeof(struct SDL_PrivateAudioData)); 201 audio_handle = NULL; 202 203 /* Set the function pointers */ 204 this->ThreadInit = NTO_ThreadInit; 205 this->OpenAudio = NTO_OpenAudio; 206 this->WaitAudio = NTO_WaitAudio; 207 this->PlayAudio = NTO_PlayAudio; 208 this->GetAudioBuf = NTO_GetAudioBuf; 209 this->CloseAudio = NTO_CloseAudio; 210 211 this->free = NTO_DeleteAudioDevice; 212 213 return this; 214 } 215 216 AudioBootStrap QNXNTOAUDIO_bootstrap = 217 { 218 DRIVER_NAME, "QNX6 QSA-NTO Audio", 219 NTO_AudioAvailable, 220 NTO_CreateAudioDevice 221 }; 222 223 /* This function waits until it is possible to write a full sound buffer */ 224 static void NTO_WaitAudio(_THIS) 225 { 226 fd_set wfds; 227 int selectret; 228 229 FD_ZERO(&wfds); 230 FD_SET(audio_fd, &wfds); 231 232 do { 233 selectret=select(audio_fd + 1, NULL, &wfds, NULL, NULL); 234 switch (selectret) 235 { 236 case -1: 237 case 0: SDL_SetError("NTO_WaitAudio(): select() failed: %s\n", strerror(errno)); 238 return; 239 default: if (FD_ISSET(audio_fd, &wfds)) 240 { 241 return; 242 } 243 break; 244 } 245 } while(1); 246 } 247 248 static void NTO_PlayAudio(_THIS) 249 { 250 int written, rval; 251 int towrite; 252 void* pcmbuffer; 253 254 if (!this->enabled) 255 { 256 return; 257 } 258 259 towrite = this->spec.size; 260 pcmbuffer = pcm_buf; 261 262 /* Write the audio data, checking for EAGAIN (buffer full) and underrun */ 263 do { 264 written = snd_pcm_plugin_write(audio_handle, pcm_buf, towrite); 265 if (written != towrite) 266 { 267 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) 268 { 269 /* Let a little CPU time go by and try to write again */ 270 SDL_Delay(1); 271 /* if we wrote some data */ 272 towrite -= written; 273 pcmbuffer += written * this->spec.channels; 274 continue; 275 } 276 else 277 { 278 if ((errno == EINVAL) || (errno == EIO)) 279 { 280 SDL_memset(&cstatus, 0, sizeof(cstatus)); 281 cstatus.channel = SND_PCM_CHANNEL_PLAYBACK; 282 if ((rval = snd_pcm_plugin_status(audio_handle, &cstatus)) < 0) 283 { 284 SDL_SetError("NTO_PlayAudio(): snd_pcm_plugin_status failed: %s\n", snd_strerror(rval)); 285 return; 286 } 287 if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY)) 288 { 289 if ((rval = snd_pcm_plugin_prepare(audio_handle, SND_PCM_CHANNEL_PLAYBACK)) < 0) 290 { 291 SDL_SetError("NTO_PlayAudio(): snd_pcm_plugin_prepare failed: %s\n", snd_strerror(rval)); 292 return; 293 } 294 } 295 continue; 296 } 297 else 298 { 299 return; 300 } 301 } 302 } 303 else 304 { 305 /* we wrote all remaining data */ 306 towrite -= written; 307 pcmbuffer += written * this->spec.channels; 308 } 309 } while ((towrite > 0) && (this->enabled)); 310 311 /* If we couldn't write, assume fatal error for now */ 312 if (towrite != 0) 313 { 314 this->enabled = 0; 315 } 316 317 return; 318 } 319 320 static Uint8* NTO_GetAudioBuf(_THIS) 321 { 322 return pcm_buf; 323 } 324 325 static void NTO_CloseAudio(_THIS) 326 { 327 int rval; 328 329 this->enabled = 0; 330 331 if (audio_handle != NULL) 332 { 333 if ((rval = snd_pcm_plugin_flush(audio_handle, SND_PCM_CHANNEL_PLAYBACK)) < 0) 334 { 335 SDL_SetError("NTO_CloseAudio(): snd_pcm_plugin_flush failed: %s\n", snd_strerror(rval)); 336 return; 337 } 338 if ((rval = snd_pcm_close(audio_handle)) < 0) 339 { 340 SDL_SetError("NTO_CloseAudio(): snd_pcm_close failed: %s\n",snd_strerror(rval)); 341 return; 342 } 343 audio_handle = NULL; 344 } 345 } 346 347 static int NTO_OpenAudio(_THIS, SDL_AudioSpec* spec) 348 { 349 int rval; 350 int format; 351 Uint16 test_format; 352 int found; 353 354 audio_handle = NULL; 355 this->enabled = 0; 356 357 if (pcm_buf != NULL) 358 { 359 SDL_FreeAudioMem(pcm_buf); 360 pcm_buf = NULL; 361 } 362 363 /* initialize channel transfer parameters to default */ 364 NTO_InitAudioParams(&cparams); 365 366 /* Open the audio device */ 367 rval = snd_pcm_open_preferred(&audio_handle, &cardno, &deviceno, OPEN_FLAGS); 368 if (rval < 0) 369 { 370 SDL_SetError("NTO_OpenAudio(): snd_pcm_open failed: %s\n", snd_strerror(rval)); 371 return (-1); 372 } 373 374 if (!NTO_CheckBuggyCards(this, QSA_MMAP_WORKAROUND)) 375 { 376 /* enable count status parameter */ 377 if ((rval = snd_pcm_plugin_set_disable(audio_handle, PLUGIN_DISABLE_MMAP)) < 0) 378 { 379 SDL_SetError("snd_pcm_plugin_set_disable failed: %s\n", snd_strerror(rval)); 380 return (-1); 381 } 382 } 383 384 /* Try for a closest match on audio format */ 385 format = 0; 386 /* can't use format as SND_PCM_SFMT_U8 = 0 in nto */ 387 found = 0; 388 389 for (test_format=SDL_FirstAudioFormat(spec->format); !found ;) 390 { 391 /* if match found set format to equivalent ALSA format */ 392 switch (test_format) 393 { 394 case AUDIO_U8: 395 format = SND_PCM_SFMT_U8; 396 found = 1; 397 break; 398 case AUDIO_S8: 399 format = SND_PCM_SFMT_S8; 400 found = 1; 401 break; 402 case AUDIO_S16LSB: 403 format = SND_PCM_SFMT_S16_LE; 404 found = 1; 405 break; 406 case AUDIO_S16MSB: 407 format = SND_PCM_SFMT_S16_BE; 408 found = 1; 409 break; 410 case AUDIO_U16LSB: 411 format = SND_PCM_SFMT_U16_LE; 412 found = 1; 413 break; 414 case AUDIO_U16MSB: 415 format = SND_PCM_SFMT_U16_BE; 416 found = 1; 417 break; 418 default: 419 break; 420 } 421 422 if (!found) 423 { 424 test_format = SDL_NextAudioFormat(); 425 } 426 } 427 428 /* assumes test_format not 0 on success */ 429 if (test_format == 0) 430 { 431 SDL_SetError("NTO_OpenAudio(): Couldn't find any hardware audio formats"); 432 return (-1); 433 } 434 435 spec->format = test_format; 436 437 /* Set the audio format */ 438 cparams.format.format = format; 439 440 /* Set mono or stereo audio (currently only two channels supported) */ 441 cparams.format.voices = spec->channels; 442 443 /* Set rate */ 444 cparams.format.rate = spec->freq; 445 446 /* Setup the transfer parameters according to cparams */ 447 rval = snd_pcm_plugin_params(audio_handle, &cparams); 448 if (rval < 0) 449 { 450 SDL_SetError("NTO_OpenAudio(): snd_pcm_channel_params failed: %s\n", snd_strerror(rval)); 451 return (-1); 452 } 453 454 /* Make sure channel is setup right one last time */ 455 SDL_memset(&csetup, 0x00, sizeof(csetup)); 456 csetup.channel = SND_PCM_CHANNEL_PLAYBACK; 457 if (snd_pcm_plugin_setup(audio_handle, &csetup) < 0) 458 { 459 SDL_SetError("NTO_OpenAudio(): Unable to setup playback channel\n"); 460 return -1; 461 } 462 463 464 /* Calculate the final parameters for this audio specification */ 465 SDL_CalculateAudioSpec(spec); 466 467 pcm_len = spec->size; 468 469 if (pcm_len==0) 470 { 471 pcm_len = csetup.buf.block.frag_size * spec->channels * (snd_pcm_format_width(format)/8); 472 } 473 474 /* Allocate memory to the audio buffer and initialize with silence (Note that 475 buffer size must be a multiple of fragment size, so find closest multiple) 476 */ 477 pcm_buf = (Uint8*)SDL_AllocAudioMem(pcm_len); 478 if (pcm_buf == NULL) 479 { 480 SDL_SetError("NTO_OpenAudio(): pcm buffer allocation failed\n"); 481 return (-1); 482 } 483 SDL_memset(pcm_buf, spec->silence, pcm_len); 484 485 /* get the file descriptor */ 486 if ((audio_fd = snd_pcm_file_descriptor(audio_handle, SND_PCM_CHANNEL_PLAYBACK)) < 0) 487 { 488 SDL_SetError("NTO_OpenAudio(): snd_pcm_file_descriptor failed with error code: %s\n", snd_strerror(rval)); 489 return (-1); 490 } 491 492 /* Trigger audio playback */ 493 rval = snd_pcm_plugin_prepare(audio_handle, SND_PCM_CHANNEL_PLAYBACK); 494 if (rval < 0) 495 { 496 SDL_SetError("snd_pcm_plugin_prepare failed: %s\n", snd_strerror(rval)); 497 return (-1); 498 } 499 500 this->enabled = 1; 501 502 /* Get the parent process id (we're the parent of the audio thread) */ 503 parent = getpid(); 504 505 /* We're really ready to rock and roll. :-) */ 506 return (0); 507 } 508