[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(¶ms);
+ 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(¶ms);
+
+ /* 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