Merge branch 'speaker_selection1'

This commit is contained in:
ejurgensen 2020-05-04 17:49:07 +02:00
commit 945aefaaa5
12 changed files with 497 additions and 578 deletions

View File

@ -47,7 +47,7 @@ general {
# When starting playback, autoselect speaker (if none of the previously
# selected speakers/outputs are available)
# speaker_autoselect = yes
# speaker_autoselect = no
# Most modern systems have a high-resolution clock, but if you are on an
# unusual platform and experience audio drop-outs, you can try changing

View File

@ -54,7 +54,7 @@ static cfg_opt_t sec_general[] =
CFG_BOOL("ipv6", cfg_true, CFGF_NONE),
CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE),
CFG_INT("cache_daap_threshold", 1000, CFGF_NONE),
CFG_BOOL("speaker_autoselect", cfg_true, CFGF_NONE),
CFG_BOOL("speaker_autoselect", cfg_false, CFGF_NONE),
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
CFG_BOOL("high_resolution_clock", cfg_false, CFGF_NONE),
#else

View File

@ -75,6 +75,9 @@ static struct output_definition *outputs[] = {
NULL
};
// Default volume (must be from 0 - 100)
#define OUTPUTS_DEFAULT_VOLUME 50
// When we stop, we keep the outputs open for a while, just in case we are
// actually just restarting. This timeout determines how long we wait before
// full stop.
@ -210,11 +213,13 @@ deferred_cb(int fd, short what, void *arg)
// The device has left the building (stopped/failed), and the backend
// is not using it any more
if (!device->advertised && !device->session)
if (device && !device->advertised && !device->session)
{
outputs_device_remove(device);
device = NULL;
}
else if (device)
device->state = state;
DPRINTF(E_DBG, L_PLAYER, "Making deferred callback to %s, id was %d\n", player_pmap(cb), callback_id);
@ -239,9 +244,12 @@ stop_timer_cb(int fd, short what, void *arg)
}
static void
device_stop_cb(struct output_device *device, enum output_device_state status)
device_stop_cb(struct output_device *device, enum output_device_state state)
{
if (status == OUTPUT_STATE_FAILED)
if (device)
device->state = state;
if (state == OUTPUT_STATE_FAILED)
DPRINTF(E_WARN, L_PLAYER, "Failed to stop device\n");
else
DPRINTF(E_INFO, L_PLAYER, "Device stopped properly\n");
@ -406,6 +414,16 @@ device_list_sort(void)
while (swaps > 0);
}
// Convenience function
static inline int
device_state_update(struct output_device *device, int ret)
{
if (ret < 0)
device->state = OUTPUT_STATE_FAILED;
return ret;
}
static void
metadata_cb_send(int fd, short what, void *arg)
{
@ -461,6 +479,70 @@ metadata_send(enum output_types type, uint32_t item_id, bool startup, output_met
}
/* ----------------------------- Volume helpers ----------------------------- */
static int
rel_to_vol(int relvol, int master_volume)
{
float vol;
if (relvol == 100)
return master_volume;
vol = ((float)relvol * (float)master_volume) / 100.0;
return (int)vol;
}
static int
vol_to_rel(int volume, int master_volume)
{
float rel;
if (volume == master_volume)
return 100;
rel = ((float)volume / (float)master_volume) * 100.0;
return (int)rel;
}
static void
vol_adjust(void)
{
struct output_device *device;
int selected_highest = -1;
int all_highest = -1;
for (device = output_device_list; device; device = device->next)
{
if (device->selected && (device->volume > selected_highest))
selected_highest = device->volume;
if (device->volume > all_highest)
all_highest = device->volume;
}
outputs_master_volume = (selected_highest >= 0) ? selected_highest : all_highest;
for (device = output_device_list; device; device = device->next)
{
if (!device->selected && (device->volume > outputs_master_volume))
device->volume = outputs_master_volume;
device->relvol = vol_to_rel(device->volume, outputs_master_volume);
}
#ifdef DEBUG_VOLUME
DPRINTF(E_DBG, L_PLAYER, "*** Master: %d\n", outputs_master_volume);
for (device = output_device_list; device; device = device->next)
{
DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d selected %d\n", device->name, device->volume, device->relvol, device->selected);
}
#endif
}
/* ----------------------------------- API ---------------------------------- */
struct output_device *
@ -474,7 +556,7 @@ outputs_device_get(uint64_t device_id)
return device;
}
DPRINTF(E_LOG, L_PLAYER, "Output device with id %" PRIu64 " has disappeared from our list\n", device_id);
DPRINTF(E_WARN, L_PLAYER, "Output device with id %" PRIu64 " has disappeared from our list\n", device_id);
return NULL;
}
@ -613,7 +695,7 @@ outputs_listener_notify(void)
/* ---------------------------- Called by player ---------------------------- */
struct output_device *
outputs_device_add(struct output_device *add, bool new_deselect, int default_volume)
outputs_device_add(struct output_device *add, bool new_deselect)
{
struct output_device *device;
char *keep_name;
@ -637,7 +719,7 @@ outputs_device_add(struct output_device *add, bool new_deselect, int default_vol
if (ret < 0)
{
device->selected = 0;
device->volume = default_volume;
device->volume = (outputs_master_volume >= 0) ? outputs_master_volume : OUTPUTS_DEFAULT_VOLUME;;
}
free(device->name);
@ -686,9 +768,11 @@ outputs_device_add(struct output_device *add, bool new_deselect, int default_vol
device_list_sort();
vol_adjust();
device->advertised = 1;
listener_notify(LISTENER_SPEAKER);
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
return device;
}
@ -732,37 +816,62 @@ outputs_device_remove(struct output_device *remove)
outputs_device_free(remove);
listener_notify(LISTENER_SPEAKER);
vol_adjust();
listener_notify(LISTENER_SPEAKER | LISTENER_VOLUME);
}
void
outputs_device_select(struct output_device *device)
{
device->selected = 1;
device->prevent_playback = 0;
device->busy = 0;
vol_adjust();
}
void
outputs_device_deselect(struct output_device *device)
{
device->selected = 0;
vol_adjust();
}
int
outputs_device_start(struct output_device *device, output_status_cb cb)
outputs_device_start(struct output_device *device, output_status_cb cb, bool only_probe)
{
if (outputs[device->type]->disabled || !outputs[device->type]->device_start)
int ret;
if (outputs[device->type]->disabled || !outputs[device->type]->device_start || !outputs[device->type]->device_probe)
return -1;
if (device->session)
{
DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_start() called for a device that already has a session\n");
return -1;
}
return 0; // Device is already running, nothing to do
return outputs[device->type]->device_start(device, callback_add(device, cb));
if (only_probe)
ret = outputs[device->type]->device_probe(device, callback_add(device, cb));
else
ret = outputs[device->type]->device_start(device, callback_add(device, cb));
return device_state_update(device, ret);;
}
int
outputs_device_stop(struct output_device *device, output_status_cb cb)
{
int ret;
if (outputs[device->type]->disabled || !outputs[device->type]->device_stop)
return -1;
if (!device->session)
{
DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_stop() called for a device that has no session\n");
return -1;
}
return 0; // Device is already stopped, nothing to do
return outputs[device->type]->device_stop(device, callback_add(device, cb));
ret = outputs[device->type]->device_stop(device, callback_add(device, cb));
return device_state_update(device, ret);
}
int
@ -772,52 +881,58 @@ outputs_device_stop_delayed(struct output_device *device, output_status_cb cb)
return -1;
if (!device->session)
{
DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_stop_delayed() called for a device that has no session\n");
return -1;
}
return 0; // Device is already stopped, nothing to do
outputs[device->type]->device_cb_set(device, callback_add(device, cb));
event_add(device->stop_timer, &outputs_stop_timeout);
return 0;
return 1;
}
int
outputs_device_flush(struct output_device *device, output_status_cb cb)
{
int ret;
if (outputs[device->type]->disabled || !outputs[device->type]->device_flush)
return -1;
if (!device->session)
return -1;
return 0; // Nothing to flush
return outputs[device->type]->device_flush(device, callback_add(device, cb));
ret = outputs[device->type]->device_flush(device, callback_add(device, cb));
return device_state_update(device, ret);
}
int
outputs_device_probe(struct output_device *device, output_status_cb cb)
void
outputs_device_volume_register(struct output_device *device, int absvol, int relvol)
{
if (outputs[device->type]->disabled || !outputs[device->type]->device_probe)
return -1;
if (absvol > -1)
device->volume = absvol;
else if (relvol > -1)
device->volume = rel_to_vol(relvol, outputs_master_volume);
if (device->session)
{
DPRINTF(E_LOG, L_PLAYER, "Bug! outputs_device_probe() called for a device that already has a session\n");
return -1;
}
vol_adjust();
return outputs[device->type]->device_probe(device, callback_add(device, cb));
listener_notify(LISTENER_VOLUME);
}
int
outputs_device_volume_set(struct output_device *device, output_status_cb cb)
{
int ret;
if (outputs[device->type]->disabled || !outputs[device->type]->device_volume_set)
return -1;
return outputs[device->type]->device_volume_set(device, callback_add(device, cb));
if (!device->session)
return 0; // Device isn't active
ret = outputs[device->type]->device_volume_set(device, callback_add(device, cb));
return device_state_update(device, ret);
}
int
@ -832,10 +947,14 @@ outputs_device_volume_to_pct(struct output_device *device, const char *volume)
int
outputs_device_quality_set(struct output_device *device, struct media_quality *quality, output_status_cb cb)
{
int ret;
if (outputs[device->type]->disabled || !outputs[device->type]->device_quality_set)
return -1;
return outputs[device->type]->device_quality_set(device, quality, callback_add(device, cb));
ret = outputs[device->type]->device_quality_set(device, quality, callback_add(device, cb));
return device_state_update(device, ret);
}
void
@ -876,30 +995,37 @@ outputs_device_free(struct output_device *device)
free(device);
}
// The return value will be the number of devices we need to wait for, either
// because they are starting or shutting down. The return value is only negative
// if we don't have to wait, i.e. all the selected devices failed immediately.
int
outputs_flush(output_status_cb cb)
outputs_start(output_status_cb started_cb, output_status_cb stopped_cb, bool only_probe)
{
struct output_device *device;
int count = 0;
int ret;
int pending = 0;
int ret = -1;
for (device = output_device_list; device; device = device->next)
{
ret = outputs_device_flush(device, cb);
if (device->selected)
ret = outputs_device_start(device, started_cb, only_probe);
else
ret = outputs_device_stop(device, stopped_cb);
if (ret < 0)
continue;
count++;
pending += ret;
}
return count;
return (pending > 0) ? pending : ret;
}
int
outputs_stop(output_status_cb cb)
{
struct output_device *device;
int count = 0;
int pending = 0;
int ret;
for (device = output_device_list; device; device = device->next)
@ -911,10 +1037,10 @@ outputs_stop(output_status_cb cb)
if (ret < 0)
continue;
count++;
pending += ret;
}
return count;
return pending;
}
int
@ -928,6 +1054,69 @@ outputs_stop_delayed_cancel(void)
return 0;
}
int
outputs_flush(output_status_cb cb)
{
struct output_device *device;
int pending = 0;
int ret;
for (device = output_device_list; device; device = device->next)
{
ret = outputs_device_flush(device, cb);
if (ret < 0)
continue;
pending += ret;
}
return pending;
}
int
outputs_volume_set(int volume, output_status_cb cb)
{
struct output_device *device;
int pending = 0;
int ret;
if (outputs_master_volume == volume)
return 0;
outputs_master_volume = volume;
for (device = output_device_list; device; device = device->next)
{
if (!device->selected)
continue;
device->volume = rel_to_vol(device->relvol, outputs_master_volume);
ret = outputs_device_volume_set(device, cb);
if (ret < 0)
continue;
pending += ret;
}
listener_notify(LISTENER_VOLUME);
return pending;
}
int
outputs_sessions_count(void)
{
struct output_device *device;
int count = 0;
for (device = output_device_list; device; device = device->next)
if (device->session)
count++;
return count;
}
void
outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *quality, struct timespec *pts)
{
@ -1004,6 +1193,8 @@ outputs_init(void)
int ret;
int i;
outputs_master_volume = -1;
CHECK_NULL(L_PLAYER, outputs_deferredev = evtimer_new(evbase_player, deferred_cb, NULL));
no_output = 1;

View File

@ -122,6 +122,10 @@ struct output_device
// Type of output (string)
const char *type_name;
// Last state that the backend returned to the handlers in outputs.c. This
// field must only be set in outputs.c (not in the backends/player).
enum output_device_state state;
// Misc device flags
unsigned selected:1;
unsigned advertised:1;
@ -218,6 +222,11 @@ struct output_definition
// Deinitialization function called at shutdown
void (*deinit)(void);
// For all the below that take callbacks, the return values are:
// - negative: error
// - zero: ok, won't make a callback
// - positive: number of callbacks that will be made
// Prepare a playback session on device and call back
int (*device_start)(struct output_device *device, int callback_id);
@ -293,17 +302,26 @@ outputs_listener_notify(void);
/* ---------------------------- Called by player ---------------------------- */
int
outputs_master_volume;
// Ownership of *add is transferred, so don't address after calling. Instead you
// can address the return value (which is not the same if the device was already
// in the list.
struct output_device *
outputs_device_add(struct output_device *add, bool new_deselect, int default_volume);
outputs_device_add(struct output_device *add, bool new_deselect);
void
outputs_device_remove(struct output_device *remove);
void
outputs_device_select(struct output_device *device);
void
outputs_device_deselect(struct output_device *device);
int
outputs_device_start(struct output_device *device, output_status_cb cb);
outputs_device_start(struct output_device *device, output_status_cb cb, bool only_probe);
int
outputs_device_stop(struct output_device *device, output_status_cb cb);
@ -314,8 +332,8 @@ outputs_device_stop_delayed(struct output_device *device, output_status_cb cb);
int
outputs_device_flush(struct output_device *device, output_status_cb cb);
int
outputs_device_probe(struct output_device *device, output_status_cb cb);
void
outputs_device_volume_register(struct output_device *device, int absvol, int relvol);
int
outputs_device_volume_set(struct output_device *device, output_status_cb cb);
@ -333,14 +351,23 @@ void
outputs_device_free(struct output_device *device);
int
outputs_flush(output_status_cb cb);
outputs_start(output_status_cb started_cb, output_status_cb stopped_cb, bool only_probe);
int
outputs_stop(output_status_cb cb);
int
outputs_flush(output_status_cb cb);
int
outputs_volume_set(int volume, output_status_cb cb);
int
outputs_stop_delayed_cancel(void);
int
outputs_sessions_count(void);
void
outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *quality, struct timespec *pts);

View File

@ -1143,7 +1143,7 @@ alsa_device_start(struct output_device *device, int callback_id)
as->state = OUTPUT_STATE_CONNECTED;
alsa_status(as);
return 0;
return 1;
}
static int
@ -1155,7 +1155,7 @@ alsa_device_stop(struct output_device *device, int callback_id)
as->state = OUTPUT_STATE_STOPPED;
alsa_status(as); // Will terminate the session since the state is STOPPED
return 0;
return 1;
}
static int
@ -1169,7 +1169,7 @@ alsa_device_flush(struct output_device *device, int callback_id)
as->state = OUTPUT_STATE_CONNECTED;
alsa_status(as);
return 0;
return 1;
}
static int
@ -1184,7 +1184,7 @@ alsa_device_probe(struct output_device *device, int callback_id)
as->state = OUTPUT_STATE_STOPPED;
alsa_status(as); // Will terminate the session since the state is STOPPED
return 0;
return 1;
}
static int

View File

@ -1932,7 +1932,7 @@ cast_device_start_generic(struct output_device *device, int callback_id, cast_re
cast_session_cleanup(cs);
}
else
return 0;
return 1;
}
cs = cast_session_make(device, AF_INET, callback_id);
@ -1950,7 +1950,7 @@ cast_device_start_generic(struct output_device *device, int callback_id, cast_re
return -1;
}
return 0;
return 1;
}
static int
@ -1974,7 +1974,7 @@ cast_device_stop(struct output_device *device, int callback_id)
cast_session_shutdown(cs, CAST_STATE_NONE);
return 0;
return 1;
}
static int
@ -1986,7 +1986,7 @@ cast_device_flush(struct output_device *device, int callback_id)
cs->state = CAST_STATE_MEDIA_CONNECTED;
cast_status(cs);
return 0;
return 1;
}
static void

View File

@ -115,7 +115,7 @@ dummy_device_start(struct output_device *device, int callback_id)
dummy_status(ds);
return 0;
return 1;
}
static int
@ -128,7 +128,7 @@ dummy_device_stop(struct output_device *device, int callback_id)
dummy_status(ds);
return 0;
return 1;
}
static int
@ -141,7 +141,7 @@ dummy_device_flush(struct output_device *device, int callback_id)
dummy_status(ds);
return 0;
return 1;
}
static int
@ -158,7 +158,7 @@ dummy_device_probe(struct output_device *device, int callback_id)
dummy_status(ds);
return 0;
return 1;
}
static int

View File

@ -302,7 +302,7 @@ fifo_device_start(struct output_device *device, int callback_id)
fifo_status(fifo_session);
return 0;
return 1;
}
static int
@ -320,7 +320,7 @@ fifo_device_stop(struct output_device *device, int callback_id)
fifo_session->state = OUTPUT_STATE_STOPPED;
fifo_status(fifo_session);
return 0;
return 1;
}
static int
@ -335,7 +335,7 @@ fifo_device_flush(struct output_device *device, int callback_id)
fifo_session->state = OUTPUT_STATE_CONNECTED;
fifo_status(fifo_session);
return 0;
return 1;
}
static int
@ -362,7 +362,7 @@ fifo_device_probe(struct output_device *device, int callback_id)
fifo_status(fifo_session);
return 0;
return 1;
}
static int

View File

@ -754,7 +754,7 @@ pulse_device_start(struct output_device *device, int callback_id)
pulse_status(ps);
return 0;
return 1;
}
static int
@ -768,7 +768,7 @@ pulse_device_stop(struct output_device *device, int callback_id)
stream_close(ps, close_cb);
return 0;
return 1;
}
static int
@ -801,7 +801,7 @@ pulse_device_flush(struct output_device *device, int callback_id)
pa_threaded_mainloop_unlock(pulse.mainloop);
return 0;
return 1;
}
static int
@ -823,7 +823,7 @@ pulse_device_probe(struct output_device *device, int callback_id)
return -1;
}
return 0;
return 1;
}
static void

View File

@ -100,6 +100,12 @@
#define RAOP_MD_WANTS_ARTWORK (1 << 1)
#define RAOP_MD_WANTS_PROGRESS (1 << 2)
// ATV4 and Homepod disconnect for reasons that are not clear, but sending them
// progress metadata at regular intervals reduces the problem. The below
// interval was determined via testing, see:
// https://github.com/ejurgensen/forked-daapd/issues/734#issuecomment-622959334
#define RAOP_KEEP_ALIVE_INTERVAL 25
// This is an arbitrary value which just needs to be kept in sync with the config
#define RAOP_CONFIG_MAX_VOLUME 11
@ -142,6 +148,8 @@ enum raop_state {
RAOP_STATE_CONNECTED = RAOP_STATE_F_CONNECTED | 0x01,
// Media data is being sent
RAOP_STATE_STREAMING = RAOP_STATE_F_CONNECTED | 0x02,
// Session teardown in progress (-> going to STOPPED state)
RAOP_STATE_TEARDOWN = RAOP_STATE_F_CONNECTED | 0x03,
// Session is failed, couldn't startup or error occurred
RAOP_STATE_FAILED = RAOP_STATE_F_FAILED | 0x01,
// Password issue: unknown password or bad password
@ -337,7 +345,7 @@ static struct output_metadata *raop_cur_metadata;
/* Keep-alive timer - hack for ATV's with tvOS 10 */
static struct event *keep_alive_timer;
static struct timeval keep_alive_tv = { 30, 0 };
static struct timeval keep_alive_tv = { RAOP_KEEP_ALIVE_INTERVAL, 0 };
/* Sessions */
static struct raop_master_session *raop_master_sessions;
@ -1224,7 +1232,6 @@ raop_send_req_teardown(struct raop_session *rs, evrtsp_req_cb cb, const char *lo
return -1;
}
rs->state = RAOP_STATE_CONNECTED;
rs->reqs_in_flight++;
evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL);
@ -1737,8 +1744,12 @@ raop_status(struct raop_session *rs)
case RAOP_STATE_STREAMING:
state = OUTPUT_STATE_STREAMING;
break;
case RAOP_STATE_TEARDOWN:
DPRINTF(E_LOG, L_RAOP, "Bug! raop_status() called with transitional state (TEARDOWN)\n");
state = OUTPUT_STATE_STOPPED;
break;
default:
DPRINTF(E_LOG, L_RAOP, "Bug! Unhandled state in raop_status()\n");
DPRINTF(E_LOG, L_RAOP, "Bug! Unhandled state in raop_status(): %d\n", rs->state);
state = OUTPUT_STATE_FAILED;
}
@ -1956,6 +1967,9 @@ session_teardown(struct raop_session *rs, const char *log_caller)
deferred_session_failure(rs);
}
// Change state immediately so we won't write any more to the device
rs->state = RAOP_STATE_TEARDOWN;
return ret;
}
@ -3514,6 +3528,8 @@ raop_startup_cancel(struct raop_session *rs)
return;
}
rs->state = RAOP_STATE_TEARDOWN;
ret = raop_send_req_teardown(rs, raop_cb_startup_cancel, "startup_cancel");
if (ret < 0)
session_failure(rs);
@ -3759,7 +3775,7 @@ raop_cb_startup_setup(struct evrtsp_request *req, void *arg)
token = strtok_r(token, ";=", &ptr);
while (token)
{
DPRINTF(E_DBG, L_RAOP, "token: %s\n", token);
DPRINTF(E_SPAM, L_RAOP, "token: %s\n", token);
if (strcmp(token, "server_port") == 0)
{
@ -4693,7 +4709,7 @@ raop_device_start_generic(struct output_device *device, int callback_id, bool on
ret = raop_send_req_options(rs, raop_cb_startup_options, "device_start");
if (ret == 0)
return 0;
return 1;
else
{
DPRINTF(E_WARN, L_RAOP, "Could not send verification or OPTIONS request on IPv6\n");
@ -4719,7 +4735,7 @@ raop_device_start_generic(struct output_device *device, int callback_id, bool on
return -1;
}
return 0;
return 1;
}
static int
@ -4741,7 +4757,9 @@ raop_device_stop(struct output_device *device, int callback_id)
rs->callback_id = callback_id;
return session_teardown(rs, "device_stop");
session_teardown(rs, "device_stop");
return 1;
}
static int
@ -4759,7 +4777,7 @@ raop_device_flush(struct output_device *device, int callback_id)
rs->callback_id = callback_id;
return 0;
return 1;
}
static void

File diff suppressed because it is too large Load Diff

View File

@ -111,6 +111,9 @@ player_speaker_prevent_playback_set(uint64_t id, bool prevent_playback);
int
player_speaker_busy_set(uint64_t id, bool busy);
void
player_speaker_resurrect(void *arg);
int
player_playback_start(void);