From b6e1269cf250da783f81c6ba0f0c1e31b6a8a3b6 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 27 May 2018 01:42:39 +0200 Subject: [PATCH] [raop/dacp/player] Support for Airplay speakers to register volume updates Implements Active-Remote, which is sent to the speaker, so it can use this to tell us who it is when it makes dacp request with a device-volume update. --- src/httpd_dacp.c | 63 ++++++++++++++++++++------------------------- src/outputs.c | 12 +++++++++ src/outputs.h | 6 +++++ src/outputs/raop.c | 41 +++++++++++++++++++++++++++++ src/player.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++ src/player.h | 3 +++ 6 files changed, 154 insertions(+), 35 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index 4bd3ebe3..55f794dc 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -59,7 +59,7 @@ struct dacp_update_request { }; typedef void (*dacp_propget)(struct evbuffer *evbuf, struct player_status *status, struct db_queue_item *queue_item); -typedef void (*dacp_propset)(const char *value, struct evkeyvalq *query); +typedef void (*dacp_propset)(const char *value, struct httpd_request *hreq); struct dacp_prop_map { char *desc; @@ -108,17 +108,17 @@ dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *sta static void -dacp_propset_volume(const char *value, struct evkeyvalq *query); +dacp_propset_volume(const char *value, struct httpd_request *hreq); static void -dacp_propset_devicevolume(const char *value, struct evkeyvalq *query); +dacp_propset_devicevolume(const char *value, struct httpd_request *hreq); static void -dacp_propset_playingtime(const char *value, struct evkeyvalq *query); +dacp_propset_playingtime(const char *value, struct httpd_request *hreq); static void -dacp_propset_shufflestate(const char *value, struct evkeyvalq *query); +dacp_propset_shufflestate(const char *value, struct httpd_request *hreq); static void -dacp_propset_repeatstate(const char *value, struct evkeyvalq *query); +dacp_propset_repeatstate(const char *value, struct httpd_request *hreq); static void -dacp_propset_userrating(const char *value, struct evkeyvalq *query); +dacp_propset_userrating(const char *value, struct httpd_request *hreq); /* gperf static hash, dacp_prop.gperf */ @@ -746,7 +746,7 @@ dacp_playstatus_update_handler(short event_mask) int ret; // Only send status update on player change events - if (!(event_mask & LISTENER_PLAYER)) + if (!(event_mask & (LISTENER_PLAYER | LISTENER_VOLUME))) return; #ifdef HAVE_EVENTFD @@ -903,7 +903,7 @@ dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *sta /* Properties setters */ static void -dacp_propset_volume(const char *value, struct evkeyvalq *query) +dacp_propset_volume(const char *value, struct httpd_request *hreq) { const char *param; uint64_t id; @@ -918,7 +918,7 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query) return; } - param = evhttp_find_header(query, "speaker-id"); + param = evhttp_find_header(hreq->query, "speaker-id"); if (param) { ret = safe_atou64(param, &id); @@ -933,7 +933,7 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query) return; } - param = evhttp_find_header(query, "include-speaker-id"); + param = evhttp_find_header(hreq->query, "include-speaker-id"); if (param) { ret = safe_atou64(param, &id); @@ -952,33 +952,26 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query) } static void -dacp_propset_devicevolume(const char *value, struct evkeyvalq *query) +dacp_propset_devicevolume(const char *value, struct httpd_request *hreq) { - float raop_volume; - int volume; + struct evkeyvalq *headers; + const char *remote; + uint32_t id; - raop_volume = atof(value); + headers = evhttp_request_get_input_headers(hreq->req); + remote = evhttp_find_header(headers, "Active-Remote"); - // Basic sanity check, don't want to set to max volume on invalid volume - if (raop_volume == 0.0 && value[0] != '0') + if (!headers || !remote || (safe_atou32(remote, &id) < 0)) { - DPRINTF(E_LOG, L_DACP, "dmcp.device-volume is invalid: %s\n", value); + DPRINTF(E_LOG, L_DACP, "Request for setting device-volume has invalid Active-Remote: '%s'\n", remote); return; } - // RAOP volume: -144.0 is off, -30.0 - 0 maps to 0 - 100 - if (raop_volume > -30.0 && raop_volume <= 0.0) - volume = (int)(100.0 * raop_volume / 30.0 + 100.0); - else - volume = 0; - - DPRINTF(E_DBG, L_DACP, "Propset volume to %s, new volume is %d\n", value, volume); - -// TODO: How to find the id so we can call something like player_volume_speaker_info(id, volume); + player_volume_byactiveremote(id, value); } static void -dacp_propset_playingtime(const char *value, struct evkeyvalq *query) +dacp_propset_playingtime(const char *value, struct httpd_request *hreq) { struct timeval tv; int ret; @@ -997,7 +990,7 @@ dacp_propset_playingtime(const char *value, struct evkeyvalq *query) } static void -dacp_propset_shufflestate(const char *value, struct evkeyvalq *query) +dacp_propset_shufflestate(const char *value, struct httpd_request *hreq) { int enable; int ret; @@ -1014,7 +1007,7 @@ dacp_propset_shufflestate(const char *value, struct evkeyvalq *query) } static void -dacp_propset_repeatstate(const char *value, struct evkeyvalq *query) +dacp_propset_repeatstate(const char *value, struct httpd_request *hreq) { int mode; int ret; @@ -1031,7 +1024,7 @@ dacp_propset_repeatstate(const char *value, struct evkeyvalq *query) } static void -dacp_propset_userrating(const char *value, struct evkeyvalq *query) +dacp_propset_userrating(const char *value, struct httpd_request *hreq) { const char *param; uint32_t itemid; @@ -1046,9 +1039,9 @@ dacp_propset_userrating(const char *value, struct evkeyvalq *query) return; } - param = evhttp_find_header(query, "item-spec"); // Remote + param = evhttp_find_header(hreq->query, "item-spec"); // Remote if (!param) - param = evhttp_find_header(query, "song-spec"); // Retune + param = evhttp_find_header(hreq->query, "song-spec"); // Retune if (!param) { @@ -2459,7 +2452,7 @@ dacp_reply_setproperty(struct httpd_request *hreq) } if (dpm->propset) - dpm->propset(param->value, hreq->query); + dpm->propset(param->value, hreq); else DPRINTF(E_WARN, L_DACP, "No setter method for DACP property %s\n", dpm->desc); } @@ -2785,7 +2778,7 @@ dacp_init(void) return -1; } - listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER); + listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER | LISTENER_VOLUME); return 0; diff --git a/src/outputs.c b/src/outputs.c index 8dd54498..e269c761 100644 --- a/src/outputs.c +++ b/src/outputs.c @@ -132,6 +132,18 @@ outputs_device_volume_set(struct output_device *device, output_status_cb cb) return -1; } +int +outputs_device_volume_to_pct(struct output_device *device, const char *volume) +{ + if (outputs[device->type]->disabled) + return -1; + + if (outputs[device->type]->device_volume_to_pct) + return outputs[device->type]->device_volume_to_pct(device, volume); + else + return -1; +} + void outputs_playback_start(uint64_t next_pkt, struct timespec *ts) { diff --git a/src/outputs.h b/src/outputs.h index 7e8b1e05..9c635a5a 100644 --- a/src/outputs.h +++ b/src/outputs.h @@ -179,6 +179,9 @@ struct output_definition // Set the volume and call back int (*device_volume_set)(struct output_device *device, output_status_cb cb); + // Convert device internal representation of volume to our pct scale + int (*device_volume_to_pct)(struct output_device *device, const char *volume); + // Start/stop playback on devices that were started void (*playback_start)(uint64_t next_pkt, struct timespec *ts); void (*playback_stop)(void); @@ -217,6 +220,9 @@ outputs_device_free(struct output_device *device); int outputs_device_volume_set(struct output_device *device, output_status_cb cb); +int +outputs_device_volume_to_pct(struct output_device *device, const char *value); + void outputs_playback_start(uint64_t next_pkt, struct timespec *ts); diff --git a/src/outputs/raop.c b/src/outputs/raop.c index a98e219a..d8f403f0 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -1197,6 +1197,11 @@ raop_add_headers(struct raop_session *rs, struct evrtsp_request *req, enum evrts evrtsp_add_header(req->output_headers, "Client-Instance", buf); evrtsp_add_header(req->output_headers, "DACP-ID", buf); + // We set Active-Remote as 32 bit unsigned decimal, as at least my device + // can't handle any larger. Must be aligned with volume_byactiveremote(). + snprintf(buf, sizeof(buf), "%" PRIu32, (uint32_t)rs->device->id); + evrtsp_add_header(req->output_headers, "Active-Remote", buf); + if (rs->session) evrtsp_add_header(req->output_headers, "Session", rs->session); @@ -2402,6 +2407,41 @@ raop_volume_from_pct(int volume, char *name) return raop_volume; } +static int +raop_volume_to_pct(struct output_device *rd, const char *volume) +{ + float raop_volume; + cfg_t *airplay; + int max_volume; + + raop_volume = atof(volume); + + // Basic sanity check + if (raop_volume == 0.0 && volume[0] != '0') + { + DPRINTF(E_LOG, L_RAOP, "RAOP device volume is invalid: '%s'\n", volume); + return -1; + } + + max_volume = RAOP_CONFIG_MAX_VOLUME; + + airplay = cfg_gettsec(cfg, "airplay", rd->name); + if (airplay) + max_volume = cfg_getint(airplay, "max_volume"); + + if ((max_volume < 1) || (max_volume > RAOP_CONFIG_MAX_VOLUME)) + { + DPRINTF(E_LOG, L_RAOP, "Config has bad max_volume (%d) for device '%s', using default instead\n", max_volume, rd->name); + max_volume = RAOP_CONFIG_MAX_VOLUME; + } + + // RAOP volume: -144.0 is off, -30.0 - 0 scaled by max_volume maps to 0 - 100 + if (raop_volume > -30.0 && raop_volume <= 0.0) + return (int)(100.0 * (raop_volume / 30.0 + 1.0) * RAOP_CONFIG_MAX_VOLUME / (float)max_volume); + else + return 0; +} + static int raop_set_volume_internal(struct raop_session *rs, int volume, evrtsp_req_cb cb) { @@ -4905,6 +4945,7 @@ struct output_definition output_raop = .device_probe = raop_device_probe, .device_free_extra = raop_device_free_extra, .device_volume_set = raop_set_volume_one, + .device_volume_to_pct = raop_volume_to_pct, .playback_start = raop_playback_start, .playback_stop = raop_playback_stop, .write = raop_v2_write, diff --git a/src/player.c b/src/player.c index 7acfab21..6d0cb253 100644 --- a/src/player.c +++ b/src/player.c @@ -129,6 +129,11 @@ struct volume_param { uint64_t spk_id; }; +struct activeremote_param { + uint32_t activeremote; + const char *value; +}; + struct spk_enum { spk_enum_cb cb; @@ -2720,6 +2725,52 @@ volume_setabs_speaker(void *arg, int *retval) return COMMAND_END; } +// Just updates internal volume params (does not make actual requests to the speaker) +static enum command_state +volume_byactiveremote(void *arg, int *retval) +{ + struct activeremote_param *ar_param = arg; + struct output_device *device; + uint32_t activeremote; + int volume; + + *retval = 0; + activeremote = ar_param->activeremote; + + for (device = dev_list; device; device = device->next) + { + if ((uint32_t)device->id == activeremote) + break; + } + + if (!device) + { + DPRINTF(E_LOG, L_DACP, "Could not find speaker with Active-Remote id %d\n", activeremote); + *retval = -1; + return COMMAND_END; + } + + volume = outputs_device_volume_to_pct(device, ar_param->value); // Only converts + if (volume < 0) + { + DPRINTF(E_LOG, L_DACP, "Could not parse volume given by Active-Remote id %d\n", activeremote); + *retval = -1; + return COMMAND_END; + } + + device->volume = volume; + + volume_master_find(); + +#ifdef DEBUG_RELVOL + DPRINTF(E_DBG, L_PLAYER, "*** %s: abs %d rel %d\n", device->name, device->volume, device->relvol); +#endif + + listener_notify(LISTENER_VOLUME); + + return COMMAND_END; +} + static enum command_state repeat_set(void *arg, int *retval) { @@ -3099,6 +3150,19 @@ player_volume_setabs_speaker(uint64_t id, int vol) return ret; } +int +player_volume_byactiveremote(uint32_t activeremote, const char *value) +{ + struct activeremote_param ar_param; + int ret; + + ar_param.activeremote = activeremote; + ar_param.value = value; + + ret = commands_exec_sync(cmdbase, volume_byactiveremote, NULL, &ar_param); + return ret; +} + int player_repeat_set(enum repeat_mode mode) { diff --git a/src/player.h b/src/player.h index ad58cefa..4771e41f 100644 --- a/src/player.h +++ b/src/player.h @@ -137,6 +137,9 @@ player_volume_setrel_speaker(uint64_t id, int relvol); int player_volume_setabs_speaker(uint64_t id, int vol); +int +player_volume_byactiveremote(uint32_t activeremote, const char *value); + int player_repeat_set(enum repeat_mode mode);