From 65732ccaf6f0ecace7cc45a6840660eb626f6de4 Mon Sep 17 00:00:00 2001 From: chme Date: Tue, 25 Oct 2016 21:23:09 +0200 Subject: [PATCH 1/2] [outputs] New output type 'fifo' --- forked-daapd.conf | 7 + src/Makefile.am | 2 +- src/conffile.c | 9 + src/logger.c | 2 +- src/logger.h | 3 +- src/outputs.c | 2 + src/outputs.h | 1 + src/outputs/fifo.c | 503 +++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 526 insertions(+), 3 deletions(-) create mode 100644 src/outputs/fifo.c diff --git a/forked-daapd.conf b/forked-daapd.conf index 8bf974d1..d347a71e 100644 --- a/forked-daapd.conf +++ b/forked-daapd.conf @@ -182,6 +182,13 @@ audio { # offset = 0 } +# Pipe output +# Allows forked-daapd to output audio data to a named pipe +#fifo { +# nickname = "fifo" +# path = "/path/to/fifo" +#} + # AirPlay/Airport Express device settings # (make sure you get the capitalization of the device name right) #airplay "My AirPlay device" { diff --git a/src/Makefile.am b/src/Makefile.am index 2663261b..c92cf2c5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -107,7 +107,7 @@ forked_daapd_SOURCES = main.c \ queue.c queue.h \ worker.c worker.h \ outputs.h outputs.c \ - outputs/raop.c outputs/streaming.c outputs/dummy.c \ + outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \ $(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \ evrtsp/rtsp.c evrtp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \ $(SPOTIFY_SRC) \ diff --git a/src/conffile.c b/src/conffile.c index df948ba6..8d01de03 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -112,6 +112,14 @@ static cfg_opt_t sec_airplay[] = CFG_END() }; +/* FIFO section structure */ +static cfg_opt_t sec_fifo[] = + { + CFG_STR("nickname", "fifo", CFGF_NONE), + CFG_STR("path", NULL, CFGF_NONE), + CFG_END() + }; + /* Spotify section structure */ static cfg_opt_t sec_spotify[] = { @@ -151,6 +159,7 @@ static cfg_opt_t toplvl_cfg[] = CFG_SEC("library", sec_library, CFGF_NONE), CFG_SEC("audio", sec_audio, CFGF_NONE), CFG_SEC("airplay", sec_airplay, CFGF_MULTI | CFGF_TITLE), + CFG_SEC("fifo", sec_fifo, CFGF_NONE), CFG_SEC("spotify", sec_spotify, CFGF_NONE), CFG_SEC("sqlite", sec_sqlite, CFGF_NONE), CFG_SEC("mpd", sec_mpd, CFGF_NONE), diff --git a/src/logger.c b/src/logger.c index a293503e..b84dd364 100644 --- a/src/logger.c +++ b/src/logger.c @@ -43,7 +43,7 @@ static int threshold; static int console; static char *logfilename; static FILE *logfile; -static char *labels[] = { "config", "daap", "db", "httpd", "http", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache", "mpd", "stream", "cast" }; +static char *labels[] = { "config", "daap", "db", "httpd", "http", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache", "mpd", "stream", "cast", "fifo" }; static char *severities[] = { "FATAL", "LOG", "WARN", "INFO", "DEBUG", "SPAM" }; diff --git a/src/logger.h b/src/logger.h index d91a4041..7e4f0829 100644 --- a/src/logger.h +++ b/src/logger.h @@ -33,8 +33,9 @@ #define L_MPD 24 #define L_STREAMING 25 #define L_CAST 26 +#define L_FIFO 27 -#define N_LOGDOMAINS 27 +#define N_LOGDOMAINS 28 /* Severities */ #define E_FATAL 0 diff --git a/src/outputs.c b/src/outputs.c index 2a96a65c..c6cd1bb4 100644 --- a/src/outputs.c +++ b/src/outputs.c @@ -34,6 +34,7 @@ extern struct output_definition output_raop; extern struct output_definition output_streaming; extern struct output_definition output_dummy; +extern struct output_definition output_fifo; #ifdef ALSA extern struct output_definition output_alsa; #endif @@ -49,6 +50,7 @@ static struct output_definition *outputs[] = { &output_raop, &output_streaming, &output_dummy, + &output_fifo, #ifdef ALSA &output_alsa, #endif diff --git a/src/outputs.h b/src/outputs.h index 25c77c80..52b16a6d 100644 --- a/src/outputs.h +++ b/src/outputs.h @@ -52,6 +52,7 @@ enum output_types OUTPUT_TYPE_RAOP, OUTPUT_TYPE_STREAMING, OUTPUT_TYPE_DUMMY, + OUTPUT_TYPE_FIFO, #ifdef ALSA OUTPUT_TYPE_ALSA, #endif diff --git a/src/outputs/fifo.c b/src/outputs/fifo.c new file mode 100644 index 00000000..6fb81f15 --- /dev/null +++ b/src/outputs/fifo.c @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2016 Christian Meffert + * + * 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 + +#include + +#include "conffile.h" +#include "logger.h" +#include "player.h" +#include "outputs.h" + +#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ + +struct fifo_session +{ + enum output_device_state state; + + char *path; + + int input_fd; + int output_fd; + + int created; + + struct event *deferredev; + output_status_cb defer_cb; + + /* Do not dereference - only passed to the status cb */ + struct output_device *device; + struct output_session *output_session; + output_status_cb status_cb; +}; + +/* From player.c */ +extern struct event_base *evbase_player; + +static struct fifo_session *sessions; + +/* Forwards */ +static void +defer_cb(int fd, short what, void *arg); + + +/* ---------------------------- FIFO HANDLING ---------------------------- */ + +static void +fifo_delete(struct fifo_session *fifo_session) +{ + DPRINTF(E_DBG, L_FIFO, "Removing FIFO \"%s\"\n", fifo_session->path); + + if (unlink(fifo_session->path) < 0) + { + DPRINTF(E_WARN, L_FIFO, "Could not remove FIFO \"%s\": %d\n", fifo_session->path, errno); + return; + } + + fifo_session->created = 0; +} + +static void +fifo_close(struct fifo_session *fifo_session) +{ + struct stat st; + + if (fifo_session->input_fd > 0) + { + close(fifo_session->input_fd); + fifo_session->input_fd = -1; + } + + if (fifo_session->output_fd > 0) + { + close(fifo_session->output_fd); + fifo_session->output_fd = -1; + } + + if (fifo_session->created && (stat(fifo_session->path, &st) == 0)) + fifo_delete(fifo_session); +} + +static int +fifo_make(struct fifo_session *fifo_session) +{ + DPRINTF(E_DBG, L_FIFO, "Creating FIFO \"%s\"\n", fifo_session->path); + + if (mkfifo(fifo_session->path, 0666) < 0) + { + DPRINTF(E_LOG, L_FIFO, "Could not create FIFO \"%s\": %d\n", fifo_session->path, errno); + return -1; + } + + fifo_session->created = 1; + + return 0; +} + +static int +fifo_check(struct fifo_session *fifo_session) +{ + struct stat st; + + if (stat(fifo_session->path, &st) < 0) + { + if (errno == ENOENT) + { + /* Path doesn't exist */ + return fifo_make(fifo_session); + } + + DPRINTF(E_LOG, L_FIFO, "Failed to stat FIFO \"%s\": %d\n", fifo_session->path, errno); + return -1; + } + + if (!S_ISFIFO(st.st_mode)) + { + DPRINTF(E_LOG, L_FIFO, "\"%s\" already exists, but is not a FIFO\n", fifo_session->path); + return -1; + } + + return 0; +} + +static int +fifo_open(struct fifo_session *fifo_session) +{ + int ret; + + ret = fifo_check(fifo_session); + if (ret < 0) + return -1; + + fifo_session->input_fd = open(fifo_session->path, O_RDONLY | O_NONBLOCK, 0); + if (fifo_session->input_fd < 0) + { + DPRINTF(E_LOG, L_FIFO, "Could not open FIFO \"%s\" for reading: %d\n", fifo_session->path, errno); + fifo_close(fifo_session); + return -1; + } + + fifo_session->output_fd = open(fifo_session->path, O_WRONLY | O_NONBLOCK, 0); + if (fifo_session->output_fd < 0) + { + DPRINTF(E_LOG, L_FIFO, "Could not open FIFO \"%s\" for writing: %d\n", fifo_session->path, errno); + fifo_close(fifo_session); + return -1; + } + + return 0; +} + +static void +fifo_empty(struct fifo_session *fifo_session) +{ + char buf[FIFO_BUFFER_SIZE]; + int bytes = 1; + + while (bytes > 0 && errno != EINTR) + bytes = read(fifo_session->input_fd, buf, FIFO_BUFFER_SIZE); + + if (bytes < 0 && errno != EAGAIN) + { + DPRINTF(E_LOG, L_FIFO, "Flush of FIFO \"%s\" failed: %d\n", fifo_session->path, errno); + } +} + +/* ---------------------------- SESSION HANDLING ---------------------------- */ + +static void +fifo_session_free(struct fifo_session *fifo_session) +{ + event_free(fifo_session->deferredev); + + free(fifo_session->output_session); + free(fifo_session); + + fifo_session = NULL; +} + +static void +fifo_session_cleanup(struct fifo_session *fifo_session) +{ + // Normally some here code to remove from linked list - here we just say: + sessions = NULL; + + fifo_session_free(fifo_session); +} + +static struct fifo_session * +fifo_session_make(struct output_device *device, output_status_cb cb) +{ + struct output_session *output_session; + struct fifo_session *fifo_session; + + output_session = calloc(1, sizeof(struct output_session)); + fifo_session = calloc(1, sizeof(struct fifo_session)); + if (!output_session || !fifo_session) + { + DPRINTF(E_LOG, L_FIFO, "Out of memory for fifo session\n"); + return NULL; + } + + fifo_session->deferredev = evtimer_new(evbase_player, defer_cb, fifo_session); + if (!fifo_session->deferredev) + { + DPRINTF(E_LOG, L_FIFO, "Out of memory for fifo deferred event\n"); + free(output_session); + free(fifo_session); + return NULL; + } + + output_session->session = fifo_session; + output_session->type = device->type; + + fifo_session->output_session = output_session; + fifo_session->state = OUTPUT_STATE_CONNECTED; + fifo_session->device = device; + fifo_session->status_cb = cb; + + fifo_session->created = 0; + fifo_session->path = device->extra_device_info; + fifo_session->input_fd = -1; + fifo_session->output_fd = -1; + + sessions = fifo_session; + + return fifo_session; +} + + +/* ---------------------------- STATUS HANDLERS ----------------------------- */ + +// Maps our internal state to the generic output state and then makes a callback +// to the player to tell that state +static void +defer_cb(int fd, short what, void *arg) +{ + struct fifo_session *ds = arg; + + if (ds->defer_cb) + ds->defer_cb(ds->device, ds->output_session, ds->state); + + if (ds->state == OUTPUT_STATE_STOPPED) + fifo_session_cleanup(ds); +} + +static void +fifo_status(struct fifo_session *fifo_session) +{ + fifo_session->defer_cb = fifo_session->status_cb; + event_active(fifo_session->deferredev, 0, 0); + fifo_session->status_cb = NULL; +} + + +/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */ + +static int +fifo_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime) +{ + struct fifo_session *fifo_session; + int ret; + + fifo_session = fifo_session_make(device, cb); + if (!fifo_session) + return -1; + + ret = fifo_open(fifo_session); + if (ret < 0) + return -1; + + fifo_status(fifo_session); + + return 0; +} + +static void +fifo_device_stop(struct output_session *output_session) +{ + struct fifo_session *fifo_session = output_session->session; + + fifo_close(fifo_session); + + fifo_session->state = OUTPUT_STATE_STOPPED; + fifo_status(fifo_session); +} + +static int +fifo_device_probe(struct output_device *device, output_status_cb cb) +{ + struct fifo_session *fifo_session; + int ret; + + fifo_session = fifo_session_make(device, cb); + if (!fifo_session) + return -1; + + ret = fifo_open(fifo_session); + if (ret < 0) + { + fifo_session_cleanup(fifo_session); + return -1; + } + + fifo_close(fifo_session); + + fifo_session->status_cb = cb; + fifo_session->state = OUTPUT_STATE_STOPPED; + + fifo_status(fifo_session); + + return 0; +} + +static int +fifo_device_volume_set(struct output_device *device, output_status_cb cb) +{ + struct fifo_session *fifo_session; + + if (!device->session || !device->session->session) + return 0; + + fifo_session = device->session->session; + + fifo_session->status_cb = cb; + fifo_status(fifo_session); + + return 1; +} + +static void +fifo_playback_start(uint64_t next_pkt, struct timespec *ts) +{ + struct fifo_session *fifo_session = sessions; + + if (!fifo_session) + return; + + fifo_session->state = OUTPUT_STATE_STREAMING; + fifo_status(fifo_session); +} + +static void +fifo_playback_stop(void) +{ + struct fifo_session *fifo_session = sessions; + + if (!fifo_session) + return; + + fifo_session->state = OUTPUT_STATE_CONNECTED; + fifo_status(fifo_session); +} + +static int +fifo_flush(output_status_cb cb, uint64_t rtptime) +{ + struct fifo_session *fifo_session = sessions; + + if (!fifo_session) + return 0; + + fifo_empty(fifo_session); + + fifo_session->status_cb = cb; + fifo_session->state = OUTPUT_STATE_CONNECTED; + fifo_status(fifo_session); + return 0; +} + +static void +fifo_write(uint8_t *buf, uint64_t rtptime) +{ + struct fifo_session *fifo_session = sessions; + size_t length = STOB(AIRTUNES_V2_PACKET_SAMPLES); + ssize_t bytes; + + if (!fifo_session || !fifo_session->device->selected) + return; + + while (1) + { + bytes = write(fifo_session->output_fd, buf, length); + if (bytes > 0) + return; + + if (bytes < 0) + { + switch (errno) + { + case EAGAIN: + /* The pipe is full, so empty it */ + fifo_empty(fifo_session); + continue; + case EINTR: + continue; + } + + DPRINTF(E_LOG, L_FIFO, "Failed to write to FIFO %s: %d\n", fifo_session->path, errno); + return; + } + } +} + +static void +fifo_set_status_cb(struct output_session *session, output_status_cb cb) +{ + struct fifo_session *fifo_session = session->session; + + fifo_session->status_cb = cb; +} + +static int +fifo_init(void) +{ + struct output_device *device; + cfg_t *cfg_fifo; + char *nickname; + char *path; + + cfg_fifo = cfg_getsec(cfg, "fifo"); + if (!cfg_fifo) + return -1; + + path = cfg_getstr(cfg_fifo, "path"); + if (!path) + return -1; + + nickname = cfg_getstr(cfg_fifo, "nickname"); + + device = calloc(1, sizeof(struct output_device)); + if (!device) + { + DPRINTF(E_LOG, L_FIFO, "Out of memory for fifo device\n"); + return -1; + } + + device->id = 100; + device->name = strdup(nickname); + device->type = OUTPUT_TYPE_FIFO; + device->type_name = outputs_name(device->type); + device->advertised = 1; + device->has_video = 0; + device->extra_device_info = path; + DPRINTF(E_INFO, L_FIFO, "Adding fifo output device '%s' with path '%s'\n", nickname, path); + + player_device_add(device); + + return 0; +} + +static void +fifo_deinit(void) +{ + return; +} + +struct output_definition output_fifo = +{ + .name = "fifo", + .type = OUTPUT_TYPE_FIFO, + .priority = 98, + .disabled = 0, + .init = fifo_init, + .deinit = fifo_deinit, + .device_start = fifo_device_start, + .device_stop = fifo_device_stop, + .device_probe = fifo_device_probe, + .device_volume_set = fifo_device_volume_set, + .playback_start = fifo_playback_start, + .playback_stop = fifo_playback_stop, + .write = fifo_write, + .flush = fifo_flush, + .status_cb = fifo_set_status_cb, +}; From 4f2d994151bf9d09e266b45120801d48b6a958ca Mon Sep 17 00:00:00 2001 From: chme Date: Tue, 25 Oct 2016 21:24:04 +0200 Subject: [PATCH 2/2] [outputs] Remove unused old alsa output --- src/laudio_alsa.c | 738 ---------------------------------------------- 1 file changed, 738 deletions(-) delete mode 100644 src/laudio_alsa.c 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, - };