Sophie

Sophie

distrib > * > 2008.0 > x86_64 > by-pkgid > 2b471379494a2acc128492c7574ce12f > files > 10

xmms-1.2.10-35mdv2008.0.src.rpm

--- Output/alsa/configure.c-dist	2005-01-09 22:11:33.000000000 +0100
+++ Output/alsa/configure.c	2005-01-09 22:08:07.000000000 +0100
@@ -20,8 +20,8 @@
 #include <stdio.h>
 
 static GtkWidget *configure_win = NULL;
-static GtkWidget *buffer_time_spin, *period_time_spin;
-static GtkWidget *mmap_button, *mixer_card_spin, *softvolume_toggle_button;
+static GtkWidget *buffer_time_spin, *period_time_spin, *thread_buffer_time_spin;
+static GtkWidget *mthread_button, *mmap_button, *mixer_card_spin, *softvolume_toggle_button;
 
 static GtkWidget *devices_combo, *mixer_devices_combo;
 
@@ -36,6 +36,8 @@ static void configure_win_ok_cb(GtkWidge
 	alsa_cfg.pcm_device = GET_CHARS(GTK_COMBO(devices_combo)->entry);
 	alsa_cfg.buffer_time = GET_SPIN_INT(buffer_time_spin);
 	alsa_cfg.period_time = GET_SPIN_INT(period_time_spin);
+	alsa_cfg.thread_buffer_time = GET_SPIN_INT(thread_buffer_time_spin);
+	alsa_cfg.multi_thread = GET_TOGGLE(mthread_button);
 	alsa_cfg.mmap = GET_TOGGLE(mmap_button);
 	alsa_cfg.soft_volume = GET_TOGGLE(softvolume_toggle_button);
 	alsa_cfg.mixer_card = GET_SPIN_INT(mixer_card_spin);
@@ -51,6 +53,8 @@ void alsa_save_config(void)
 	
 	xmms_cfg_write_int(cfgfile, "ALSA", "buffer_time", alsa_cfg.buffer_time);
 	xmms_cfg_write_int(cfgfile, "ALSA", "period_time", alsa_cfg.period_time);
+	xmms_cfg_write_int(cfgfile, "ALSA", "thread_buffer_time", alsa_cfg.thread_buffer_time);
+	xmms_cfg_write_boolean(cfgfile, "ALSA", "multi_thread", alsa_cfg.multi_thread);
 	xmms_cfg_write_boolean(cfgfile,"ALSA","mmap",alsa_cfg.mmap);
 	xmms_cfg_write_string(cfgfile,"ALSA","pcm_device", alsa_cfg.pcm_device);
 	xmms_cfg_write_int(cfgfile, "ALSA", "mixer_card", alsa_cfg.mixer_card);
@@ -212,8 +216,8 @@ void alsa_configure(void)
 	GtkWidget *dev_vbox, *adevice_frame, *adevice_box;
 	GtkWidget *mixer_frame, *mixer_box, *mixer_card_box;
 	GtkWidget *buffer_frame, *buffer_vbox, *buffer_table;
-	GtkWidget *buffer_time_label, *period_time_label;
-	GtkObject *buffer_time_adj, *period_time_adj, *mixer_card_adj;
+	GtkWidget *buffer_time_label, *period_time_label, *thread_buffer_time_label;
+	GtkObject *buffer_time_adj, *period_time_adj, *thread_buffer_time_adj, *mixer_card_adj;
 	GtkWidget *bbox, *ok, *cancel;
 	
 	if (configure_win)
@@ -312,7 +316,7 @@ void alsa_configure(void)
 	
 	gtk_container_set_border_width(GTK_CONTAINER(buffer_vbox), 5);
 
-	buffer_table = gtk_table_new(2, 2, FALSE);
+	buffer_table = gtk_table_new(2, 3, FALSE);
 	gtk_table_set_row_spacings(GTK_TABLE(buffer_table), 5);
 	gtk_table_set_col_spacings(GTK_TABLE(buffer_table), 5);
 	gtk_box_pack_start(GTK_BOX(buffer_vbox), buffer_table, FALSE, FALSE, 0);
@@ -345,6 +349,25 @@ void alsa_configure(void)
 	gtk_table_attach(GTK_TABLE(buffer_table), period_time_spin,
 			 1, 2, 1, 2, 0, 0, 0, 0);
 	
+	thread_buffer_time_label = gtk_label_new(_("Thread buffer time (ms):"));
+	gtk_label_set_justify(GTK_LABEL(thread_buffer_time_label), GTK_JUSTIFY_LEFT);
+	gtk_misc_set_alignment(GTK_MISC(thread_buffer_time_label), 0, 0.5);
+	gtk_table_attach(GTK_TABLE(buffer_table), thread_buffer_time_label,
+			 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
+	thread_buffer_time_adj = gtk_adjustment_new(alsa_cfg.thread_buffer_time,
+						    1000, 1000000, 100, 100, 100);
+	thread_buffer_time_spin = gtk_spin_button_new(GTK_ADJUSTMENT(thread_buffer_time_adj),
+						      8, 0);
+	
+	gtk_widget_set_usize(thread_buffer_time_spin, 60, -1);
+	gtk_table_attach(GTK_TABLE(buffer_table), thread_buffer_time_spin,
+			 1, 2, 2, 3, 0, 0, 0, 0);
+	
+	mthread_button = gtk_check_button_new_with_label(_("Multi-thread mode"));
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mthread_button),
+				     alsa_cfg.multi_thread);
+	gtk_box_pack_start(GTK_BOX(buffer_vbox), mthread_button, FALSE, FALSE, 0);
+	
 	mmap_button = gtk_check_button_new_with_label(_("Mmap mode"));
 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mmap_button),
 				     alsa_cfg.mmap);
--- Output/alsa/audio.c-dist	2005-01-11 17:57:21.374002224 +0100
+++ Output/alsa/audio.c	2005-01-11 17:58:04.891386576 +0100
@@ -17,36 +17,56 @@
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ *
+ *  CHANGES
+ *
+ *  2005.01.05  Takashi Iwai <tiwai@suse.de>
+ *	Impelemented the multi-threaded mode with an audio-thread.
+ *	Many fixes and cleanups.
  */
 
 #include "alsa.h"
 #include <ctype.h>
+#include <pthread.h>
 #include <libxmms/xconvert.h>
 
 static snd_pcm_t		*alsa_pcm 	    = NULL;
-static snd_pcm_status_t		*alsa_status 	    = NULL;
-static snd_pcm_channel_area_t   *areas              = NULL;
 
 static snd_output_t		*logs 		    = NULL;
 
-static int 			 alsa_bps 	    = 0;
-static guint64			 alsa_total_written = 0;
+static guint64			 alsa_total_written = 0;	/* input bytes */
+static guint64			 alsa_hw_written    = 0;	/* output bytes */
+static gint			 output_time_offset = 0;
+
+/* device buffer/period sizes in bytes */
+static int hw_buffer_size, hw_period_size;		/* in output bytes */
+static int hw_buffer_size_in, hw_period_size_in;	/* in input bytes */
 
 /* Set/Get volume */
 static snd_mixer_elem_t 	*pcm_element 	    = NULL;
 static snd_mixer_t 		*mixer 		    = NULL;
 
-static gboolean mmap, force_start, going, paused;
+static gboolean mmap, going, paused, multi_thread;
 
-static gpointer buffer;
+static gboolean alsa_can_pause;
+
+/* for audio thread */
+static pthread_t audio_thread;		/* audio loop thread */
+static int thread_buffer_size;		/* size of intermediate buffer in bytes */
+static char *thread_buffer;		/* audio intermediate buffer */
+static int rd_index, wr_index;		/* current read/write position in int-buffer */
+static gboolean pause_request;		/* pause status currently requested */
+static gint flush_request;		/* flush status (time) currently requested */
 
-static int alsa_can_pause;
 
 struct snd_format {
 	unsigned int rate;
 	unsigned int channels;
 	snd_pcm_format_t format;
 	AFormat xmms_format;
+	int sample_bits;
+	int bps;
 };
 
 static struct snd_format *inputf = NULL;
@@ -55,7 +75,7 @@ static struct snd_format *outputf = NULL
 
 static int alsa_setup(struct snd_format *f);
 static void alsa_mmap_audio(char *data, int length);
-static void alsa_write_audio(gpointer data, int length);
+static void alsa_write_audio(char *data, int length);
 
 static struct snd_format * snd_format_from_xmms(AFormat fmt, int rate, int channels);
 
@@ -71,22 +91,10 @@ static const struct {
 } format_table[] =
 {{FMT_S16_LE, SND_PCM_FORMAT_S16_LE},
  {FMT_S16_BE, SND_PCM_FORMAT_S16_BE},
- {FMT_S16_NE,
-#ifdef WORDS_BIGENDIAN
-  SND_PCM_FORMAT_S16_BE
-#else
-  SND_PCM_FORMAT_S16_LE
-#endif
- },
+ {FMT_S16_NE, SND_PCM_FORMAT_S16},
  {FMT_U16_LE, SND_PCM_FORMAT_U16_LE},
  {FMT_U16_BE, SND_PCM_FORMAT_U16_BE},
- {FMT_U16_NE,
-#ifdef WORDS_BIGENDIAN
-  SND_PCM_FORMAT_U16_BE
-#else
-  SND_PCM_FORMAT_U16_LE
-#endif
- },
+ {FMT_U16_NE, SND_PCM_FORMAT_U16},
  {FMT_U8, SND_PCM_FORMAT_U8},
  {FMT_S8, SND_PCM_FORMAT_S8},
 };
@@ -106,176 +114,9 @@ static void debug(char *str, ...)
 	}
 }
 
-int alsa_playing(void)
-{
-	if (!going || paused)
-		return FALSE;
-
-	return(snd_pcm_state(alsa_pcm) == SND_PCM_STATE_RUNNING);
-}
-
-static void xrun_recover(void)
-{
-	int err;
-
-	if (alsa_cfg.debug)
-	{
-		snd_pcm_status_alloca(&alsa_status);
-		if ((err = snd_pcm_status(alsa_pcm, alsa_status)) < 0)
-			g_warning("xrun_recover(): snd_pcm_status() failed");
-		else
-		{
-			printf("Status:\n");
-			snd_pcm_status_dump(alsa_status, logs);
-		}
-	}
-
-	if (snd_pcm_state(alsa_pcm) == SND_PCM_STATE_XRUN)
-	{
-		if ((err = snd_pcm_prepare(alsa_pcm)) < 0)
-			g_warning("xrun_recover(): snd_pcm_prepare() failed.");
-	}
-}
-
-static snd_pcm_sframes_t alsa_get_avail(void)
-{
-	snd_pcm_sframes_t ret;
-	if ((ret = snd_pcm_avail_update(alsa_pcm)) == -EPIPE)
-		xrun_recover();
-	else if (ret < 0)
-	{
-		g_warning("alsa_get_avail(): snd_pcm_avail_update() failed: %s",
-			  snd_strerror(-ret));
-		return 0;
-	}
-	else
-		return ret;
-	if ((ret = snd_pcm_avail_update(alsa_pcm)) < 0)
-	{
-		g_warning("alsa_get_avail(): snd_pcm_avail_update() failed: %s",
-			  snd_strerror(-ret));
-		return 0;
-	}
-	return ret;
-}
-
-int alsa_free(void)
-{
-	if (paused)
-		return 0;
-	else
-	{
-		int err;
-		if (force_start &&
-		    snd_pcm_state(alsa_pcm) == SND_PCM_STATE_PREPARED)
-		{
-			if ((err = snd_pcm_start(alsa_pcm)) < 0)
-				g_warning("alsa_free(): snd_pcm_start() "
-					  "failed: %s", snd_strerror(-err));
-			else
-				debug("Stream started");
-		}
-		force_start = TRUE;
-
-		return snd_pcm_frames_to_bytes(alsa_pcm, alsa_get_avail());
-	}
-}
-
-void alsa_pause(short p)
-{
-	debug("alsa_pause");
-	if (p)
-		paused = TRUE;
-
-	if (alsa_can_pause)
-		snd_pcm_pause(alsa_pcm, p);
-	else if (p)
-		snd_pcm_drop(alsa_pcm);
-
-	if (!p)
-		paused = FALSE;
-}
-
-void alsa_close(void)
-{
-	int err, started;
-
-	debug("Closing device");
-
-	started = going;
-	going = 0;
-
-	pcm_element = NULL;
-
-	if (mixer)
-	{
-		snd_mixer_close(mixer);
-		mixer = NULL;
-	}
-
-	if (alsa_pcm != NULL)
-	{
-		if (started)
-			if ((err = snd_pcm_drop(alsa_pcm)) < 0)
-				g_warning("alsa_pcm_drop() failed: %s",
-					  snd_strerror(-err));
-
-		if ((err = snd_pcm_close(alsa_pcm)) < 0)
-			g_warning("alsa_pcm_close() failed: %s",
-				  snd_strerror(-err));
-		alsa_pcm = NULL;
-	}
-
-	if (mmap) {
-		g_free(buffer);
-		buffer = NULL;
-
-		g_free(areas);
-		areas = NULL;
-	}
-
-	xmms_convert_buffers_destroy(convertb);
-	convertb = NULL;
-	g_free(inputf);
-	inputf = NULL;
-	g_free(effectf);
-	effectf = NULL;
-
-	alsa_save_config();
-
-	debug("Device closed");
-}
-
-static void alsa_reopen(struct snd_format *f)
-{
-	unsigned int tmp = alsa_get_written_time();
-
-	if (alsa_pcm != NULL)
-	{
-		snd_pcm_close(alsa_pcm);
-		alsa_pcm = NULL;
-	}
-
-	if (mmap) {
-		g_free(buffer);
-		buffer = NULL;
-		
-		g_free(areas);
-		areas = NULL;
-	}
-
-	if (alsa_setup(f) < 0)
-		g_warning("Failed to reopen the audio device");
-
-	alsa_total_written = tmp;
-	snd_pcm_prepare(alsa_pcm);
-}
-
-void alsa_flush(int time)
-{
-	alsa_total_written = (guint64) time * alsa_bps / 1000;
-}
-
+/*
+ * mixer stuff
+ */
 static void parse_mixer_name(char *str, char **name, int *index)
 {
 	char *end;
@@ -337,7 +178,7 @@ int alsa_get_mixer(snd_mixer_t **mixer, 
 }
 	
 
-snd_mixer_elem_t* alsa_get_mixer_elem(snd_mixer_t *mixer, char *name, int index)
+static snd_mixer_elem_t* alsa_get_mixer_elem(snd_mixer_t *mixer, char *name, int index)
 {
 	snd_mixer_selem_id_t *selem_id;
 	snd_mixer_elem_t* elem;
@@ -353,7 +194,7 @@ snd_mixer_elem_t* alsa_get_mixer_elem(sn
 	return elem;
 }
 
-int alsa_setup_mixer(void)
+static int alsa_setup_mixer(void)
 {
 	char *name;
 	long int a, b;
@@ -406,6 +247,15 @@ int alsa_setup_mixer(void)
 	return 0;
 }
 
+static void alsa_cleanup_mixer(void)
+{
+	pcm_element = NULL;
+	if (mixer) {
+		snd_mixer_close(mixer);
+		mixer = NULL;
+	}
+}
+
 void alsa_get_volume(int *l, int *r)
 {
 	static gboolean first = TRUE;
@@ -461,25 +311,216 @@ void alsa_set_volume(int l, int r)
 }
 
 
+/*
+ * audio stuff
+ */
+
+int alsa_playing(void)
+{
+	if (!going || paused || alsa_pcm == NULL)
+		return FALSE;
+
+	return(snd_pcm_state(alsa_pcm) == SND_PCM_STATE_RUNNING);
+}
+
+
+/* handle generic errors */
+static int alsa_handle_error(int err)
+{
+	switch (err) {
+	case -EPIPE: /* XRUN */
+		if (alsa_cfg.debug) {
+			snd_pcm_status_t *alsa_status;
+			snd_pcm_status_alloca(&alsa_status);
+			if (snd_pcm_status(alsa_pcm, alsa_status) < 0)
+				g_warning("xrun_recover(): snd_pcm_status() failed");
+			else {
+				printf("Status:\n");
+				snd_pcm_status_dump(alsa_status, logs);
+			}
+		}
+		return snd_pcm_prepare(alsa_pcm);
+
+	case -ESTRPIPE: /* suspend */
+		while ((err = snd_pcm_resume(alsa_pcm)) == -EAGAIN)
+			sleep(1);	/* wait until suspend flag is released */
+		if (err < 0) {
+			g_warning("suspend_recover(): snd_pcm_resume() failed.");
+			return snd_pcm_prepare(alsa_pcm);
+		}
+		break;
+	}
+
+	return err;
+}
+
+/* update and get the available space on h/w buffer (in frames) */
+static snd_pcm_sframes_t alsa_get_avail(void)
+{
+	snd_pcm_sframes_t ret;
+
+	if (alsa_pcm == NULL)
+		return 0;
+
+	while ((ret = snd_pcm_avail_update(alsa_pcm)) < 0) {
+		ret = alsa_handle_error(ret);
+		if (ret < 0) {
+			g_warning("alsa_get_avail(): snd_pcm_avail_update() failed: %s",
+				  snd_strerror(-ret));
+			return 0;
+		}
+	}
+	return ret;
+}
+
+/* do pause operation */
+static void alsa_do_pause(gboolean p)
+{
+	if (paused == p)
+		return;
+
+	if (alsa_pcm) {
+		if (alsa_can_pause) {
+			snd_pcm_pause(alsa_pcm, p);
+		} else if (p) {
+			snd_pcm_drop(alsa_pcm);
+			snd_pcm_prepare(alsa_pcm);
+		}
+	}
+	paused = p;
+}
+
+void alsa_pause(short p)
+{
+	debug("alsa_pause");
+	if (multi_thread)
+		pause_request = p;
+	else
+		alsa_do_pause(p);
+}
+
+/* close PCM and release associated resources */
+static void alsa_close_pcm(void)
+{
+	if (alsa_pcm) {
+		int err;
+		snd_pcm_drop(alsa_pcm);
+		if ((err = snd_pcm_close(alsa_pcm)) < 0)
+			g_warning("alsa_pcm_close() failed: %s",
+				  snd_strerror(-err));
+		alsa_pcm = NULL;
+	}
+}
+
+/* reopen ALSA PCM */
+static int alsa_reopen(struct snd_format *f)
+{
+	/* remember the current position */
+	output_time_offset += (alsa_hw_written * 1000) / outputf->bps;
+	alsa_hw_written = 0;
+
+	alsa_close_pcm();
+
+	return alsa_setup(f);
+}
+
+/* do flush (drop) operation */
+static void alsa_do_flush(int time)
+{
+	if (alsa_pcm) {
+		snd_pcm_drop(alsa_pcm);
+		snd_pcm_prepare(alsa_pcm);
+	}
+	/* correct the offset */
+	output_time_offset = time;
+	alsa_total_written = (guint64) time * inputf->bps / 1000;
+	rd_index = wr_index = alsa_hw_written = 0;
+}
+
+void alsa_flush(int time)
+{
+	if (multi_thread) {
+		flush_request = time;
+		while (flush_request != -1)
+			xmms_usleep(10000);
+	} else
+		alsa_do_flush(time);
+}
+
+void alsa_close(void)
+{
+	if (! going)
+		return;
+
+	debug("Closing device");
+
+	going = 0;
+
+	if (multi_thread)
+		pthread_join(audio_thread, NULL);
+	else
+		alsa_close_pcm();
+
+	alsa_cleanup_mixer();
+
+	xmms_convert_buffers_destroy(convertb);
+	convertb = NULL;
+	g_free(inputf);
+	inputf = NULL;
+	g_free(effectf);
+	effectf = NULL;
+	g_free(outputf);
+	outputf = NULL;
+
+	alsa_save_config();
+
+	if (alsa_cfg.debug)
+		snd_output_close(logs);
+	debug("Device closed");
+}
+
+/* return the size of audio data filled in the audio thread buffer */
+static int get_thread_buffer_filled(void)
+{
+	if (wr_index >= rd_index)
+		return wr_index - rd_index;
+	return thread_buffer_size - (rd_index - wr_index);
+}
+
+/* get the free space on buffer */
+int alsa_free(void)
+{
+	int result = 0;
+	if (multi_thread)
+		result = thread_buffer_size - get_thread_buffer_filled() - 1;
+	else if (! paused && alsa_pcm)
+		result = snd_pcm_frames_to_bytes(alsa_pcm, alsa_get_avail());
+	return result;
+}
+
 int alsa_get_output_time(void)
 {
 	snd_pcm_sframes_t delay;
-	ssize_t db = 0;
+	guint64 bytes = 0;
 
-	if (!going)
+	if (!going || alsa_pcm == NULL)
 		return 0;
 
-	if (!snd_pcm_delay(alsa_pcm, &delay))
-		db = snd_pcm_frames_to_bytes(alsa_pcm, delay);
-	
-	if (db < alsa_total_written)
-		return ((alsa_total_written - db) * 1000 / alsa_bps);
-	return 0;
+	if (!snd_pcm_delay(alsa_pcm, &delay)) {
+		bytes = snd_pcm_frames_to_bytes(alsa_pcm, delay);
+		if (alsa_hw_written < bytes)
+			bytes = 0;
+		else
+			bytes = alsa_hw_written - bytes;
+	}
+	return output_time_offset + (bytes * 1000) / outputf->bps;
 }
 
 int alsa_get_written_time(void)
 {
-	return (alsa_total_written * 1000 / alsa_bps);
+	if (!going)
+		return 0;
+	return (alsa_total_written * 1000) / inputf->bps;
 }
 
 #define STEREO_ADJUST(type, type2, endian)					\
@@ -584,53 +625,47 @@ static void volume_adjust(void* data, in
 }
 
 
-void alsa_write(gpointer data, int length)
+/* transfer data to audio h/w; length is given in bytes
+ *
+ * data can be modified via effect plugin, rate conversion or
+ * software volume before passed to audio h/w
+ */
+static void alsa_do_write(gpointer data, int length)
 {
-	EffectPlugin *ep;
+	EffectPlugin *ep = NULL;
+	int new_freq;
+	int new_chn;
+	AFormat f;
 
 	if (paused)
 		return;
 
-	force_start = FALSE;
-
-	if (effects_enabled() && (ep = get_current_effect_plugin()))
-	{
-		int new_freq = inputf->rate;
-		int new_chn = inputf->channels;
-		AFormat f = inputf->xmms_format;
+	new_freq = inputf->rate;
+	new_chn = inputf->channels;
+	f = inputf->xmms_format;
 		
-		if (ep->query_format)
-		{
-			ep->query_format(&f, &new_freq, &new_chn);
-			
-			if (f != effectf->xmms_format ||
-			    new_freq != effectf->rate ||
-			    new_chn != effectf->channels)
-			{
-				debug("Changing audio format for effect plugin");
-
-				g_free(effectf);
-				effectf = snd_format_from_xmms(f, new_freq,
-							       new_chn);
-				alsa_reopen(effectf);
-			}
-			
-		}
+	if (effects_enabled() && (ep = get_current_effect_plugin()) &&
+	    ep->query_format)
+		ep->query_format(&f, &new_freq, &new_chn);
 	
+	if (f != effectf->xmms_format || new_freq != effectf->rate ||
+	    new_chn != effectf->channels) {
+		debug("Changing audio format for effect plugin");
+		g_free(effectf);
+		effectf = snd_format_from_xmms(f, new_freq, new_chn);
+		if (alsa_reopen(effectf) < 0) {
+			/* fatal error... */
+			alsa_close();
+			return;
+		}
+	}
+
+	if (ep) {
 		length = ep->mod_samples(&data, length,
 					 inputf->xmms_format,
 					 inputf->rate,
 					 inputf->channels);
 	}
-	else if (effectf)
-	{
-		g_free(effectf);
-		effectf = NULL;
-		effectf = snd_format_from_xmms(inputf->xmms_format,
-					       inputf->rate,
-					       inputf->channels);
-		alsa_reopen(inputf);
-	}
 
 	if (alsa_convert_func != NULL)
 		length = alsa_convert_func(convertb, &data, length);
@@ -650,7 +685,31 @@ void alsa_write(gpointer data, int lengt
 		alsa_write_audio(data, length);
 }
 
-static void alsa_write_audio(gpointer data, int length)
+/* write callback */
+void alsa_write(gpointer data, int length)
+{
+	if (multi_thread) {
+		int cnt;
+		char *src = (char *)data;
+
+		alsa_total_written += length;
+		while (length > 0) {
+			int wr;
+			cnt = MIN(length, thread_buffer_size - wr_index);
+			memcpy(thread_buffer + wr_index, src, cnt);
+			wr = (wr_index + cnt) % thread_buffer_size;
+			wr_index = wr;
+			length -= cnt;
+			src += cnt;
+		}
+	} else {
+		alsa_do_write(data, length);
+		alsa_total_written += length;
+	}
+}
+
+/* transfer data to audio h/w via normal write */
+static void alsa_write_audio(char *data, int length)
 {
 	snd_pcm_sframes_t written_frames;
 
@@ -663,73 +722,153 @@ static void alsa_write_audio(gpointer da
 		{
 			int written = snd_pcm_frames_to_bytes(alsa_pcm,
 							      written_frames);
-			alsa_total_written += written;
 			length -= written;
-			data = (char*) data + written;
+			data += written;
+			alsa_hw_written += written;
 		}
-		else if (written_frames == -EPIPE)
-			xrun_recover();
-		else
-		{
-			g_warning("alsa_write_audio(): write error: %s",
-				  snd_strerror(-written_frames));
-			break;
+		else {
+			int err = alsa_handle_error((int)written_frames);
+			if (err < 0) {
+				g_warning("alsa_write_audio(): write error: %s",
+					  snd_strerror(-err));
+				break;
+			}
 		}
 	}
 }
 
+/* transfer data to audio h/w via mmap
+ *
+ * basically, it makes sense only in the single thread mode.
+ * also, don't expect too much efficiency over mmap...
+ */
 static void alsa_mmap_audio(char *data, int length)
 {
-	int cnt = 0, err;
-	snd_pcm_uframes_t offset, frames, frame;
-	const snd_pcm_channel_area_t *chan_areas = areas;
-	int channel_offset = 0, channel;
-	ssize_t sample_size, offset_bytes, step;
+	int cnt, err;
+	snd_pcm_uframes_t offset, frames;
+	const snd_pcm_channel_area_t *chan_areas;
+	snd_pcm_channel_area_t src_area;
+	int ch, channels, sample_bits;
+
+	if (snd_pcm_state(alsa_pcm) == SND_PCM_STATE_XRUN)
+		alsa_handle_error(-EPIPE);
 
+	/* need to call this before snd_pcm_mmap_begin() */
 	alsa_get_avail();
 
+	channels = outputf->channels;
+	sample_bits = outputf->sample_bits;
 	while (length > 0)
 	{
 		frames = snd_pcm_bytes_to_frames(alsa_pcm, length);
-		if ((err = snd_pcm_mmap_begin(alsa_pcm, &chan_areas, &offset, &frames) < 0))
+		if ((err = snd_pcm_mmap_begin(alsa_pcm, &chan_areas, &offset, &frames) < 0)) {
 			g_warning("alsa_mmap_audio(): snd_pcm_mmap_begin() "
 				  "failed: %s", snd_strerror(-err));
+			break;
+		}
 
 		cnt = snd_pcm_frames_to_bytes(alsa_pcm, frames);
 
-		sample_size = snd_pcm_samples_to_bytes(alsa_pcm, 1);
-		step = chan_areas[0].step / 8;
-		offset_bytes = offset * step;
-
-		for (frame = 0; frame < frames; frame++)
-		{
-			for (channel = 0; channel < outputf->channels; channel++)
-			{
-				char *ptr = chan_areas[channel].addr;
-				memcpy(ptr + chan_areas[channel].first / 8 +
-				       offset_bytes,
-				       data + channel_offset, sample_size);
-				channel_offset += sample_size;
-			}
-			offset_bytes += step;
+		src_area.addr = data;
+		src_area.first = 0;
+		src_area.step = channels * sample_bits;
+		for (ch = 0; ch < channels; ch++) {
+			snd_pcm_area_copy(&chan_areas[ch], offset,
+					  &src_area, 0, frames, outputf->format);
+			src_area.first += sample_bits;
 		}
 			
 		err = snd_pcm_mmap_commit(alsa_pcm, offset, frames);
-		if (err == -EPIPE)
-			xrun_recover();
-		else if (err < 0)
-			g_warning("alsa_mmap_audio(): snd_pcm_mmap_commit() "
-				  "failed: %s", snd_strerror(-err));
-		else if (err != frames)
-			g_warning("alsa_mmap_audio(): snd_pcm_mmap_commit "
-				  "returned %d, expected %d", err, (int)frames);
-		
-		alsa_total_written += cnt;
-		
+		if (err < 0) {
+			err = alsa_handle_error(err);
+			if (err < 0)
+				g_warning("alsa_mmap_audio(): snd_pcm_mmap_commit() "
+					  "failed: %s", snd_strerror(-err));
+		}
+		else {
+			if (err != frames)
+				g_warning("alsa_mmap_audio(): snd_pcm_mmap_commit "
+					  "returned %d, expected %d", err, (int)frames);
+			data += cnt;
+			length -= cnt;
+			alsa_hw_written += cnt;
+		}
+	}
+
+	/* PCM isn't started automatically in the case of mmap mode, so
+	 * we need to trigger manually
+	 */
+	if (snd_pcm_state(alsa_pcm) == SND_PCM_STATE_PREPARED) {
+		if (alsa_hw_written >= hw_period_size)
+			snd_pcm_start(alsa_pcm);
+	}
+}
+
+/* transfer audio data from thread buffer to h/w */
+static void alsa_write_out_thread_data(void)
+{
+	gint length, cnt, avail;
+
+	length = MIN(hw_period_size_in, get_thread_buffer_filled());
+	avail = snd_pcm_frames_to_bytes(alsa_pcm, alsa_get_avail());
+	length = MIN(length, avail);
+	while (length > 0) {
+		int rd;
+		cnt = MIN(length, thread_buffer_size - rd_index);
+		alsa_do_write(thread_buffer + rd_index, cnt);
+		rd = (rd_index + cnt) % thread_buffer_size;
+		rd_index = rd;
 		length -= cnt;
 	}
 }
 
+/* audio thread loop */
+/* FIXME: proper lock? */
+static void *alsa_loop(void *arg)
+{
+	int npfds = snd_pcm_poll_descriptors_count(alsa_pcm);
+	struct pollfd *pfds;
+	unsigned short *revents;
+
+	if (npfds <= 0)
+		goto _error;
+	pfds = alloca(sizeof(*pfds) * npfds);
+	revents = alloca(sizeof(*revents) * npfds);
+	while (going && alsa_pcm) {
+		if (! paused && get_thread_buffer_filled() > hw_period_size_in) {
+			snd_pcm_poll_descriptors(alsa_pcm, pfds, npfds);
+			if (poll(pfds, npfds, 10) > 0) { 
+				/* need to check revents.  poll() with dmix returns
+				 * a postive value even if no data is available
+				 */
+				int i;
+				snd_pcm_poll_descriptors_revents(alsa_pcm, pfds, npfds, revents);
+				for (i = 0; i < npfds; i++)
+					if (revents[i] & POLLOUT) {
+						alsa_write_out_thread_data();
+						break;
+					}
+			}
+		} else
+			xmms_usleep(10000);
+
+		if (pause_request != paused)
+			alsa_do_pause(pause_request);
+
+		if (flush_request != -1) {
+			alsa_do_flush(flush_request);
+			flush_request = -1;
+		}
+	}
+
+ _error:
+	alsa_close_pcm();
+	g_free(thread_buffer);
+	thread_buffer = NULL;
+	pthread_exit(NULL);
+}
+
+/* open callback */
 int alsa_open(AFormat fmt, int rate, int nch)
 {
 	debug("Opening device");
@@ -751,13 +890,30 @@ int alsa_open(AFormat fmt, int rate, int
 
 	convertb = xmms_convert_buffers_new();
 	
-	alsa_total_written = 0;
+	output_time_offset = 0;
+	alsa_total_written = alsa_hw_written = 0;
 	going = TRUE;
 	paused = FALSE;
-	force_start = FALSE;
 	
-	snd_pcm_prepare(alsa_pcm);
+	multi_thread = alsa_cfg.multi_thread;
+	debug("ALSA: multi_thread = %d\n", multi_thread);
 	
+	if (multi_thread) {
+		thread_buffer_size = (guint64)alsa_cfg.thread_buffer_time * inputf->bps / 1000;
+		if (thread_buffer_size < hw_buffer_size)
+			thread_buffer_size = hw_buffer_size * 2;
+		if (thread_buffer_size < 8192)
+			thread_buffer_size = 8192;
+		thread_buffer_size += hw_buffer_size;
+		thread_buffer_size -= thread_buffer_size % hw_period_size;
+		thread_buffer = g_malloc0(thread_buffer_size);
+		wr_index = rd_index = 0;
+		pause_request = FALSE;
+		flush_request = -1;
+
+		pthread_create(&audio_thread, NULL, alsa_loop, NULL);
+	}
+
 	return 1;
 }
 
@@ -787,6 +943,8 @@ static struct snd_format * snd_format_fr
 
 	f->rate = rate;
 	f->channels = channels;
+	f->sample_bits = snd_pcm_format_physical_width(f->format);
+	f->bps = (rate * f->sample_bits * channels) >> 3;
 
 	return f;
 }
@@ -806,7 +964,7 @@ static int alsa_setup(struct snd_format 
 	int err;
 	snd_pcm_hw_params_t *hwparams;
 	snd_pcm_sw_params_t *swparams;
-	int alsa_buffer_time, bits_per_sample;
+	int alsa_buffer_time;
 	unsigned int alsa_period_time;
 	snd_pcm_uframes_t alsa_buffer_size, alsa_period_size;
 
@@ -816,9 +974,8 @@ static int alsa_setup(struct snd_format 
 	alsa_stereo_convert_func = NULL;
 	alsa_frequency_convert_func = NULL;
 
-	outputf = snd_format_from_xmms(effectf->xmms_format,
-				       effectf->rate,
-				       effectf->channels);
+	g_free(outputf);
+	outputf = snd_format_from_xmms(f->xmms_format, f->rate, f->channels);
 
 	debug("Opening device: %s", alsa_cfg.pcm_device);
 	/* FIXME: Can snd_pcm_open() return EAGAIN? */
@@ -829,10 +986,14 @@ static int alsa_setup(struct snd_format 
 		g_warning("alsa_setup(): Failed to open pcm device (%s): %s",
 			  alsa_cfg.pcm_device, snd_strerror(-err));
 		alsa_pcm = NULL;
+		g_free(outputf);
+		outputf = NULL;
 		return -1;
 	}
-	snd_pcm_nonblock(alsa_pcm, FALSE);
 
+	/* doesn't care about non-blocking */
+	/* snd_pcm_nonblock(alsa_pcm, 0); */
+	
 	if (alsa_cfg.debug)
 	{
 		snd_pcm_info_t *info;
@@ -894,17 +1055,17 @@ static int alsa_setup(struct snd_format 
 				break;
 			}
 		}
-		if (outputf->format != effectf->format)
+		if (outputf->format != f->format)
 		{
 			outputf->xmms_format =
 				format_from_alsa(outputf->format);
 			debug("Converting format from %d to %d",
-			      effectf->xmms_format, outputf->xmms_format);
+			      f->xmms_format, outputf->xmms_format);
 			if (outputf->xmms_format < 0)
 				return -1;
 			alsa_convert_func =
 				xmms_convert_get_func(outputf->xmms_format,
-						      effectf->xmms_format);
+						      f->xmms_format);
 			if (alsa_convert_func == NULL)
 				return -1;
 		}
@@ -918,14 +1079,14 @@ static int alsa_setup(struct snd_format 
 	}
 
 	snd_pcm_hw_params_set_channels_near(alsa_pcm, hwparams, &outputf->channels);
-	if (outputf->channels != effectf->channels)
+	if (outputf->channels != f->channels)
 	{
 		debug("Converting channels from %d to %d",
-		      effectf->channels, outputf->channels);
+		      f->channels, outputf->channels);
 		alsa_stereo_convert_func =
 			xmms_convert_get_channel_func(outputf->xmms_format,
 						      outputf->channels,
-						      effectf->channels);
+						      f->channels);
 		if (alsa_stereo_convert_func == NULL)
 			return -1;
 	}
@@ -936,10 +1097,10 @@ static int alsa_setup(struct snd_format 
 		g_warning("alsa_setup(): No usable samplerate available.");
 		return -1;
 	}
-	if (outputf->rate != effectf->rate)
+	if (outputf->rate != f->rate)
 	{
 		debug("Converting samplerate from %d to %d",
-		      effectf->rate, outputf->rate);
+		      f->rate, outputf->rate);
 		alsa_frequency_convert_func =
 			xmms_convert_get_frequency_func(outputf->xmms_format,
 							outputf->channels);
@@ -947,6 +1108,9 @@ static int alsa_setup(struct snd_format 
 			return -1;
 	}
 
+	outputf->sample_bits = snd_pcm_format_physical_width(outputf->format);
+	outputf->bps = (outputf->rate * outputf->sample_bits * outputf->channels) >> 3;
+
 	alsa_buffer_time = alsa_cfg.buffer_time * 1000;
 	if ((err = snd_pcm_hw_params_set_buffer_time_near(alsa_pcm, hwparams,
 							  &alsa_buffer_time, 0)) < 0)
@@ -1011,27 +1175,25 @@ static int alsa_setup(struct snd_format 
 		snd_pcm_dump(alsa_pcm, logs);
 	}
 
-	bits_per_sample = snd_pcm_format_physical_width(outputf->format);
-	alsa_bps = (outputf->rate * bits_per_sample * outputf->channels) >> 3;
-
-	if (mmap)
-	{
-		int chn;
-		buffer = g_malloc(alsa_period_size * bits_per_sample / 8 * outputf->channels);
-		areas = g_malloc0(outputf->channels * sizeof(snd_pcm_channel_area_t));
-		
-		for (chn = 0; chn < outputf->channels; chn++)
-		{
-			areas[chn].addr = buffer;
-			areas[chn].first = chn * bits_per_sample;
-			areas[chn].step = outputf->channels * bits_per_sample;
-		}
+	hw_buffer_size = snd_pcm_frames_to_bytes(alsa_pcm, alsa_buffer_size);
+	hw_period_size = snd_pcm_frames_to_bytes(alsa_pcm, alsa_period_size);
+	if (inputf->bps != outputf->bps) {
+		hw_buffer_size_in = ((guint64)hw_buffer_size * inputf->bps +
+				     outputf->bps/2) / outputf->bps;
+		hw_period_size_in = ((guint64)hw_period_size * inputf->bps +
+				     outputf->bps/2) / outputf->bps;
+	} else {
+		hw_buffer_size_in = hw_buffer_size;
+		hw_period_size_in = hw_period_size;
 	}
 
 	debug("Device setup: buffer time: %i, size: %i.", alsa_buffer_time,
-	      snd_pcm_frames_to_bytes(alsa_pcm, alsa_buffer_size));
+	      hw_buffer_size);
+	debug("Device setup: period time: %i, size: %i.", alsa_period_time,
+	      hw_period_size);
 	debug("bits per sample: %i; frame size: %i; Bps: %i",
-	      bits_per_sample, snd_pcm_frames_to_bytes(alsa_pcm, 1), alsa_bps);
+	      snd_pcm_format_physical_width(outputf->format),
+	      snd_pcm_frames_to_bytes(alsa_pcm, 1), outputf->bps);
 
 	return 0;
 }
--- Output/alsa/init.c-dist	2005-01-09 22:11:33.000000000 +0100
+++ Output/alsa/init.c	2005-01-09 22:09:40.000000000 +0100
@@ -29,8 +29,10 @@ void alsa_init(void)
 	memset(&alsa_cfg, 0, sizeof (alsa_cfg));
 	alsa_cfg.buffer_time = 500;
 	alsa_cfg.period_time = 50;
+	alsa_cfg.thread_buffer_time = 3000;
 	alsa_cfg.debug = 0;
-	alsa_cfg.mmap = 1;
+	alsa_cfg.multi_thread = 1;
+	alsa_cfg.mmap = 0;
 	alsa_cfg.vol.left = 100;
 	alsa_cfg.vol.right = 100;
 
@@ -44,8 +46,9 @@ void alsa_init(void)
 	xmms_cfg_read_int(cfgfile, "ALSA", "mixer_card", &alsa_cfg.mixer_card);
 	xmms_cfg_read_int(cfgfile, "ALSA", "buffer_time", &alsa_cfg.buffer_time);
 	xmms_cfg_read_int(cfgfile, "ALSA", "period_time", &alsa_cfg.period_time);
+	xmms_cfg_read_int(cfgfile, "ALSA", "thread_buffer_time", &alsa_cfg.thread_buffer_time);
+	xmms_cfg_read_boolean(cfgfile, "ALSA", "multi_thread", &alsa_cfg.multi_thread);
 	xmms_cfg_read_boolean(cfgfile, "ALSA", "mmap", &alsa_cfg.mmap);
-	xmms_cfg_read_int(cfgfile, "ALSA", "period_time", &alsa_cfg.period_time);
 	xmms_cfg_read_boolean(cfgfile, "ALSA", "soft_volume",
 			      &alsa_cfg.soft_volume);
 	xmms_cfg_read_int(cfgfile, "ALSA", "volume_left", &alsa_cfg.vol.left);
--- Output/alsa/alsa.h-dist	2005-01-09 22:11:32.000000000 +0100
+++ Output/alsa/alsa.h	2005-01-09 21:51:07.000000000 +0100
@@ -50,7 +50,9 @@ struct alsa_config
 	char *mixer_device;
 	int buffer_time;
 	int period_time;
+	int thread_buffer_time;
 	gboolean debug;
+	gboolean multi_thread;
 	gboolean mmap;
 	struct
 	{
@@ -65,8 +67,6 @@ void alsa_init(void);
 void alsa_about(void);
 void alsa_configure(void);
 int alsa_get_mixer(snd_mixer_t **mixer, int card);
-snd_mixer_elem_t* alsa_get_mixer_elem(snd_mixer_t *mixer, char *name, int index);
-int alsa_setup_mixer(void);
 void alsa_save_config(void);
 
 void alsa_get_volume(int *l, int *r);