[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