diff --git a/INSTALL b/INSTALL index 4788aa37..908db207 100644 --- a/INSTALL +++ b/INSTALL @@ -136,8 +136,8 @@ Libraries: from - libunistring 0.9.3+ from - - libasound (optional - ALSA support, recommended for Linux) - normally already installed as part of your distro + - libasound (optional - local audio) + often already installed as part of your distro - libplist 0.16+ (optional - iTunes XML support) from - libspotify (optional - Spotify support) diff --git a/configure.ac b/configure.ac index 52f67fff..99e8d903 100644 --- a/configure.ac +++ b/configure.ac @@ -212,18 +212,19 @@ AM_CONDITIONAL(COND_MPD, [test "x$enable_mpd" != "xno"]) dnl --- End options --- dnl Selection of local audio sound system +dnl TODO exchange oss4 with Pulseaudio case "$host" in *-*-linux-*) use_alsa=true use_oss4=false ;; *-*-kfreebsd*-*|*-*-freebsd*) - use_alsa=false - use_oss4=true + use_alsa=true + use_oss4=false ;; esac -AC_ARG_WITH(alsa, AS_HELP_STRING([--with-alsa], [use ALSA (default Linux=yes, FreeBSD=no)]), [ +AC_ARG_WITH(alsa, AS_HELP_STRING([--with-alsa], [use ALSA (default yes)]), [ AS_IF([test "x$with_alsa" = "xyes"], [use_alsa=true], [use_alsa=false]) ]) if test x$use_alsa = xtrue; then @@ -232,15 +233,6 @@ if test x$use_alsa = xtrue; then fi AM_CONDITIONAL(COND_ALSA, test x$use_alsa = xtrue) -AC_ARG_WITH(oss4, AS_HELP_STRING([--with-oss4], [use OSS4 (default Linux=no, FreeBSD=yes)]), [ - AS_IF([test "x$with_oss4" = "xyes"], [use_oss4=true], [use_oss4=false]) -]) -if test x$use_oss4 = xtrue; then - CPPFLAGS="${CPPFLAGS} -DOSS4" - AC_CHECK_HEADER(sys/soundcard.h, , AC_MSG_ERROR([sys/soundcard.h not found])) -fi -AM_CONDITIONAL(COND_OSS4, test x$use_oss4 = xtrue) - dnl Checks for header files. AC_HEADER_STDC AC_HEADER_SYS_WAIT diff --git a/forked-daapd.conf b/forked-daapd.conf index e2d3a9e8..a80b9452 100644 --- a/forked-daapd.conf +++ b/forked-daapd.conf @@ -153,7 +153,7 @@ audio { # Name - used in the speaker list in Remote nickname = "Computer" - # Type of the output (alsa, oss4, dummy) + # Type of the output (alsa or dummy) # type = "alsa" # Audio device name for local audio output @@ -162,6 +162,12 @@ audio { # Mixer channel to use for volume control - ALSA/Linux only # If not set, PCM will be used if available, otherwise Master. # mixer = "" + + # If your local audio is out of sync with AirPlay, you can adjust this + # value. Positive values correspond to moving local audio ahead, + # negative correspond to delaying it. The unit is samples, where is + # 44100 = 1 second. The offset must be between -44100 and 44100. +# offset = 0 } # AirPlay/Airport Express device settings diff --git a/src/Makefile.am b/src/Makefile.am index cce264bf..32fe6011 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,11 +26,7 @@ MPD_SRC=mpd.c mpd.h endif if COND_ALSA -ALSA_SRC=laudio_alsa.c -endif - -if COND_OSS4 -OSS4_SRC=laudio_oss4.c +ALSA_SRC=outputs/alsa.c endif GPERF_FILES = \ @@ -106,15 +102,12 @@ forked_daapd_SOURCES = main.c \ player.c player.h \ queue.c queue.h \ worker.c worker.h \ - $(ALSA_SRC) $(OSS4_SRC) \ outputs.h outputs.c \ - laudio_dummy.c \ - laudio.c laudio.h \ - outputs/raop.c \ + outputs/raop.c outputs/streaming.c outputs/dummy.c \ + $(ALSA_SRC) $(CHROMECAST_SRC) \ evrtsp/rtsp.c evrtp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \ $(SPOTIFY_SRC) \ $(LASTFM_SRC) \ - $(CHROMECAST_SRC) \ $(MPD_SRC) \ listener.c listener.h diff --git a/src/conffile.c b/src/conffile.c index c3fff9a1..c8571f3d 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -94,12 +94,9 @@ 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__) - CFG_STR("card", "/dev/dsp", CFGF_NONE), -#endif CFG_STR("mixer", NULL, CFGF_NONE), + CFG_INT("offset", 0, CFGF_NONE), CFG_END() }; diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index 5cb83277..f00ae1f8 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -52,7 +52,7 @@ extern struct event_base *evbase_httpd; // Should prevent that we keep transcoding to dead connections #define STREAMING_CONNECTION_TIMEOUT 60 -// Linked list of Icecast requests +// Linked list of mp3 streaming requests struct streaming_session { struct evhttp_request *req; struct streaming_session *next; @@ -108,7 +108,6 @@ streaming_fail_cb(struct evhttp_connection *evcon, void *arg) { DPRINTF(E_INFO, L_STREAMING, "No more clients, will stop streaming\n"); event_del(streamingev); - player_streaming_stop(); } } @@ -122,16 +121,16 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg) int len; int ret; - if (!streaming_sessions) - return; - - // Callback from player (EV_READ) + // Player wrote data to the pipe (EV_READ) if (event & EV_READ) { ret = read(streaming_pipe[0], &streaming_rawbuf, STREAMING_RAWBUF_SIZE); if (ret < 0) return; + if (!streaming_sessions) + return; + decoded = transcode_raw2frame(streaming_rawbuf, STREAMING_RAWBUF_SIZE); if (!decoded) { @@ -153,6 +152,9 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg) player_get_status(&streaming_player_status); } + if (!streaming_sessions) + return; + if (streaming_player_status.status != PLAY_PAUSED) return; @@ -177,22 +179,6 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg) evbuffer_free(evbuf); } -// Thread: player -static int -streaming_cb(uint8_t *rawbuf, size_t size) -{ - if (size != STREAMING_RAWBUF_SIZE) - { - DPRINTF(E_LOG, L_STREAMING, "Bug! Buffer size in streaming_cb does not equal input from player\n"); - return -1; - } - - if (write(streaming_pipe[1], rawbuf, size) < 0) - return -1; - - return 0; -} - // Thread: player (not fully thread safe, but hey...) static void player_change_cb(enum listener_event_type type) @@ -200,6 +186,20 @@ player_change_cb(enum listener_event_type type) streaming_player_changed = 1; } +// Thread: player (also prone to race conditions, mostly during deinit) +void +streaming_write(uint8_t *buf, uint64_t rtptime) +{ + int ret; + + if (!streaming_sessions) + return; + + ret = write(streaming_pipe[1], buf, STREAMING_RAWBUF_SIZE); + if (ret < 0) + DPRINTF(E_LOG, L_STREAMING, "Error writing to streaming pipe\n"); +} + int streaming_is_request(struct evhttp_request *req, char *uri) { @@ -269,8 +269,6 @@ streaming_request(struct evhttp_request *req) evhttp_connection_set_timeout(evcon, STREAMING_CONNECTION_TIMEOUT); evhttp_connection_set_closecb(evcon, streaming_fail_cb, session); - player_streaming_start(streaming_cb); - return 0; } @@ -403,18 +401,20 @@ streaming_deinit(void) if (!streaming_initialized) return; - player_streaming_stop(); - - event_free(streamingev); + session = streaming_sessions; + streaming_sessions = NULL; // Stops writing and sending next = NULL; - for (session = streaming_sessions; session; session = next) + while (session) { evhttp_send_reply_end(session->req); next = session->next; free(session); + session = next; } + event_free(streamingev); + listener_remove(player_change_cb); close(streaming_pipe[0]); diff --git a/src/httpd_streaming.h b/src/httpd_streaming.h index 5e1486b6..2fcbc805 100644 --- a/src/httpd_streaming.h +++ b/src/httpd_streaming.h @@ -10,6 +10,9 @@ * if a suitable ffmpeg/libav encoder is not present at runtime. */ +void +streaming_write(uint8_t *buf, uint64_t rtptime); + int streaming_is_request(struct evhttp_request *req, char *uri); diff --git a/src/laudio.c b/src/laudio.c deleted file mode 100644 index 31943479..00000000 --- a/src/laudio.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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 deleted file mode 100644 index fce80d0f..00000000 --- a/src/laudio.h +++ /dev/null @@ -1,80 +0,0 @@ - -#ifndef __LAUDIO_H__ -#define __LAUDIO_H__ - -#define LAUDIO_F_STARTED (1 << 15) - -enum laudio_state - { - LAUDIO_CLOSED = 0, - LAUDIO_STOPPING = 1, - LAUDIO_OPEN = 2, - LAUDIO_STARTED = LAUDIO_F_STARTED, - LAUDIO_RUNNING = LAUDIO_F_STARTED | 0x01, - - LAUDIO_FAILED = -1, - }; - -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); - -uint64_t -laudio_get_pos(void); - -void -laudio_set_volume(int vol); - -int -laudio_start(uint64_t cur_pos, uint64_t next_pkt); - -void -laudio_stop(void); - -int -laudio_open(void); - -void -laudio_close(void); - -int -laudio_init(laudio_status_cb cb); - -void -laudio_deinit(void); - -#endif /* !__LAUDIO_H__ */ diff --git a/src/laudio_dummy.c b/src/laudio_dummy.c deleted file mode 100644 index 6f3597ea..00000000 --- a/src/laudio_dummy.c +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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 deleted file mode 100644 index c4b6424f..00000000 --- a/src/laudio_oss4.c +++ /dev/null @@ -1,427 +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 -#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_buf_threshold; -static int pcm_retry; - -static struct pcm_packet *pcm_pkt_head; -static struct pcm_packet *pcm_pkt_tail; - -static char *card_name; -static int oss_fd; - -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 void -laudio_oss4_write(uint8_t *buf, uint64_t rtptime) -{ - struct pcm_packet *pkt; - int scratch; - int 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)) - { - /* Start audio output */ - scratch = PCM_ENABLE_OUTPUT; - ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not enable output: %s\n", strerror(errno)); - - update_status(LAUDIO_FAILED); - return; - } - - update_status(LAUDIO_RUNNING); - } - - pkt = pcm_pkt_head; - - while (pkt) - { - nsamp = write(oss_fd, pkt->samples + pkt->offset, sizeof(pkt->samples) - pkt->offset); - if (nsamp < 0) - { - if (errno == EAGAIN) - { - pcm_retry++; - - if (pcm_retry < 10) - return; - } - - DPRINTF(E_LOG, L_LAUDIO, "Write error: %s\n", strerror(errno)); - - update_status(LAUDIO_FAILED); - return; - } - - pcm_retry = 0; - - pkt->offset += nsamp; - - nsamp = BTOS(nsamp); - pcm_pos += 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 the buffer fill up too much */ - if (nsamp == AIRTUNES_V2_PACKET_SAMPLES) - break; - } -} - -static uint64_t -laudio_oss4_get_pos(void) -{ - int delay; - int ret; - - ret = ioctl(oss_fd, SNDCTL_DSP_GETODELAY, &delay); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not obtain output delay: %s\n", strerror(errno)); - - return pcm_pos; - } - - return pcm_pos - BTOS(delay); -} - -static void -laudio_oss4_set_volume(int vol) -{ - int oss_vol; - int ret; - - vol = vol & 0xff; - oss_vol = vol | (vol << 8); - - ret = ioctl(oss_fd, SNDCTL_DSP_SETPLAYVOL, &oss_vol); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set volume: %s\n", strerror(errno)); - - return; - } - - DPRINTF(E_DBG, L_LAUDIO, "Setting PCM volume to %d (real: %d)\n", vol, (oss_vol & 0xff)); -} - -static int -laudio_oss4_start(uint64_t cur_pos, uint64_t next_pkt) -{ - int scratch; - int ret; - - 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 */ - pcm_pos = next_pkt; - while (pcm_pos > cur_pos) - pcm_pos -= AIRTUNES_V2_PACKET_SAMPLES; - - pcm_start_pos = next_pkt + pcm_buf_threshold; - - /* FIXME check for OSS - Compensate threshold, as it's taken into account by snd_pcm_delay() */ - pcm_pos += pcm_buf_threshold; - - 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_retry = 0; - - scratch = 0; - ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set trigger: %s\n", strerror(errno)); - - return -1; - } - - update_status(LAUDIO_STARTED); - - return 0; -} - -static void -laudio_oss4_stop(void) -{ - struct pcm_packet *pkt; - int ret; - - update_status(LAUDIO_STOPPING); - - ret = ioctl(oss_fd, SNDCTL_DSP_HALT_OUTPUT, NULL); - if (ret < 0) - DPRINTF(E_LOG, L_LAUDIO, "Failed to halt output: %s\n", strerror(errno)); - - 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 -laudio_oss4_open(void) -{ - audio_buf_info bi; - oss_sysinfo si; - int scratch; - int ret; - - oss_fd = open(card_name, O_RDWR | O_NONBLOCK); - if (oss_fd < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not open sound device: %s\n", strerror(errno)); - - return -1; - } - - ret = ioctl(oss_fd, SNDCTL_SYSINFO, &si); - if ((ret < 0) || (si.versionnum < 0x040000)) - { - DPRINTF(E_LOG, L_LAUDIO, "Your OSS version (%s) is unavailable or too old; version 4.0.0+ is required\n", si.version); - - goto out_fail; - } - - scratch = 0; - ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Could not set trigger: %s\n", strerror(errno)); - - goto out_fail; - } - - scratch = AFMT_S16_LE; - errno = 0; - ret = ioctl(oss_fd, SNDCTL_DSP_SETFMT, &scratch); - if ((ret < 0) || (scratch != AFMT_S16_LE)) - { - if (errno) - DPRINTF(E_LOG, L_LAUDIO, "Could not set sample format (S16 LE): %s\n", strerror(errno)); - else - DPRINTF(E_LOG, L_LAUDIO, "Sample format S16 LE not supported\n"); - - goto out_fail; - } - - scratch = 2; - errno = 0; - ret = ioctl(oss_fd, SNDCTL_DSP_CHANNELS, &scratch); - if ((ret < 0) || (scratch != 2)) - { - if (errno) - DPRINTF(E_LOG, L_LAUDIO, "Could not set stereo: %s\n", strerror(errno)); - else - DPRINTF(E_LOG, L_LAUDIO, "Stereo not supported\n"); - - goto out_fail; - } - - scratch = 44100; - errno = 0; - ret = ioctl(oss_fd, SNDCTL_DSP_SPEED, &scratch); - if ((ret < 0) || (scratch != 44100)) - { - if (errno) - DPRINTF(E_LOG, L_LAUDIO, "Could not set speed (44100): %s\n", strerror(errno)); - else - DPRINTF(E_LOG, L_LAUDIO, "Sample rate 44100 not supported\n"); - - goto out_fail; - } - - ret = ioctl(oss_fd, SNDCTL_DSP_GETOSPACE, &bi); - if (ret < 0) - { - DPRINTF(E_LOG, L_LAUDIO, "Couldn't get output buffer status: %s\n", strerror(errno)); - - goto out_fail; - } - - pcm_buf_threshold = (BTOS(bi.bytes) / AIRTUNES_V2_PACKET_SAMPLES) * AIRTUNES_V2_PACKET_SAMPLES; - - update_status(LAUDIO_OPEN); - - return 0; - - out_fail: - close(oss_fd); - oss_fd = -1; - - return -1; -} - -static void -laudio_oss4_close(void) -{ - struct pcm_packet *pkt; - int ret; - - ret = ioctl(oss_fd, SNDCTL_DSP_HALT_OUTPUT, NULL); - if (ret < 0) - DPRINTF(E_LOG, L_LAUDIO, "Failed to halt output: %s\n", strerror(errno)); - - close(oss_fd); - oss_fd = -1; - - 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_oss4_init(laudio_status_cb cb, cfg_t *cfg_audio) -{ - status_cb = cb; - - card_name = cfg_getstr(cfg_audio, "card"); - - return 0; -} - -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, - }; - diff --git a/src/outputs.c b/src/outputs.c index c78e94de..0ca17fbc 100644 --- a/src/outputs.c +++ b/src/outputs.c @@ -32,36 +32,26 @@ #include "outputs.h" extern struct output_definition output_raop; -#ifdef CHROMECAST -extern struct output_definition output_cast; -#endif -/* TODO extern struct output_definition output_streaming; +extern struct output_definition output_dummy; #ifdef ALSA extern struct output_definition output_alsa; #endif -#ifdef OSS4 -extern struct output_definition output_oss4; +#ifdef CHROMECAST +extern struct output_definition output_cast; #endif -extern struct output_definition output_dummy; -*/ // Must be in sync with enum output_types static struct output_definition *outputs[] = { &output_raop, -#ifdef CHROMECAST - &output_cast, -#endif -/* TODO &output_streaming, + &output_dummy, #ifdef ALSA &output_alsa, #endif -#ifdef OSS4 - &output_oss4, +#ifdef CHROMECAST + &output_cast, #endif - &output_dummy, -*/ NULL }; @@ -315,6 +305,11 @@ outputs_metadata_free(struct output_metadata *omd) } } +int +outputs_priority(struct output_device *device) +{ + return outputs[device->type]->priority; +} const char * outputs_name(enum output_types type) @@ -338,6 +333,9 @@ outputs_init(void) return -1; } + if (!outputs[i]->init) + continue; + ret = outputs[i]->init(); if (ret < 0) outputs[i]->disabled = 1; @@ -358,7 +356,10 @@ outputs_deinit(void) for (i = 0; outputs[i]; i++) { - if (!outputs[i]->disabled) + if (outputs[i]->disabled) + continue; + + if (outputs[i]->deinit) outputs[i]->deinit(); } } diff --git a/src/outputs.h b/src/outputs.h index 63df7158..518a76ac 100644 --- a/src/outputs.h +++ b/src/outputs.h @@ -14,21 +14,48 @@ * When a device is started the output backend will typically create a session. * This session is only passed around as an opaque object in this interface. * + * Here is the sequence of commands from the player to the outputs, and the + * callback from the output once the command has been executed. Commands marked + * with * may make multiple callbacks if multiple sessions are affected. + * (TODO should callbacks always be deferred?) + * + * PLAYER OUTPUT PLAYER CB + * speaker_activate -> device_start -> device_activate_cb + * -> (if playback) -> playback_start -> device_streaming_cb* (or no cb) + * -> (else if playback not active) -> device_streaming_cb + * -> (fail) -> device_stop -> device_lost_cb + * speaker_activate -> device_probe -> device_probe_cb + * speaker_deactivate -> device_stop -> device_shutdown_cb + * volume_set -> device_volume_set -> device_command_cb + * -> -> device_streaming_cb + * (volume_setrel/abs_speaker is the same) + * playback_start_item -> device_start -> device_restart_cb + * -> (success) -> device_streaming_cb + * -> (fail) -> device_stop -> device_lost_cb + * playback_start_bh -> playback_start -> device_streaming_cb* (or no cb) + * playback_stop -> flush -> device_command_cb* + * -> -> device_streaming_cb* + * -> -> playback_stop -> device_streaming_cb* + * playback_pause -> flush -> device_command_cb* + * -> -> device_streaming_cb* + * -> -> playback_stop -> device_streaming_cb* + * playback_abort -> playback_stop -> device_streaming_cb* (or no cb) + * device_streaming_cb -> device_streaming_cb (re-add) + * */ // Must be in sync with outputs[] in outputs.c enum output_types { OUTPUT_TYPE_RAOP, + OUTPUT_TYPE_STREAMING, + OUTPUT_TYPE_DUMMY, +#ifdef ALSA + OUTPUT_TYPE_ALSA, +#endif #ifdef CHROMECAST OUTPUT_TYPE_CAST, #endif -/* TODO - OUTPUT_TYPE_STREAMING, - OUTPUT_TYPE_ALSA, - OUTPUT_TYPE_OSS, - OUTPUT_TYPE_DUMMY, -*/ }; /* Output session state */ @@ -117,7 +144,6 @@ struct output_definition // Priority to give this output when autoselecting an output, 1 is highest // 1 = highest priority, 0 = don't autoselect - // TODO Not implemented yet int priority; // Set to 1 if the output initialization failed @@ -210,6 +236,9 @@ outputs_metadata_prune(uint64_t rtptime); void outputs_metadata_free(struct output_metadata *omd); +int +outputs_priority(struct output_device *device); + const char * outputs_name(enum output_types type); diff --git a/src/outputs/alsa.c b/src/outputs/alsa.c new file mode 100644 index 00000000..ecec35cc --- /dev/null +++ b/src/outputs/alsa.c @@ -0,0 +1,1051 @@ +/* + * Copyright (C) 2015-2016 Espen Jürgensen + * 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 + +#include "conffile.h" +#include "logger.h" +#include "player.h" +#include "outputs.h" + +#define PACKET_SIZE STOB(AIRTUNES_V2_PACKET_SAMPLES) +// The maximum number of samples that the output is allowed to get behind (or +// ahead) of the player position, before compensation is attempted +#define ALSA_MAX_LATENCY 352 +// If latency is jumping up and down we don't do compensation since we probably +// wouldn't do a good job. This sets the maximum the latency is allowed to vary +// within the 10 seconds where we measure latency each second. +#define ALSA_MAX_LATENCY_VARIANCE 352 + +// TODO Unglobalise these and add support for multiple sound cards +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 int offset; + +#define ALSA_F_STARTED (1 << 15) + +enum alsa_state +{ + ALSA_STATE_STOPPED = 0, + ALSA_STATE_STARTED = ALSA_F_STARTED, + ALSA_STATE_STREAMING = ALSA_F_STARTED | 0x01, + + ALSA_STATE_FAILED = -1, +}; + +enum alsa_sync_state +{ + ALSA_SYNC_OK, + ALSA_SYNC_AHEAD, + ALSA_SYNC_BEHIND, +}; + +struct alsa_session +{ + enum alsa_state state; + + char *devname; + + uint64_t pos; + uint64_t start_pos; + + int32_t last_latency; + int sync_counter; + + // An array that will hold the packets we prebuffer. The length of the array + // is prebuf_len (measured in rtp_packets) + uint8_t *prebuf; + uint32_t prebuf_len; + uint32_t prebuf_head; + uint32_t prebuf_tail; + + int volume; + + 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; + + struct alsa_session *next; +}; + +/* From player.c */ +extern struct event_base *evbase_player; + +static struct alsa_session *sessions; + +/* Forwards */ +static void +defer_cb(int fd, short what, void *arg); + +/* ---------------------------- SESSION HANDLING ---------------------------- */ + +static void +prebuf_free(struct alsa_session *as) +{ + if (as->prebuf) + free(as->prebuf); + + as->prebuf = NULL; + as->prebuf_len = 0; + as->prebuf_head = 0; + as->prebuf_tail = 0; +} + +static void +alsa_session_free(struct alsa_session *as) +{ + event_free(as->deferredev); + + prebuf_free(as); + + free(as->output_session); + free(as); + + as = NULL; +} + +static void +alsa_session_cleanup(struct alsa_session *as) +{ + struct alsa_session *s; + + if (as == sessions) + sessions = sessions->next; + else + { + for (s = sessions; s && (s->next != as); s = s->next) + ; /* EMPTY */ + + if (!s) + DPRINTF(E_WARN, L_LAUDIO, "WARNING: struct alsa_session not found in list; BUG!\n"); + else + s->next = as->next; + } + + alsa_session_free(as); +} + +static struct alsa_session * +alsa_session_make(struct output_device *device, output_status_cb cb) +{ + struct output_session *os; + struct alsa_session *as; + + os = calloc(1, sizeof(struct output_session)); + as = calloc(1, sizeof(struct alsa_session)); + if (!os || !as) + { + DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA session\n"); + return NULL; + } + + as->deferredev = evtimer_new(evbase_player, defer_cb, as); + if (!as->deferredev) + { + DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA deferred event\n"); + free(os); + free(as); + return NULL; + } + + os->session = as; + os->type = device->type; + + as->output_session = os; + as->state = ALSA_STATE_STOPPED; + as->device = device; + as->status_cb = cb; + as->volume = device->volume; + as->devname = card_name; + + as->next = sessions; + sessions = as; + + return as; +} + + +/* ---------------------------- 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 alsa_session *as = arg; + enum output_device_state state; + + switch (as->state) + { + case ALSA_STATE_FAILED: + state = OUTPUT_STATE_FAILED; + break; + case ALSA_STATE_STOPPED: + state = OUTPUT_STATE_STOPPED; + break; + case ALSA_STATE_STARTED: + state = OUTPUT_STATE_CONNECTED; + break; + case ALSA_STATE_STREAMING: + state = OUTPUT_STATE_STREAMING; + break; + default: + DPRINTF(E_LOG, L_LAUDIO, "Bug! Unhandled state in alsa_status()\n"); + state = OUTPUT_STATE_FAILED; + } + + if (as->defer_cb) + as->defer_cb(as->device, as->output_session, state); + + if (!(as->state & ALSA_F_STARTED)) + alsa_session_cleanup(as); +} + +// Note: alsa_states also nukes the session if it is not ALSA_F_STARTED +static void +alsa_status(struct alsa_session *as) +{ + as->defer_cb = as->status_cb; + event_active(as->deferredev, 0, 0); + as->status_cb = NULL; +} + + +/* ------------------------------- MISC HELPERS ----------------------------- */ + +/*static int +start_threshold_set(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 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 +device_open(struct alsa_session *as) +{ + snd_pcm_hw_params_t *hw_params; + snd_pcm_uframes_t bufsize; + 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; + } + + 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; + } + + 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; + + ret = mixer_open(); + if (ret < 0) + { + DPRINTF(E_LOG, L_LAUDIO, "Could not open mixer\n"); + + goto out_fail; + } + + return 0; + + out_fail: + if (hw_params) + snd_pcm_hw_params_free(hw_params); + + snd_pcm_close(hdl); + hdl = NULL; + + return -1; +} + +static void +device_close(void) +{ + 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; + } +} + +static void +playback_start(struct alsa_session *as, uint64_t pos, uint64_t start_pos) +{ + snd_output_t *output; + snd_pcm_state_t state; + char *debug_pcm_cfg; + int ret; + + state = snd_pcm_state(hdl); + if (state != SND_PCM_STATE_PREPARED) + { + if (state == SND_PCM_STATE_RUNNING) + snd_pcm_drop(hdl); + + ret = snd_pcm_prepare(hdl); + if (ret < 0) + { + DPRINTF(E_LOG, L_LAUDIO, "Could not prepare ALSA device '%s' (state %d): %s\n", as->devname, state, snd_strerror(ret)); + return; + } + } + + // Clear prebuffer in case start somehow got called twice without a stop in between + prebuf_free(as); + + // Adjust the starting position with the configured value + start_pos -= offset; + + // The difference between pos and start_pos should match the 2 second + // buffer that AirPlay uses. We will not use alsa's buffer for the initial + // buffering, because my sound card's start_threshold is not to be counted on. + // Instead we allocate our own buffer, and when it is time to play we write as + // much as we can to alsa's buffer. + as->prebuf_len = (start_pos - pos) / AIRTUNES_V2_PACKET_SAMPLES + 1; + if (as->prebuf_len > (3 * 44100 - offset) / AIRTUNES_V2_PACKET_SAMPLES) + { + DPRINTF(E_LOG, L_LAUDIO, "Sanity check of prebuf_len (%" PRIu32 " packets) failed\n", as->prebuf_len); + return; + } + DPRINTF(E_DBG, L_LAUDIO, "Will prebuffer %d packets\n", as->prebuf_len); + + as->prebuf = malloc(as->prebuf_len * PACKET_SIZE); + if (!as->prebuf) + { + DPRINTF(E_LOG, L_LAUDIO, "Out of memory for audio buffer (requested %" PRIu32 " packets)\n", as->prebuf_len); + return; + } + + as->pos = pos; + as->start_pos = start_pos - AIRTUNES_V2_PACKET_SAMPLES; + + // 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); + } + + as->state = ALSA_STATE_STREAMING; +} + + +// This function writes the sample buf into either the prebuffer or directly to +// ALSA, depending on how much room there is in ALSA, and whether we are +// prebuffering or not. It also transfers from the the prebuffer to ALSA, if +// needed. Returns 0 on success, negative on error. +static int +buffer_write(struct alsa_session *as, uint8_t *buf, snd_pcm_sframes_t *avail, int prebuffering, int prebuf_empty) +{ + uint8_t *pkt; + int npackets; + snd_pcm_sframes_t nsamp; + snd_pcm_sframes_t ret; + + nsamp = AIRTUNES_V2_PACKET_SAMPLES; + + if (prebuffering || !prebuf_empty || *avail < AIRTUNES_V2_PACKET_SAMPLES) + { + pkt = &as->prebuf[as->prebuf_head * PACKET_SIZE]; + + memcpy(pkt, buf, PACKET_SIZE); + + as->prebuf_head = (as->prebuf_head + 1) % as->prebuf_len; + + if (prebuffering || *avail < AIRTUNES_V2_PACKET_SAMPLES) + return 0; // No actual writing + + // We will now set buf so that we will transfer as much as possible to ALSA + buf = &as->prebuf[as->prebuf_tail * PACKET_SIZE]; + + if (as->prebuf_head > as->prebuf_tail) + npackets = as->prebuf_head - as->prebuf_tail; + else + npackets = as->prebuf_len - as->prebuf_tail; + + nsamp = npackets * AIRTUNES_V2_PACKET_SAMPLES; + while (nsamp > *avail) + { + npackets -= 1; + nsamp -= AIRTUNES_V2_PACKET_SAMPLES; + } + + as->prebuf_tail = (as->prebuf_tail + npackets) % as->prebuf_len; + } + + ret = snd_pcm_writei(hdl, buf, nsamp); + if (ret < 0) + return ret; + + if (ret != nsamp) + DPRINTF(E_WARN, L_LAUDIO, "ALSA partial write detected\n"); + + *avail -= ret; + + return 0; +} + +// Checks if ALSA's playback position is ahead or behind the player's +enum alsa_sync_state +sync_check(struct alsa_session *as, uint64_t rtptime, snd_pcm_sframes_t delay, int prebuf_empty) +{ + enum alsa_sync_state sync; + struct timespec now; + uint64_t cur_pos; + uint64_t pb_pos; + int32_t latency; + int npackets; + + sync = ALSA_SYNC_OK; + + if (player_get_current_pos(&cur_pos, &now, 0) != 0) + return sync; + + if (!prebuf_empty) + npackets = (as->prebuf_head - (as->prebuf_tail + 1) + as->prebuf_len) % as->prebuf_len + 1; + else + npackets = 0; + + pb_pos = rtptime - delay - AIRTUNES_V2_PACKET_SAMPLES * npackets; + latency = cur_pos - (pb_pos - offset); + + // If the latency is low or very different from our last measurement, we reset the sync_counter + if (abs(latency) < ALSA_MAX_LATENCY || abs(as->last_latency - latency) > ALSA_MAX_LATENCY_VARIANCE) + { + as->sync_counter = 0; + sync = ALSA_SYNC_OK; + } + // If we have measured a consistent latency for 10 seconds, then we take action + else if (as->sync_counter >= 10 * 126) + { + DPRINTF(E_INFO, L_LAUDIO, "Taking action to compensate for ALSA latency of %d samples\n", latency); + + as->sync_counter = 0; + if (latency > 0) + sync = ALSA_SYNC_BEHIND; + else + sync = ALSA_SYNC_AHEAD; + } + + as->last_latency = latency; + + if (latency) + DPRINTF(E_SPAM, L_LAUDIO, "Sync %d cur_pos %" PRIu64 ", pb_pos %" PRIu64 " (diff %d, delay %li), pos %" PRIu64 "\n", sync, cur_pos, pb_pos, latency, delay, as->pos); + + return sync; +} + +static void +playback_write(struct alsa_session *as, uint8_t *buf, uint64_t rtptime) +{ + snd_pcm_sframes_t ret; + snd_pcm_sframes_t avail; + snd_pcm_sframes_t delay; + enum alsa_sync_state sync; + int prebuffering; + int prebuf_empty; + + prebuffering = (as->pos < as->start_pos); + prebuf_empty = (as->prebuf_head == as->prebuf_tail); + + as->pos += AIRTUNES_V2_PACKET_SAMPLES; + + if (prebuffering) + { + buffer_write(as, buf, NULL, prebuffering, prebuf_empty); + return; + } + + ret = snd_pcm_avail_delay(hdl, &avail, &delay); + if (ret < 0) + goto alsa_error; + + // Every second we do a sync check + sync = ALSA_SYNC_OK; + as->sync_counter++; + if (as->sync_counter % 126 == 0) + sync = sync_check(as, rtptime, delay, prebuf_empty); + + // Skip write -> reduce the delay + if (sync == ALSA_SYNC_BEHIND) + return; + + ret = buffer_write(as, buf, &avail, prebuffering, prebuf_empty); + // Double write -> increase the delay + if (sync == ALSA_SYNC_AHEAD && (ret == 0)) + ret = buffer_write(as, buf, &avail, prebuffering, prebuf_empty); + if (ret < 0) + goto alsa_error; + + return; + + alsa_error: + if (ret == -EPIPE) + { + DPRINTF(E_WARN, L_LAUDIO, "ALSA buffer underrun\n"); + + ret = snd_pcm_prepare(hdl); + if (ret < 0) + { + DPRINTF(E_WARN, L_LAUDIO, "ALSA couldn't recover from underrun: %s\n", snd_strerror(ret)); + return; + } + + // Fill the prebuf with audio before restarting, so we don't underrun again + as->start_pos = as->pos + AIRTUNES_V2_PACKET_SAMPLES * (as->prebuf_len - 1); + + return; + } + + DPRINTF(E_LOG, L_LAUDIO, "ALSA write error: %s\n", snd_strerror(ret)); + + as->state = ALSA_STATE_FAILED; + alsa_status(as); +} + +static void +playback_pos_get(uint64_t *pos, uint64_t next_pkt) +{ + uint64_t cur_pos; + struct timespec now; + int ret; + + ret = player_get_current_pos(&cur_pos, &now, 0); + if (ret < 0) + { + DPRINTF(E_LOG, L_LAUDIO, "Could not get playback position, setting to next_pkt - 2 seconds\n"); + cur_pos = next_pkt - 88200; + } + + // Make pos the rtptime of the packet containing cur_pos + *pos = next_pkt; + while (*pos > cur_pos) + *pos -= AIRTUNES_V2_PACKET_SAMPLES; +} + +/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */ + +static int +alsa_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime) +{ + struct alsa_session *as; + int ret; + + as = alsa_session_make(device, cb); + if (!as) + return -1; + + ret = device_open(as); + if (ret < 0) + return -1; + + as->state = ALSA_STATE_STARTED; + alsa_status(as); + + return 0; +} + +static void +alsa_device_stop(struct output_session *session) +{ + struct alsa_session *as = session->session; + + device_close(); + + as->state = ALSA_STATE_STOPPED; + alsa_status(as); +} + +static int +alsa_device_probe(struct output_device *device, output_status_cb cb) +{ + struct alsa_session *as; + int ret; + + as = alsa_session_make(device, cb); + if (!as) + return -1; + + ret = device_open(as); + if (ret < 0) + { + alsa_session_cleanup(as); + return -1; + } + + device_close(); + + as->state = ALSA_STATE_STOPPED; + alsa_status(as); + + return 0; +} + +static int +alsa_device_volume_set(struct output_device *device, output_status_cb cb) +{ + struct alsa_session *as; + int pcm_vol; + + if (!device->session || !device->session->session) + return 0; + + as = device->session->session; + + if (!mixer_hdl || !vol_elem) + return 0; + + snd_mixer_handle_events(mixer_hdl); + + if (!snd_mixer_selem_is_active(vol_elem)) + return 0; + + switch (device->volume) + { + case 0: + pcm_vol = vol_min; + break; + + case 100: + pcm_vol = vol_max; + break; + + default: + pcm_vol = vol_min + (device->volume * (vol_max - vol_min)) / 100; + break; + } + + DPRINTF(E_DBG, L_LAUDIO, "Setting ALSA volume to %d (%d)\n", pcm_vol, device->volume); + + snd_mixer_selem_set_playback_volume_all(vol_elem, pcm_vol); + + as->status_cb = cb; + alsa_status(as); + + return 1; +} + +static void +alsa_playback_start(uint64_t next_pkt, struct timespec *ts) +{ + struct alsa_session *as; + uint64_t pos; + + if (!sessions) + return; + + playback_pos_get(&pos, next_pkt); + + DPRINTF(E_DBG, L_LAUDIO, "Starting ALSA audio (pos %" PRIu64 ", next_pkt %" PRIu64 ")\n", pos, next_pkt); + + for (as = sessions; as; as = as->next) + playback_start(as, pos, next_pkt); +} + +static void +alsa_playback_stop(void) +{ + struct alsa_session *as; + + for (as = sessions; as; as = as->next) + { + snd_pcm_drop(hdl); + prebuf_free(as); + + as->state = ALSA_STATE_STARTED; + alsa_status(as); + } +} + +static void +alsa_write(uint8_t *buf, uint64_t rtptime) +{ + struct alsa_session *as; + uint64_t pos; + + for (as = sessions; as; as = as->next) + { + if (as->state == ALSA_STATE_STARTED) + { + playback_pos_get(&pos, rtptime); + + DPRINTF(E_DBG, L_LAUDIO, "Starting ALSA device '%s' (pos %" PRIu64 ", rtptime %" PRIu64 ")\n", as->devname, pos, rtptime); + + playback_start(as, pos, rtptime); + } + + playback_write(as, buf, rtptime); + } +} + +static int +alsa_flush(output_status_cb cb, uint64_t rtptime) +{ + struct alsa_session *as; + int i; + + i = 0; + for (as = sessions; as; as = as->next) + { + i++; + + snd_pcm_drop(hdl); + prebuf_free(as); + + as->status_cb = cb; + as->state = ALSA_STATE_STARTED; + alsa_status(as); + } + + return i; +} + +static void +alsa_set_status_cb(struct output_session *session, output_status_cb cb) +{ + struct alsa_session *as = session->session; + + as->status_cb = cb; +} + +static int +alsa_init(void) +{ + struct output_device *device; + cfg_t *cfg_audio; + char *nickname; + char *type; + + cfg_audio = cfg_getsec(cfg, "audio"); + type = cfg_getstr(cfg_audio, "type"); + + if (type && (strcasecmp(type, "alsa") != 0)) + return -1; + + card_name = cfg_getstr(cfg_audio, "card"); + mixer_name = cfg_getstr(cfg_audio, "mixer"); + nickname = cfg_getstr(cfg_audio, "nickname"); + offset = cfg_getint(cfg_audio, "offset"); + if (abs(offset) > 44100) + { + DPRINTF(E_LOG, L_LAUDIO, "The ALSA offset (%d) set in the configuration is out of bounds\n", offset); + offset = 44100 * (offset/abs(offset)); + } + + device = calloc(1, sizeof(struct output_device)); + if (!device) + { + DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA device\n"); + return -1; + } + + device->id = 0; + device->name = nickname; + device->type = OUTPUT_TYPE_ALSA; + device->type_name = outputs_name(device->type); + device->advertised = 1; + device->has_video = 0; + + DPRINTF(E_INFO, L_LAUDIO, "Adding ALSA device '%s' using friendly name '%s'\n", card_name, nickname); + + player_device_add(device); + + snd_lib_error_set_handler(logger_alsa); + + hdl = NULL; + mixer_hdl = NULL; + vol_elem = NULL; + + return 0; +} + +static void +alsa_deinit(void) +{ + snd_lib_error_set_handler(NULL); +} + +struct output_definition output_alsa = +{ + .name = "ALSA", + .type = OUTPUT_TYPE_ALSA, + .priority = 3, + .disabled = 0, + .init = alsa_init, + .deinit = alsa_deinit, + .device_start = alsa_device_start, + .device_stop = alsa_device_stop, + .device_probe = alsa_device_probe, + .device_volume_set = alsa_device_volume_set, + .playback_start = alsa_playback_start, + .playback_stop = alsa_playback_stop, + .write = alsa_write, + .flush = alsa_flush, + .status_cb = alsa_set_status_cb, +}; diff --git a/src/outputs/dummy.c b/src/outputs/dummy.c new file mode 100644 index 00000000..058d0455 --- /dev/null +++ b/src/outputs/dummy.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2016 Espen Jürgensen + * + * 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 + */ + + +/* This file includes much of the boilerplate code required for making an + * audio output for forked-daapd. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "conffile.h" +#include "logger.h" +#include "player.h" +#include "outputs.h" + +struct dummy_session +{ + enum output_device_state state; + + 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; + +struct dummy_session *sessions; + +/* Forwards */ +static void +defer_cb(int fd, short what, void *arg); + +/* ---------------------------- SESSION HANDLING ---------------------------- */ + +static void +dummy_session_free(struct dummy_session *ds) +{ + event_free(ds->deferredev); + + free(ds->output_session); + free(ds); + + ds = NULL; +} + +static void +dummy_session_cleanup(struct dummy_session *ds) +{ + // Normally some here code to remove from linked list - here we just say: + sessions = NULL; + + dummy_session_free(ds); +} + +static struct dummy_session * +dummy_session_make(struct output_device *device, output_status_cb cb) +{ + struct output_session *os; + struct dummy_session *ds; + + os = calloc(1, sizeof(struct output_session)); + ds = calloc(1, sizeof(struct dummy_session)); + if (!os || !ds) + { + DPRINTF(E_LOG, L_LAUDIO, "Out of memory for dummy session\n"); + return NULL; + } + + ds->deferredev = evtimer_new(evbase_player, defer_cb, ds); + if (!ds->deferredev) + { + DPRINTF(E_LOG, L_LAUDIO, "Out of memory for dummy deferred event\n"); + free(os); + free(ds); + return NULL; + } + + os->session = ds; + os->type = device->type; + + ds->output_session = os; + ds->state = OUTPUT_STATE_CONNECTED; + ds->device = device; + ds->status_cb = cb; + + sessions = ds; + + return ds; +} + + +/* ---------------------------- 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 dummy_session *ds = arg; + + if (ds->defer_cb) + ds->defer_cb(ds->device, ds->output_session, ds->state); + + if (ds->state == OUTPUT_STATE_STOPPED) + dummy_session_cleanup(ds); +} + +static void +dummy_status(struct dummy_session *ds) +{ + ds->defer_cb = ds->status_cb; + event_active(ds->deferredev, 0, 0); + ds->status_cb = NULL; +} + + +/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */ + +static int +dummy_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime) +{ + struct dummy_session *ds; + + ds = dummy_session_make(device, cb); + if (!ds) + return -1; + + dummy_status(ds); + + return 0; +} + +static void +dummy_device_stop(struct output_session *session) +{ + struct dummy_session *ds = session->session; + + ds->state = OUTPUT_STATE_STOPPED; + dummy_status(ds); +} + +static int +dummy_device_probe(struct output_device *device, output_status_cb cb) +{ + struct dummy_session *ds; + + ds = dummy_session_make(device, cb); + if (!ds) + return -1; + + ds->status_cb = cb; + ds->state = OUTPUT_STATE_STOPPED; + + dummy_status(ds); + + return 0; +} + +static int +dummy_device_volume_set(struct output_device *device, output_status_cb cb) +{ + struct dummy_session *ds; + + if (!device->session || !device->session->session) + return 0; + + ds = device->session->session; + + ds->status_cb = cb; + dummy_status(ds); + + return 1; +} + +static void +dummy_playback_start(uint64_t next_pkt, struct timespec *ts) +{ + struct dummy_session *ds = sessions; + + if (!sessions) + return; + + ds->state = OUTPUT_STATE_STREAMING; + dummy_status(ds); +} + +static void +dummy_playback_stop(void) +{ + struct dummy_session *ds = sessions; + + if (!sessions) + return; + + ds->state = OUTPUT_STATE_CONNECTED; + dummy_status(ds); +} + +static void +dummy_set_status_cb(struct output_session *session, output_status_cb cb) +{ + struct dummy_session *ds = session->session; + + ds->status_cb = cb; +} + +static int +dummy_init(void) +{ + struct output_device *device; + cfg_t *cfg_audio; + char *nickname; + char *type; + + cfg_audio = cfg_getsec(cfg, "audio"); + type = cfg_getstr(cfg_audio, "type"); + if (!type || (strcasecmp(type, "dummy") != 0)) + return -1; + + nickname = cfg_getstr(cfg_audio, "nickname"); + + device = calloc(1, sizeof(struct output_device)); + if (!device) + { + DPRINTF(E_LOG, L_LAUDIO, "Out of memory for dummy device\n"); + return -1; + } + + device->id = 0; + device->name = nickname; + device->type = OUTPUT_TYPE_DUMMY; + device->type_name = outputs_name(device->type); + device->advertised = 1; + device->has_video = 0; + + DPRINTF(E_INFO, L_LAUDIO, "Adding dummy output device '%s'\n", nickname); + + player_device_add(device); + + return 0; +} + +static void +dummy_deinit(void) +{ + return; +} + +struct output_definition output_dummy = +{ + .name = "dummy", + .type = OUTPUT_TYPE_DUMMY, + .priority = 99, + .disabled = 0, + .init = dummy_init, + .deinit = dummy_deinit, + .device_start = dummy_device_start, + .device_stop = dummy_device_stop, + .device_probe = dummy_device_probe, + .device_volume_set = dummy_device_volume_set, + .playback_start = dummy_playback_start, + .playback_stop = dummy_playback_stop, + .status_cb = dummy_set_status_cb, +}; diff --git a/src/outputs/raop.c b/src/outputs/raop.c index 7744c443..a36f2771 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -1340,6 +1340,7 @@ raop_send_req_teardown(struct raop_session *rs, evrtsp_req_cb cb) return -1; } + rs->state = RAOP_STATE_CONNECTED; rs->reqs_in_flight++; evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL); diff --git a/src/outputs/streaming.c b/src/outputs/streaming.c new file mode 100644 index 00000000..00a963bc --- /dev/null +++ b/src/outputs/streaming.c @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 Espen Jürgensen + * + * 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 + */ + +#include +#include +#include +#include + +#include "outputs.h" +#include "httpd_streaming.h" + + +struct output_definition output_streaming = +{ + .name = "mp3 streaming", + .type = OUTPUT_TYPE_STREAMING, + .priority = 0, + .disabled = 0, + .write = streaming_write, +}; diff --git a/src/player.c b/src/player.c index f0ad0b35..35358d51 100644 --- a/src/player.c +++ b/src/player.c @@ -54,7 +54,6 @@ /* Audio outputs */ #include "outputs.h" -#include "laudio.h" /* Audio inputs */ #include "transcode.h" @@ -81,8 +80,6 @@ #define PLAYER_DEFAULT_VOLUME 50 // Used to keep the player from getting ahead of a rate limited source (see below) #define PLAYER_TICKS_MAX_OVERRUN 2 -// Skips ticks for about 2 secs (seems to bring us back in sync for about 20 min) -#define PLAYER_TICKS_SKIP 126 struct player_source { @@ -271,6 +268,8 @@ static struct timespec packet_time = { 0, AIRTUNES_V2_STREAM_PERIOD }; // Will be positive if we need to skip some source reads (see below) static int ticks_skip; +static int debug_counter; + /* Sync source */ static enum player_sync_source pb_sync_source; @@ -286,14 +285,7 @@ static int dev_autoselect; //TODO [player] Is this still necessary? static struct output_device *dev_list; /* Output status */ -static enum laudio_state laudio_status; -static int laudio_selected; -static int laudio_volume; -static int laudio_relvol; static int output_sessions; -static int streaming_selected; - -static player_streaming_cb streaming_write; /* Commands */ static struct player_command *cur_cmd; @@ -306,7 +298,10 @@ static struct player_source *cur_playing; static struct player_source *cur_streaming; static uint32_t cur_plid; static uint32_t cur_plversion; + static struct evbuffer *audio_buf; +static uint8_t rawbuf[STOB(AIRTUNES_V2_PACKET_SAMPLES)]; + /* Play queue */ static struct queue *queue; @@ -391,9 +386,6 @@ volume_master_update(int newvol) master_volume = newvol; - if (laudio_selected) - laudio_relvol = vol_to_rel(laudio_volume); - for (device = dev_list; device; device = device->next) { if (device->selected) @@ -409,9 +401,6 @@ volume_master_find(void) newmaster = -1; - if (laudio_selected) - newmaster = laudio_volume; - for (device = dev_list; device; device = device->next) { if (device->selected && (device->volume > newmaster)) @@ -423,22 +412,6 @@ volume_master_find(void) /* Device select/deselect hooks */ -static void -speaker_select_laudio(void) -{ - laudio_selected = 1; - - if (laudio_volume > master_volume) - { - if (player_state == PLAY_STOPPED || master_volume == -1) - volume_master_update(laudio_volume); - else - laudio_volume = master_volume; - } - - laudio_relvol = vol_to_rel(laudio_volume); -} - static void speaker_select_output(struct output_device *device) { @@ -455,15 +428,6 @@ speaker_select_output(struct output_device *device) device->relvol = vol_to_rel(device->volume); } -static void -speaker_deselect_laudio(void) -{ - laudio_selected = 0; - - if (laudio_volume == master_volume) - volume_master_find(); -} - static void speaker_deselect_output(struct output_device *device) { @@ -517,36 +481,6 @@ player_get_current_pos_clock(uint64_t *pos, struct timespec *ts, int commit) return 0; } -static int -player_get_current_pos_laudio(uint64_t *pos, struct timespec *ts, int commit) -{ - int ret; - - *pos = laudio_get_pos(); - - ret = clock_gettime_with_res(CLOCK_MONOTONIC, ts, &timer_res); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Couldn't get clock: %s\n", strerror(errno)); - - return -1; - } - - if (commit) - { - pb_pos = *pos; - - pb_pos_stamp.tv_sec = ts->tv_sec; - pb_pos_stamp.tv_nsec = ts->tv_nsec; - -#ifdef DEBUG_SYNC - DPRINTF(E_DBG, L_PLAYER, "Pos: %" PRIu64 " (laudio)\n", *pos); -#endif - } - - return 0; -} - int player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit) { @@ -555,8 +489,8 @@ player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit) case PLAYER_SYNC_CLOCK: return player_get_current_pos_clock(pos, ts, commit); - case PLAYER_SYNC_LAUDIO: - return player_get_current_pos_laudio(pos, ts, commit); + default: + DPRINTF(E_LOG, L_PLAYER, "Bug! player_get_current_pos called with unknown source\n"); } return -1; @@ -619,54 +553,6 @@ playerqueue_clear(struct player_command *cmd); static void player_metadata_send(struct player_metadata *pmd); -static void -player_laudio_status_cb(enum laudio_state status) -{ - struct timespec ts; - uint64_t pos; - - switch (status) - { - /* Switch sync to clock sync */ - case LAUDIO_STOPPING: - DPRINTF(E_DBG, L_PLAYER, "Local audio stopping\n"); - - laudio_status = status; - - /* Synchronize pb_pos and pb_pos_stamp before laudio stops entirely */ - player_get_current_pos_laudio(&pos, &ts, 1); - - pb_sync_source = PLAYER_SYNC_CLOCK; - break; - - /* Switch sync to laudio sync */ - case LAUDIO_RUNNING: - DPRINTF(E_DBG, L_PLAYER, "Local audio running\n"); - - laudio_status = status; - - pb_sync_source = PLAYER_SYNC_LAUDIO; - break; - - case LAUDIO_FAILED: - DPRINTF(E_DBG, L_PLAYER, "Local audio failed\n"); - - pb_sync_source = PLAYER_SYNC_CLOCK; - - laudio_close(); - - if (output_sessions == 0) - playback_abort(); - - speaker_deselect_laudio(); - break; - - default: - laudio_status = status; - break; - } -} - /* Callback from the worker thread (async operation as it may block) */ static void playcount_inc_cb(void *arg) @@ -1324,6 +1210,9 @@ source_play() ret = stream_play(cur_streaming); + ticks_skip = 0; + memset(rawbuf, 0, sizeof(rawbuf)); + return ret; } @@ -1580,11 +1469,9 @@ source_read(uint8_t *buf, int len, uint64_t rtptime) return nbytes; } - static void -playback_write(void) +playback_write(int read_skip) { - uint8_t rawbuf[STOB(AIRTUNES_V2_PACKET_SAMPLES)]; int ret; source_check(); @@ -1595,25 +1482,21 @@ playback_write(void) last_rtptime += AIRTUNES_V2_PACKET_SAMPLES; - memset(rawbuf, 0, sizeof(rawbuf)); - - ret = source_read(rawbuf, sizeof(rawbuf), last_rtptime); - if (ret < 0) + if (!read_skip) { - DPRINTF(E_DBG, L_PLAYER, "Error reading from source, aborting playback\n"); + ret = source_read(rawbuf, sizeof(rawbuf), last_rtptime); + if (ret < 0) + { + DPRINTF(E_DBG, L_PLAYER, "Error reading from source, aborting playback\n"); - playback_abort(); - return; + playback_abort(); + return; + } } + else + DPRINTF(E_SPAM, L_PLAYER, "Skipping read\n"); - if (streaming_selected) - streaming_write(rawbuf, sizeof(rawbuf)); - - if (laudio_status & LAUDIO_F_STARTED) - laudio_write(rawbuf, last_rtptime); - - if (output_sessions > 0) - outputs_write(rawbuf, last_rtptime); + outputs_write(rawbuf, last_rtptime); } static void @@ -1622,6 +1505,8 @@ player_playback_cb(int fd, short what, void *arg) struct timespec next_tick; uint64_t overrun; int ret; + int skip; + int skip_first; // Check if we missed any timer expirations overrun = 0; @@ -1639,6 +1524,14 @@ player_playback_cb(int fd, short what, void *arg) overrun = ret; #endif /* __linux__ */ +/*debug_counter++; +if (debug_counter % 2000 == 0) +{ + DPRINTF(E_LOG, L_PLAYER, "Sleep a bit!!\n"); + usleep(5 * AIRTUNES_V2_STREAM_PERIOD / 1000); + DPRINTF(E_LOG, L_PLAYER, "Wake again\n"); +}*/ + // The reason we get behind the playback timer may be that we are playing a // network stream OR that the source is slow to open OR some interruption. // For streams, we might be consuming faster than the stream delivers, so @@ -1647,24 +1540,28 @@ player_playback_cb(int fd, short what, void *arg) // from the stream server. // // Our strategy to catch up with the timer depends on the source: - // - streams: We will skip reading data every second tick until we have - // skipt PLAYER_TICKS_SKIP ticks. That should make the source - // catch up. RTP destinations should be able to handle this - // gracefully if we just give them an rtptime that lets them know - // that some packets were "lost". + // - streams: We will skip reading data every second until we have countered + // the overrun by skipping reads for a number of ticks that is + // 3 times the overrun. That should make the source catch up. To + // keep the output happy we resend the previous rawbuf when we + // have skipped a read. // - files: Just read and write like crazy until we have caught up. + skip_first = 0; if (overrun > PLAYER_TICKS_MAX_OVERRUN) { DPRINTF(E_WARN, L_PLAYER, "Behind the playback timer with %" PRIu64 " ticks, initiating catch up\n", overrun); if (cur_streaming->data_kind == DATA_KIND_HTTP || cur_streaming->data_kind == DATA_KIND_PIPE) - ticks_skip = 2 * PLAYER_TICKS_SKIP + 1; + { + ticks_skip = 3 * overrun; + // We always skip after a timer overrun, since another read will + // probably just give another time overrun + skip_first = 1; + } else ticks_skip = 0; } - else if (ticks_skip > 0) - ticks_skip--; // Decide how many packets to send next_tick = timespec_add(pb_timer_last, tick_interval); @@ -1673,11 +1570,13 @@ player_playback_cb(int fd, short what, void *arg) do { - // Skip reading and writing every second tick if we are behind a nonfile source - if (ticks_skip % 2 == 0) - playback_write(); - else - last_rtptime += AIRTUNES_V2_PACKET_SAMPLES; + skip = skip_first || ((ticks_skip > 0) && ((last_rtptime / AIRTUNES_V2_PACKET_SAMPLES) % 126 == 0)); + + playback_write(skip); + + skip_first = 0; + if (skip) + ticks_skip--; packet_timer_last = timespec_add(packet_timer_last, packet_time); } @@ -1691,6 +1590,40 @@ player_playback_cb(int fd, short what, void *arg) } /* Helpers */ +static void +device_list_sort(void) +{ + struct output_device *device; + struct output_device *next; + struct output_device *prev; + int swaps; + + // Swap sorting since even the most inefficient sorting should do fine here + do + { + swaps = 0; + prev = NULL; + for (device = dev_list; device && device->next; device = device->next) + { + next = device->next; + if ( (outputs_priority(device) > outputs_priority(next)) || + (outputs_priority(device) == outputs_priority(next) && strcasecmp(device->name, next->name) > 0) ) + { + if (device == dev_list) + dev_list = next; + if (prev) + prev->next = next; + + device->next = next->next; + next->next = device; + swaps++; + } + prev = device; + } + } + while (swaps > 0); +} + static void device_remove(struct output_device *remove) { @@ -1817,6 +1750,8 @@ device_add(struct player_command *cmd) outputs_device_free(add); } + device_list_sort(); + return 0; } @@ -1894,6 +1829,8 @@ device_streaming_cb(struct output_device *device, struct output_session *session { int ret; + DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_streaming_cb\n", outputs_name(device->type)); + ret = device_check(device); if (ret < 0) { @@ -1938,6 +1875,8 @@ device_streaming_cb(struct output_device *device, struct output_session *session static void device_command_cb(struct output_device *device, struct output_session *session, enum output_device_state status) { + DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_command_cb\n", outputs_name(device->type)); + cur_cmd->output_requests_pending--; outputs_status_cb(session, device_streaming_cb); @@ -1961,6 +1900,8 @@ device_shutdown_cb(struct output_device *device, struct output_session *session, { int ret; + DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_shutdown_cb\n", outputs_name(device->type)); + cur_cmd->output_requests_pending--; if (output_sessions) @@ -1995,6 +1936,8 @@ device_shutdown_cb(struct output_device *device, struct output_session *session, static void device_lost_cb(struct output_device *device, struct output_session *session, enum output_device_state status) { + DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_lost_cb\n", outputs_name(device->type)); + /* We lost that device during startup for some reason, not much we can do here */ if (status == OUTPUT_STATE_FAILED) DPRINTF(E_WARN, L_PLAYER, "Failed to stop lost device\n"); @@ -2008,6 +1951,8 @@ device_activate_cb(struct output_device *device, struct output_session *session, struct timespec ts; int ret; + DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_activate_cb\n", outputs_name(device->type)); + cur_cmd->output_requests_pending--; ret = device_check(device); @@ -2079,6 +2024,8 @@ device_probe_cb(struct output_device *device, struct output_session *session, en { int ret; + DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_probe_cb\n", outputs_name(device->type)); + cur_cmd->output_requests_pending--; ret = device_check(device); @@ -2126,6 +2073,8 @@ device_restart_cb(struct output_device *device, struct output_session *session, { int ret; + DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_restart_cb\n", outputs_name(device->type)); + cur_cmd->output_requests_pending--; ret = device_check(device); @@ -2167,11 +2116,7 @@ device_restart_cb(struct output_device *device, struct output_session *session, static void playback_abort(void) { - if (laudio_status != LAUDIO_CLOSED) - laudio_close(); - - if (output_sessions > 0) - outputs_playback_stop(); + outputs_playback_stop(); pb_timer_stop(); @@ -2339,9 +2284,6 @@ playback_stop(struct player_command *cmd) { struct player_source *ps_playing; - if (laudio_status != LAUDIO_CLOSED) - laudio_close(); - /* We may be restarting very soon, so we don't bring the devices to a * full stop just yet; this saves time when restarting, which is nicer * for the user. @@ -2364,7 +2306,7 @@ playback_stop(struct player_command *cmd) metadata_purge(); - /* We're async if we need to flush RAOP devices */ + /* We're async if we need to flush devices */ if (cmd->output_requests_pending > 0) return 1; /* async */ @@ -2377,27 +2319,13 @@ playback_start_bh(struct player_command *cmd) { int ret; - if ((laudio_status == LAUDIO_CLOSED) && (output_sessions == 0)) + if (output_sessions == 0) { DPRINTF(E_LOG, L_PLAYER, "Cannot start playback: no output started\n"); goto out_fail; } - /* Start laudio first as it can fail, but can be stopped easily if needed */ - if (laudio_status == LAUDIO_OPEN) - { - laudio_set_volume(laudio_volume); - - ret = laudio_start(pb_pos, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Local audio failed to start\n"); - - goto out_fail; - } - } - ret = clock_gettime_with_res(CLOCK_MONOTONIC, &pb_pos_stamp, &timer_res); if (ret < 0) { @@ -2423,8 +2351,7 @@ playback_start_bh(struct player_command *cmd) goto out_fail; /* Everything OK, start outputs */ - if (output_sessions > 0) - outputs_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &pb_pos_stamp); + outputs_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &pb_pos_stamp); status_update(PLAY_PLAYING); @@ -2505,16 +2432,7 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii) metadata_trigger(1); - - /* Start local audio if needed */ - if (laudio_selected && (laudio_status == LAUDIO_CLOSED)) - { - ret = laudio_open(); - if (ret < 0) - DPRINTF(E_LOG, L_PLAYER, "Could not open local audio, will try AirPlay\n"); - } - - /* Start RAOP sessions on selected devices if needed */ + /* Start sessions on selected devices */ cmd->output_requests_pending = 0; for (device = dev_list; device; device = device->next) @@ -2533,29 +2451,29 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii) } } - /* Try to autoselect a non-selected RAOP device if the above failed */ - if ((laudio_status == LAUDIO_CLOSED) && (cmd->output_requests_pending == 0) && (output_sessions == 0)) + /* Try to autoselect a non-selected device if the above failed */ + if ((cmd->output_requests_pending == 0) && (output_sessions == 0)) for (device = dev_list; device; device = device->next) { - if (!device->session) - { - speaker_select_output(device); - ret = outputs_device_start(device, device_restart_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); - if (ret < 0) - { - DPRINTF(E_DBG, L_PLAYER, "Could not autoselect %s device '%s'\n", device->type_name, device->name); - speaker_deselect_output(device); - continue; - } + if ((outputs_priority(device) == 0) || device->session) + continue; - DPRINTF(E_INFO, L_PLAYER, "Autoselecting %s device '%s'\n", device->type_name, device->name); - cmd->output_requests_pending++; - break; + speaker_select_output(device); + ret = outputs_device_start(device, device_restart_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); + if (ret < 0) + { + DPRINTF(E_DBG, L_PLAYER, "Could not autoselect %s device '%s'\n", device->type_name, device->name); + speaker_deselect_output(device); + continue; } + + DPRINTF(E_INFO, L_PLAYER, "Autoselecting %s device '%s'\n", device->type_name, device->name); + cmd->output_requests_pending++; + break; } /* No luck finding valid output */ - if ((laudio_status == LAUDIO_CLOSED) && (cmd->output_requests_pending == 0) && (output_sessions == 0)) + if ((cmd->output_requests_pending == 0) && (output_sessions == 0)) { DPRINTF(E_LOG, L_PLAYER, "Could not start playback: no output selected or couldn't start any output\n"); @@ -2563,7 +2481,7 @@ playback_start_item(struct player_command *cmd, struct queue_item *qii) return -1; } - /* We're async if we need to start RAOP devices */ + /* We're async if we need to start devices */ if (cmd->output_requests_pending > 0) return 1; /* async */ @@ -2810,9 +2728,6 @@ playback_pause(struct player_command *cmd) cmd->output_requests_pending = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); - if (laudio_status != LAUDIO_CLOSED) - laudio_stop(); - pb_timer_stop(); source_pause(pos); @@ -2821,7 +2736,7 @@ playback_pause(struct player_command *cmd) metadata_purge(); - /* We're async if we need to flush RAOP devices */ + /* We're async if we need to flush devices */ if (cmd->output_requests_pending > 0) return 1; /* async */ @@ -2835,25 +2750,11 @@ speaker_enumerate(struct player_command *cmd) struct output_device *device; struct spk_enum *spk_enum; struct spk_flags flags; - char *laudio_name; spk_enum = cmd->arg.spk_enum; - laudio_name = cfg_getstr(cfg_getsec(cfg, "audio"), "nickname"); - - /* Auto-select local audio if there are no other output devices */ - if (!dev_list && !laudio_selected) - speaker_select_laudio(); - - flags.selected = laudio_selected; - flags.has_password = 0; - flags.has_video = 0; - - spk_enum->cb(0, laudio_name, laudio_relvol, flags, spk_enum->arg); - #ifdef DEBUG_RELVOL DPRINTF(E_DBG, L_PLAYER, "*** master: %d\n", master_volume); - DPRINTF(E_DBG, L_PLAYER, "*** laudio: abs %d rel %d\n", laudio_volume, laudio_relvol); #endif for (device = dev_list; device; device = device->next) @@ -2878,113 +2779,49 @@ speaker_enumerate(struct player_command *cmd) static int speaker_activate(struct output_device *device) { - struct timespec ts; - uint64_t pos; int ret; if (!device) { - /* Local */ - DPRINTF(E_DBG, L_PLAYER, "Activating local audio\n"); + DPRINTF(E_LOG, L_PLAYER, "Bug! speaker_activate called with device\n"); + return -1; + } - if (laudio_status == LAUDIO_CLOSED) + if (player_state == PLAY_PLAYING) + { + DPRINTF(E_DBG, L_PLAYER, "Activating %s device '%s'\n", device->type_name, device->name); + + ret = outputs_device_start(device, device_activate_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); + if (ret < 0) { - ret = laudio_open(); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not open local audio\n"); - - return -1; - } + DPRINTF(E_LOG, L_PLAYER, "Could not start %s device '%s'\n", device->type_name, device->name); + return -1; } - - if (player_state == PLAY_PLAYING) - { - laudio_set_volume(laudio_volume); - - ret = player_get_current_pos(&pos, &ts, 0); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not get current stream position for local audio start\n"); - - laudio_close(); - return -1; - } - - ret = laudio_start(pos, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Local playback failed to start\n"); - - laudio_close(); - return -1; - } - } - - return 0; } else { - if (player_state == PLAY_PLAYING) + DPRINTF(E_DBG, L_PLAYER, "Probing %s device '%s'\n", device->type_name, device->name); + + ret = outputs_device_probe(device, device_probe_cb); + if (ret < 0) { - DPRINTF(E_DBG, L_PLAYER, "Activating %s device '%s'\n", device->type_name, device->name); - - ret = outputs_device_start(device, device_activate_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not start %s device '%s'\n", device->type_name, device->name); - - return -1; - } + DPRINTF(E_LOG, L_PLAYER, "Could not probe %s device '%s'\n", device->type_name, device->name); + return -1; } - else - { - DPRINTF(E_DBG, L_PLAYER, "Probing %s device '%s'\n", device->type_name, device->name); - - ret = outputs_device_probe(device, device_probe_cb); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not probe %s device '%s'\n", device->type_name, device->name); - - return -1; - } - } - - return 1; } - return -1; + return 0; } static int speaker_deactivate(struct output_device *device) { - if (!device) - { - /* Local */ - DPRINTF(E_DBG, L_PLAYER, "Deactivating local audio\n"); + DPRINTF(E_DBG, L_PLAYER, "Deactivating %s device '%s'\n", device->type_name, device->name); - if (laudio_status == LAUDIO_CLOSED) - return 0; + outputs_status_cb(device->session, device_shutdown_cb); + outputs_device_stop(device->session); - if (laudio_status & LAUDIO_F_STARTED) - laudio_stop(); - - laudio_close(); - - return 0; - } - else - { - DPRINTF(E_DBG, L_PLAYER, "Deactivating %s device '%s'\n", device->type_name, device->name); - - outputs_status_cb(device->session, device_shutdown_cb); - outputs_device_stop(device->session); - - return 1; - } - - return -1; + return 0; } static int @@ -3045,9 +2882,8 @@ speaker_set(struct player_command *cmd) if (cmd->ret != -2) cmd->ret = -1; } - - /* ret = 1 if RAOP needs to take action */ - cmd->output_requests_pending += ret; + else + cmd->output_requests_pending++; } } else @@ -3067,57 +2903,8 @@ speaker_set(struct player_command *cmd) if (cmd->ret != -2) cmd->ret = -1; } - - /* ret = 1 if RAOP needs to take action */ - cmd->output_requests_pending += ret; - } - } - } - - /* Local audio */ - for (i = 1; i <= nspk; i++) - { - if (ids[i] == 0) - break; - } - - if (i <= nspk) - { - DPRINTF(E_DBG, L_PLAYER, "Local audio selected\n"); - - if (!laudio_selected) - speaker_select_laudio(); - - if (!(laudio_status & LAUDIO_F_STARTED)) - { - ret = speaker_activate(NULL); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not activate local audio output\n"); - - speaker_deselect_laudio(); - - if (cmd->ret != -2) - cmd->ret = -1; - } - } - } - else - { - DPRINTF(E_DBG, L_PLAYER, "Local audio NOT selected\n"); - - if (laudio_selected) - speaker_deselect_laudio(); - - if (laudio_status != LAUDIO_CLOSED) - { - ret = speaker_deactivate(NULL); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Could not deactivate local audio output\n"); - - if (cmd->ret != -2) - cmd->ret = -1; + else + cmd->output_requests_pending++; } } } @@ -3143,16 +2930,6 @@ volume_set(struct player_command *cmd) master_volume = volume; - if (laudio_selected) - { - laudio_volume = rel_to_vol(laudio_relvol); - laudio_set_volume(laudio_volume); - -#ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** laudio: abs %d rel %d\n", laudio_volume, laudio_relvol); -#endif - } - cmd->output_requests_pending = 0; for (device = dev_list; device; device = device->next) @@ -3188,38 +2965,25 @@ volume_setrel_speaker(struct player_command *cmd) id = cmd->arg.vol_param.spk_id; relvol = cmd->arg.vol_param.volume; - if (id == 0) + for (device = dev_list; device; device = device->next) { - laudio_relvol = relvol; - laudio_volume = rel_to_vol(relvol); - laudio_set_volume(laudio_volume); + if (device->id != id) + continue; + + if (!device->selected) + return 0; + + device->relvol = relvol; + device->volume = rel_to_vol(relvol); #ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** laudio: abs %d rel %d\n", laudio_volume, laudio_relvol); -#endif - } - else - { - for (device = dev_list; device; device = device->next) - { - if (device->id != id) - continue; - - if (!device->selected) - return 0; - - device->relvol = relvol; - device->volume = rel_to_vol(relvol); - -#ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); + DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); #endif - if (device->session) - cmd->output_requests_pending = outputs_device_volume_set(device, device_command_cb); + if (device->session) + cmd->output_requests_pending = outputs_device_volume_set(device, device_command_cb); - break; - } + break; } listener_notify(LISTENER_VOLUME); @@ -3242,19 +3006,6 @@ volume_setabs_speaker(struct player_command *cmd) master_volume = volume; - if (id == 0) - { - laudio_relvol = 100; - laudio_volume = volume; - laudio_set_volume(laudio_volume); - } - else - laudio_relvol = vol_to_rel(laudio_volume); - -#ifdef DEBUG_RELVOL - DPRINTF(E_DBG, L_PLAYER, "*** laudio: abs %d rel %d\n", laudio_volume, laudio_relvol); -#endif - for (device = dev_list; device; device = device->next) { if (!device->selected) @@ -4014,19 +3765,6 @@ player_playback_prev(void) return ret; } -void -player_streaming_start(player_streaming_cb cb) -{ - streaming_write = cb; - streaming_selected = 1; -} - -void -player_streaming_stop(void) -{ - streaming_selected = 0; -} - void player_speaker_enumerate(spk_enum_cb cb, void *arg) @@ -4566,10 +4304,6 @@ player(void *arg) /* Save selected devices */ db_speaker_clear_all(); - ret = db_speaker_save(0, laudio_selected, laudio_volume, "Local audio"); - if (ret < 0) - DPRINTF(E_LOG, L_PLAYER, "Could not save state for local audio\n"); - for (device = dev_list; device; device = device->next) { ret = db_speaker_save(device->id, device->selected, device->volume, device->name); @@ -4608,8 +4342,6 @@ player_init(void) master_volume = -1; - laudio_selected = 0; - laudio_status = LAUDIO_CLOSED; output_sessions = 0; cur_cmd = NULL; @@ -4668,12 +4400,6 @@ player_init(void) gcry_randomize(&rnd, sizeof(rnd), GCRY_STRONG_RANDOM); last_rtptime = ((uint64_t)1 << 32) | rnd; - ret = db_speaker_get(0, &laudio_selected, &laudio_volume); - if (ret < 0) - laudio_volume = PLAYER_DEFAULT_VOLUME; - else if (laudio_selected) - speaker_select_laudio(); // Run the select helper - audio_buf = evbuffer_new(); if (!audio_buf) { @@ -4743,13 +4469,6 @@ player_init(void) event_add(cmdev, NULL); event_add(pb_timer_ev, NULL); - ret = laudio_init(player_laudio_status_cb); - if (ret < 0) - { - DPRINTF(E_LOG, L_PLAYER, "Local audio init failed\n"); - goto laudio_fail; - } - ret = outputs_init(); if (ret < 0) { @@ -4775,8 +4494,6 @@ player_init(void) thread_fail: outputs_deinit(); outputs_fail: - laudio_deinit(); - laudio_fail: evnew_fail: event_base_free(evbase_player); evbase_fail: @@ -4832,7 +4549,6 @@ player_deinit(void) evbuffer_free(audio_buf); - laudio_deinit(); outputs_deinit(); close(exit_pipe[0]); diff --git a/src/player.h b/src/player.h index eef8d5b9..c69a69fa 100644 --- a/src/player.h +++ b/src/player.h @@ -70,7 +70,6 @@ struct player_status { }; typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg); -typedef int (*player_streaming_cb)(uint8_t *rawbuf, size_t size); struct player_history { @@ -131,12 +130,6 @@ player_playback_next(void); int player_playback_prev(void); -void -player_streaming_start(player_streaming_cb cb); - -void -player_streaming_stop(void); - int player_volume_set(int vol);