mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-27 15:45:56 -05:00
Merge branch 'outputs2'
This commit is contained in:
commit
0bdcb3c11f
4
INSTALL
4
INSTALL
@ -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)
|
||||
|
16
configure.ac
16
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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
};
|
||||
|
||||
|
@ -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]);
|
||||
|
@ -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);
|
||||
|
||||
|
155
src/laudio.c
155
src/laudio.c
@ -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();
|
||||
}
|
80
src/laudio.h
80
src/laudio.h
@ -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__ */
|
@ -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,
|
||||
};
|
@ -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,
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
1051
src/outputs/alsa.c
Normal file
File diff suppressed because it is too large
Load Diff
296
src/outputs/dummy.c
Normal file
296
src/outputs/dummy.c
Normal 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,
|
||||
};
|
@ -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
35
src/outputs/streaming.c
Normal 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,
|
||||
};
|
610
src/player.c
610
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]);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user