Mercurial > audlegacy-plugins
view src/alsa-ng/alsa-core.c @ 3198:83b1a4e5f453
alsa-ng: Keep mixer open even when playback stopped.
author | John Lindgren <john.lindgren@tds.net> |
---|---|
date | Wed, 22 Jul 2009 16:42:16 -0400 |
parents | d2e01ca06335 |
children | 263c7d983100 |
line wrap: on
line source
/* * Audacious ALSA Plugin (-ng) * Copyright (c) 2009 William Pitcock <nenolod@dereferenced.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define ALSA_DEBUG #include "alsa-stdinc.h" alsaplug_cfg_t alsaplug_cfg; static snd_pcm_t *pcm_handle = NULL; static alsaplug_ringbuf_t pcm_ringbuf; static gboolean pcm_going = FALSE; static GThread *audio_thread = NULL; static gint bps; static gsize wr_total = 0; static gsize wr_hwframes = 0; static gint flush_request, paused; static GMutex * pcm_state_mutex; static GCond * pcm_state_cond, * pcm_flush_cond; extern void alsaplug_configure(void); extern void alsaplug_get_config(void); /******************************************************************************** * ALSA Mixer setting functions. * ********************************************************************************/ static snd_mixer_t *amixer = NULL; static gboolean mixer_ready = FALSE; static snd_mixer_elem_t * alsaplug_get_mixer_elem_by_name(snd_mixer_t *mixer, gchar *name) { snd_mixer_selem_id_t *selem_id; snd_mixer_elem_t *elem; g_return_val_if_fail(mixer != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); snd_mixer_selem_id_alloca(&selem_id); snd_mixer_selem_id_set_name(selem_id, name); elem = snd_mixer_find_selem(mixer, selem_id); if (elem == NULL) return NULL; snd_mixer_selem_set_playback_volume_range(elem, 0, 100); return elem; } /* try to determine the best choice... may need tweaking. --nenolod */ static snd_mixer_elem_t * alsaplug_guess_mixer_elem(snd_mixer_t *mixer) { gchar * elem_names[] = {"PCM", "Wave", "Front", "Master"}; gint i; snd_mixer_elem_t *elem; if (alsaplug_cfg.mixer_device != NULL) return alsaplug_get_mixer_elem_by_name(mixer, alsaplug_cfg.mixer_device); for (i = 0; i < G_N_ELEMENTS(elem_names); i++) { elem = alsaplug_get_mixer_elem_by_name(mixer, elem_names[i]); if (elem != NULL) return elem; } return NULL; } gint alsaplug_mixer_new_for_card(snd_mixer_t **mixer, const gchar *card) { gint ret; ret = snd_mixer_open(mixer, 0); if (ret < 0) { _ERROR("mixer initialization failed: %s", snd_strerror(ret)); return ret; } ret = snd_mixer_attach(*mixer, card); if (ret < 0) { snd_mixer_close(*mixer); _ERROR("failed to attach to hardware mixer: %s", snd_strerror(ret)); return ret; } ret = snd_mixer_selem_register(*mixer, NULL, NULL); if (ret < 0) { snd_mixer_detach(*mixer, card); snd_mixer_close(*mixer); _ERROR("failed to register hardware mixer: %s", snd_strerror(ret)); return ret; } ret = snd_mixer_load(*mixer); if (ret < 0) { snd_mixer_detach(*mixer, card); snd_mixer_close(*mixer); _ERROR("failed to load hardware mixer controls: %s", snd_strerror(ret)); return ret; } return 0; } gint alsaplug_mixer_new(snd_mixer_t **mixer) { return alsaplug_mixer_new_for_card(mixer, alsaplug_cfg.mixer_card); } static void alsaplug_set_volume(gint l, gint r) { snd_mixer_elem_t *elem = alsaplug_guess_mixer_elem(amixer); if (elem == NULL) return; if (snd_mixer_selem_is_playback_mono(elem)) { gint vol = (l > r) ? l : r; snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_MONO, vol); if (snd_mixer_selem_has_playback_switch(elem)) snd_mixer_selem_set_playback_switch(elem, SND_MIXER_SCHN_MONO, vol != 0); } else { snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, l); snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, r); if (snd_mixer_selem_has_playback_switch(elem) && !snd_mixer_selem_has_playback_switch_joined(elem)) { snd_mixer_selem_set_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, l != 0); snd_mixer_selem_set_playback_switch(elem, SND_MIXER_SCHN_FRONT_RIGHT, r != 0); } } snd_mixer_handle_events(amixer); } static void alsaplug_get_volume(gint *l, gint *r) { snd_mixer_elem_t *elem = alsaplug_guess_mixer_elem(amixer); long left, right; if (elem == NULL) { * l = 50; * r = 50; return; } snd_mixer_handle_events(amixer); if (snd_mixer_selem_is_playback_mono(elem)) { snd_mixer_selem_get_playback_volume (elem, SND_MIXER_SCHN_MONO, & left); snd_mixer_selem_get_playback_volume (elem, SND_MIXER_SCHN_MONO, & right); } else { snd_mixer_selem_get_playback_volume (elem, SND_MIXER_SCHN_FRONT_LEFT, & left); snd_mixer_selem_get_playback_volume (elem, SND_MIXER_SCHN_FRONT_RIGHT, & right); } * l = left; * r = right; } /******************************************************************************** * ALSA PCM I/O functions. * ********************************************************************************/ static void alsaplug_write_buffer(gpointer data, gint length) { snd_pcm_sframes_t wr_frames; while (length > 0) { gint frames = snd_pcm_bytes_to_frames(pcm_handle, length); wr_frames = snd_pcm_writei(pcm_handle, data, frames); if (wr_frames > 0) { gint written = snd_pcm_frames_to_bytes(pcm_handle, wr_frames); length -= written; data += written; } else { gint err = snd_pcm_recover(pcm_handle, wr_frames, 1); if (err < 0) _ERROR("(write) snd_pcm_recover: %s", snd_strerror(err)); return; } } } static gpointer alsaplug_loop(gpointer unused) { guchar buf[2048]; int size; while (pcm_going) { g_mutex_lock (pcm_state_mutex); if (flush_request != -1) { alsaplug_ringbuffer_reset (& pcm_ringbuf); snd_pcm_drop(pcm_handle); snd_pcm_prepare(pcm_handle); wr_total = flush_request * (long long) bps / 1000; flush_request = -1; g_cond_broadcast(pcm_flush_cond); } size = alsaplug_ringbuffer_used (& pcm_ringbuf); if (size == 0 || paused) { g_cond_wait (pcm_state_cond, pcm_state_mutex); g_mutex_unlock (pcm_state_mutex); continue; } if (size > sizeof buf) size = sizeof buf; alsaplug_ringbuffer_read (& pcm_ringbuf, buf, size); g_mutex_unlock (pcm_state_mutex); alsaplug_write_buffer (buf, size); } snd_pcm_drain(pcm_handle); snd_pcm_close(pcm_handle); pcm_handle = NULL; audio_thread = NULL; alsaplug_ringbuffer_destroy(&pcm_ringbuf); return NULL; } /******************************************************************************** * Output Plugin API implementation. * ********************************************************************************/ static OutputPluginInitStatus alsaplug_init(void) { gint card = -1; pcm_state_mutex = g_mutex_new(); pcm_state_cond = g_cond_new(); pcm_flush_cond = g_cond_new(); if (snd_card_next(&card) != 0) return OUTPUT_PLUGIN_INIT_NO_DEVICES; alsaplug_get_config(); if (alsaplug_cfg.pcm_device == NULL) alsaplug_cfg.pcm_device = g_strdup("default"); if (alsaplug_cfg.mixer_card == NULL) alsaplug_cfg.mixer_card = g_strdup("default"); if (!alsaplug_mixer_new(&amixer)) mixer_ready = TRUE; return OUTPUT_PLUGIN_INIT_FOUND_DEVICES; } static void alsaplug_cleanup(void) { if (mixer_ready == TRUE) { snd_mixer_detach(amixer, alsaplug_cfg.mixer_card); snd_mixer_close(amixer); amixer = NULL; mixer_ready = FALSE; } } static gint alsaplug_open_audio(AFormat fmt, gint rate, gint nch) { gint err, bitwidth, ringbuf_size, buf_size; snd_pcm_format_t afmt; snd_pcm_hw_params_t *hwparams = NULL; guint rate_ = rate; afmt = alsaplug_format_convert(fmt); if (afmt == SND_PCM_FORMAT_UNKNOWN) { _ERROR("unsupported format requested: %d -> %d", fmt, afmt); return -1; } if ((err = snd_pcm_open(&pcm_handle, alsaplug_cfg.pcm_device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { _ERROR("snd_pcm_open: %s", snd_strerror(err)); pcm_handle = NULL; return -1; } snd_pcm_hw_params_alloca(&hwparams); snd_pcm_hw_params_any(pcm_handle, hwparams); snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(pcm_handle, hwparams, afmt); snd_pcm_hw_params_set_channels(pcm_handle, hwparams, nch); snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 1); snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &rate_, 0); if (rate_ != rate) { _ERROR("sample rate %d is not supported (got %d)", rate, rate_); return -1; } err = snd_pcm_hw_params(pcm_handle, hwparams); if (err < 0) { _ERROR("snd_pcm_hw_params failed: %s", snd_strerror(err)); return -1; } bitwidth = snd_pcm_format_physical_width(afmt); bps = (rate * bitwidth * nch) >> 3; buf_size = aud_cfg->output_buffer_size ? aud_cfg->output_buffer_size : 500; ringbuf_size = buf_size * bps / 1000; if (alsaplug_ringbuffer_init(&pcm_ringbuf, ringbuf_size) == -1) { _ERROR("alsaplug_ringbuffer_init failed"); return -1; } pcm_going = TRUE; flush_request = -1; audio_thread = g_thread_create(alsaplug_loop, NULL, TRUE, NULL); return 1; } static void alsaplug_close_audio(void) { g_mutex_lock(pcm_state_mutex); pcm_going = FALSE; wr_total = 0; wr_hwframes = 0; bps = 0; g_cond_broadcast (pcm_state_cond); g_mutex_unlock(pcm_state_mutex); if (audio_thread != NULL) g_thread_join(audio_thread); audio_thread = NULL; } static void alsaplug_write_audio(gpointer data, gint length) { g_mutex_lock(pcm_state_mutex); wr_total += length; alsaplug_ringbuffer_write(&pcm_ringbuf, data, length); g_cond_broadcast (pcm_state_cond); g_mutex_unlock(pcm_state_mutex); } static gint alsaplug_output_time(void) { gint ret = 0; snd_pcm_sframes_t delay; gsize bytes = wr_total; g_mutex_lock(pcm_state_mutex); if (pcm_going && pcm_handle != NULL) { guint d = alsaplug_ringbuffer_used(&pcm_ringbuf); if (!snd_pcm_delay(pcm_handle, &delay)) d += snd_pcm_frames_to_bytes(pcm_handle, delay); if (bytes < d) bytes = 0; else bytes -= d; ret = bytes * (long long) 1000 / bps; } g_mutex_unlock(pcm_state_mutex); return ret; } static gint alsaplug_written_time(void) { gint ret = 0; g_mutex_lock(pcm_state_mutex); if (pcm_going) ret = wr_total * (long long) 1000 / bps; g_mutex_unlock(pcm_state_mutex); return ret; } static gint alsaplug_buffer_free(void) { gint ret; g_mutex_lock(pcm_state_mutex); if (pcm_going == FALSE) ret = 0; else ret = alsaplug_ringbuffer_free(&pcm_ringbuf); g_mutex_unlock(pcm_state_mutex); return ret; } static void alsaplug_flush(gint time) { /* make the request... */ g_mutex_lock(pcm_state_mutex); flush_request = time; g_cond_broadcast(pcm_state_cond); /* ...then wait for the transaction to complete. */ g_cond_wait(pcm_flush_cond, pcm_state_mutex); g_mutex_unlock(pcm_state_mutex); } static gint alsaplug_buffer_playing(void) { gint ret; g_mutex_lock(pcm_state_mutex); if (pcm_going == FALSE) ret = 0; else ret = alsaplug_ringbuffer_used(&pcm_ringbuf) != 0; g_mutex_unlock(pcm_state_mutex); return ret; } static void alsaplug_pause(short p) { g_mutex_lock (pcm_state_mutex); paused = p; g_cond_broadcast (pcm_state_cond); g_mutex_unlock (pcm_state_mutex); } /******************************************************************************** * Plugin glue. * ********************************************************************************/ static OutputPlugin alsa_op = { .description = "ALSA Output Plugin (-ng)", .probe_priority = 1, .init = alsaplug_init, .cleanup = alsaplug_cleanup, .open_audio = alsaplug_open_audio, .close_audio = alsaplug_close_audio, .write_audio = alsaplug_write_audio, .output_time = alsaplug_output_time, .written_time = alsaplug_written_time, .buffer_free = alsaplug_buffer_free, .buffer_playing = alsaplug_buffer_playing, .flush = alsaplug_flush, .pause = alsaplug_pause, .set_volume = alsaplug_set_volume, .get_volume = alsaplug_get_volume, .configure = alsaplug_configure, }; OutputPlugin *alsa_oplist[] = { &alsa_op, NULL }; SIMPLE_OUTPUT_PLUGIN(alsa, alsa_oplist);