diff --git a/src/laudio_alsa.c b/src/laudio_alsa.c deleted file mode 100644 index 99daa528..00000000 --- a/src/laudio_alsa.c +++ /dev/null @@ -1,738 +0,0 @@ -/* - * Copyright (C) 2010 Julien BLACHE - * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifdef HAVE_CONFIG_H -# include -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "conffile.h" -#include "logger.h" -#include "player.h" -#include "laudio.h" - - -struct pcm_packet -{ - uint8_t samples[STOB(AIRTUNES_V2_PACKET_SAMPLES)]; - - uint64_t rtptime; - - size_t offset; - - struct pcm_packet *next; -}; - -static uint64_t pcm_pos; -static uint64_t pcm_start_pos; -static int pcm_last_error; -static int pcm_recovery; -static int pcm_buf_threshold; -static int pcm_period_size; - -static struct pcm_packet *pcm_pkt_head; -static struct pcm_packet *pcm_pkt_tail; - -static char *card_name; -static char *mixer_name; -static snd_pcm_t *hdl; -static snd_mixer_t *mixer_hdl; -static snd_mixer_elem_t *vol_elem; -static long vol_min; -static long vol_max; - -static enum laudio_state pcm_status; -static laudio_status_cb status_cb; - - -static void -update_status(enum laudio_state status) -{ - pcm_status = status; - status_cb(status); -} - -static int -laudio_alsa_xrun_recover(int err) -{ - int ret; - - if (err != 0) - pcm_last_error = err; - - /* Buffer underrun */ - if (err == -EPIPE) - { - DPRINTF(E_WARN, L_LAUDIO, "PCM buffer underrun\n"); - - ret = snd_pcm_prepare(hdl); - if (ret < 0) - { - DPRINTF(E_WARN, L_LAUDIO, "Couldn't recover from underrun: %s\n", snd_strerror(ret)); - return 1; - } - - return 0; - } - /* Device suspended */ - else if (pcm_last_error == -ESTRPIPE) - { - ret = snd_pcm_resume(hdl); - if (ret == -EAGAIN) - { - pcm_recovery++; - - return 2; - } - else if (ret < 0) - { - pcm_recovery = 0; - - ret = snd_pcm_prepare(hdl); - if (ret < 0) - { - DPRINTF(E_WARN, L_LAUDIO, "Couldn't recover from suspend: %s\n", snd_strerror(ret)); - return 1; - } - } - - pcm_recovery = 0; - return 0; - } - - return err; -} - -static int -laudio_alsa_set_start_threshold(snd_pcm_uframes_t threshold) -{ - snd_pcm_sw_params_t *sw_params; - int ret; - - ret = snd_pcm_sw_params_malloc(&sw_params); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not allocate sw params: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - ret = snd_pcm_sw_params_current(hdl, sw_params); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not retrieve current sw params: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - ret = snd_pcm_sw_params_set_start_threshold(hdl, sw_params, threshold); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set start threshold: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - ret = snd_pcm_sw_params(hdl, sw_params); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set sw params: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - return 0; - - out_fail: - snd_pcm_sw_params_free(sw_params); - - return -1; -} - -static void -laudio_alsa_write(uint8_t *buf, uint64_t rtptime) -{ - struct pcm_packet *pkt; - snd_pcm_sframes_t nsamp; - int ret; - - pkt = (struct pcm_packet *)malloc(sizeof(struct pcm_packet)); - if (!pkt) - { - DPRINTF(E_LOG, L_LAUDIO, "Out of memory for PCM pkt\n"); - - update_status(LAUDIO_FAILED); - return; - } - - memcpy(pkt->samples, buf, sizeof(pkt->samples)); - - pkt->rtptime = rtptime; - pkt->offset = 0; - pkt->next = NULL; - - if (pcm_pkt_tail) - { - pcm_pkt_tail->next = pkt; - pcm_pkt_tail = pkt; - } - else - { - pcm_pkt_head = pkt; - pcm_pkt_tail = pkt; - } - - if (pcm_pos < pcm_pkt_head->rtptime) - { - pcm_pos += AIRTUNES_V2_PACKET_SAMPLES; - - return; - } - else if ((pcm_status != LAUDIO_RUNNING) && (pcm_pos >= pcm_start_pos)) - { - update_status(LAUDIO_RUNNING); - } - - pkt = pcm_pkt_head; - - while (pkt) - { - if (pcm_recovery) - { - ret = laudio_alsa_xrun_recover(0); - if ((ret == 2) && (pcm_recovery < 10)) - return; - else - { - if (ret == 2) - DPRINTF(E_LOG, L_LAUDIO, "Couldn't recover PCM device after 10 tries, aborting\n"); - - update_status(LAUDIO_FAILED); - return; - } - } - - nsamp = snd_pcm_writei(hdl, pkt->samples + pkt->offset, BTOS(sizeof(pkt->samples) - pkt->offset)); - if ((nsamp == -EPIPE) || (nsamp == -ESTRPIPE)) - { - ret = laudio_alsa_xrun_recover(nsamp); - if ((ret < 0) || (ret == 1)) - { - if (ret < 0) - DPRINTF(E_LOG, L_LAUDIO, "PCM write error: %s\n", snd_strerror(ret)); - - update_status(LAUDIO_FAILED); - return; - } - else if (ret != 0) - return; - - continue; - } - else if (nsamp < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "PCM write error: %s\n", snd_strerror(nsamp)); - - update_status(LAUDIO_FAILED); - return; - } - - pcm_last_error = 0; - pcm_pos += nsamp; - - pkt->offset += STOB(nsamp); - if (pkt->offset == sizeof(pkt->samples)) - { - pcm_pkt_head = pkt->next; - - if (pkt == pcm_pkt_tail) - pcm_pkt_tail = NULL; - - free(pkt); - - pkt = pcm_pkt_head; - } - - /* Don't let ALSA fill up the buffer too much */ - if (nsamp == AIRTUNES_V2_PACKET_SAMPLES) - return; - } -} - -static uint64_t -laudio_alsa_get_pos(void) -{ - snd_pcm_sframes_t delay; - int ret; - - if (pcm_pos == 0) - return 0; - - if (pcm_last_error != 0) - return pcm_pos; - - if (snd_pcm_state(hdl) != SND_PCM_STATE_RUNNING) - return pcm_pos; - - ret = snd_pcm_delay(hdl, &delay); - if (ret < 0) - { - DPRINTF(E_WARN, L_LAUDIO, "Could not obtain PCM delay: %s\n", snd_strerror(ret)); - - return pcm_pos; - } - - return pcm_pos - delay; -} - -static void -laudio_alsa_set_volume(int vol) -{ - int pcm_vol; - - if (!mixer_hdl || !vol_elem) - return; - - snd_mixer_handle_events(mixer_hdl); - - if (!snd_mixer_selem_is_active(vol_elem)) - return; - - switch (vol) - { - case 0: - pcm_vol = vol_min; - break; - - case 100: - pcm_vol = vol_max; - break; - - default: - pcm_vol = vol_min + (vol * (vol_max - vol_min)) / 100; - break; - } - - DPRINTF(E_DBG, L_LAUDIO, "Setting PCM volume to %d (%d)\n", pcm_vol, vol); - - snd_mixer_selem_set_playback_volume_all(vol_elem, pcm_vol); -} - -static int -laudio_alsa_start(uint64_t cur_pos, uint64_t next_pkt) -{ - snd_output_t *output; - char *debug_pcm_cfg; - int ret; - - ret = snd_pcm_prepare(hdl); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not prepare PCM device: %s\n", snd_strerror(ret)); - - return -1; - } - - DPRINTF(E_DBG, L_LAUDIO, "Start local audio curpos %" PRIu64 ", next_pkt %" PRIu64 "\n", cur_pos, next_pkt); - - /* Make pcm_pos the rtptime of the packet containing cur_pos */ - pcm_pos = next_pkt; - while (pcm_pos > cur_pos) - pcm_pos -= AIRTUNES_V2_PACKET_SAMPLES; - - pcm_start_pos = next_pkt + pcm_period_size; - - /* Compensate period size, otherwise get_pos won't be correct */ - pcm_pos += pcm_period_size; - - DPRINTF(E_DBG, L_LAUDIO, "PCM pos %" PRIu64 ", start pos %" PRIu64 "\n", pcm_pos, pcm_start_pos); - - pcm_pkt_head = NULL; - pcm_pkt_tail = NULL; - - pcm_last_error = 0; - pcm_recovery = 0; - - // alsa doesn't actually seem to wait for this threshold? - ret = laudio_alsa_set_start_threshold(pcm_buf_threshold); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set PCM start threshold for local audio start\n"); - - return -1; - } - - // Dump PCM config data for E_DBG logging - ret = snd_output_buffer_open(&output); - if (ret == 0) - { - if (snd_pcm_dump_setup(hdl, output) == 0) - { - snd_output_buffer_string(output, &debug_pcm_cfg); - DPRINTF(E_DBG, L_LAUDIO, "Dump of sound device config:\n%s\n", debug_pcm_cfg); - } - - snd_output_close(output); - } - - update_status(LAUDIO_STARTED); - - return 0; -} - -static void -laudio_alsa_stop(void) -{ - struct pcm_packet *pkt; - - update_status(LAUDIO_STOPPING); - - snd_pcm_drop(hdl); - - for (pkt = pcm_pkt_head; pcm_pkt_head; pkt = pcm_pkt_head) - { - pcm_pkt_head = pkt->next; - - free(pkt); - } - - pcm_pkt_head = NULL; - pcm_pkt_tail = NULL; - - update_status(LAUDIO_OPEN); -} - -static int -mixer_open(void) -{ - snd_mixer_elem_t *elem; - snd_mixer_elem_t *master; - snd_mixer_elem_t *pcm; - snd_mixer_elem_t *custom; - snd_mixer_selem_id_t *sid; - int ret; - - ret = snd_mixer_open(&mixer_hdl, 0); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Failed to open mixer: %s\n", snd_strerror(ret)); - - mixer_hdl = NULL; - return -1; - } - - ret = snd_mixer_attach(mixer_hdl, card_name); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Failed to attach mixer: %s\n", snd_strerror(ret)); - - goto out_close; - } - - ret = snd_mixer_selem_register(mixer_hdl, NULL, NULL); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Failed to register mixer: %s\n", snd_strerror(ret)); - - goto out_detach; - } - - ret = snd_mixer_load(mixer_hdl); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Failed to load mixer: %s\n", snd_strerror(ret)); - - goto out_detach; - } - - /* Grab interesting elements */ - snd_mixer_selem_id_alloca(&sid); - - pcm = NULL; - master = NULL; - custom = NULL; - for (elem = snd_mixer_first_elem(mixer_hdl); elem; elem = snd_mixer_elem_next(elem)) - { - snd_mixer_selem_get_id(elem, sid); - - if (mixer_name && (strcmp(snd_mixer_selem_id_get_name(sid), mixer_name) == 0)) - { - custom = elem; - break; - } - else if (strcmp(snd_mixer_selem_id_get_name(sid), "PCM") == 0) - pcm = elem; - else if (strcmp(snd_mixer_selem_id_get_name(sid), "Master") == 0) - master = elem; - } - - if (mixer_name) - { - if (custom) - vol_elem = custom; - else - { - DPRINTF(E_LOG, L_LAUDIO, "Failed to open configured mixer element '%s'\n", mixer_name); - - goto out_detach; - } - } - else if (pcm) - vol_elem = pcm; - else if (master) - vol_elem = master; - else - { - DPRINTF(E_LOG, L_LAUDIO, "Failed to open PCM or Master mixer element\n"); - - goto out_detach; - } - - /* Get min & max volume */ - snd_mixer_selem_get_playback_volume_range(vol_elem, &vol_min, &vol_max); - - return 0; - - out_detach: - snd_mixer_detach(mixer_hdl, card_name); - out_close: - snd_mixer_close(mixer_hdl); - mixer_hdl = NULL; - vol_elem = NULL; - - return -1; -} - -static int -laudio_alsa_open(void) -{ - snd_pcm_hw_params_t *hw_params; - snd_pcm_uframes_t bufsize; - snd_pcm_uframes_t period_size; - int ret; - - hw_params = NULL; - - ret = snd_pcm_open(&hdl, card_name, SND_PCM_STREAM_PLAYBACK, 0); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not open playback device: %s\n", snd_strerror(ret)); - - return -1; - } - - /* HW params */ - ret = snd_pcm_hw_params_malloc(&hw_params); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not allocate hw params: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - ret = snd_pcm_hw_params_any(hdl, hw_params); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not retrieve hw params: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - ret = snd_pcm_hw_params_set_access(hdl, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set access method: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - ret = snd_pcm_hw_params_set_format(hdl, hw_params, SND_PCM_FORMAT_S16_LE); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set S16LE format: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - ret = snd_pcm_hw_params_set_channels(hdl, hw_params, 2); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set stereo output: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - ret = snd_pcm_hw_params_set_rate(hdl, hw_params, 44100, 0); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Hardware doesn't support 44.1 kHz: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - ret = snd_pcm_hw_params_get_buffer_size_max(hw_params, &bufsize); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not get max buffer size: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - DPRINTF(E_DBG, L_LAUDIO, "Max buffer size is %lu samples\n", bufsize); - - ret = snd_pcm_hw_params_set_buffer_size_max(hdl, hw_params, &bufsize); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set buffer size to max: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - // With a small period size we seem to get underruns because the period time - // passes before we manage to feed with samples (if the player is slightly - // behind - especially critical during startup when the buffer is low) - // Internet suggests period_size should be /2 bufsize, but default seems to be - // much lower, so compromise on /4 (but not more than 65536 frames = almost 2 sec). - period_size = bufsize / 4; - if (period_size > 65536) - period_size = 65536; - - ret = snd_pcm_hw_params_set_period_size_near(hdl, hw_params, &period_size, NULL); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set period size: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - DPRINTF(E_DBG, L_LAUDIO, "Buffer size is %lu samples, period size is %lu samples\n", bufsize, period_size); - - ret = snd_pcm_hw_params(hdl, hw_params); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set hw params: %s\n", snd_strerror(ret)); - - goto out_fail; - } - - snd_pcm_hw_params_free(hw_params); - hw_params = NULL; - - pcm_pos = 0; - pcm_last_error = 0; - pcm_recovery = 0; - pcm_buf_threshold = ((bufsize - period_size) / AIRTUNES_V2_PACKET_SAMPLES) * AIRTUNES_V2_PACKET_SAMPLES; - pcm_period_size = period_size; - - ret = mixer_open(); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not open mixer\n"); - - goto out_fail; - } - - update_status(LAUDIO_OPEN); - - return 0; - - out_fail: - if (hw_params) - snd_pcm_hw_params_free(hw_params); - - snd_pcm_close(hdl); - hdl = NULL; - - return -1; -} - -static void -laudio_alsa_close(void) -{ - struct pcm_packet *pkt; - - snd_pcm_close(hdl); - hdl = NULL; - - if (mixer_hdl) - { - snd_mixer_detach(mixer_hdl, card_name); - snd_mixer_close(mixer_hdl); - - mixer_hdl = NULL; - vol_elem = NULL; - } - - for (pkt = pcm_pkt_head; pcm_pkt_head; pkt = pcm_pkt_head) - { - pcm_pkt_head = pkt->next; - - free(pkt); - } - - pcm_pkt_head = NULL; - pcm_pkt_tail = NULL; - - update_status(LAUDIO_CLOSED); -} - - -static int -laudio_alsa_init(laudio_status_cb cb, cfg_t *cfg_audio) -{ - snd_lib_error_set_handler(logger_alsa); - - status_cb = cb; - - card_name = cfg_getstr(cfg_audio, "card"); - mixer_name = cfg_getstr(cfg_audio, "mixer"); - - hdl = NULL; - mixer_hdl = NULL; - vol_elem = NULL; - - return 0; -} - -static void -laudio_alsa_deinit(void) -{ - snd_lib_error_set_handler(NULL); -} - -audio_output audio_alsa = { - .name = "alsa", - .init = &laudio_alsa_init, - .deinit = &laudio_alsa_deinit, - .start = &laudio_alsa_start, - .stop = &laudio_alsa_stop, - .open = &laudio_alsa_open, - .close = &laudio_alsa_close, - .pos = &laudio_alsa_get_pos, - .write = &laudio_alsa_write, - .volume = &laudio_alsa_set_volume, - };