All of lore.kernel.org
 help / color / mirror / Atom feed
From: Takashi Iwai <tiwai@suse.de>
To: Frank Barknecht <fbar@footils.org>
Cc: alsa-devel@lists.sourceforge.net
Subject: Re: The date of releasing 1.0.8
Date: Wed, 12 Jan 2005 19:24:14 +0100	[thread overview]
Message-ID: <s5hacrebj5d.wl@alsa2.suse.de> (raw)
In-Reply-To: <20050111002141.GE13505@fliwatut.scifi>

[-- Attachment #1: Type: text/plain, Size: 421 bytes --]

At Tue, 11 Jan 2005 01:21:41 +0100,
Frank Barknecht wrote:
> 
> XMMS however in the past had rather bad ALSA support, at least with
> some of the more "difficult" cards like those based on the
> ICE-chipsets. I don't know, if this is still valid, but I don't trust
> my ogg files to xmms anymore. ;)

Last week I played with xmms a bit, and fixed/improved ALSA output
plugin.  The test patch is attached below.


Takashi

[-- Attachment #2: Type: text/plain, Size: 32006 bytes --]

--- 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);

  reply	other threads:[~2005-01-12 18:24 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2005-01-10 23:30 The date of releasing 1.0.8 Lee, TommyX C
2005-01-11  0:21 ` Frank Barknecht
2005-01-12 18:24   ` Takashi Iwai [this message]
2005-01-13 19:40     ` Thierry Vignaud
2005-01-13 19:42       ` Takashi Iwai
2005-01-13 21:17         ` Thierry Vignaud
2005-01-20 19:08     ` Thierry Vignaud
  -- strict thread matches above, loose matches on Subject: below --
2005-01-05  0:00 Lee, TommyX C
2005-01-05  0:31 ` Lee Revell
2005-01-05 13:27   ` Takashi Iwai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=s5hacrebj5d.wl@alsa2.suse.de \
    --to=tiwai@suse.de \
    --cc=alsa-devel@lists.sourceforge.net \
    --cc=fbar@footils.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.