From c8e793fd0c60c2d41087b6aaf292ba9ab6ba9882 Mon Sep 17 00:00:00 2001 From: chme Date: Tue, 31 Mar 2015 17:31:14 +0200 Subject: [PATCH] [laudio] Makes the output type for local audio configurable and introduces a dummy output for local audio Changes configure.ac to control the with-alsa and with-oss4 separately. This makes it possible to only use the dummy output and compile without alse+oss4. --- configure.ac | 16 +++-- forked-daapd.conf | 3 + src/Makefile.am | 4 +- src/conffile.c | 1 + src/laudio.c | 155 ++++++++++++++++++++++++++++++++++++++++++++ src/laudio.h | 33 ++++++++++ src/laudio_alsa.c | 79 +++++++++++++++-------- src/laudio_dummy.c | 158 +++++++++++++++++++++++++++++++++++++++++++++ src/laudio_oss4.c | 52 +++++++++------ 9 files changed, 450 insertions(+), 51 deletions(-) create mode 100644 src/laudio.c create mode 100644 src/laudio_dummy.c diff --git a/configure.ac b/configure.ac index fedb4509..ceccdc5d 100644 --- a/configure.ac +++ b/configure.ac @@ -73,19 +73,21 @@ AC_ARG_ENABLE(lastfm, AS_HELP_STRING([--enable-lastfm], [enable LastFM support ( case "$host" in *-*-linux-*) + use_alsa=true use_oss4=false ;; *-*-kfreebsd*-*|*-*-freebsd*) + use_alsa=false use_oss4=true ;; esac AC_ARG_WITH(oss4, AS_HELP_STRING([--with-oss4], [use OSS4 (default Linux=no, FreeBSD=yes)]), - use_oss4=true; + AS_IF([test "x$with_oss4" = "xyes"],[use_oss4=true],[use_oss4=false]); ) AC_ARG_WITH(alsa, AS_HELP_STRING([--with-alsa], [use ALSA (default Linux=yes, FreeBSD=no)]), - use_oss4=false; + AS_IF([test "x$with_alsa" = "xyes"],[use_alsa=true],[use_alsa=false]); ) AC_ARG_ENABLE(mpd, AS_HELP_STRING([--enable-mpd], [enable MPD client protocol support (default=no)]), @@ -98,7 +100,7 @@ AM_CONDITIONAL(COND_ITUNES, test x$use_itunes = xtrue) AM_CONDITIONAL(COND_SPOTIFY, test x$use_spotify = xtrue) AM_CONDITIONAL(COND_LASTFM, test x$use_lastfm = xtrue) AM_CONDITIONAL(COND_OSS4, test x$use_oss4 = xtrue) -AM_CONDITIONAL(COND_ALSA, test x$use_oss4 != xtrue) +AM_CONDITIONAL(COND_ALSA, test x$use_alsa = xtrue) AM_CONDITIONAL(COND_MPD, test x$use_mpd = xtrue) dnl Checks for libraries. @@ -226,11 +228,15 @@ if test x$use_lastfm = xtrue; then AC_DEFINE(HAVE_MXML_GETOPAQUE, 1, [Define to 1 if your mxml has mxmlGetOpaque.])) fi -if test x$use_oss4 != xtrue; then +if test x$use_alsa = xtrue; then PKG_CHECK_MODULES(ALSA, [ alsa ]) AC_DEFINE(LAUDIO_USE_ALSA, 1, [define if local audio output uses ALSA]) -else + CPPFLAGS="${CPPFLAGS} -DALSA" +fi + +if test x$use_oss4 = xtrue; then AC_CHECK_HEADER(sys/soundcard.h, , AC_MSG_ERROR([sys/soundcard.h not found])) + CPPFLAGS="${CPPFLAGS} -DOSS4" fi case "$host" in diff --git a/forked-daapd.conf b/forked-daapd.conf index eac8c442..2a5cf0ec 100644 --- a/forked-daapd.conf +++ b/forked-daapd.conf @@ -139,6 +139,9 @@ library { audio { # Name - used in the speaker list in Remote nickname = "Computer" + + # Type of the output (alsa, oss4, dummy) +# type = "alsa" # Audio device name for local audio output # card = "default" diff --git a/src/Makefile.am b/src/Makefile.am index 43e15990..dba09f3f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -121,7 +121,9 @@ forked_daapd_SOURCES = main.c \ daap_query.c daap_query.h \ player.c player.h \ worker.c worker.h \ - $(ALSA_SRC) $(OSS4_SRC) laudio.h \ + $(ALSA_SRC) $(OSS4_SRC) \ + laudio_dummy.c \ + laudio.c laudio.h \ raop.c raop.h \ $(RTSP_SRC) \ scan-wma.c \ diff --git a/src/conffile.c b/src/conffile.c index e2781a91..ad3f398e 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -92,6 +92,7 @@ static cfg_opt_t sec_library[] = static cfg_opt_t sec_audio[] = { CFG_STR("nickname", "Computer", CFGF_NONE), + CFG_STR("type", NULL, CFGF_NONE), #ifdef __linux__ CFG_STR("card", "default", CFGF_NONE), #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) diff --git a/src/laudio.c b/src/laudio.c new file mode 100644 index 00000000..31943479 --- /dev/null +++ b/src/laudio.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 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 "conffile.h" +#include "logger.h" +#include "player.h" +#include "laudio.h" + +#ifdef ALSA +extern audio_output audio_alsa; +#endif +#ifdef OSS4 +extern audio_output audio_oss4; +#endif + +extern audio_output audio_dummy; + +static audio_output *outputs[] = { +#ifdef ALSA + &audio_alsa, +#endif +#ifdef OSS4 + &audio_oss4, +#endif + &audio_dummy, + NULL +}; + +static audio_output *output; + +struct pcm_packet +{ + uint8_t samples[STOB(AIRTUNES_V2_PACKET_SAMPLES)]; + + uint64_t rtptime; + + size_t offset; + + struct pcm_packet *next; +}; + + + +void +laudio_write(uint8_t *buf, uint64_t rtptime) +{ + output->write(buf, rtptime); +} + +uint64_t +laudio_get_pos(void) +{ + return output->pos(); +} + +void +laudio_set_volume(int vol) +{ + output->volume(vol); +} + +int +laudio_start(uint64_t cur_pos, uint64_t next_pkt) +{ + return output->start(cur_pos, next_pkt); +} + +void +laudio_stop(void) +{ + output->stop(); +} + + +int +laudio_open(void) +{ + return output->open(); +} + +void +laudio_close(void) +{ + output->close(); +} + + +int +laudio_init(laudio_status_cb cb) +{ + cfg_t *cfg_audio; + char *type; + int i; + + cfg_audio = cfg_getsec(cfg, "audio"); + type = cfg_getstr(cfg_audio, "type"); + + output = NULL; + if (type) + { + DPRINTF(E_DBG, L_LAUDIO, "Searching for local audio output: '%s'\n", type); + for (i = 0; outputs[i]; i++) + { + if (0 == strcmp(type, outputs[i]->name)) + { + output = outputs[i]; + } + } + + if (!output) + DPRINTF(E_WARN, L_LAUDIO, "No local audio output '%s' available, falling back to default output\n", type); + } + + if (!output) + { + output = outputs[0]; + } + + DPRINTF(E_INFO, L_LAUDIO, "Local audio output: '%s'\n", output->name); + + return output->init(cb, cfg_audio); +} + +void +laudio_deinit(void) +{ + output->deinit(); +} diff --git a/src/laudio.h b/src/laudio.h index c37649c3..fce80d0f 100644 --- a/src/laudio.h +++ b/src/laudio.h @@ -17,6 +17,39 @@ enum laudio_state typedef void (*laudio_status_cb)(enum laudio_state status); +typedef struct +{ + // Identifier of th audio output + char *name; + + // Initialization function called during startup + int (*init)(laudio_status_cb cb, cfg_t *cfg_audio); + + // Deinitialization function called at shutdown + void (*deinit)(void); + + // Function to open the output called at playback start or speaker activiation + int (*open)(void); + + // Function called after opening the output (during playback start or speaker activiation + int (*start)(uint64_t cur_pos, uint64_t next_pkt); + + // block of samples + void (*write)(uint8_t *buf, uint64_t rtptime); + + // Stopping audio playback + void (*stop)(void); + + // Closes the output + void (*close)(void); + + // Returns the rtptime of the packet thats is currently playing + uint64_t (*pos)(); + + // Sets the volum for the output + void (*volume)(int vol); +} audio_output; + void laudio_write(uint8_t *buf, uint64_t rtptime); diff --git a/src/laudio_alsa.c b/src/laudio_alsa.c index 6234805f..1e1e15e6 100644 --- a/src/laudio_alsa.c +++ b/src/laudio_alsa.c @@ -76,7 +76,7 @@ update_status(enum laudio_state status) } static int -laudio_xrun_recover(int err) +laudio_alsa_xrun_recover(int err) { int ret; @@ -127,7 +127,7 @@ laudio_xrun_recover(int err) } static int -laudio_set_start_threshold(snd_pcm_uframes_t threshold) +laudio_alsa_set_start_threshold(snd_pcm_uframes_t threshold) { snd_pcm_sw_params_t *sw_params; int ret; @@ -172,12 +172,13 @@ laudio_set_start_threshold(snd_pcm_uframes_t threshold) return -1; } -void -laudio_write(uint8_t *buf, uint64_t rtptime) +static void +laudio_alsa_write(uint8_t *buf, uint64_t rtptime) { struct pcm_packet *pkt; snd_pcm_sframes_t nsamp; int ret; + snd_pcm_sframes_t delay; pkt = (struct pcm_packet *)malloc(sizeof(struct pcm_packet)); if (!pkt) @@ -214,7 +215,7 @@ laudio_write(uint8_t *buf, uint64_t rtptime) else if ((pcm_status != LAUDIO_RUNNING) && (pcm_pos + pcm_buf_threshold >= pcm_start_pos)) { /* Kill threshold */ - ret = laudio_set_start_threshold(0); + ret = laudio_alsa_set_start_threshold(0); if (ret < 0) DPRINTF(E_WARN, L_LAUDIO, "Couldn't set PCM start threshold to 0 for output start\n"); @@ -223,11 +224,23 @@ laudio_write(uint8_t *buf, uint64_t rtptime) pkt = pcm_pkt_head; + ret = snd_pcm_delay(hdl, &delay); + DPRINTF(E_SPAM, L_LAUDIO, "Current delay %" PRIu64 "\n", delay); + snd_pcm_state_t pcmstate = snd_pcm_state(hdl); + if (pcmstate == SND_PCM_STATE_RUNNING) + { + DPRINTF(E_SPAM, L_LAUDIO, "PCM-state == RUNNING\n"); + } + else if (pcmstate == SND_PCM_STATE_PREPARED) + { + DPRINTF(E_SPAM, L_LAUDIO, "PCM-state == PREPARED\n"); + } + while (pkt) { if (pcm_recovery) { - ret = laudio_xrun_recover(0); + ret = laudio_alsa_xrun_recover(0); if ((ret == 2) && (pcm_recovery < 10)) return; else @@ -243,7 +256,7 @@ laudio_write(uint8_t *buf, uint64_t rtptime) nsamp = snd_pcm_writei(hdl, pkt->samples + pkt->offset, BTOS(sizeof(pkt->samples) - pkt->offset)); if ((nsamp == -EPIPE) || (nsamp == -ESTRPIPE)) { - ret = laudio_xrun_recover(nsamp); + ret = laudio_alsa_xrun_recover(nsamp); if ((ret < 0) || (ret == 1)) { if (ret < 0) @@ -287,8 +300,8 @@ laudio_write(uint8_t *buf, uint64_t rtptime) } } -uint64_t -laudio_get_pos(void) +static uint64_t +laudio_alsa_get_pos(void) { snd_pcm_sframes_t delay; int ret; @@ -307,8 +320,8 @@ laudio_get_pos(void) return pcm_pos - delay; } -void -laudio_set_volume(int vol) +static void +laudio_alsa_set_volume(int vol) { int pcm_vol; @@ -340,8 +353,8 @@ laudio_set_volume(int vol) snd_mixer_selem_set_playback_volume_all(vol_elem, pcm_vol); } -int -laudio_start(uint64_t cur_pos, uint64_t next_pkt) +static int +laudio_alsa_start(uint64_t cur_pos, uint64_t next_pkt) { int ret; @@ -353,6 +366,7 @@ laudio_start(uint64_t cur_pos, uint64_t next_pkt) return -1; } + DPRINTF(E_DBG, L_LAUDIO, "Start local audio curpos %" PRIu64 ", next_pkt %" PRIu64 "\n", cur_pos, next_pkt); DPRINTF(E_DBG, L_LAUDIO, "PCM will start after %d samples (%d packets)\n", pcm_buf_threshold, pcm_buf_threshold / AIRTUNES_V2_PACKET_SAMPLES); /* Make pcm_pos the rtptime of the packet containing cur_pos */ @@ -373,7 +387,7 @@ laudio_start(uint64_t cur_pos, uint64_t next_pkt) pcm_last_error = 0; pcm_recovery = 0; - ret = laudio_set_start_threshold(pcm_buf_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"); @@ -386,8 +400,8 @@ laudio_start(uint64_t cur_pos, uint64_t next_pkt) return 0; } -void -laudio_stop(void) +static void +laudio_alsa_stop(void) { struct pcm_packet *pkt; @@ -509,8 +523,8 @@ mixer_open(void) return -1; } -int -laudio_open(void) +static int +laudio_alsa_open(void) { snd_pcm_hw_params_t *hw_params; snd_pcm_uframes_t bufsize; @@ -633,8 +647,8 @@ laudio_open(void) return -1; } -void -laudio_close(void) +static void +laudio_alsa_close(void) { struct pcm_packet *pkt; @@ -664,15 +678,15 @@ laudio_close(void) } -int -laudio_init(laudio_status_cb cb) +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_getsec(cfg, "audio"), "card"); - mixer_name = cfg_getstr(cfg_getsec(cfg, "audio"), "mixer"); + card_name = cfg_getstr(cfg_audio, "card"); + mixer_name = cfg_getstr(cfg_audio, "mixer"); hdl = NULL; mixer_hdl = NULL; @@ -681,8 +695,21 @@ laudio_init(laudio_status_cb cb) return 0; } -void -laudio_deinit(void) +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, + }; diff --git a/src/laudio_dummy.c b/src/laudio_dummy.c new file mode 100644 index 00000000..6f3597ea --- /dev/null +++ b/src/laudio_dummy.c @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2015 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 "conffile.h" +#include "logger.h" +#include "misc.h" +#include "player.h" +#include "laudio.h" + + +static enum laudio_state pcm_status; +static laudio_status_cb status_cb; + +static struct timespec timer_res; +static struct timespec ts; +static uint64_t pcmpos; + + + +static uint64_t +laudio_dummy_get_pos(void) +{ + struct timespec cur_timer_res; + struct timespec cur_ts; + uint64_t delta; + int ret; + + ret = clock_gettime_with_res(CLOCK_MONOTONIC, &cur_ts, &cur_timer_res); + if (ret < 0) + { + DPRINTF(E_LOG, L_LAUDIO, "Couldn't get clock: %s\n", strerror(errno)); + return -1; + } + + delta = (cur_ts.tv_sec - ts.tv_sec) * 1000000 + (cur_ts.tv_nsec - ts.tv_nsec) / 1000; + delta = (delta * 44100) / 1000000; + + DPRINTF(E_DBG, L_LAUDIO, "Start: %" PRIu64 ", Pos: %" PRIu64 "\n", pcmpos, delta); + + return (pcmpos + delta); +} + +static void +laudio_dummy_write(uint8_t *buf, uint64_t rtptime) +{ + uint64_t pos; + + pos = laudio_dummy_get_pos(); + + if (pcm_status != LAUDIO_RUNNING && pos > (pcmpos + 88200)) + { + pcm_status = LAUDIO_RUNNING; + status_cb(LAUDIO_RUNNING); + } +} + +static void +laudio_dummy_set_volume(int vol) +{ +} + +static int +laudio_dummy_start(uint64_t cur_pos, uint64_t next_pkt) +{ + int ret; + + ret = clock_gettime_with_res(CLOCK_MONOTONIC, &ts, &timer_res); + + if (ret < 0) + { + DPRINTF(E_LOG, L_LAUDIO, "Couldn't get current clock: %s\n", strerror(errno)); + return -1; + } + + pcmpos = cur_pos; + + pcm_status = LAUDIO_STARTED; + status_cb(LAUDIO_STARTED); + + return 0; +} + +static void +laudio_dummy_stop(void) +{ + pcm_status = LAUDIO_STOPPING; + status_cb(LAUDIO_STOPPING); + pcm_status = LAUDIO_OPEN; + status_cb(LAUDIO_OPEN); +} + +static int +laudio_dummy_open(void) +{ + pcm_status = LAUDIO_OPEN; + status_cb(LAUDIO_OPEN); + return 0; +} + +static void +laudio_dummy_close(void) +{ + pcm_status = LAUDIO_CLOSED; + status_cb(LAUDIO_CLOSED); +} + + +static int +laudio_dummy_init(laudio_status_cb cb, cfg_t *cfg_audio) +{ + status_cb = cb; + return 0; +} + +static void +laudio_dummy_deinit(void) +{ +} + +audio_output audio_dummy = { + .name = "dummy", + .init = &laudio_dummy_init, + .deinit = &laudio_dummy_deinit, + .start = &laudio_dummy_start, + .stop = &laudio_dummy_stop, + .open = &laudio_dummy_open, + .close = &laudio_dummy_close, + .pos = &laudio_dummy_get_pos, + .write = &laudio_dummy_write, + .volume = &laudio_dummy_set_volume, + }; diff --git a/src/laudio_oss4.c b/src/laudio_oss4.c index 1c21249b..c4b6424f 100644 --- a/src/laudio_oss4.c +++ b/src/laudio_oss4.c @@ -73,8 +73,8 @@ update_status(enum laudio_state status) status_cb(status); } -void -laudio_write(uint8_t *buf, uint64_t rtptime) +static void +laudio_oss4_write(uint8_t *buf, uint64_t rtptime) { struct pcm_packet *pkt; int scratch; @@ -175,8 +175,8 @@ laudio_write(uint8_t *buf, uint64_t rtptime) } } -uint64_t -laudio_get_pos(void) +static uint64_t +laudio_oss4_get_pos(void) { int delay; int ret; @@ -192,8 +192,8 @@ laudio_get_pos(void) return pcm_pos - BTOS(delay); } -void -laudio_set_volume(int vol) +static void +laudio_oss4_set_volume(int vol) { int oss_vol; int ret; @@ -212,8 +212,8 @@ laudio_set_volume(int vol) DPRINTF(E_DBG, L_LAUDIO, "Setting PCM volume to %d (real: %d)\n", vol, (oss_vol & 0xff)); } -int -laudio_start(uint64_t cur_pos, uint64_t next_pkt) +static int +laudio_oss4_start(uint64_t cur_pos, uint64_t next_pkt) { int scratch; int ret; @@ -251,8 +251,8 @@ laudio_start(uint64_t cur_pos, uint64_t next_pkt) return 0; } -void -laudio_stop(void) +static void +laudio_oss4_stop(void) { struct pcm_packet *pkt; int ret; @@ -276,8 +276,8 @@ laudio_stop(void) update_status(LAUDIO_OPEN); } -int -laudio_open(void) +static int +laudio_oss4_open(void) { audio_buf_info bi; oss_sysinfo si; @@ -369,8 +369,8 @@ laudio_open(void) return -1; } -void -laudio_close(void) +static void +laudio_oss4_close(void) { struct pcm_packet *pkt; int ret; @@ -396,18 +396,32 @@ laudio_close(void) } -int -laudio_init(laudio_status_cb cb) +static int +laudio_oss4_init(laudio_status_cb cb, cfg_t *cfg_audio) { status_cb = cb; - card_name = cfg_getstr(cfg_getsec(cfg, "audio"), "card"); + card_name = cfg_getstr(cfg_audio, "card"); return 0; } -void -laudio_deinit(void) +static void +laudio_oss4_deinit(void) { /* EMPTY */ } + +audio_output audio_oss4 = { + .name = "oss4", + .init = &laudio_oss4_init, + .deinit = &laudio_oss4_deinit, + .start = &laudio_oss4_start, + .stop = &laudio_oss4_stop, + .open = &laudio_oss4_open, + .close = &laudio_oss4_close, + .pos = &laudio_oss4_get_pos, + .write = &laudio_oss4_write, + .volume = &laudio_oss4_set_volume, + }; +