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);