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