[MPlayer-dev-eng] [PATCH 3/5] ao_alsa: make behaviour more compatible with the OSS driver

Clemens Ladisch clemens at ladisch.de
Mon Jan 30 09:14:42 CET 2006


This patch makes the driver's behaviour more compatible with that of the
OSS driver:
- set ao_data.outburst to the period size, and align all data transfers
  to this value;
- support more sample formats;
- use snd_pcm_format_physical_width() to avoid some hardcoded numbers;
- set the PCM software parameters to more OSS-compatible values;
- fix play() error handling;
- make the get_space() return value a multiple of the period size, and
  never return more than MAX_OUTBURST;
- simplify get_delay(), and avoid the delay becoming too much negative
  after an underrun.

Index: MPlayer/libao2/ao_alsa.c
===================================================================
--- MPlayer.orig/libao2/ao_alsa.c	2006-01-29 19:11:47.000000000 +0100
+++ MPlayer/libao2/ao_alsa.c	2006-01-29 21:41:03.000000000 +0100
@@ -63,8 +63,6 @@ static int alsa_fragsize = 4096;
 static int alsa_fragcount = 16;
 static snd_pcm_uframes_t chunk_size = 1024;//is alsa_fragsize / 4
 
-#define MIN_CHUNK_SIZE 1024
-
 static size_t bytes_per_sample;
 
 int ao_mmap = 0;
@@ -264,6 +262,7 @@ static int init(int rate_hz, int channel
     int block;
     strarg_t device;
     snd_pcm_uframes_t bufsize;
+    snd_pcm_uframes_t boundary;
     opt_t subopts[] = {
       {"mmap", OPT_ARG_BOOL, &ao_mmap, NULL},
       {"block", OPT_ARG_BOOL, &block, NULL},
@@ -289,7 +288,6 @@ static int init(int rate_hz, int channel
     ao_data.samplerate = rate_hz;
     ao_data.format = format;
     ao_data.channels = channels;
-    ao_data.outburst = OUTBURST;
 
     switch (format)
       {
@@ -317,6 +315,12 @@ static int init(int rate_hz, int channel
       case AF_FORMAT_S16_BE:
 	alsa_format = SND_PCM_FORMAT_S16_BE;
 	break;
+      case AF_FORMAT_U32_LE:
+	alsa_format = SND_PCM_FORMAT_U32_LE;
+	break;
+      case AF_FORMAT_U32_BE:
+	alsa_format = SND_PCM_FORMAT_U32_BE;
+	break;
       case AF_FORMAT_S32_LE:
 	alsa_format = SND_PCM_FORMAT_S32_LE;
 	break;
@@ -326,6 +330,15 @@ static int init(int rate_hz, int channel
       case AF_FORMAT_FLOAT_LE:
 	alsa_format = SND_PCM_FORMAT_FLOAT_LE;
 	break;
+      case AF_FORMAT_FLOAT_BE:
+	alsa_format = SND_PCM_FORMAT_FLOAT_BE;
+	break;
+      case AF_FORMAT_MU_LAW:
+	alsa_format = SND_PCM_FORMAT_MU_LAW;
+	break;
+      case AF_FORMAT_A_LAW:
+	alsa_format = SND_PCM_FORMAT_A_LAW;
+	break;
 
       default:
 	alsa_format = SND_PCM_FORMAT_MPEG; //? default should be -1
@@ -519,37 +532,9 @@ static int init(int rate_hz, int channel
 	  return(0);
         }
 
-    ao_data.bps = ao_data.channels * ao_data.samplerate;
-
-    //setting bw according to the input-format. resolution seems to be always s16_le or
-    //u16_le so 32bit is probably obsolet. 
-    switch(alsa_format)
-      {
-      case SND_PCM_FORMAT_S8:
-      case SND_PCM_FORMAT_U8:
-	ao_data.bps *= 1;
-	break;
-      case SND_PCM_FORMAT_S16_LE:
-      case SND_PCM_FORMAT_U16_LE:
-      case SND_PCM_FORMAT_S16_BE:
-      case SND_PCM_FORMAT_U16_BE:
-	ao_data.bps *= 2;
-	break;
-      case SND_PCM_FORMAT_S32_LE:
-      case SND_PCM_FORMAT_S32_BE:
-      case SND_PCM_FORMAT_FLOAT_LE:
-	ao_data.bps *= 4;
-	break;
-      case -1:
-	mp_msg(MSGT_AO,MSGL_ERR,"alsa-init: invalid format (%s) requested - output disabled\n",af_fmt2str_short(format));
-	return(0);
-	break;
-      default:
-	ao_data.bps *= 2;
-	mp_msg(MSGT_AO,MSGL_WARN,"alsa-init: couldn't convert to right format. setting bps to: %d", ao_data.bps);
-      }
-
-    bytes_per_sample = ao_data.bps / ao_data.samplerate;
+      bytes_per_sample = snd_pcm_format_physical_width(alsa_format) / 8;
+      bytes_per_sample *= ao_data.channels;
+      ao_data.bps = ao_data.samplerate * bytes_per_sample;
 
 #ifdef BUFFERTIME
       {
@@ -620,6 +605,61 @@ static int init(int rate_hz, int channel
 	  mp_msg(MSGT_AO,MSGL_V,"alsa-init: got buffersize=%i\n", ao_data.buffersize);
       }
 
+      if ((err = snd_pcm_hw_params_get_period_size(alsa_hwparams, &chunk_size, NULL)) < 0) {
+	mp_msg(MSGT_AO,MSGL_ERR,"alsa-init: unable to get period size: %s\n", snd_strerror(err));
+      } else {
+	mp_msg(MSGT_AO,MSGL_V,"alsa-init: got period size %li\n", chunk_size);
+      }
+      ao_data.outburst = chunk_size * bytes_per_sample;
+
+      /* setting software parameters */
+      if ((err = snd_pcm_sw_params_current(alsa_handler, alsa_swparams)) < 0) {
+	mp_msg(MSGT_AO,MSGL_ERR,"alsa-init: unable to get sw-parameters: %s\n",
+	       snd_strerror(err));
+	return 0;
+      }
+      if ((err = snd_pcm_sw_params_get_boundary(alsa_swparams, &boundary)) < 0) {
+	mp_msg(MSGT_AO,MSGL_ERR,"alsa-init: unable to get boundary: %s\n",
+	       snd_strerror(err));
+	return 0;
+      }
+      /* wake up only when a whole period can be written */
+      if ((err = snd_pcm_sw_params_set_avail_min(alsa_handler, alsa_swparams, chunk_size)) < 0) {
+	mp_msg(MSGT_AO,MSGL_ERR,"alsa-init: unable to set wakeup point: %s\n",
+	       snd_strerror(err));
+	return 0;
+      }
+      /* transfer only whole periods */
+      if ((err = snd_pcm_sw_params_set_xfer_align(alsa_handler, alsa_swparams, chunk_size)) < 0) {
+	mp_msg(MSGT_AO,MSGL_ERR,"alsa-init: unable to set transfer alignment: %s\n",
+	       snd_strerror(err));
+	return 0;
+      }
+      /* start playing when one period has been written */
+      if ((err = snd_pcm_sw_params_set_start_threshold(alsa_handler, alsa_swparams, chunk_size)) < 0) {
+	mp_msg(MSGT_AO,MSGL_ERR,"alsa-init: unable to set start threshold: %s\n",
+	       snd_strerror(err));
+	return 0;
+      }
+      /* disable underrun reporting */
+      if ((err = snd_pcm_sw_params_set_stop_threshold(alsa_handler, alsa_swparams, boundary)) < 0) {
+	mp_msg(MSGT_AO,MSGL_ERR,"alsa-init: unable to set stop threshold: %s\n",
+	       snd_strerror(err));
+	return 0;
+      }
+      /* play silence when there is an underrun */
+      if ((err = snd_pcm_sw_params_set_silence_size(alsa_handler, alsa_swparams, boundary)) < 0) {
+	mp_msg(MSGT_AO,MSGL_ERR,"alsa-init: unable to set silence size: %s\n",
+	       snd_strerror(err));
+	return 0;
+      }
+      if ((err = snd_pcm_sw_params(alsa_handler, alsa_swparams)) < 0) {
+	mp_msg(MSGT_AO,MSGL_ERR,"alsa-init: unable to set sw-parameters: %s\n",
+	       snd_strerror(err));
+	return 0;
+      }
+      /* end setting sw-params */
+
       if ((err = snd_pcm_prepare(alsa_handler)) < 0)
 	{
 	  mp_msg(MSGT_AO,MSGL_ERR,"alsa-init: pcm prepare error: %s\n", snd_strerror(err));
@@ -736,37 +776,6 @@ do { \
 } while (0)
 #endif
 
-/* I/O error handler */
-static int xrun(u_char *str_mode)
-{
-  int err;
-  snd_pcm_status_t *status;
-
-  snd_pcm_status_alloca(&status);
-  
-  if ((err = snd_pcm_status(alsa_handler, status))<0) {
-    mp_msg(MSGT_AO,MSGL_ERR,"status error: %s", snd_strerror(err));
-    return(0);
-  }
-
-  if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
-    struct timeval now, diff, tstamp;
-    gettimeofday(&now, 0);
-    snd_pcm_status_get_trigger_tstamp(status, &tstamp);
-    timersub(&now, &tstamp, &diff);
-    mp_msg(MSGT_AO,MSGL_INFO,"alsa-%s: xrun of at least %.3f msecs. resetting stream\n",
-	   str_mode,
-	   diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
-  }
-
-  if ((err = snd_pcm_prepare(alsa_handler))<0) {
-    mp_msg(MSGT_AO,MSGL_ERR,"xrun: prepare error: %s", snd_strerror(err));
-    return(0);
-  }
-
-  return(1); /* ok, data should be accepted again */
-}
-
 /*
     plays 'len' bytes of 'data'
     returns: number of bytes played
@@ -776,10 +785,7 @@ static int xrun(u_char *str_mode)
 
 static int play(void* data, int len, int flags)
 {
-
-  //bytes_per_sample is always 4 for 2 chn S16_LE
-  int num_frames = len / bytes_per_sample;
-  char *output_samples = (char *)data;
+  int num_frames = (len - len % ao_data.outburst) / bytes_per_sample;
   snd_pcm_sframes_t res = 0;
 
   //mp_msg(MSGT_AO,MSGL_ERR,"alsa-play: frames=%i, len=%i\n",num_frames,len);
@@ -789,45 +795,32 @@ static int play(void* data, int len, int
     return 0;
   }
 
-  while (num_frames > 0) {
+  if (num_frames == 0)
+    return 0;
 
-    res = snd_pcm_writei(alsa_handler, (void *)output_samples, num_frames);
+  do {
+      res = snd_pcm_writei(alsa_handler, data, num_frames);
 
-      if (res == -EPIPE) {  /* underrun */
-	if (xrun("play") <= 0) {
-	  mp_msg(MSGT_AO,MSGL_ERR,"alsa-play: xrun reset error");
-	  return(0);
-	}
+      if (res == -EINTR) {
+	/* nothing to do */
+	res = 0;
       }
       else if (res == -ESTRPIPE) {	/* suspend */
 	mp_msg(MSGT_AO,MSGL_INFO,"alsa-play: pcm in suspend mode. trying to resume\n");
 	while ((res = snd_pcm_resume(alsa_handler)) == -EAGAIN)
 	  sleep(1);
       }
-      else if (res < 0) {
-	mp_msg(MSGT_AO,MSGL_INFO,"alsa-play: unknown status, trying to reset soundcard\n");
+      if (res < 0) {
+	mp_msg(MSGT_AO,MSGL_ERR,"alsa-play: write error: %s\n", snd_strerror(res));
+	mp_msg(MSGT_AO,MSGL_INFO,"alsa-play: trying to reset soundcard\n");
 	if ((res = snd_pcm_prepare(alsa_handler)) < 0) {
-	   mp_msg(MSGT_AO,MSGL_ERR,"alsa-play: snd prepare error");
+	  mp_msg(MSGT_AO,MSGL_ERR,"alsa-play: pcm prepare error: %s\n", snd_strerror(res));
 	  return(0);
-	  break;
 	}
       }
+  } while (res == 0);
 
-      if (res > 0) {
-
-	/* output_samples += ao_data.channels * res; */
-	output_samples += res * bytes_per_sample;
-
-	num_frames -= res;
-      }
-
-  } //end while
-
-  if (res < 0) {
-    mp_msg(MSGT_AO,MSGL_ERR,"alsa-play: write error %s", snd_strerror(res));
-    return 0;
-  }
-  return len - len % bytes_per_sample;
+  return res < 0 ? 0 : res * bytes_per_sample;
 }
 
 /* how many byes are free in the buffer */
@@ -835,10 +828,7 @@ static int get_space()
 {
     snd_pcm_status_t *status;
     int ret;
-    char *str_status;
 
-    //snd_pcm_sframes_t avail_frames = 0;
-    
     snd_pcm_status_alloca(&status);
     
     if ((ret = snd_pcm_status(alsa_handler, status)) < 0)
@@ -846,87 +836,33 @@ static int get_space()
 	mp_msg(MSGT_AO,MSGL_ERR,"alsa-space: cannot get pcm status: %s\n", snd_strerror(ret));
 	return(0);
     }
-    
-    switch(snd_pcm_status_get_state(status))
-    {
-    case SND_PCM_STATE_OPEN:
-      str_status = "open";
-      ret = snd_pcm_status_get_avail(status) * bytes_per_sample;
-      break;
-    case SND_PCM_STATE_PREPARED:
-	str_status = "prepared";
-	ret = snd_pcm_status_get_avail(status) * bytes_per_sample;
-	break;
-    case SND_PCM_STATE_RUNNING:
-      ret = snd_pcm_status_get_avail(status) * bytes_per_sample;
-      //avail_frames = snd_pcm_avail_update(alsa_handler) * bytes_per_sample;
-      if (str_status != "open" && str_status != "prepared")
-	str_status = "running";
-      break;
-    case SND_PCM_STATE_PAUSED:
-      mp_msg(MSGT_AO,MSGL_V,"alsa-space: paused");
-      str_status = "paused";
-      ret = 0;
-      break;
-    case SND_PCM_STATE_XRUN:
-      xrun("space");
-      str_status = "xrun";
-      ret = 0;
-      break;
-    default:
-      str_status = "undefined";
-      ret = snd_pcm_status_get_avail(status) * bytes_per_sample;
-      if (ret <= 0) {
-	xrun("space");
-      }
-    }
 
-    if (snd_pcm_status_get_state(status) != SND_PCM_STATE_RUNNING)
-      mp_msg(MSGT_AO,MSGL_V,"alsa-space: free space = %i, %s --\n", ret, str_status);
-    
-    if (ret < 0) {
-      mp_msg(MSGT_AO,MSGL_ERR,"negative value!!\n");
-      ret = 0;
-    }
- 
-    // workaround for too small value returned
-    if (ret < MIN_CHUNK_SIZE)
-      ret = 0;
-
-    return(ret);
+    ret = snd_pcm_status_get_avail(status) * bytes_per_sample;
+    if (ret > MAX_OUTBURST)
+	    ret = MAX_OUTBURST;
+    return ret - ret % ao_data.outburst;
 }
 
 /* delay in seconds between first and last sample in buffer */
 static float get_delay()
 {
-
   if (alsa_handler) {
 
-    snd_pcm_status_t *status;
-    float ret;
+    snd_pcm_sframes_t delay;
     
-    snd_pcm_status_alloca(&status);
+    if (snd_pcm_delay(alsa_handler, &delay) < 0)
+      return 0;
     
-    if ((ret = snd_pcm_status(alsa_handler, status)) < 0)
-    {
-	mp_msg(MSGT_AO,MSGL_ERR,"alsa-delay: cannot get pcm status: %s\n", snd_strerror(ret));
-    }
-    
-    switch(snd_pcm_status_get_state(status))
-    {
-	case SND_PCM_STATE_OPEN:
-	case SND_PCM_STATE_PREPARED:
-	case SND_PCM_STATE_RUNNING:
-	    ret = (float)snd_pcm_status_get_delay(status)/(float)ao_data.samplerate;
-	    break;
-	default:
-	    ret = 0;
+    if (delay < 0) {
+#if SND_LIB_VERSION >= 0x000901 /* snd_pcm_forward() exists since 0.9.0rc8 */
+      /* underrun - move the application pointer forward to catch up */
+      delay = -delay;
+      delay -= delay % (ao_data.outburst / bytes_per_sample);
+      snd_pcm_forward(alsa_handler, delay);
+#endif
+      delay = 0;
     }
-    
-
-    if (ret < 0)
-      ret = 0;
-    return(ret);
+    return (float)delay / (float)ao_data.samplerate;
     
   } else {
     return(0);




More information about the MPlayer-dev-eng mailing list