diff src/audio/alsa/SDL_alsa_audio.c @ 3627:631173ffd68f

Merged r4991:5154 from branches/SDL-1.2/src/audio/alsa: many 1.2.14 ALSA fixes.
author Ryan C. Gordon <icculus@icculus.org>
date Sun, 10 Jan 2010 07:40:12 +0000
parents 4e83cdb58134
children f7b03b6838cb
line wrap: on
line diff
--- a/src/audio/alsa/SDL_alsa_audio.c	Sun Jan 10 06:32:41 2010 +0000
+++ b/src/audio/alsa/SDL_alsa_audio.c	Sun Jan 10 07:40:12 2010 +0000
@@ -41,20 +41,19 @@
 /* The tag name used by ALSA audio */
 #define DRIVER_NAME         "alsa"
 
-/* The default ALSA audio driver */
-#define DEFAULT_DEVICE	"default"
-
 static int (*ALSA_snd_pcm_open)
   (snd_pcm_t **, const char *, snd_pcm_stream_t, int);
 static int (*ALSA_snd_pcm_close) (snd_pcm_t * pcm);
 static snd_pcm_sframes_t(*ALSA_snd_pcm_writei)
   (snd_pcm_t *, const void *, snd_pcm_uframes_t);
-static int (*ALSA_snd_pcm_resume) (snd_pcm_t *);
+static int (*ALSA_snd_pcm_recover) (snd_pcm_t *, int, int);
 static int (*ALSA_snd_pcm_prepare) (snd_pcm_t *);
 static int (*ALSA_snd_pcm_drain) (snd_pcm_t *);
 static const char *(*ALSA_snd_strerror) (int);
 static size_t(*ALSA_snd_pcm_hw_params_sizeof) (void);
 static size_t(*ALSA_snd_pcm_sw_params_sizeof) (void);
+static void (*ALSA_snd_pcm_hw_params_copy)
+  (snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *);
 static int (*ALSA_snd_pcm_hw_params_any) (snd_pcm_t *, snd_pcm_hw_params_t *);
 static int (*ALSA_snd_pcm_hw_params_set_access)
   (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
@@ -62,26 +61,30 @@
   (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
 static int (*ALSA_snd_pcm_hw_params_set_channels)
   (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
+static int (*ALSA_snd_pcm_hw_params_get_channels)
+  (const snd_pcm_hw_params_t *, unsigned int *);
 static int (*ALSA_snd_pcm_hw_params_set_rate_near)
   (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
 static int (*ALSA_snd_pcm_hw_params_set_period_size_near)
   (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
-static int (*ALSA_snd_pcm_hw_params_get_channels)
-  (const snd_pcm_hw_params_t *, unsigned int *);
-static snd_pcm_sframes_t(*ALSA_snd_pcm_hw_params_get_period_size)
-  (const snd_pcm_hw_params_t *);
+static int (*ALSA_snd_pcm_hw_params_get_period_size)
+  (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
 static int (*ALSA_snd_pcm_hw_params_set_periods_near)
   (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
-static int (*ALSA_snd_pcm_hw_params_get_periods) (snd_pcm_hw_params_t *);
+static int (*ALSA_snd_pcm_hw_params_get_periods)
+  (const snd_pcm_hw_params_t *, unsigned int *, int *);
+static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)
+  (snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
+static int (*ALSA_snd_pcm_hw_params_get_buffer_size)
+  (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
 static int (*ALSA_snd_pcm_hw_params) (snd_pcm_t *, snd_pcm_hw_params_t *);
 static int (*ALSA_snd_pcm_sw_params_current) (snd_pcm_t *,
                                               snd_pcm_sw_params_t *);
 static int (*ALSA_snd_pcm_sw_params_set_start_threshold)
   (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
-static int (*ALSA_snd_pcm_sw_params_set_avail_min)
-  (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
 static int (*ALSA_snd_pcm_sw_params) (snd_pcm_t *, snd_pcm_sw_params_t *);
 static int (*ALSA_snd_pcm_nonblock) (snd_pcm_t *, int);
+static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
 #define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
 #define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
 
@@ -116,12 +119,13 @@
     SDL_ALSA_SYM(snd_pcm_open);
     SDL_ALSA_SYM(snd_pcm_close);
     SDL_ALSA_SYM(snd_pcm_writei);
-    SDL_ALSA_SYM(snd_pcm_resume);
+    SDL_ALSA_SYM(snd_pcm_recover);
     SDL_ALSA_SYM(snd_pcm_prepare);
     SDL_ALSA_SYM(snd_pcm_drain);
     SDL_ALSA_SYM(snd_strerror);
     SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
     SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
+    SDL_ALSA_SYM(snd_pcm_hw_params_copy);
     SDL_ALSA_SYM(snd_pcm_hw_params_any);
     SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
     SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
@@ -132,12 +136,14 @@
     SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
     SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_near);
     SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
+    SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near);
+    SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size);
     SDL_ALSA_SYM(snd_pcm_hw_params);
     SDL_ALSA_SYM(snd_pcm_sw_params_current);
     SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
-    SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
     SDL_ALSA_SYM(snd_pcm_sw_params);
     SDL_ALSA_SYM(snd_pcm_nonblock);
+    SDL_ALSA_SYM(snd_pcm_wait);
     return 0;
 }
 
@@ -196,12 +202,17 @@
 
     device = SDL_getenv("AUDIODEV");    /* Is there a standard variable name? */
     if (device == NULL) {
-        if (channels == 6)
-            device = "surround51";
-        else if (channels == 4)
-            device = "surround40";
-        else
-            device = DEFAULT_DEVICE;
+        switch (channels) {
+        case 6:
+            device = "plug:surround51";
+            break;
+        case 4:
+            device = "plug:surround40";
+            break;
+        default:
+            device = "default";
+            break;
+        }
     }
     return device;
 }
@@ -211,19 +222,7 @@
 static void
 ALSA_WaitDevice(_THIS)
 {
-    /* Check to see if the thread-parent process is still alive */
-    {
-        static int cnt = 0;
-        /* Note that this only works with thread implementations 
-           that use a different process id for each thread.
-         */
-        /* Check every 10 loops */
-        if (this->hidden->parent && (((++cnt) % 10) == 0)) {
-            if (kill(this->hidden->parent, 0) < 0 && errno == ESRCH) {
-                this->enabled = 0;
-            }
-        }
-    }
+    /* We're in blocking mode, so there's nothing to do here */
 }
 
 
@@ -297,41 +296,38 @@
 ALSA_PlayDevice(_THIS)
 {
     int status;
-    int sample_len;
-    signed short *sample_buf;
+    const Uint8 *sample_buf = (const Uint8 *) this->hidden->mixbuf;
+    const int frame_size = (((int) (this->spec.format & 0xFF)) / 8) *
+                                this->spec.channels;
+    snd_pcm_uframes_t frames_left = ((snd_pcm_uframes_t) this->spec.samples);
 
     swizzle_alsa_channels(this);
 
-    sample_len = this->spec.samples;
-    sample_buf = (signed short *) this->hidden->mixbuf;
-
-    while (sample_len > 0) {
+    while ( frames_left > 0 && this->enabled ) {
+        /* !!! FIXME: This works, but needs more testing before going live */
+        /*ALSA_snd_pcm_wait(this->hidden->pcm_handle, -1);*/
         status = ALSA_snd_pcm_writei(this->hidden->pcm_handle,
-                                     sample_buf, sample_len);
+                                     sample_buf, frames_left);
 
         if (status < 0) {
             if (status == -EAGAIN) {
+                /* Apparently snd_pcm_recover() doesn't handle this case -
+                   does it assume snd_pcm_wait() above? */
                 SDL_Delay(1);
                 continue;
             }
-            if (status == -ESTRPIPE) {
-                do {
-                    SDL_Delay(1);
-                    status = ALSA_snd_pcm_resume(this->hidden->pcm_handle);
-                } while (status == -EAGAIN);
-            }
-            if (status < 0) {
-                status = ALSA_snd_pcm_prepare(this->hidden->pcm_handle);
-            }
+            status = ALSA_snd_pcm_recover(this->hidden->pcm_handle, status, 0);
             if (status < 0) {
                 /* Hmm, not much we can do - abort */
+                fprintf(stderr, "ALSA write failed (unrecoverable): %s\n",
+                        ALSA_snd_strerror(status));
                 this->enabled = 0;
                 return;
             }
             continue;
         }
-        sample_buf += status * this->spec.channels;
-        sample_len -= status;
+        sample_buf += status * frame_size;
+        frames_left -= status;
     }
 }
 
@@ -360,6 +356,118 @@
 }
 
 static int
+ALSA_finalize_hardware(_THIS, snd_pcm_hw_params_t *hwparams, int override)
+{
+    int status;
+    snd_pcm_uframes_t bufsize;
+
+    /* "set" the hardware with the desired parameters */
+    status = ALSA_snd_pcm_hw_params(this->hidden->pcm_handle, hwparams);
+    if ( status < 0 ) {
+        return(-1);
+    }
+
+    /* Get samples for the actual buffer size */
+    status = ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize);
+    if ( status < 0 ) {
+        return(-1);
+    }
+    if ( !override && bufsize != this->spec.samples * 2 ) {
+        return(-1);
+    }
+
+    /* !!! FIXME: Is this safe to do? */
+    this->spec.samples = bufsize / 2;
+
+    /* This is useful for debugging */
+    if ( SDL_getenv("SDL_AUDIO_ALSA_DEBUG") ) {
+        snd_pcm_uframes_t persize = 0;
+        unsigned int periods = 0;
+
+        ALSA_snd_pcm_hw_params_get_period_size(hwparams, &persize, NULL);
+        ALSA_snd_pcm_hw_params_get_periods(hwparams, &periods, NULL);
+
+        fprintf(stderr,
+            "ALSA: period size = %ld, periods = %u, buffer size = %lu\n",
+            persize, periods, bufsize);
+    }
+
+    return(0);
+}
+
+static int
+ALSA_set_period_size(_THIS, snd_pcm_hw_params_t *params, int override)
+{
+    const char *env;
+    int status;
+    snd_pcm_hw_params_t *hwparams;
+    snd_pcm_uframes_t frames;
+    unsigned int periods;
+
+    /* Copy the hardware parameters for this setup */
+    snd_pcm_hw_params_alloca(&hwparams);
+    ALSA_snd_pcm_hw_params_copy(hwparams, params);
+
+    if ( !override ) {
+        env = SDL_getenv("SDL_AUDIO_ALSA_SET_PERIOD_SIZE");
+        if ( env ) {
+            override = SDL_atoi(env);
+            if ( override == 0 ) {
+                return(-1);
+            }
+        }
+    }
+
+    frames = this->spec.samples;
+    status = ALSA_snd_pcm_hw_params_set_period_size_near(
+                this->hidden->pcm_handle, hwparams, &frames, NULL);
+    if ( status < 0 ) {
+        return(-1);
+    }
+
+    periods = 2;
+    status = ALSA_snd_pcm_hw_params_set_periods_near(
+                this->hidden->pcm_handle, hwparams, &periods, NULL);
+    if ( status < 0 ) {
+        return(-1);
+    }
+
+    return ALSA_finalize_hardware(this, hwparams, override);
+}
+
+static int
+ALSA_set_buffer_size(_THIS, snd_pcm_hw_params_t *params, int override)
+{
+    const char *env;
+    int status;
+    snd_pcm_hw_params_t *hwparams;
+    snd_pcm_uframes_t frames;
+
+    /* Copy the hardware parameters for this setup */
+    snd_pcm_hw_params_alloca(&hwparams);
+    ALSA_snd_pcm_hw_params_copy(hwparams, params);
+
+    if ( !override ) {
+        env = SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE");
+        if ( env ) {
+            override = SDL_atoi(env);
+            if ( override == 0 ) {
+                return(-1);
+            }
+        }
+    }
+
+    frames = this->spec.samples * 2;
+    status = ALSA_snd_pcm_hw_params_set_buffer_size_near(
+                    this->hidden->pcm_handle, hwparams, &frames);
+    if ( status < 0 ) {
+        return(-1);
+    }
+
+    return ALSA_finalize_hardware(this, hwparams, override);
+}
+
+static int
 ALSA_OpenDevice(_THIS, const char *devname, int iscapture)
 {
     int status = 0;
@@ -367,10 +475,8 @@
     snd_pcm_hw_params_t *hwparams = NULL;
     snd_pcm_sw_params_t *swparams = NULL;
     snd_pcm_format_t format = 0;
-    snd_pcm_uframes_t frames = 0;
     SDL_AudioFormat test_format = 0;
     unsigned int rate = 0;
-    unsigned int periods = 0;
     unsigned int channels = 0;
 
     /* Initialize all variables that we clean on shutdown */
@@ -499,40 +605,15 @@
     this->spec.freq = rate;
 
     /* Set the buffer size, in samples */
-    frames = this->spec.samples;
-    status = ALSA_snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams,
-                                                         &frames, NULL);
-    if ( status < 0 ) {
-        ALSA_CloseDevice(this);
-        SDL_SetError("ALSA: Couldn't set audio frequency: %s",
-                     ALSA_snd_strerror(status));
-        return(-1);
+    if ( ALSA_set_period_size(this, hwparams, 0) < 0 &&
+         ALSA_set_buffer_size(this, hwparams, 0) < 0 ) {
+        /* Failed to set desired buffer size, do the best you can... */
+        if ( ALSA_set_period_size(this, hwparams, 1) < 0 ) {
+            ALSA_CloseDevice(this);
+            SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status));
+            return(-1);
+        }
     }
-    this->spec.samples = frames;
-
-    periods = 2;
-    ALSA_snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams,
-                                            &periods, NULL);
-
-    /* "set" the hardware with the desired parameters */
-    status = ALSA_snd_pcm_hw_params(pcm_handle, hwparams);
-    if (status < 0) {
-        ALSA_CloseDevice(this);
-        SDL_SetError("ALSA: Couldn't set hardware audio parameters: %s",
-                     ALSA_snd_strerror(status));
-        return 0;
-    }
-#if AUDIO_DEBUG
-    {
-        snd_pcm_sframes_t bufsize;
-        int fragments;
-        bufsize = ALSA_snd_pcm_hw_params_get_period_size(hwparams);
-        fragments = ALSA_snd_pcm_hw_params_get_periods(hwparams);
-        fprintf(stderr, "ALSA: bufsize = %ld, fragments = %d\n", bufsize,
-                fragments);
-    }
-#endif
-
     /* Set the software parameters */
     snd_pcm_sw_params_alloca(&swparams);
     status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
@@ -543,20 +624,13 @@
         return 0;
     }
     status =
-        ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 0);
+        ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1);
     if (status < 0) {
         ALSA_CloseDevice(this);
         SDL_SetError("ALSA: Couldn't set start threshold: %s",
                      ALSA_snd_strerror(status));
         return 0;
     }
-    status =
-        ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, frames);
-    if (status < 0) {
-        ALSA_CloseDevice(this);
-        SDL_SetError("Couldn't set avail min: %s", ALSA_snd_strerror(status));
-        return 0;
-    }
     status = ALSA_snd_pcm_sw_params(pcm_handle, swparams);
     if (status < 0) {
         ALSA_CloseDevice(this);
@@ -578,9 +652,6 @@
     }
     SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
 
-    /* Get the parent process id (we're the parent of the audio thread) */
-    this->hidden->parent = getpid();
-
     /* Switch to blocking mode for playback */
     ALSA_snd_pcm_nonblock(pcm_handle, 0);
 
@@ -615,7 +686,7 @@
 
 
 AudioBootStrap ALSA_bootstrap = {
-    DRIVER_NAME, "ALSA 0.9 PCM audio", ALSA_Init, 0
+    DRIVER_NAME, "ALSA PCM audio", ALSA_Init, 0
 };
 
 /* vi: set ts=4 sw=4 expandtab: */