changeset 8572:46dbfa8c1999

new EsounD audio driver (-ao esd)
author jkeil
date Fri, 27 Dec 2002 16:02:57 +0000
parents 3a12d4e79322
children b2e4f9dab7ad
files ChangeLog Makefile configure libao2/ao_esd.c libao2/audio_out.c
diffstat 5 files changed, 510 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri Dec 27 01:19:41 2002 +0000
+++ b/ChangeLog	Fri Dec 27 16:02:57 2002 +0000
@@ -1,6 +1,8 @@
 
 mplayer (0.90)
 
+    * -ao esd: new EsounD audio driver
+	
   rc2:
     General:
     * DOCS/tech/encoding-tips.txt and tech/directfb.txt
--- a/Makefile	Fri Dec 27 01:19:41 2002 +0000
+++ b/Makefile	Fri Dec 27 16:02:57 2002 +0000
@@ -34,7 +34,7 @@
 OBJS_MPLAYER = $(SRCS_MPLAYER:.c=.o)
 
 VO_LIBS = $(AA_LIB) $(X_LIB) $(SDL_LIB) $(GGI_LIB) $(MP1E_LIB) $(MLIB_LIB) $(SVGA_LIB) $(DIRECTFB_LIB) $(GIF_LIB) 
-AO_LIBS = $(ARTS_LIB) $(NAS_LIB) $(SGIAUDIO_LIB)
+AO_LIBS = $(ARTS_LIB) $(ESD_LIB) $(NAS_LIB) $(SGIAUDIO_LIB)
 CODEC_LIBS = $(AV_LIB) $(FAME_LIB) $(MAD_LIB) $(VORBIS_LIB) $(FAAD_LIB) $(LIBLZO_LIB) $(XVID_LIB) $(DECORE_LIB) $(PNG_LIB) $(Z_LIB) $(JPEG_LIB) $(ALSA_LIB) $(XMMS_LIB)
 COMMON_LIBS = libmpcodecs/libmpcodecs.a mp3lib/libMP3.a liba52/liba52.a libmpeg2/libmpeg2.a $(W32_LIB) $(DS_LIB) libaf/libaf.a libmpdemux/libmpdemux.a input/libinput.a $(PP_LIB) postproc/libswscale.a linux/libosdep.a $(CSS_LIB) $(CODEC_LIBS) $(FREETYPE_LIB) $(TERMCAP_LIB) $(CDPARANOIA_LIB) $(STREAMING_LIB) $(WIN32_LIB)
 
--- a/configure	Fri Dec 27 01:19:41 2002 +0000
+++ b/configure	Fri Dec 27 16:02:57 2002 +0000
@@ -211,6 +211,7 @@
 Audio output:
   --disable-ossaudio     disable OSS sound support [autodetect]
   --disable-arts         disable aRts sound support [autodetect]
+  --disable-esd          disable esd sound support [autodetect]
   --disable-alsa         disable alsa sound support [autodetect]
   --disable-sunaudio     disable Sun sound support [autodetect]
   --disable-win32waveout disable windows waveout sound support [autodetect]
@@ -975,6 +976,7 @@
 _rtc=auto
 _ossaudio=auto
 _arts=auto
+_esd=auto
 _liblzo=auto
 _mad=auto
 _vorbis=auto
@@ -1112,6 +1114,8 @@
   --disable-ossaudio)	_ossaudio=no	;;
   --enable-arts)	_arts=yes	;;
   --disable-arts)	_arts=no	;;
+  --enable-esd)		_esd=yes	;;
+  --disable-esd)	_esd=no		;;
   --enable-mad)		_mad=yes	;;
   --disable-mad)	_mad=no		;;
   --enable-liblzo)	_liblzo=yes	;;
@@ -3181,6 +3185,32 @@
 echores "$_arts"
 
 
+echocheck "EsounD"
+if test "$_esd" = auto ; then
+  _esd=no
+  if ( esd-config --version ) >> "$TMPLOG" 2>&1 ; then
+
+cat > $TMPC << EOF
+#include <esd.h>
+int main(void) { return 0; }
+EOF
+cc_check `esd-config --libs` `esd-config --cflags` && ( "$TMPO" >> "$TMPLOG" 2>&1 ) && _esd=yes
+
+  fi
+fi
+
+if test "$_esd" = yes ; then
+  _def_esd='#define USE_ESD 1'
+  _aosrc="$_aosrc ao_esd.c"
+  _aomodules="esd $_aomodules"
+  _ld_esd=`esd-config --libs`
+  _inc_esd=`esd-config --cflags`
+else
+  _noaomodules="esd $_noaomodules"
+fi
+echores "$_esd"
+
+
 echocheck "ALSA audio"
 if test "$_alsa" != no ; then
   _alsa=no
@@ -4711,6 +4741,8 @@
 NAS_LIB = $_ld_nas
 ARTS_LIB = $_ld_arts
 ARTS_INC = $_inc_arts
+ESD_LIB = $_ld_esd
+ESD_INC = $_inc_esd
 SGIAUDIO_LIB = $_ld_sgiaudio
 
 # input/demuxer/codecs
@@ -5027,6 +5059,7 @@
 $_def_alsa5
 $_def_alsa9
 $_def_arts
+$_def_esd
 $_def_sys_asoundlib_h
 $_def_alsa_asoundlib_h
 $_def_sunaudio
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libao2/ao_esd.c	Fri Dec 27 16:02:57 2002 +0000
@@ -0,0 +1,468 @@
+/*
+ * ao_esd - EsounD audio output driver for MPlayer
+ *
+ * Juergen Keil <jk@tools.de>
+ *
+ * This driver is distributed under the terms of the GPL
+ *
+ * TODO / known problems:
+ * - does not work well when the esd daemon has autostandby disabled
+ *   (workaround: run esd with option "-as 2" - fortunatelly this is
+ *    the default)
+ * - plays noise on a linux 2.4.4 kernel with a SB16PCI card, when using
+ *   a local tcp connection to the esd daemon; there is no noise when using
+ *   a unix domain socket connection.
+ *   (there are EIO errors reported by the sound card driver, so this is
+ *   most likely a linux sound card driver problem)
+ */
+
+#include "../config.h"
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef	__svr4__
+#include <stropts.h>
+#endif
+#include <esd.h>
+
+#include "audio_out.h"
+#include "audio_out_internal.h"
+#include "afmt.h"
+#include "../config.h"
+#include "../mp_msg.h"
+
+
+#undef	ESD_DEBUG
+
+#if	ESD_DEBUG
+#define	dprintf(...)	printf(__VA_ARGS__)
+#else
+#define	dprintf(...)	/**/
+#endif
+
+
+#define	ESD_CLIENT_NAME	"MPlayer"
+#define	ESD_MAX_DELAY	(1.0f)	/* max amount of data buffered in esd (#sec) */
+
+
+static ao_info_t info =
+{
+    "EsounD audio output",
+    "esd",
+    "Juergen Keil <jk@tools.de>",
+    ""
+};
+
+LIBAO_EXTERN(esd)
+
+static int esd_fd = -1;
+static int esd_play_fd = -1;
+static esd_server_info_t *esd_svinfo;
+static int esd_latency;
+static int esd_bytes_per_sample;
+static unsigned long esd_samples_written;
+static struct timeval esd_play_start;
+
+
+/*
+ * to set/get/query special features/parameters
+ */
+static int control(int cmd, int arg)
+{
+    esd_player_info_t *esd_pi;
+    esd_info_t        *esd_i;
+    time_t	       now;
+    static time_t      vol_cache_time;
+    static ao_control_vol_t vol_cache;
+
+    switch (cmd) {
+    case AOCONTROL_GET_VOLUME:
+	time(&now);
+	if (now == vol_cache_time) {
+	    *(ao_control_vol_t *)arg = vol_cache;
+	    return CONTROL_OK;
+	}
+
+	dprintf("esd: get vol\n");
+	if ((esd_i = esd_get_all_info(esd_fd)) == NULL)
+	    return CONTROL_ERROR;
+
+	for (esd_pi = esd_i->player_list; esd_pi != NULL; esd_pi = esd_pi->next)
+	    if (strcmp(esd_pi->name, ESD_CLIENT_NAME) == 0)
+		break;
+
+	if (esd_pi != NULL) {
+	    ao_control_vol_t *vol = (ao_control_vol_t *)arg;
+	    vol->left =  esd_pi->left_vol_scale  * 100 / ESD_VOLUME_BASE;
+	    vol->right = esd_pi->right_vol_scale * 100 / ESD_VOLUME_BASE;
+
+	    vol_cache = *vol;
+	    vol_cache_time = now;
+	}
+	esd_free_all_info(esd_i);
+	
+	return CONTROL_OK;
+
+    case AOCONTROL_SET_VOLUME:
+	dprintf("esd: set vol\n");
+	if ((esd_i = esd_get_all_info(esd_fd)) == NULL)
+	    return CONTROL_ERROR;
+
+	for (esd_pi = esd_i->player_list; esd_pi != NULL; esd_pi = esd_pi->next)
+	    if (strcmp(esd_pi->name, ESD_CLIENT_NAME) == 0)
+		break;
+
+	if (esd_pi != NULL) {
+	    ao_control_vol_t *vol = (ao_control_vol_t *)arg;
+	    esd_set_stream_pan(esd_fd, esd_pi->source_id,
+			       vol->left  * ESD_VOLUME_BASE / 100,
+			       vol->right * ESD_VOLUME_BASE / 100);
+
+	    vol_cache = *vol;
+	    time(&vol_cache_time);
+	}
+	esd_free_all_info(esd_i);
+	return CONTROL_OK;
+
+    default:
+	return CONTROL_UNKNOWN;
+    }
+}
+
+
+/*
+ * open & setup audio device
+ * return: 1=success 0=fail
+ */
+static int init(int rate_hz, int channels, int format, int flags)
+{
+    esd_format_t esd_fmt;
+    int bytes_per_sample;
+    int fl;
+
+    if (esd_fd < 0) {
+	esd_fd = esd_open_sound(NULL);
+	if (esd_fd < 0) {
+	    mp_msg(MSGT_AO, MSGL_ERR, "AO: [esd] esd_open_sound failed: %s\n",
+		   strerror(errno));
+	    return 0;
+	}
+
+	esd_svinfo = esd_get_server_info(esd_fd);
+     /*
+	if (esd_svinfo) {
+	    mp_msg(MSGT_AO, MSGL_INFO, "AO: [esd] server info:\n");
+	    esd_print_server_info(esd_svinfo);
+	}
+      */
+
+	esd_latency = esd_get_latency(esd_fd);
+	/* mp_msg(MSGT_AO, MSGL_INFO, "AO: [esd] latency: %d\n", esd_latency); */
+    }
+
+    esd_fmt = ESD_STREAM | ESD_PLAY;
+
+#if	ESD_RESAMPLES
+    /* let the esd daemon convert sample rate */
+#else
+    /* let mplayer's audio filter convert the sample rate */
+    if (esd_svinfo != NULL)
+	rate_hz = esd_svinfo->rate;
+#endif
+    ao_data.samplerate = rate_hz;
+
+    
+    /* EsounD can play mono or stereo */
+    switch (channels) {
+    case 1:
+	esd_fmt |= ESD_MONO;
+	ao_data.channels = bytes_per_sample = 1;
+	break;
+    default:
+	esd_fmt |= ESD_STEREO;
+	ao_data.channels = bytes_per_sample = 2;
+	break;
+    }
+
+    /* EsounD can play 8bit unsigned and 16bit signed native */
+    switch (format) {
+    case AFMT_S8:
+    case AFMT_U8:
+	esd_fmt |= ESD_BITS8;
+	ao_data.format = AFMT_U8;
+	break;
+    default:
+	esd_fmt |= ESD_BITS16;
+	ao_data.format = AFMT_S16_NE;
+	bytes_per_sample *= 2;
+	break;
+    }
+
+    esd_play_fd = esd_play_stream_fallback(esd_fmt, rate_hz,
+					   NULL, ESD_CLIENT_NAME);
+    if (esd_play_fd < 0) {
+	mp_msg(MSGT_AO, MSGL_ERR,
+	       "AO: [esd] failed to open esd playback stream: %s\n",
+	       strerror(errno));
+	return 0;
+    }
+
+    /* enable non-blocking i/o on the socket connection to the esd server */
+    if ((fl = fcntl(esd_play_fd, F_GETFL)) >= 0)
+	fcntl(esd_play_fd, F_SETFL, O_NDELAY|fl);
+
+#if ESD_DEBUG
+    {
+	int sbuf, rbuf, len;
+	len = sizeof(sbuf);
+	getsockopt(esd_play_fd, SOL_SOCKET, SO_SNDBUF, &sbuf, &len);
+	len = sizeof(rbuf);
+	getsockopt(esd_play_fd, SOL_SOCKET, SO_RCVBUF, &rbuf, &len);
+	dprintf("esd: send/receive socket buffer space %d/%d bytes\n",
+		sbuf, rbuf);
+    }
+#endif
+
+    ao_data.bps = bytes_per_sample * rate_hz;
+    ao_data.outburst = ao_data.bps > 100000 ? 4*ESD_BUF_SIZE : 2*ESD_BUF_SIZE;
+
+    esd_play_start.tv_sec = 0;
+    esd_samples_written = 0;
+    esd_bytes_per_sample = bytes_per_sample;
+
+    return 1;
+}
+
+
+/*
+ * close audio device
+ */
+static void uninit()
+{
+    if (esd_play_fd >= 0) {
+	esd_close(esd_play_fd);
+	esd_play_fd = -1;
+    }
+
+    if (esd_svinfo) {
+	esd_free_server_info(esd_svinfo);
+	esd_svinfo = NULL;
+    }
+
+    if (esd_fd >= 0) {
+	esd_close(esd_fd);
+	esd_fd = -1;
+    }
+}
+
+
+/*
+ * plays 'len' bytes of 'data'
+ * it should round it down to outburst*n
+ * return: number of bytes played
+ */
+static int play(void* data, int len, int flags)
+{
+    int offs;
+    int nwritten;
+    int nsamples;
+    int remainder, n;
+    int saved_fl;
+
+    /* round down buffersize to a multiple of ESD_BUF_SIZE bytes */
+    len = len / ESD_BUF_SIZE * ESD_BUF_SIZE;
+    if (len <= 0)
+	return 0;
+
+#define	SINGLE_WRITE 0
+#if	SINGLE_WRITE
+    nwritten = write(esd_play_fd, data, len);
+#else
+    for (offs = 0; offs + ESD_BUF_SIZE <= len; offs += ESD_BUF_SIZE) {
+	/*
+	 * note: we're writing to a non-blocking socket here.
+	 * A partial write means, that the socket buffer is full.
+	 */
+	nwritten = write(esd_play_fd, (char*)data + offs, ESD_BUF_SIZE);
+	if (nwritten != ESD_BUF_SIZE) {
+	    if (nwritten < 0 && errno != EAGAIN) {
+		dprintf("esd play: write failed: %s\n", strerror(errno));
+	    }
+	    break;
+	}
+    }
+    nwritten = offs;
+#endif
+
+    if (nwritten > 0 && nwritten % ESD_BUF_SIZE != 0) {
+
+	/*
+	 * partial write of an audio block of ESD_BUF_SIZE bytes.
+	 *
+	 * Send the remainder of that block as well; this avoids a busy
+	 * polling loop in the esd daemon, which waits for the rest of
+	 * the incomplete block using reads from a non-blocking
+	 * socket. This busy polling loop wastes CPU cycles on the
+	 * esd server machine, and we're trying to avoid that.
+	 * (esd 0.2.28+ has the busy polling read loop, 0.2.22 inserts
+	 * 0 samples which is bad as well)
+	 *
+	 * Let's hope the blocking write does not consume too much time.
+	 *
+	 * (fortunatelly, this piece of code is not used when playing
+	 * sound on the local machine - on solaris at least)
+	 */
+	remainder = ESD_BUF_SIZE - nwritten % ESD_BUF_SIZE;
+	dprintf("esd play: partial audio block written, remainder %d \n",
+		remainder);
+
+	/* blocking write of remaining bytes for the partial audio block */
+	saved_fl = fcntl(esd_play_fd, F_GETFL);
+	fcntl(esd_play_fd, F_SETFL, saved_fl & ~O_NDELAY);
+	n = write(esd_play_fd, (char *)data + nwritten, remainder);
+	fcntl(esd_play_fd, F_SETFL, saved_fl);
+
+	if (n != remainder) {
+	    mp_msg(MSGT_AO, MSGL_ERR,
+		   "AO: [esd] send remainer of audio block failed, %d/%d\n",
+		   n, remainder);
+	} else
+	    nwritten += n;
+    }
+
+    if (nwritten > 0) {
+	if (!esd_play_start.tv_sec)
+	    gettimeofday(&esd_play_start, NULL);
+	nsamples = nwritten / esd_bytes_per_sample;
+	esd_samples_written += nsamples;
+ 
+	dprintf("esd play: %d %lu\n", nsamples, esd_samples_written);
+    } else {
+	dprintf("esd play: blocked / %lu\n", esd_samples_written);
+    }
+
+    return nwritten;
+}
+
+
+/*
+ * stop playing, keep buffers (for pause)
+ */
+static void audio_pause()
+{
+    /*
+     * not possible with esd.  the esd daemom will continue playing
+     * buffered data (not more than ESD_MAX_DELAY seconds of samples)
+     */
+}
+
+
+/*
+ * resume playing, after audio_pause()
+ */
+static void audio_resume()
+{
+    /*
+     * not possible with esd.
+     *
+     * Let's hope the pause was long enough that the esd ran out of
+     * buffered data;  we restart our time based delay computation
+     * for an audio resume.
+     */
+    esd_play_start.tv_sec = 0;
+    esd_samples_written = 0;
+}
+
+
+/*
+ * stop playing and empty buffers (for seeking/pause)
+ */
+static void reset()
+{
+#ifdef	__svr4__
+    /* throw away data buffered in the esd connection */
+    if (ioctl(esd_play_fd, I_FLUSH, FLUSHW)) 
+	perror("I_FLUSH");
+#endif
+}
+
+
+/*
+ * return: how many bytes can be played without blocking
+ */
+static int get_space()
+{
+    struct timeval tmout;
+    fd_set wfds;
+    float current_delay;
+    int space;
+
+    /* 
+     * Don't buffer too much data in the esd daemon.
+     *
+     * If we send too much, esd will block in write()s to the sound
+     * device, and the consequence is a huge slow down for things like
+     * esd_get_all_info().
+     */
+    if ((current_delay = get_delay()) >= ESD_MAX_DELAY) {
+	dprintf("esd get_space: too much data buffered\n");
+	return 0;
+    }
+
+    FD_ZERO(&wfds);
+    FD_SET(esd_play_fd, &wfds);
+    tmout.tv_sec = 0;
+    tmout.tv_usec = 0;
+
+    if (select(esd_play_fd + 1, NULL, &wfds, NULL, &tmout) != 1)
+	return 0;
+
+    if (!FD_ISSET(esd_play_fd, &wfds))
+	return 0;
+
+    /* try to fill 50% of the remaining "free" buffer space */
+    space = (ESD_MAX_DELAY - current_delay) * ao_data.bps * 0.5f;
+
+    /* round up to next multiple of ESD_BUF_SIZE */
+    space = (space + ESD_BUF_SIZE-1) / ESD_BUF_SIZE * ESD_BUF_SIZE;
+
+    dprintf("esd get_space: %d\n", space);
+    return space;
+}
+
+
+/*
+ * return: delay in seconds between first and last sample in buffer
+ */
+static float get_delay()
+{
+    struct timeval now;
+    double buffered_samples_time;
+    double play_time;
+
+    if (!esd_play_start.tv_sec)
+	return 0;
+
+    buffered_samples_time = (float)esd_samples_written / ao_data.samplerate;
+    gettimeofday(&now, NULL);
+    play_time  =  now.tv_sec  - esd_play_start.tv_sec;
+    play_time += (now.tv_usec - esd_play_start.tv_usec) / 1000000.;
+    
+    /* dprintf("esd delay: %f %f\n", play_time, buffered_samples_time); */
+
+    if (play_time > buffered_samples_time) {
+	dprintf("esd: underflow\n");
+	esd_play_start.tv_sec = 0;
+	esd_samples_written = 0;
+	return 0;
+    }
+
+    dprintf("esd: get_delay %f\n", buffered_samples_time - play_time);
+    return buffered_samples_time - play_time;
+}
--- a/libao2/audio_out.c	Fri Dec 27 01:19:41 2002 +0000
+++ b/libao2/audio_out.c	Fri Dec 27 16:02:57 2002 +0000
@@ -19,6 +19,9 @@
 #ifdef USE_ARTS
 extern ao_functions_t audio_out_arts;
 #endif
+#ifdef USE_ESD
+extern ao_functions_t audio_out_esd;
+#endif
 extern ao_functions_t audio_out_null;
 #ifdef HAVE_ALSA5
  extern ao_functions_t audio_out_alsa5;
@@ -79,6 +82,9 @@
 #ifdef USE_ARTS
         &audio_out_arts,
 #endif
+#ifdef USE_ESD
+        &audio_out_esd,
+#endif
 #ifdef HAVE_NAS
 	&audio_out_nas,
 #endif