Merge branch 'outputs2'

This commit is contained in:
ejurgensen 2016-04-14 09:50:33 +02:00
commit 0bdcb3c11f
19 changed files with 1648 additions and 1355 deletions

View File

@ -136,8 +136,8 @@ Libraries:
from <http://zlib.net/>
- libunistring 0.9.3+
from <http://www.gnu.org/software/libunistring/#downloading>
- 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 <http://github.com/JonathanBeck/libplist/downloads>
- libspotify (optional - Spotify support)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()
};

View File

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

View File

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

View File

@ -1,155 +0,0 @@
/*
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.com>
*
* 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 <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#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();
}

View File

@ -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__ */

View File

@ -1,158 +0,0 @@
/*
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.com>
*
* 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 <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#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,
};

View File

@ -1,427 +0,0 @@
/*
* Copyright (C) 2010 Julien BLACHE <jb@jblache.org>
*
* 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 <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#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,
};

View File

@ -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();
}
}

View File

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

1051
src/outputs/alsa.c Normal file

File diff suppressed because it is too large Load Diff

296
src/outputs/dummy.c Normal file
View File

@ -0,0 +1,296 @@
/*
* Copyright (C) 2016 Espen Jürgensen <espenjurgensen@gmail.com>
*
* 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 <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#include <event2/event.h>
#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,
};

View File

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

35
src/outputs/streaming.c Normal file
View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2016 Espen Jürgensen <espenjurgensen@gmail.com>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#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,
};

View File

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

View File

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