[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.
This commit is contained in:
ejurgensen 2018-05-27 01:42:39 +02:00
parent 646bf37f17
commit b6e1269cf2
6 changed files with 154 additions and 35 deletions

View File

@ -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_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 { struct dacp_prop_map {
char *desc; char *desc;
@ -108,17 +108,17 @@ dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *sta
static void static void
dacp_propset_volume(const char *value, struct evkeyvalq *query); dacp_propset_volume(const char *value, struct httpd_request *hreq);
static void static void
dacp_propset_devicevolume(const char *value, struct evkeyvalq *query); dacp_propset_devicevolume(const char *value, struct httpd_request *hreq);
static void static void
dacp_propset_playingtime(const char *value, struct evkeyvalq *query); dacp_propset_playingtime(const char *value, struct httpd_request *hreq);
static void static void
dacp_propset_shufflestate(const char *value, struct evkeyvalq *query); dacp_propset_shufflestate(const char *value, struct httpd_request *hreq);
static void static void
dacp_propset_repeatstate(const char *value, struct evkeyvalq *query); dacp_propset_repeatstate(const char *value, struct httpd_request *hreq);
static void 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 */ /* gperf static hash, dacp_prop.gperf */
@ -746,7 +746,7 @@ dacp_playstatus_update_handler(short event_mask)
int ret; int ret;
// Only send status update on player change events // Only send status update on player change events
if (!(event_mask & LISTENER_PLAYER)) if (!(event_mask & (LISTENER_PLAYER | LISTENER_VOLUME)))
return; return;
#ifdef HAVE_EVENTFD #ifdef HAVE_EVENTFD
@ -903,7 +903,7 @@ dacp_propget_extendedmediakind(struct evbuffer *evbuf, struct player_status *sta
/* Properties setters */ /* Properties setters */
static void static void
dacp_propset_volume(const char *value, struct evkeyvalq *query) dacp_propset_volume(const char *value, struct httpd_request *hreq)
{ {
const char *param; const char *param;
uint64_t id; uint64_t id;
@ -918,7 +918,7 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query)
return; return;
} }
param = evhttp_find_header(query, "speaker-id"); param = evhttp_find_header(hreq->query, "speaker-id");
if (param) if (param)
{ {
ret = safe_atou64(param, &id); ret = safe_atou64(param, &id);
@ -933,7 +933,7 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query)
return; return;
} }
param = evhttp_find_header(query, "include-speaker-id"); param = evhttp_find_header(hreq->query, "include-speaker-id");
if (param) if (param)
{ {
ret = safe_atou64(param, &id); ret = safe_atou64(param, &id);
@ -952,33 +952,26 @@ dacp_propset_volume(const char *value, struct evkeyvalq *query)
} }
static void static void
dacp_propset_devicevolume(const char *value, struct evkeyvalq *query) dacp_propset_devicevolume(const char *value, struct httpd_request *hreq)
{ {
float raop_volume; struct evkeyvalq *headers;
int volume; 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 (!headers || !remote || (safe_atou32(remote, &id) < 0))
if (raop_volume == 0.0 && value[0] != '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; return;
} }
// RAOP volume: -144.0 is off, -30.0 - 0 maps to 0 - 100 player_volume_byactiveremote(id, value);
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);
} }
static void static void
dacp_propset_playingtime(const char *value, struct evkeyvalq *query) dacp_propset_playingtime(const char *value, struct httpd_request *hreq)
{ {
struct timeval tv; struct timeval tv;
int ret; int ret;
@ -997,7 +990,7 @@ dacp_propset_playingtime(const char *value, struct evkeyvalq *query)
} }
static void static void
dacp_propset_shufflestate(const char *value, struct evkeyvalq *query) dacp_propset_shufflestate(const char *value, struct httpd_request *hreq)
{ {
int enable; int enable;
int ret; int ret;
@ -1014,7 +1007,7 @@ dacp_propset_shufflestate(const char *value, struct evkeyvalq *query)
} }
static void static void
dacp_propset_repeatstate(const char *value, struct evkeyvalq *query) dacp_propset_repeatstate(const char *value, struct httpd_request *hreq)
{ {
int mode; int mode;
int ret; int ret;
@ -1031,7 +1024,7 @@ dacp_propset_repeatstate(const char *value, struct evkeyvalq *query)
} }
static void static void
dacp_propset_userrating(const char *value, struct evkeyvalq *query) dacp_propset_userrating(const char *value, struct httpd_request *hreq)
{ {
const char *param; const char *param;
uint32_t itemid; uint32_t itemid;
@ -1046,9 +1039,9 @@ dacp_propset_userrating(const char *value, struct evkeyvalq *query)
return; return;
} }
param = evhttp_find_header(query, "item-spec"); // Remote param = evhttp_find_header(hreq->query, "item-spec"); // Remote
if (!param) if (!param)
param = evhttp_find_header(query, "song-spec"); // Retune param = evhttp_find_header(hreq->query, "song-spec"); // Retune
if (!param) if (!param)
{ {
@ -2459,7 +2452,7 @@ dacp_reply_setproperty(struct httpd_request *hreq)
} }
if (dpm->propset) if (dpm->propset)
dpm->propset(param->value, hreq->query); dpm->propset(param->value, hreq);
else else
DPRINTF(E_WARN, L_DACP, "No setter method for DACP property %s\n", dpm->desc); DPRINTF(E_WARN, L_DACP, "No setter method for DACP property %s\n", dpm->desc);
} }
@ -2785,7 +2778,7 @@ dacp_init(void)
return -1; return -1;
} }
listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER); listener_add(dacp_playstatus_update_handler, LISTENER_PLAYER | LISTENER_VOLUME);
return 0; return 0;

View File

@ -132,6 +132,18 @@ outputs_device_volume_set(struct output_device *device, output_status_cb cb)
return -1; 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 void
outputs_playback_start(uint64_t next_pkt, struct timespec *ts) outputs_playback_start(uint64_t next_pkt, struct timespec *ts)
{ {

View File

@ -179,6 +179,9 @@ struct output_definition
// Set the volume and call back // Set the volume and call back
int (*device_volume_set)(struct output_device *device, output_status_cb cb); 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 // Start/stop playback on devices that were started
void (*playback_start)(uint64_t next_pkt, struct timespec *ts); void (*playback_start)(uint64_t next_pkt, struct timespec *ts);
void (*playback_stop)(void); void (*playback_stop)(void);
@ -217,6 +220,9 @@ outputs_device_free(struct output_device *device);
int int
outputs_device_volume_set(struct output_device *device, output_status_cb cb); 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 void
outputs_playback_start(uint64_t next_pkt, struct timespec *ts); outputs_playback_start(uint64_t next_pkt, struct timespec *ts);

View File

@ -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, "Client-Instance", buf);
evrtsp_add_header(req->output_headers, "DACP-ID", 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) if (rs->session)
evrtsp_add_header(req->output_headers, "Session", 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; 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 static int
raop_set_volume_internal(struct raop_session *rs, int volume, evrtsp_req_cb cb) 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_probe = raop_device_probe,
.device_free_extra = raop_device_free_extra, .device_free_extra = raop_device_free_extra,
.device_volume_set = raop_set_volume_one, .device_volume_set = raop_set_volume_one,
.device_volume_to_pct = raop_volume_to_pct,
.playback_start = raop_playback_start, .playback_start = raop_playback_start,
.playback_stop = raop_playback_stop, .playback_stop = raop_playback_stop,
.write = raop_v2_write, .write = raop_v2_write,

View File

@ -129,6 +129,11 @@ struct volume_param {
uint64_t spk_id; uint64_t spk_id;
}; };
struct activeremote_param {
uint32_t activeremote;
const char *value;
};
struct spk_enum struct spk_enum
{ {
spk_enum_cb cb; spk_enum_cb cb;
@ -2720,6 +2725,52 @@ volume_setabs_speaker(void *arg, int *retval)
return COMMAND_END; 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 static enum command_state
repeat_set(void *arg, int *retval) repeat_set(void *arg, int *retval)
{ {
@ -3099,6 +3150,19 @@ player_volume_setabs_speaker(uint64_t id, int vol)
return ret; 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 int
player_repeat_set(enum repeat_mode mode) player_repeat_set(enum repeat_mode mode)
{ {

View File

@ -137,6 +137,9 @@ player_volume_setrel_speaker(uint64_t id, int relvol);
int int
player_volume_setabs_speaker(uint64_t id, int vol); player_volume_setabs_speaker(uint64_t id, int vol);
int
player_volume_byactiveremote(uint32_t activeremote, const char *value);
int int
player_repeat_set(enum repeat_mode mode); player_repeat_set(enum repeat_mode mode);