[xiph-cvs] cvs commit: ao/src/plugins/alsa09 ao_alsa09.c

Stan Seibert volsung at xiph.org
Sat Aug 30 09:48:27 PDT 2003



volsung     03/08/30 12:48:27

  Modified:    .        configure.in
               src/plugins/alsa09 ao_alsa09.c
  Log:
  Reverted previous ALSA patches and committed fixes from bug 153.  Fixes
  underrun bugs being experienced by mpg321 users.

Revision  Changes    Path
1.47      +14 -1     ao/configure.in

Index: configure.in
===================================================================
RCS file: /usr/local/cvsroot/ao/configure.in,v
retrieving revision 1.46
retrieving revision 1.47
diff -u -r1.46 -r1.47
--- configure.in	29 Aug 2003 18:18:29 -0000	1.46
+++ configure.in	30 Aug 2003 16:48:26 -0000	1.47
@@ -200,17 +200,30 @@
 
 dnl Check for ALSA 0.9.x
 
-AC_ARG_ENABLE(alsa09, [  --enable-alsa09           include alsa 0.9 output plugin ],
+AC_ARG_ENABLE(alsa09, [  --enable-alsa09         include alsa 0.9 output plugin ],
 [ BUILD_ALSA09="$enableval" ],[ BUILD_ALSA09="yes" ])
 
 if test "$BUILD_ALSA09" = "yes"; then
+
+   AC_ARG_ENABLE(alsa09-mmap, [  --enable-alsa09-mmap          use mmio with alsa 0.9 ],
+   [ BUILD_ALSA09MMIO="$enableval" ],[ BUILD_ALSA09MMIO="yes" ])
+
    AC_CHECK_LIB(asound, snd_pcm_open, have_alsa09=yes, have_alsa09=no)
    AC_CHECK_HEADER(alsa/asoundlib.h, , have_alsa09=no)
    AM_CONDITIONAL(HAVE_ALSA09,test "x$have_alsa09" = xyes)
+
+   if test "$BUILD_ALSA09MMIO" = "yes" ; then
+      AC_CHECK_HEADER(sys/mman.h, have_alsa09mmio=yes, have_alsa09mmio=no)
+      AM_CONDITIONAL(HAVE_ALSA09MMIO,test "x$have_alsa09mmio" = xyes)
+   fi
+
 fi
 
 if test "x$have_alsa09" = xyes; then
         ALSA09_LIBS="-lasound"
+	if test "x$have_alsa09mmio" = xyes; then
+		AC_DEFINE(USE_ALSA_MMIO)
+	fi
 else
         ALSA09_LIBS=""
 fi

<p><p>1.16      +305 -126  ao/src/plugins/alsa09/ao_alsa09.c

Index: ao_alsa09.c
===================================================================
RCS file: /usr/local/cvsroot/ao/src/plugins/alsa09/ao_alsa09.c,v
retrieving revision 1.15
retrieving revision 1.16
diff -u -r1.15 -r1.16
--- ao_alsa09.c	30 Aug 2003 15:31:27 -0000	1.15
+++ ao_alsa09.c	30 Aug 2003 16:48:26 -0000	1.16
@@ -21,12 +21,10 @@
  *  along with GNU Make; see the file COPYING.  If not, write to
  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  *
+ *  Largely rewritten 2/18/2002 Kevin Cody Jr <kevinc at wuff.dhs.org>
+ *
  */
 
-/* use new API's */
-#define ALSA_PCM_NEW_HW_PARAMS_API
-#define ALSA_PCM_NEW_SW_PARAMS_API
-
 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
@@ -38,20 +36,30 @@
 #include <ao/ao.h>
 #include <ao/plugin.h>
 
-#define AO_ALSA_BUF_SIZE 4096
-#define AO_ALSA_PERIODS  4
+/* default 500 millisecond buffer */
+#define AO_ALSA_BUFFER_TIME 500000
+
+/* the period time is calculated if not given as an option
+ * note, playback starts after four of these are in the buffer */
+#define AO_ALSA_PERIOD_TIME 0
+
+/* number of samples between interrupts
+ * supplying a period_time to ao overrides the use of this  */
+#define AO_ALSA_SAMPLE_XFER 256
 
 static char *ao_alsa_options[] = {
         "dev",
-	"buf_size",
-        "periods"
+	"buffer_time",
+        "period_time"
 };
+
+
 static ao_info ao_alsa_info =
 {
         AO_TYPE_LIVE,
         "Advanced Linux Sound Architecture (ALSA) output",
         "alsa09",
-	"Bill Currie <bill at taniwha.org>",
+	"Bill Currie <bill at taniwha.org>/Kevin Cody, Jr. <kevinc at wuff.dhs.org>",
         "Outputs to the Advanced Linux Sound Architecture version 0.9.x.",
         AO_FMT_NATIVE,
         35,
@@ -63,13 +71,18 @@
 typedef struct ao_alsa_internal
 {
         snd_pcm_t *pcm_handle;
-	int buf_size;
+	int buffer_time;
+	int period_time;
+	int buffer_size;
+	int period_size;
         int sample_size;
-	int periods;
+	int bitformat;
         char *dev;
+	char *cmd;
 } ao_alsa_internal;
 
 
+/* determine if parameters are requires for this particular plugin */
 int ao_plugin_test()
 {
         snd_pcm_t *handle;
@@ -89,12 +102,14 @@
 }
 
 
+/* return the address of the driver info structure */
 ao_info *ao_plugin_driver_info(void)
 {
         return &ao_alsa_info;
 }
 
 
+/* initialize internal data structures */
 int ao_plugin_device_init(ao_device *device)
 {
         ao_alsa_internal *internal;
@@ -102,22 +117,23 @@
         internal = (ao_alsa_internal *) malloc(sizeof(ao_alsa_internal));
 
         if (internal == NULL)	
-		return 0; /* Could not initialize device memory */
+		return 0;
         
-	internal->buf_size = AO_ALSA_BUF_SIZE;
-	internal->periods = AO_ALSA_PERIODS;
-	internal->dev = strdup ("default");
-	if (!internal->dev) {
+	internal->buffer_time = AO_ALSA_BUFFER_TIME;
+	internal->period_time = AO_ALSA_PERIOD_TIME;
+
+	if (!(internal->dev = strdup("default"))) {
                 free (internal);
                 return 0;
         }
         
         device->internal = internal;
 
-	return 1; /* Memory alloc successful */
+	return 1;
 }
 
 
+/* pass application parameters regarding the sound device */
 int ao_plugin_set_option(ao_device *device, const char *key, const char *value)
 {
         ao_alsa_internal *internal = (ao_alsa_internal *) device->internal;
@@ -125,183 +141,346 @@
         if (!strcmp(key, "dev")) {
                 if (internal->dev)
                         free (internal->dev);
-		internal->dev = strdup (value);
-		if (!internal->dev)
+		if (!(internal->dev = strdup(value)))
                         return 0;
         }
-	else if (!strcmp(key, "buf_size"))
-		internal->buf_size = atoi(value);
-	else if (!strcmp(key, "periods"))
-		internal->periods = atoi(value);
+	else if (!strcmp(key, "buffer_time"))
+		internal->buffer_time = atoi(value);
+	else if (!strcmp(key, "period_time"))
+		internal->period_time = atoi(value);
 
         return 1;
 }
 
-int ao_plugin_open(ao_device *device, ao_sample_format *format)
+
+/* determine the alsa bitformat for a given bitwidth and endianness */
+static inline int alsa_get_sample_bitformat(int bitwidth, int bigendian)
 {
-	ao_alsa_internal *internal = (ao_alsa_internal *) device->internal;
-	snd_pcm_hw_params_t *hwparams;
+	int ret;
+
+	switch (bitwidth) {
+	case 8  : ret = SND_PCM_FORMAT_S8;
+		  break;
+	case 16 : ret = SND_PCM_FORMAT_S16;
+		  break;
+	case 24 : ret = SND_PCM_FORMAT_S24;
+		  break;
+	case 32 : ret = SND_PCM_FORMAT_S32;
+		  break;
+	default : fprintf(stderr,"ALSA: invalid bitwidth %d\n", bitwidth);
+		  return -1;
+	}
+
+	return ret;
+}
+
 
+/* setup alsa data format and buffer geometry */
+static inline int alsa_set_hwparams(ao_alsa_internal *internal,
+		ao_sample_format *format)
+{
+	snd_pcm_hw_params_t   *params;
+	snd_pcm_access_mask_t *access;
         int err;
-	int fmt;
-	snd_pcm_uframes_t period_size;
-	char *cmd;
-	unsigned int rate;
 
+	/* allocate the hardware parameter structure */
+	snd_pcm_hw_params_alloca(&params);
+	snd_pcm_access_mask_alloca(&access);
+
+	/* fetch the current hardware parameters */
+	internal->cmd = "snd_pcm_hw_params_any";
+	err = snd_pcm_hw_params_any(internal->pcm_handle, params);
+	if (err < 0)
+		return err;
 
-	/* Open the ALSA device */
-	err = snd_pcm_open(&(internal->pcm_handle), internal->dev,
-			   SND_PCM_STREAM_PLAYBACK, 0);
+	/* create a null access mask */
+	snd_pcm_access_mask_none(access);
 
-	snd_pcm_hw_params_alloca(&hwparams);
+#ifdef USE_ALSA_MMIO
+	/* allow interleaved memory-mapped access */
+	snd_pcm_access_mask_set(access, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+#else
+	/* allow interleaved non memory-mapped access */
+	snd_pcm_access_mask_set(access, SND_PCM_ACCESS_RW_INTERLEAVED);
+#endif
+
+	/* commit the access value to params structure */
+	internal->cmd = "snd_pcm_hw_params_set_access";
+	err = snd_pcm_hw_params_set_access_mask(internal->pcm_handle,
+			params, access);
+	if (err < 0)
+		return err;
 
-	cmd = "snd_pcm_hw_params_any";
-	err = snd_pcm_hw_params_any(internal->pcm_handle, hwparams);
+	/* set the sample bitformat */
+	internal->cmd = "snd_pcm_hw_params_set_format";
+	err = snd_pcm_hw_params_set_format(internal->pcm_handle,
+			params, internal->bitformat);
         if (err < 0)
-		goto error;
+		return err;
 
-	cmd = "snd_pcm_hw_params_set_access";
-	err = snd_pcm_hw_params_set_access(internal->pcm_handle, hwparams,
-			SND_PCM_ACCESS_RW_INTERLEAVED);
+	/* set the number of channels */
+	internal->cmd = "snd_pcm_hw_params_set_channels";
+	err = snd_pcm_hw_params_set_channels(internal->pcm_handle,
+			params, format->channels);
         if (err < 0)
-		goto error;
+		return err;
 
-	switch (format->bits) {
-	case 8  : fmt = SND_PCM_FORMAT_S8;
-		  break;
-	case 16 : fmt = 
-		  device->client_byte_format == AO_FMT_BIG ?
-		  SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_S16_LE;
-		  device->driver_byte_format = device->client_byte_format;
-		  break;
-	default : return 0;
-	}
-	cmd = "snd_pcm_hw_params_set_format";
-	err = snd_pcm_hw_params_set_format(internal->pcm_handle, hwparams, fmt);
+	/* save the sample size in bytes for posterity */
+	internal->sample_size = format->bits * format->channels / 8;
+
+	/* set the sample rate */
+	internal->cmd = "snd_pcm_hw_params_set_rate_near";
+	err = snd_pcm_hw_params_set_rate_near(internal->pcm_handle,
+			params, format->rate, 0);
         if (err < 0)
-		goto error;
+		return err;
 
-	cmd = "snd_pcm_hw_params_set_channels";
-	if (format->channels == 1 || format->channels == 2)
-		err = snd_pcm_hw_params_set_channels(internal->pcm_handle,
-				hwparams, format->channels);
-	else
-		return 0;
+	/* set the length of the hardware sample buffer in milliseconds */
+	internal->cmd = "snd_pcm_hw_params_set_buffer_time_near";
+	err = snd_pcm_hw_params_set_buffer_time_near(internal->pcm_handle,
+			params, internal->buffer_time, 0);
+	if (err < 0)
+		return err;
+
+	/* save the buffer time in case alsa overrode it */
+	internal->buffer_time = err;
 
+	/* calculate a period time of one half sample time */
+	if ((internal->period_time == 0) && (format->rate > 0))
+		internal->period_time =
+			1000000 * AO_ALSA_SAMPLE_XFER / format->rate;
+
+	/* set the time per hardware sample transfer */
+	internal->cmd = "snd_pcm_hw_params_set_period_time_near";
+	err = snd_pcm_hw_params_set_period_time_near(internal->pcm_handle,
+			params, internal->period_time, 0);
         if (err < 0)
-		goto error;
-	internal->sample_size = format->bits * format->channels / 8;
+		return err;
 
-	rate = format->rate;
-	cmd = "snd_pcm_hw_params_set_rate_near";
-	/* make compatible with new API */
-	err = snd_pcm_hw_params_set_rate_near(internal->pcm_handle, hwparams,
-			&rate, 0);
+	/* commit the params structure to the hardware via ALSA */
+	internal->cmd = "snd_pcm_hw_params";
+	err = snd_pcm_hw_params(internal->pcm_handle, params);
         if (err < 0)
-		goto error;
+		return err;
+
+	/* save the period size in bytes for posterity */
+	internal->period_size = snd_pcm_hw_params_get_period_size(params, 0);
+
+	/* save the buffer size in bytes for posterity */
+	internal->buffer_size = snd_pcm_hw_params_get_buffer_size(params);
+
+	return 1;
+}
+
 
-	period_size = internal->buf_size / internal->sample_size;
-	cmd = "snd_pcm_hw_params_set_period_size_near";
-	err = snd_pcm_hw_params_set_period_size_near(internal->pcm_handle, 
-			hwparams, &period_size, 0);
+/* setup alsa data transfer behavior */
+static inline int alsa_set_swparams(ao_alsa_internal *internal)
+{
+	snd_pcm_sw_params_t   *params;
+	int err;
+
+	/* allocate the software parameter structure */
+	snd_pcm_sw_params_alloca(&params);
+
+	/* fetch the current software parameters */
+	internal->cmd = "snd_pcm_sw_params_current";
+	err = snd_pcm_sw_params_current(internal->pcm_handle, params);
+	if (err < 0)
+		return err;
+
+	/* require a minimum of one full transfer in the buffer */
+	internal->cmd = "snd_pcm_sw_params_set_avail_min";
+	err = snd_pcm_sw_params_set_avail_min(internal->pcm_handle, params,
+			internal->period_size);
+	if (err < 0)
+		return err;
+
+	/* allow transfers to start when there are four periods */
+	internal->cmd = "snd_pcm_sw_params_set_start_threshold";
+	err = snd_pcm_sw_params_set_start_threshold(internal->pcm_handle,
+			params, internal->period_size << 2);
+	if (err < 0)
+		return err;
+
+	/* do not align transfers */
+	internal->cmd = "snd_pcm_sw_params_set_xfer_align";
+	err = snd_pcm_sw_params_set_xfer_align(internal->pcm_handle, params, 1);
+	if (err < 0)
+		return err;
+
+	/* commit the params structure to ALSA */
+	internal->cmd = "snd_pcm_sw_params";
+	err = snd_pcm_sw_params(internal->pcm_handle, params);
+	if (err < 0)
+		return err;
+
+	return 1;
+}
+
+
+/* prepare the audio device for playback */
+int ao_plugin_open(ao_device *device, ao_sample_format *format)
+{
+	ao_alsa_internal *internal  = (ao_alsa_internal *) device->internal;
+	int err;
+
+	/* Get the ALSA bitformat first to make sure it's valid */
+	err = alsa_get_sample_bitformat(format->bits,
+			device->client_byte_format == AO_FMT_BIG);
         if (err < 0)
                 goto error;
 
-	internal->buf_size = period_size*internal->sample_size;
+	internal->bitformat = err;
 
-	cmd = "snd_pcm_hw_params_set_periods";
-	err = snd_pcm_hw_params_set_periods(internal->pcm_handle, hwparams,
-			internal->periods * format->channels, 0);
+	/* Open the ALSA device */
+	internal->cmd = "snd_pcm_open";
+	err = snd_pcm_open(&(internal->pcm_handle), internal->dev,
+			   SND_PCM_STREAM_PLAYBACK, 0);
         if (err < 0)
                 goto error;
 
-	cmd = "snd_pcm_hw_params";
-	err = snd_pcm_hw_params(internal->pcm_handle, hwparams);
+	/* Set up the hardware parameters, ie sample and buffer specs */
+	err = alsa_set_hwparams(internal, format);
         if (err < 0)
                 goto error;
 
-	cmd = "snd_pcm_prepare";
-	err = snd_pcm_prepare(internal->pcm_handle);
+	/* Set up the software parameters, ie de-buffering specs */
+	err = alsa_set_swparams(internal);
         if (err < 0)
                 goto error;
 
+	/* alsa's endinness will be the same as the application's */
+	if (format->bits > 8)
+		device->driver_byte_format = device->client_byte_format;
+
         return 1;
+
 error:
-	fprintf(stderr, "ALSA %s error: %s\n", cmd, snd_strerror(err));
-	snd_pcm_close(internal->pcm_handle);
+	fprintf(stderr, "ALSA %s error: %s\n",
+			internal->cmd, snd_strerror(err));
+	if (internal->pcm_handle)
+		snd_pcm_close(internal->pcm_handle);
         return 0;
 }
 
-/* xrun_recover is ripped from pcm example at alsa-project.org */
 
-/*
- *   Underrun and suspend recovery
- */
- 
-static int xrun_recovery(snd_pcm_t *handle, int err)
+/* recover from an alsa exception */
+static inline int alsa_error_recovery(ao_alsa_internal *internal, int err)
 {
-        if (err == -EPIPE) {    /* under-run */
-                err = snd_pcm_prepare(handle);
-                if (err < 0)
-                        printf("ALSA write error: Can't recovery from underrun" 
-					", prepare failed: %s\n", 
-					snd_strerror(err));
-                return 0;
-        } else if (err == -ESTRPIPE) {
-                while ((err = snd_pcm_resume(handle)) == -EAGAIN)
-                        sleep(1); /* wait until the suspend flag is released */
-                if (err < 0) {
-                        err = snd_pcm_prepare(handle);
-                        if (err < 0)
-                                printf("ALSA write error: Can't recovery from "
-						"suspend, prepare failed: %s\n",
-						snd_strerror(err));
-                }
-                return 0;
-        }
-        return err;
+	if (err == -EPIPE) {
+		/* FIXME: underrun length detection */
+		fprintf(stderr,"ALSA: underrun, at least %dms.\n", 0);
+		/* output buffer underrun */
+		internal->cmd = "underrun recovery: snd_pcm_prepare";
+		err = snd_pcm_prepare(internal->pcm_handle);
+		if (err < 0)
+			return -1;
+	} else if (err == -ESTRPIPE) {
+		/* application was suspended, wait until suspend flag clears */
+		internal->cmd = "suspend recovery: snd_pcm_prepare";
+		while ((err = snd_pcm_resume(internal->pcm_handle)) == -EAGAIN)
+			sleep (1);
+
+		if (err < 0) {
+			/* unable to wake up pcm device, restart it */
+			internal->cmd = "suspend recovery: snd_pcm_prepare";
+			err = snd_pcm_prepare(internal->pcm_handle);
+			if (err < 0)
+				return err;
+		}
+		return 0;
+	}
+
+	/* error isn't recoverable */
+	return err;
 }
 
+
+/* play num_bytes of audio data */
 int ao_plugin_play(ao_device *device, const char *output_samples, 
                 uint_32 num_bytes)
 {
         ao_alsa_internal *internal = (ao_alsa_internal *) device->internal;
-	int res;
-	char *buf = (char *)output_samples;
-	int len = num_bytes / internal->sample_size;
+       	uint_32 len = num_bytes / internal->sample_size;
+	char *ptr = (char *) output_samples;
+	int err;
 
+	/* the entire buffer might not transfer at once */
         while (len > 0) {
-		res = snd_pcm_writei(internal->pcm_handle, buf, len);
-		if(res == -EAGAIN) continue;
-		else if(res < 0) {
-			if(xrun_recovery(internal->pcm_handle, res)<0)
+		/* try to write the entire buffer at once */
+#ifdef USE_ALSA_MMIO
+		err = snd_pcm_mmap_writei(internal->pcm_handle, ptr, len);
+#else
+		err = snd_pcm_writei(internal->pcm_handle, ptr, len);
+#endif
+
+		/* it's possible that no data was transferred */
+		if (err == -EAGAIN)
+			continue;
+
+		if (err < 0) {
+			/* this might be an error, or an exception */
+			err = alsa_error_recovery(internal, err);
+			if (err < 0) {
+				fprintf(stderr,"ALSA write error: %s\n",
+						snd_strerror(err));
                                 return 0;
+			}
+
+			/* abandon the rest of the buffer */
+			break;
                 }
-		else {
-			len -= res;
-			buf += res;
-		}
+
+		/* decrement the sample counter */
+		len -= err;
+
+		/* adjust the start pointer */
+		ptr += err * internal->sample_size;
         }
 
         return 1;
 }
 
 
+/* close the audio device */
 int ao_plugin_close(ao_device *device)
 {
-	ao_alsa_internal *internal = (ao_alsa_internal *) device->internal;
+	ao_alsa_internal *internal;
 
-	snd_pcm_drain(internal->pcm_handle);
-	snd_pcm_close(internal->pcm_handle);
+	if (device) {
+		if ((internal = (ao_alsa_internal *) device->internal)) {
+			if (internal->pcm_handle) {
+				snd_pcm_drain(internal->pcm_handle);
+				snd_pcm_close(internal->pcm_handle);
+			} else
+				fprintf(stderr,"ao_plugin_close called with uninitialized ao_device->internal->pcm_handle\n");
+		} else
+			fprintf(stderr,"ao_plugin_close called with uninitialized ao_device->internal\n");
+	} else
+		fprintf(stderr,"ao_plugin_close called with uninitialized ao_device\n");
 
         return 1;
 }
 
 
+/* free the internal data structures */
 void ao_plugin_device_clear(ao_device *device)
 {
-	ao_alsa_internal *internal = (ao_alsa_internal *) device->internal;
-	if (internal->dev)
-		free (internal->dev);
-	free(internal);
+	ao_alsa_internal *internal;
+
+	if (device) {
+		if ((internal = (ao_alsa_internal *) device->internal)) {
+			if (internal->dev)
+				free (internal->dev);
+			else
+				fprintf(stderr,"ao_plugin_device_clear called with uninitialized ao_device->internal->dev\n");
+			if (internal->cmd)
+				internal->cmd = NULL;
+
+			free(device->internal);
+		} else
+			fprintf(stderr,"ao_plugin_device_clear called with uninitialized ao_device->internal\n");
+	} else
+		fprintf(stderr,"ao_plugin_device_clear called with uninitialized ao_device\n");
 }
+

<p><p>--- >8 ----
List archives:  http://www.xiph.org/archives/
Ogg project homepage: http://www.xiph.org/ogg/
To unsubscribe from this list, send a message to 'cvs-request at xiph.org'
containing only the word 'unsubscribe' in the body.  No subject is needed.
Unsubscribe messages sent to the list will be ignored/filtered.



More information about the commits mailing list