src/audio/alsa/SDL_alsa_audio.c

/* [<][>]
[^][v][top][bottom][index][help] */

FUNCTIONS

This source file includes following functions.
  1. init_pcm_cparams
  2. Audio_Available
  3. Audio_DeleteDevice
  4. Audio_CreateDevice
  5. PCM_WaitAudio
  6. PCM_PlayAudio
  7. PCM_GetAudioBuf
  8. PCM_CloseAudio
  9. PCM_OpenAudio

   1 /*
   2     SDL - Simple DirectMedia Layer
   3     Copyright (C) 1997, 1998, 1999, 2000, 2001  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@devolution.com
  21 */
  22 
  23 
  24 
  25 /* Allow access to a raw mixing buffer */
  26 
  27 #include <stdlib.h>
  28 #include <stdio.h>
  29 #include <string.h>
  30 #include <errno.h>
  31 #include <unistd.h>
  32 #include <fcntl.h>
  33 #include <signal.h>
  34 #include <sys/types.h>
  35 #include <sys/time.h>
  36 
  37 #include "SDL_audio.h"
  38 #include "SDL_error.h"
  39 #include "SDL_audiomem.h"
  40 #include "SDL_audio_c.h"
  41 #include "SDL_timer.h"
  42 #include "SDL_alsa_audio.h"
  43 
  44 /* The tag name used by ALSA audio */
  45 #define DRIVER_NAME         "alsa"
  46 
  47 /* default card and device numbers as listed in dev/snd */
  48 static int card_no = 0;
  49 static int device_no = 0;
  50 
  51 /* default channel communication parameters */
  52 #define DEFAULT_CPARAMS_RATE 22050
  53 #define DEFAULT_CPARAMS_VOICES 1
  54 #define DEFAULT_CPARAMS_FRAG_SIZE 512
  55 #define DEFAULT_CPARAMS_FRAGS_MIN 1
  56 #define DEFAULT_CPARAMS_FRAGS_MAX -1
  57 
  58 /* Open the audio device for playback, and don't block if busy */
  59 #define OPEN_FLAGS      (SND_PCM_OPEN_PLAYBACK|SND_PCM_OPEN_NONBLOCK)
  60 
  61 /* Audio driver functions */
  62 static int PCM_OpenAudio(_THIS, SDL_AudioSpec *spec);
  63 static void PCM_WaitAudio(_THIS);
  64 static void PCM_PlayAudio(_THIS);
  65 static Uint8 *PCM_GetAudioBuf(_THIS);
  66 static void PCM_CloseAudio(_THIS);
  67 
  68 /* PCM transfer channel parameters initialize function */
  69 static void init_pcm_cparams(snd_pcm_channel_params_t* cparams)
     /* [<][>][^][v][top][bottom][index][help] */
  70 {
  71         memset(cparams,0,sizeof(snd_pcm_channel_params_t));
  72 
  73         cparams->channel = SND_PCM_CHANNEL_PLAYBACK;
  74         cparams->mode = SND_PCM_MODE_BLOCK;
  75         cparams->start_mode = SND_PCM_START_DATA; //_FULL
  76         cparams->stop_mode  = SND_PCM_STOP_STOP;
  77         cparams->format.format = SND_PCM_SFMT_S16_LE;
  78         cparams->format.interleave = 1;
  79         cparams->format.rate = DEFAULT_CPARAMS_RATE;
  80         cparams->format.voices = DEFAULT_CPARAMS_VOICES;
  81         cparams->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE;
  82         cparams->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN;
  83         cparams->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX;
  84 }
  85 
  86 /* Audio driver bootstrap functions */
  87 
  88 static int Audio_Available(void)
     /* [<][>][^][v][top][bottom][index][help] */
  89 /*
  90         See if we can open a nonblocking channel.
  91         Return value '1' means we can.
  92         Return value '0' means we cannot.
  93 */
  94 {
  95         int available;
  96         int rval;
  97         snd_pcm_t *handle;
  98         snd_pcm_channel_params_t cparams;
  99 #ifdef DEBUG_AUDIO
 100         snd_pcm_channel_status_t cstatus;
 101 #endif
 102 
 103         available = 0;
 104         handle = NULL;
 105 
 106         init_pcm_cparams(&cparams);
 107         
 108         rval = snd_pcm_open(&handle, card_no, device_no, OPEN_FLAGS);
 109         if (rval >= 0)
 110         {
 111                 rval = snd_pcm_plugin_params(handle, &cparams);
 112 
 113 #ifdef DEBUG_AUDIO
 114                 snd_pcm_plugin_status(handle, &cstatus);
 115                 printf("status after snd_pcm_plugin_params call = %d\n",cstatus.status);
 116 #endif
 117                 if (rval >= 0)
 118                 {
 119                         available = 1;
 120                 }
 121                 else
 122                 {
 123                         SDL_SetError("snd_pcm_channel_params failed: %s\n", snd_strerror (rval));
 124                 }
 125 
 126         if ((rval = snd_pcm_close(handle)) < 0)
 127         {
 128             SDL_SetError("snd_pcm_close failed: %s\n",snd_strerror(rval));
 129                         available = 0;
 130         }
 131         }
 132         else
 133         {
 134        SDL_SetError("snd_pcm_open failed: %s\n", snd_strerror(rval));
 135         }
 136 
 137         return(available);
 138 }
 139 
 140 static void Audio_DeleteDevice(SDL_AudioDevice *device)
     /* [<][>][^][v][top][bottom][index][help] */
 141 {
 142         free(device->hidden);
 143         free(device);
 144 }
 145 
 146 static SDL_AudioDevice *Audio_CreateDevice(int devindex)
     /* [<][>][^][v][top][bottom][index][help] */
 147 {
 148         SDL_AudioDevice *this;
 149 
 150         /* Initialize all variables that we clean on shutdown */
 151         this = (SDL_AudioDevice *)malloc(sizeof(SDL_AudioDevice));
 152         if ( this ) {
 153                 memset(this, 0, (sizeof *this));
 154                 this->hidden = (struct SDL_PrivateAudioData *)
 155                                 malloc((sizeof *this->hidden));
 156         }
 157         if ( (this == NULL) || (this->hidden == NULL) ) {
 158                 SDL_OutOfMemory();
 159                 if ( this ) {
 160                         free(this);
 161                 }
 162                 return(0);
 163         }
 164         memset(this->hidden, 0, (sizeof *this->hidden));
 165         audio_handle = NULL;
 166 
 167         /* Set the function pointers */
 168         this->OpenAudio = PCM_OpenAudio;
 169         this->WaitAudio = PCM_WaitAudio;
 170         this->PlayAudio = PCM_PlayAudio;
 171         this->GetAudioBuf = PCM_GetAudioBuf;
 172         this->CloseAudio = PCM_CloseAudio;
 173 
 174         this->free = Audio_DeleteDevice;
 175 
 176         return this;
 177 }
 178 
 179 AudioBootStrap ALSA_bootstrap = {
 180         DRIVER_NAME, "ALSA PCM audio",
 181         Audio_Available, Audio_CreateDevice
 182 };
 183 
 184 /* This function waits until it is possible to write a full sound buffer */
 185 static void PCM_WaitAudio(_THIS)
     /* [<][>][^][v][top][bottom][index][help] */
 186 {
 187 
 188         /* Check to see if the thread-parent process is still alive */
 189         { static int cnt = 0;
 190                 /* Note that this only works with thread implementations 
 191                    that use a different process id for each thread.
 192                 */
 193                 if (parent && (((++cnt)%10) == 0)) { /* Check every 10 loops */
 194                         if ( kill(parent, 0) < 0 ) {
 195                                 this->enabled = 0;
 196                         }
 197                 }
 198         }
 199 
 200         /* See if we need to use timed audio synchronization */
 201         if ( frame_ticks ) 
 202         {
 203                 /* Use timer for general audio synchronization */
 204                 Sint32 ticks;
 205 
 206                 ticks = ((Sint32)(next_frame - SDL_GetTicks()))-FUDGE_TICKS;
 207                 if ( ticks > 0 ) 
 208                 {
 209                         SDL_Delay(ticks);
 210                 }
 211         }
 212     else 
 213         {
 214         /* Use select() for audio synchronization */
 215                 fd_set fdset;
 216             struct timeval timeout;
 217         FD_ZERO(&fdset);
 218             FD_SET(audio_fd, &fdset);
 219         timeout.tv_sec = 10;
 220             timeout.tv_usec = 0;
 221 #ifdef DEBUG_AUDIO
 222         fprintf(stderr, "Waiting for audio to get ready\n");
 223 #endif
 224             if ( select(audio_fd+1, NULL, &fdset, NULL, &timeout) <= 0 ) 
 225                 {
 226             const char *message =
 227             "Audio timeout - buggy audio driver? (disabled)";
 228                 /* In general we should never print to the screen,
 229                but in this case we have no other way of letting
 230                the user know what happened.
 231             */
 232             fprintf(stderr, "SDL: %s\n", message);
 233                 this->enabled = 0;
 234             /* Don't try to close - may hang */
 235             audio_fd = -1;
 236 #ifdef DEBUG_AUDIO
 237             fprintf(stderr, "Done disabling audio\n");
 238 #endif
 239             }
 240 #ifdef DEBUG_AUDIO
 241         fprintf(stderr, "Ready!\n");
 242 #endif
 243     }
 244 }
 245 
 246 static snd_pcm_channel_status_t cstatus;
 247 
 248 static void PCM_PlayAudio(_THIS)
     /* [<][>][^][v][top][bottom][index][help] */
 249 {
 250     int written, rval;
 251 
 252     /* Write the audio data, checking for EAGAIN (buffer full) and underrun */
 253     do {
 254                 written = snd_pcm_plugin_write(audio_handle, pcm_buf, pcm_len);
 255 #ifdef DEBUG_AUDIO
 256                 fprintf(stderr, "written = %d pcm_len = %d\n",written,pcm_len);
 257 #endif
 258                 if (written != pcm_len)
 259                 {
 260                 if (errno == EAGAIN) 
 261                         {
 262                 SDL_Delay(1);   /* Let a little CPU time go by and try to write again */
 263 #ifdef DEBUG_AUDIO
 264                                 fprintf(stderr, "errno == EAGAIN\n");
 265 #endif
 266                 }
 267                         else
 268                         {
 269                         if( (rval = snd_pcm_plugin_status(audio_handle, &cstatus)) < 0 )
 270                         {
 271                             SDL_SetError("snd_pcm_plugin_status failed: %s\n", snd_strerror(rval));
 272                             return;
 273                         }
 274                                 if ( (cstatus.status == SND_PCM_STATUS_UNDERRUN)
 275                                         ||(cstatus.status == SND_PCM_STATUS_READY) )
 276                                 {
 277 #ifdef DEBUG_AUDIO
 278                                         fprintf(stderr, "buffer underrun\n");
 279 #endif
 280                                         if ( (rval = snd_pcm_plugin_prepare (audio_handle,SND_PCM_CHANNEL_PLAYBACK)) < 0 )
 281                                         {
 282                                                 SDL_SetError("snd_pcm_plugin_prepare failed: %s\n",snd_strerror(rval) );
 283                                                 return;
 284                                         }
 285                                         /* if we reach here, try to write again */
 286                                 }
 287                         }
 288                 }
 289     } while ( (written < 0) && ((errno == 0) || (errno == EAGAIN)) );
 290 
 291     /* Set the next write frame */
 292    if ( frame_ticks ) {
 293             next_frame += frame_ticks;
 294         }
 295 
 296     /* If we couldn't write, assume fatal error for now */
 297     if ( written < 0 ) {
 298         this->enabled = 0;
 299     }
 300         return;
 301 }
 302 
 303 static Uint8 *PCM_GetAudioBuf(_THIS)
     /* [<][>][^][v][top][bottom][index][help] */
 304 {
 305         return(pcm_buf);
 306 }
 307 
 308 static void PCM_CloseAudio(_THIS)
     /* [<][>][^][v][top][bottom][index][help] */
 309 {
 310         int rval;
 311 
 312         if ( pcm_buf != NULL ) {
 313                 free(pcm_buf);
 314                 pcm_buf = NULL;
 315         }
 316         if ( audio_handle != NULL ) {
 317                 if ((rval = snd_pcm_plugin_flush(audio_handle,SND_PCM_CHANNEL_PLAYBACK)) < 0)
 318                 {
 319                 SDL_SetError("snd_pcm_plugin_flush failed: %s\n",snd_strerror(rval));
 320                         return;
 321                 }
 322                 if ((rval = snd_pcm_close(audio_handle)) < 0)
 323                 {
 324                         SDL_SetError("snd_pcm_close failed: %s\n",snd_strerror(rval));
 325                         return;
 326                 }
 327                 audio_handle = NULL;
 328         }
 329 }
 330 
 331 static int PCM_OpenAudio(_THIS, SDL_AudioSpec *spec)
     /* [<][>][^][v][top][bottom][index][help] */
 332 {
 333         int rval;
 334         snd_pcm_channel_params_t cparams;
 335         snd_pcm_channel_setup_t  csetup;
 336         int format;
 337         Uint16 test_format;
 338         int twidth;
 339 
 340         /* initialize channel transfer parameters to default */
 341         init_pcm_cparams(&cparams);
 342 
 343         /* Reset the timer synchronization flag */
 344         frame_ticks = 0.0;
 345 
 346         /* Open the audio device */
 347         
 348         rval = snd_pcm_open(&audio_handle, card_no, device_no, OPEN_FLAGS);
 349         if ( rval < 0 ) {
 350                 SDL_SetError("snd_pcm_open failed: %s\n", snd_strerror(rval));
 351                 return(-1);
 352         }
 353 
 354 #ifdef PLUGIN_DISABLE_MMAP /* This is gone in newer versions of ALSA? */
 355     /* disable count status parameter */
 356     if ((rval = snd_plugin_set_disable(audio_handle, PLUGIN_DISABLE_MMAP))<0)
 357     {
 358         SDL_SetError("snd_plugin_set_disable failed: %s\n", snd_strerror(rval));
 359         return(-1);
 360     }
 361 #endif
 362 
 363         pcm_buf = NULL;
 364 
 365         /* Try for a closest match on audio format */
 366         format = 0;
 367         for ( test_format = SDL_FirstAudioFormat(spec->format);
 368                                                 ! format && test_format; ) 
 369         {
 370 #ifdef DEBUG_AUDIO
 371                 fprintf(stderr, "Trying format 0x%4.4x spec->samples %d\n", test_format,spec->samples);
 372 #endif
 373                         /* if match found set format to equivalent ALSA format */
 374         switch ( test_format ) {
 375                         case AUDIO_U8:
 376                                 format = SND_PCM_SFMT_U8;
 377                                 cparams.buf.block.frag_size = spec->samples * spec->channels;
 378                                 break;
 379                         case AUDIO_S8:
 380                                 format = SND_PCM_SFMT_S8;
 381                                 cparams.buf.block.frag_size = spec->samples * spec->channels;
 382                                 break;
 383                         case AUDIO_S16LSB:
 384                                 format = SND_PCM_SFMT_S16_LE;
 385                                 cparams.buf.block.frag_size = spec->samples*2 * spec->channels;
 386                                 break;
 387                         case AUDIO_S16MSB:
 388                                 format = SND_PCM_SFMT_S16_BE;
 389                                 cparams.buf.block.frag_size = spec->samples*2 * spec->channels;
 390                                 break;
 391                         case AUDIO_U16LSB:
 392                                 format = SND_PCM_SFMT_U16_LE;
 393                                 cparams.buf.block.frag_size = spec->samples*2 * spec->channels;
 394                                 break;
 395                         case AUDIO_U16MSB:
 396                                 format = SND_PCM_SFMT_U16_BE;
 397                                 cparams.buf.block.frag_size = spec->samples*2 * spec->channels;
 398                                 break;
 399                         default:
 400                                 break;
 401                 }
 402                 if ( ! format ) {
 403                         test_format = SDL_NextAudioFormat();
 404                 }
 405         }
 406         if ( format == 0 ) {
 407                 SDL_SetError("Couldn't find any hardware audio formats");
 408                 return(-1);
 409         }
 410         spec->format = test_format;
 411 
 412         /* Set the audio format */
 413         cparams.format.format = format;
 414 
 415         /* Set mono or stereo audio (currently only two channels supported) */
 416         cparams.format.voices = spec->channels;
 417         
 418         #ifdef DEBUG_AUDIO
 419         printf("intializing channels %d\n", cparams.format.voices);
 420         #endif
 421         
 422         /* Set rate */
 423         cparams.format.rate = spec->freq ;
 424 
 425         /* Setup the transfer parameters according to cparams */
 426         rval = snd_pcm_plugin_params(audio_handle, &cparams);
 427         if (rval < 0) {
 428                 SDL_SetError("snd_pcm_channel_params failed: %s\n", snd_strerror (rval));
 429                 return(-1);
 430         }
 431 
 432     /*  Make sure channel is setup right one last time */
 433     memset( &csetup, 0, sizeof( csetup ) );
 434     csetup.channel = SND_PCM_CHANNEL_PLAYBACK;
 435     if ( snd_pcm_plugin_setup( audio_handle, &csetup ) < 0 )
 436     {
 437         SDL_SetError("Unable to setup playback channel\n" );
 438         return(-1);
 439     }
 440 
 441 #ifdef DEBUG_AUDIO
 442     else
 443     {
 444         fprintf(stderr,"requested format: %d\n",cparams.format.format);
 445         fprintf(stderr,"requested frag size: %d\n",cparams.buf.block.frag_size);
 446         fprintf(stderr,"requested max frags: %d\n\n",cparams.buf.block.frags_max);
 447 
 448         fprintf(stderr,"real format: %d\n", csetup.format.format );
 449         fprintf(stderr,"real frag size : %d\n", csetup.buf.block.frag_size );
 450                 fprintf(stderr,"real max frags : %d\n", csetup.buf.block.frags_max );
 451     }
 452 #endif // DEBUG_AUDIO
 453 
 454     /*  Allocate memory to the audio buffer and initialize with silence
 455         (Note that buffer size must be a multiple of fragment size, so find closest multiple)
 456     */
 457     
 458     twidth = snd_pcm_format_width(format);
 459     if (twidth < 0) {
 460         printf("snd_pcm_format_width failed\n");
 461         twidth = 0;
 462     }
 463 #ifdef DEBUG_AUDIO
 464     printf("format is %d bits wide\n",twidth);
 465 #endif      
 466     
 467     pcm_len = csetup.buf.block.frag_size * (twidth/8) * csetup.format.voices ;
 468     
 469 #ifdef DEBUG_AUDIO    
 470     printf("pcm_len set to %d\n", pcm_len);
 471 #endif
 472     
 473     if (pcm_len == 0)
 474     {
 475         pcm_len = csetup.buf.block.frag_size;
 476     }
 477     
 478     pcm_buf = (Uint8*)malloc(pcm_len);
 479     if (pcm_buf == NULL) {
 480         SDL_SetError("pcm_buf malloc failed\n");
 481         return(-1);
 482     }
 483     memset(pcm_buf,spec->silence,pcm_len);
 484 
 485 #ifdef DEBUG_AUDIO
 486         fprintf(stderr,"pcm_buf malloced and silenced.\n");
 487 #endif
 488 
 489     /* get the file descriptor */
 490     if( (audio_fd = snd_pcm_file_descriptor(audio_handle, device_no)) < 0)
 491     {
 492        fprintf(stderr, "snd_pcm_file_descriptor failed with error code: %d\n", audio_fd);
 493     }
 494 
 495         /* Trigger audio playback */
 496         rval = snd_pcm_plugin_prepare( audio_handle, SND_PCM_CHANNEL_PLAYBACK);
 497         if (rval < 0) {
 498        SDL_SetError("snd_pcm_plugin_prepare failed: %s\n", snd_strerror (rval));
 499        return(-1);
 500         }
 501         rval =  snd_pcm_playback_go(audio_handle);
 502     if (rval < 0) {
 503        SDL_SetError("snd_pcm_playback_go failed: %s\n", snd_strerror (rval));
 504        return(-1);
 505     }
 506 
 507     /* Check to see if we need to use select() workaround */
 508     { char *workaround;
 509         workaround = getenv("SDL_DSP_NOSELECT");
 510         if ( workaround ) {
 511             frame_ticks = (float)(spec->samples*1000)/spec->freq;
 512             next_frame = SDL_GetTicks()+frame_ticks;
 513         }
 514     }
 515 
 516         /* Get the parent process id (we're the parent of the audio thread) */
 517         parent = getpid();
 518 
 519         /* We're ready to rock and roll. :-) */
 520         return(0);
 521 }

/* [<][>][^][v][top][bottom][index][help] */