From 978862c2ec2c1c141ef2ed235057a024a7dbc46d Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 13 Jul 2021 22:28:34 +0200 Subject: [PATCH 1/5] Update pair_ap to 0.8 (fixes incorrect pair-list response) --- pair_ap/README.md | 13 +++++++------ pair_ap/pair.h | 2 +- pair_ap/pair_homekit.c | 22 +++++++++++++++++++--- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/pair_ap/README.md b/pair_ap/README.md index 1c855444..d6b00d65 100644 --- a/pair_ap/README.md +++ b/pair_ap/README.md @@ -2,9 +2,10 @@ C client implementation of pairing for: * Apple TV device verification, which became mandatory with tvOS 10.2 (this is called fruit mode in pair_ap) -* Homekit pairing (for AirPlay 2, not working for Home app) +* Homekit pairing (also for AirPlay 2) Credit goes to @funtax and @ViktoriiaKh for doing some of the heavy lifting. + ## Requirements - libsodium - libgcrypt or libopenssl @@ -33,7 +34,7 @@ The controller uses `/pair-add` to make sure that all devices on a network get the ID and public key of all the other devices, so that the user only needs to pair a device once. -### Normal pairing +### Normal pairing with one-time code For a normal first-time pairing, the client needs a one-time code (the device announces via mDNS whether a code is required). The client calls `/pair-pin-start` and the device displays the code. There is also QR-based @@ -42,12 +43,12 @@ pairing, which is (probably?) an encoded code. After obtaining the code, the client initiates a three step `/pair-setup` sequence, which results in both peers registering each other's ID and public key. Henceforth, a pairing is verified with the two step `/pair-verify`, where -the parties check each-others identify. Saving the peer's ID + public key isn't +the parties check eachothers identify. Saving the peer's ID + public key isn't strictly necessary if client or server doesn't care about verifying the peer, i.e. that `/pair-setup` has actually been completed. The result of `/pair-verify` is a shared secret that is used for symmetric -encryption of the following communinacation between the parties. +encryption of the following communication between the parties. ### Transient pairing Some devices don't require a code from the user for pairing (e.g. an Airport @@ -55,8 +56,7 @@ Express 2). If so, the client just needs to go through a two-step `/pair-setup` sequence which results in a shared secret, which is then used for encrypted communication. A fixed code of 3939 is used. -Such devices don't appear to be fully Homekit compatible - they will not, for -instance - appear in the Home app. +The controller can still use `/pair-add` etc. towards such devices. ## "fruit" pairing Like normal Homekit pairing, this consists of first requesting a code with @@ -69,4 +69,5 @@ shared secret. - [AirPlayAuth](https://github.com/funtax/AirPlayAuth) - [AirPlayAuth-ObjC](https://github.com/ViktoriiaKh/AirPlayAuth-ObjC) - [ap2-sender](https://github.com/ViktoriiaKh/ap2-sender) +- [airplay2-receiver](https://github.com/ckdo/airplay2-receiver) - [csrp](https://github.com/cocagne/csrp) diff --git a/pair_ap/pair.h b/pair_ap/pair.h index 6324bc15..3877e2bb 100644 --- a/pair_ap/pair.h +++ b/pair_ap/pair.h @@ -4,7 +4,7 @@ #include #define PAIR_AP_VERSION_MAJOR 0 -#define PAIR_AP_VERSION_MINOR 5 +#define PAIR_AP_VERSION_MINOR 8 #define PAIR_AP_DEVICE_ID_LEN_MAX 64 diff --git a/pair_ap/pair_homekit.c b/pair_ap/pair_homekit.c index 076ea14a..ed59807e 100644 --- a/pair_ap/pair_homekit.c +++ b/pair_ap/pair_homekit.c @@ -2679,6 +2679,7 @@ server_add_remove_request(pair_cb cb, void *cb_arg, const uint8_t *in, size_t in pair_tlv_t *device_id; pair_tlv_t *pk; char id_str[PAIR_AP_DEVICE_ID_LEN_MAX] = { 0 }; + uint8_t *public_key = NULL; request = message_process(in, in_len, &errmsg); if (!request) @@ -2687,15 +2688,21 @@ server_add_remove_request(pair_cb cb, void *cb_arg, const uint8_t *in, size_t in } device_id = pair_tlv_get_value(request, TLVType_Identifier); - pk = pair_tlv_get_value(request, TLVType_PublicKey); - if (!device_id || device_id->size >= sizeof(id_str) || !pk || pk->size != crypto_sign_PUBLICKEYBYTES) + if (!device_id || device_id->size >= sizeof(id_str)) { goto error; } + // Only present when adding + pk = pair_tlv_get_value(request, TLVType_PublicKey); + if (pk && pk->size == crypto_sign_PUBLICKEYBYTES) + { + public_key = pk->value; + } + memcpy(id_str, device_id->value, device_id->size); - cb(pk->value, id_str, cb_arg); + cb(public_key, id_str, cb_arg); pair_tlv_free(request); return 0; @@ -2757,9 +2764,18 @@ static int server_list_cb(uint8_t public_key[crypto_sign_PUBLICKEYBYTES], const char *device_id, void *cb_arg) { pair_tlv_values_t *response = cb_arg; + pair_tlv_t *previous_id; + uint8_t permissions = 1; // Means admin (TODO don't hardcode - let caller set) + + // If this isn't the first iteration (item) then we must add a separator + previous_id = pair_tlv_get_value(response, TLVType_Identifier); + if (previous_id) + pair_tlv_add_value(response, TLVType_Separator, NULL, 0); pair_tlv_add_value(response, TLVType_Identifier, (unsigned char *)device_id, strlen(device_id)); pair_tlv_add_value(response, TLVType_PublicKey, public_key, crypto_sign_PUBLICKEYBYTES); + pair_tlv_add_value(response, TLVType_Permissions, &permissions, sizeof(permissions)); + return 0; } From 1a64a56c3f807d69b9c27e8dc2b3c47ec6db453b Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 13 Jul 2021 23:21:01 +0200 Subject: [PATCH 2/5] Add Homekit pairing methods (pair-add/list/remove + configure) Also change the features flag to match airplay2-receiver --- rtsp.c | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++ shairport.c | 13 ++--- 2 files changed, 166 insertions(+), 8 deletions(-) diff --git a/rtsp.c b/rtsp.c index d0cc762c..ba09ce15 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1555,6 +1555,125 @@ void handle_get(__attribute((unused)) rtsp_conn_info *conn, __attribute((unused) #endif #ifdef CONFIG_AIRPLAY_2 +struct pairings { + char device_id[PAIR_AP_DEVICE_ID_LEN_MAX]; + uint8_t public_key[32]; + + struct pairings *next; +} *pairings; + +static struct pairings * pairing_find(const char *device_id) +{ + for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) { + if (strcmp(device_id, pairing->device_id) == 0) + return pairing; + } + return NULL; +} + +static void pairing_add(uint8_t public_key[32], const char *device_id) { + struct pairings *pairing = calloc(1, sizeof(struct pairings)); + snprintf(pairing->device_id, sizeof(pairing->device_id), "%s", device_id); + memcpy(pairing->public_key, public_key, sizeof(pairing->public_key)); + + pairing->next = pairings; + pairings = pairing; +} + +static void pairing_remove(struct pairings *pairing) { + if (pairing == pairings) { + pairings = pairing->next; + } else { + struct pairings *iter; + for (iter = pairings; iter && (iter->next != pairing); iter = iter->next) + ; /* EMPTY */ + + if (iter) + iter->next = pairing->next; + } + + free(pairing); +} + +static int pairing_add_cb(uint8_t public_key[32], const char *device_id, void *cb_arg __attribute__((unused))) { + debug(1, "pair-add cb for %s", device_id); + + struct pairings *pairing = pairing_find(device_id); + if (pairing) { + memcpy(pairing->public_key, public_key, sizeof(pairing->public_key)); + return 0; + } + + pairing_add(public_key, device_id); + return 0; +} + +static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), const char *device_id, void *cb_arg __attribute__((unused))) { + debug(1, "pair-remove cb for %s", device_id); + + struct pairings *pairing = pairing_find(device_id); + if (!pairing) { + debug(1, "pair-remove callback for unknown device"); + return -1; + } + + pairing_remove(pairing); + return 0; +} + +static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void *cb_arg __attribute__((unused))) { + debug(1, "pair-list cb"); + + for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) { + enum_cb(pairing->public_key, pairing->device_id, enum_cb_arg); + } +} + +void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { + uint8_t *body = NULL; + size_t body_len = 0; + int ret = pair_add(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_add_cb, NULL, (const uint8_t *)req->content, req->contentlength); + if (ret < 0) { + debug(1, "pair-add returned an error"); + resp->respcode = 451; + return; + } + resp->content = (char *)body; // these will be freed when the data is sent + resp->contentlength = body_len; + msg_add_header(resp, "Content-Type", "application/octet-stream"); + debug_log_rtsp_message(2, "pair-add response", resp); +} + +void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { + uint8_t *body = NULL; + size_t body_len = 0; + int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL, (const uint8_t *)req->content, req->contentlength); + if (ret < 0) { + debug(1, "pair-list returned an error"); + resp->respcode = 451; + return; + } + resp->content = (char *)body; // these will be freed when the data is sent + resp->contentlength = body_len; + msg_add_header(resp, "Content-Type", "application/octet-stream"); + debug_log_rtsp_message(2, "pair-list response", resp); +} + +void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { + uint8_t *body = NULL; + size_t body_len = 0; + int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL, (const uint8_t *)req->content, req->contentlength); + if (ret < 0) { + debug(1, "pair-remove returned an error"); + resp->respcode = 451; + return; + } + resp->content = (char *)body; // these will be freed when the data is sent + resp->contentlength = body_len; + msg_add_header(resp, "Content-Type", "application/octet-stream"); + debug_log_rtsp_message(2, "pair-remove response", resp); +} + void handle_pair_verify(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { int ret; uint8_t *body = NULL; @@ -1747,14 +1866,56 @@ void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message msg_add_header(resp, "Content-Type", "application/octet-stream"); } +/* + Identifier + 21cc689d-d5de-4814-872c-71d1426b57e0 + Enable_HK_Access_Control + + PublicKey + + qXJDhhL5F3OACL+HO7LVLQVdy0OJtavepjpF720PaOQ= + + Device_Name + MyDevice + Access_Control_Level + 0 +*/ +void handle_configure(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req __attribute__((unused)), rtsp_message *resp) { + uint8_t public_key[32]; + + pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, config.airplay_device_id); + + plist_t response_plist = plist_new_dict(); + + plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi)); + plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1)); + plist_dict_set_item(response_plist, "PublicKey", plist_new_data((const char *)public_key, sizeof(public_key))); + plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name)); + plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0)); + + plist_to_bin(response_plist, &resp->content, &resp->contentlength); + plist_free(response_plist); + + msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); + debug_log_rtsp_message(2, "POST /configure response:", resp); +} + void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { resp->respcode = 200; if (strcmp(req->path, "/pair-setup") == 0) { handle_pair_setup(conn, req, resp); } else if (strcmp(req->path, "/pair-verify") == 0) { handle_pair_verify(conn, req, resp); + } else if (strcmp(req->path, "/pair-add") == 0) { + handle_pair_add(conn, req, resp); + } else if (strcmp(req->path, "/pair-remove") == 0) { + handle_pair_remove(conn, req, resp); + } else if (strcmp(req->path, "/pair-list") == 0) { + handle_pair_list(conn, req, resp); } else if (strcmp(req->path, "/fp-setup") == 0) { handle_fp_setup(conn, req, resp); + } else if (strcmp(req->path, "/configure") == 0) { + handle_configure(conn, req, resp); } else { debug(3, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path, req->contentlength); diff --git a/shairport.c b/shairport.c index 45a08c7f..0f7f141f 100644 --- a/shairport.c +++ b/shairport.c @@ -1587,15 +1587,12 @@ int main(int argc, char **argv) { // features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist: // 496155702020608 this setting here is the source of both the plist features response and the // mDNS string. - config.airplay_features = 0x1C340405F4A00; + // note: 0x300401F4A00 works but with weird delays and stuff + config.airplay_features = 0x1C340405FCA00; // Advertised with mDNS and returned with GET /info, see // https://openairplay.github.io/airplay-spec/status_flags.html 0x4: Audio cable attached, no PIN - // required (transient pairing), no Homekit access control 0x204: Audio cable attached, - // OneTimePairingRequired 0x604: Audio cable attached, OneTimePairingRequired, allow Homekit - // access control - - // note: 0x300401F4A00 works but with weird delays and stuff - + // required (transient pairing), 0x204: Audio cable attached, OneTimePairingRequired 0x604: Audio + // cable attached, OneTimePairingRequired, device was setup for Homekit access control config.airplay_statusflags = 0x4; // Set to NULL to work with transient pairing config.airplay_pin = NULL; @@ -1622,7 +1619,7 @@ int main(int argc, char **argv) { uuid_generate_random(binuuid); char *uuid = malloc(UUID_STR_LEN); - /* Produces a UUID string at uuid consisting of lower-case letters. */ + // Produces a UUID string at uuid consisting of lower-case letters uuid_unparse_lower(binuuid, uuid); config.airplay_pi = strdup(uuid); config.airplay_gid = strdup(uuid); // initially the gid is the same as the pi From 5630e20eb02d28ce3d53abd38faab7c991986238 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 16 Jul 2021 11:24:05 +0100 Subject: [PATCH 3/5] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index c14ba33e..0f0202cb 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,12 @@ +Version 4.0-dev-182-gc0ee8d1 +==== +#### New Features +- **Home App Integration** An AirPlay 2 build of Shairport Sync now offers limited integration with the Home app. Specifically, a Shairport Sync AirPlay 2 instance can now be added as a speaker to the Home app. Thanks to [ckdo](https://github.com/ckdo) and [ejurgensen](https://github.com/ejurgensen) for figuring this out and for coding it up. It means that some Siri interaction is possible. +- **Automated Docker Builds** Thanks to the work of [Charles Omer](https://github.com/charlesomer), Docker builds on the `development` branch should appear from time to time on the [Docker Hub](https://hub.docker.com/r/mikebrady/shairport-sync). + +#### Bug Fixes +- [Crashing bug fix](https://github.com/mikebrady/nqptp/blob/main/RELEASE_NOTES.md) in `nqptp` -- thanks to [ste94pz](https://github.com/ste94pz). + Version 4.0-dev-153-g9ccde50 ==== From 3a6297bc9aca9fa2b88d7761b12e9a328764dd84 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 16 Jul 2021 15:04:51 +0100 Subject: [PATCH 4/5] Honour max_output_db setting even when ignore_volume_control is true. Spell out "Shairport Sync" in the Home app information. --- player.c | 312 ++++++++++++++++++++++++++++--------------------------- rtsp.c | 71 +++++++------ 2 files changed, 198 insertions(+), 185 deletions(-) diff --git a/player.c b/player.c index 70ba36ec..ac332a43 100644 --- a/player.c +++ b/player.c @@ -1760,27 +1760,25 @@ void *player_thread_func(void *arg) { // successive rtptimes, at worst conn->output_sample_ratio = config.output_rate / conn->input_rate; - - -// Sign extending rtptime calculations to 64 bit is needed from time to time. -// The standard rtptime is unsigned 32 bits, -// so you can do modulo 2^32 difference calculations -// and get a signed result simply by typing the result as a signed 32-bit number. + // Sign extending rtptime calculations to 64 bit is needed from time to time. -// So long as you can be sure the numbers are within 2^31 of each other, -// the sign of the result calculated in this way indicates the order of the operands. -// For example, if you subtract a from b and the result is positive, you can conclude -// b is the same as or comes after a in module 2^32 order. + // The standard rtptime is unsigned 32 bits, + // so you can do modulo 2^32 difference calculations + // and get a signed result simply by typing the result as a signed 32-bit number. -// We want to do the same with the rtptime calculations for multiples of -// the rtptimes (1, 2, 4 or 8 times), and we want to do this in signed 64-bit/ -// Therefore we need to sign extend these modulo 2^32, 2^33, 2^34, or 2^35 bit unsigned -// numbers on the same basis. + // So long as you can be sure the numbers are within 2^31 of each other, + // the sign of the result calculated in this way indicates the order of the operands. + // For example, if you subtract a from b and the result is positive, you can conclude + // b is the same as or comes after a in module 2^32 order. -// That is what the output_rtptime_sign_bit, output_rtptime_mask, output_rtptime_mask_not and -// output_rtptime_sign_mask are for -- see later, calculating the sync error. + // We want to do the same with the rtptime calculations for multiples of + // the rtptimes (1, 2, 4 or 8 times), and we want to do this in signed 64-bit/ + // Therefore we need to sign extend these modulo 2^32, 2^33, 2^34, or 2^35 bit unsigned + // numbers on the same basis. + // That is what the output_rtptime_sign_bit, output_rtptime_mask, output_rtptime_mask_not and + // output_rtptime_sign_mask are for -- see later, calculating the sync error. int output_rtptime_sign_bit; switch (conn->output_sample_ratio) { @@ -2022,7 +2020,6 @@ void *player_thread_func(void *arg) { } #endif - #ifdef CONFIG_AIRPLAY_2 if (conn->timing_type == ts_ntp) { #endif @@ -2940,45 +2937,46 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // if it's both, we haven't decided whether hw or sw should be on top // we have to consider the settings ignore_volume_control and mute. - if (config.ignore_volume_control == 0) { - if (airplay_volume == -144.0) { + if (airplay_volume == -144.0) { - if ((config.output->mute) && (config.output->mute(1) == 0)) - debug(2, - "player_volume_without_notification: volume mode is %d, airplay_volume is %f, " - "hardware mute is enabled.", - volume_mode, airplay_volume); - else { - conn->software_mute_enabled = 1; - debug(2, - "player_volume_without_notification: volume mode is %d, airplay_volume is %f, " - "software mute is enabled.", - volume_mode, airplay_volume); - } + if ((config.output->mute) && (config.output->mute(1) == 0)) + debug(2, + "player_volume_without_notification: volume mode is %d, airplay_volume is %f, " + "hardware mute is enabled.", + volume_mode, airplay_volume); + else { + conn->software_mute_enabled = 1; + debug(2, + "player_volume_without_notification: volume mode is %d, airplay_volume is %f, " + "software mute is enabled.", + volume_mode, airplay_volume); + } + + } else { + int32_t max_db = 0, min_db = 0; + switch (volume_mode) { + case vol_hw_only: + max_db = hw_max_db; + min_db = hw_min_db; + break; + case vol_sw_only: + max_db = sw_max_db; + min_db = sw_min_db; + break; + case vol_both: + // debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, + // sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); + max_db = + (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested + min_db = 0; + break; + default: + debug(1, "player_volume_without_notification: error: not in a volume mode"); + break; + } + double scaled_attenuation = max_db; + if (config.ignore_volume_control == 0) { - } else { - int32_t max_db = 0, min_db = 0; - switch (volume_mode) { - case vol_hw_only: - max_db = hw_max_db; - min_db = hw_min_db; - break; - case vol_sw_only: - max_db = sw_max_db; - min_db = sw_min_db; - break; - case vol_both: - // debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, - // sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); - max_db = - (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested - min_db = 0; - break; - default: - debug(1, "player_volume_without_notification: error: not in a volume mode"); - break; - } - double scaled_attenuation = 0.0; if (config.volume_control_profile == VCP_standard) scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points else if (config.volume_control_profile == VCP_flat) @@ -2986,116 +2984,122 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points else debug(1, "player_volume_without_notification: unrecognised volume control profile"); - - // so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward. - double hardware_attenuation = 0.0; - double software_attenuation = 0.0; - - switch (volume_mode) { - case vol_hw_only: - hardware_attenuation = scaled_attenuation; - break; - case vol_sw_only: - software_attenuation = scaled_attenuation; - break; - case vol_both: - // here, we now the attenuation required, so we have to apportion it to the sw and hw mixers - // if we give the hw priority, that means when lowering the volume, set the hw volume to its - // lowest - // before using the sw attenuation. - // similarly, if we give the sw priority, that means when lowering the volume, set the sw - // volume to its lowest - // before using the hw attenuation. - // one imagines that hw priority is likely to be much better - // if (config.volume_range_hw_priority) { - if (config.volume_range_hw_priority != 0) { - // hw priority - if ((sw_max_db - sw_min_db) > scaled_attenuation) { - software_attenuation = sw_min_db + scaled_attenuation; - hardware_attenuation = hw_min_db; - } else { - software_attenuation = sw_max_db; - hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db); - } - } else { - // sw priority - if ((hw_max_db - hw_min_db) > scaled_attenuation) { - hardware_attenuation = hw_min_db + scaled_attenuation; - software_attenuation = sw_min_db; - } else { - hardware_attenuation = hw_max_db; - software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db); - } - } - break; - default: - debug(1, "player_volume_without_notification: error: not in a volume mode"); - break; - } - - if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) { - config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value - // debug(1,"Hardware attenuation set to %f for airplay volume of - // %f.",hardware_attenuation,airplay_volume); - if (volume_mode == vol_hw_only) - conn->fix_volume = 0x10000; - } - - if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) { - double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); - // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of - // %f",software_attenuation,temp_fix_volume,airplay_volume); - - conn->fix_volume = temp_fix_volume; - - // if (config.loudness) - loudness_set_volume(software_attenuation / 100); - } - - if (config.logOutputLevel) { - inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0); - } - -#ifdef CONFIG_METADATA - // here, send the 'pvol' metadata message when the airplay volume information - // is being used by shairport sync to control the output volume - char dv[128]; - memset(dv, 0, 128); - if (volume_mode == vol_both) { - // normalise the maximum output to the hardware device's max output - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, - (scaled_attenuation - max_db + hw_max_db) / 100.0, - (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0); - } else { - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0, - min_db / 100.0, max_db / 100.0); - } - send_ssnc_metadata('pvol', dv, strlen(dv), 1); -#endif - - if (config.output->mute) - config.output->mute(0); - conn->software_mute_enabled = 0; - - debug(2, - "player_volume_without_notification: volume mode is %d, airplay volume is %f, " - "software_attenuation: %f, hardware_attenuation: %f, muting " - "is disabled.", - volume_mode, airplay_volume, software_attenuation, hardware_attenuation); } - } + // so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward. + double hardware_attenuation = 0.0; + double software_attenuation = 0.0; + + switch (volume_mode) { + case vol_hw_only: + hardware_attenuation = scaled_attenuation; + break; + case vol_sw_only: + software_attenuation = scaled_attenuation; + break; + case vol_both: + // here, we now the attenuation required, so we have to apportion it to the sw and hw mixers + // if we give the hw priority, that means when lowering the volume, set the hw volume to its + // lowest + // before using the sw attenuation. + // similarly, if we give the sw priority, that means when lowering the volume, set the sw + // volume to its lowest + // before using the hw attenuation. + // one imagines that hw priority is likely to be much better + // if (config.volume_range_hw_priority) { + if (config.volume_range_hw_priority != 0) { + // hw priority + if ((sw_max_db - sw_min_db) > scaled_attenuation) { + software_attenuation = sw_min_db + scaled_attenuation; + hardware_attenuation = hw_min_db; + } else { + software_attenuation = sw_max_db; + hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db); + } + } else { + // sw priority + if ((hw_max_db - hw_min_db) > scaled_attenuation) { + hardware_attenuation = hw_min_db + scaled_attenuation; + software_attenuation = sw_min_db; + } else { + hardware_attenuation = hw_max_db; + software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db); + } + } + break; + default: + debug(1, "player_volume_without_notification: error: not in a volume mode"); + break; + } + + if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) { + config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value + // debug(1,"Hardware attenuation set to %f for airplay volume of + // %f.",hardware_attenuation,airplay_volume); + if (volume_mode == vol_hw_only) + conn->fix_volume = 0x10000; + } + + if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) { + double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); + + if (config.ignore_volume_control == 0) + debug(2, "Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of %f", + software_attenuation, temp_fix_volume, airplay_volume); + else + debug(2, "Software attenuation set to %f, i.e %f out of 65,536. Volume control is ignored.", + software_attenuation, temp_fix_volume); + + conn->fix_volume = temp_fix_volume; + + // if (config.loudness) + loudness_set_volume(software_attenuation / 100); + } + + if (config.logOutputLevel) { + inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0); + } #ifdef CONFIG_METADATA - else { // here, send the 'pvol' metadata message when the airplay volume information // is being used by shairport sync to control the output volume char dv[128]; memset(dv, 0, 128); - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0); + if (volume_mode == vol_both) { + // normalise the maximum output to the hardware device's max output + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, + (scaled_attenuation - max_db + hw_max_db) / 100.0, + (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0); + } else { + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0, + min_db / 100.0, max_db / 100.0); + } send_ssnc_metadata('pvol', dv, strlen(dv), 1); - } #endif + if (config.output->mute) + config.output->mute(0); + conn->software_mute_enabled = 0; + + debug(2, + "player_volume_without_notification: volume mode is %d, airplay volume is %f, " + "software_attenuation: %f, hardware_attenuation: %f, muting " + "is disabled.", + volume_mode, airplay_volume, software_attenuation, hardware_attenuation); + } + /* + } + + #ifdef CONFIG_METADATA + else { + // here, send the 'pvol' metadata message when the airplay volume information + // is being used by shairport sync to control the output volume + char dv[128]; + memset(dv, 0, 128); + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0); + send_ssnc_metadata('pvol', dv, strlen(dv), 1); + } + #endif + */ // here, store the volume for possible use in the future config.airplay_volume = airplay_volume; debug_mutex_unlock(&conn->volume_control_mutex, 3); diff --git a/rtsp.c b/rtsp.c index ba09ce15..7e6ff7cf 100644 --- a/rtsp.c +++ b/rtsp.c @@ -50,8 +50,8 @@ #include -#include "config.h" #include "activity_monitor.h" +#include "config.h" #ifdef CONFIG_OPENSSL #include @@ -1560,10 +1560,9 @@ struct pairings { uint8_t public_key[32]; struct pairings *next; -} *pairings; +} * pairings; -static struct pairings * pairing_find(const char *device_id) -{ +static struct pairings *pairing_find(const char *device_id) { for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) { if (strcmp(device_id, pairing->device_id) == 0) return pairing; @@ -1595,7 +1594,8 @@ static void pairing_remove(struct pairings *pairing) { free(pairing); } -static int pairing_add_cb(uint8_t public_key[32], const char *device_id, void *cb_arg __attribute__((unused))) { +static int pairing_add_cb(uint8_t public_key[32], const char *device_id, + void *cb_arg __attribute__((unused))) { debug(1, "pair-add cb for %s", device_id); struct pairings *pairing = pairing_find(device_id); @@ -1608,7 +1608,8 @@ static int pairing_add_cb(uint8_t public_key[32], const char *device_id, void *c return 0; } -static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), const char *device_id, void *cb_arg __attribute__((unused))) { +static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), const char *device_id, + void *cb_arg __attribute__((unused))) { debug(1, "pair-remove cb for %s", device_id); struct pairings *pairing = pairing_find(device_id); @@ -1621,7 +1622,8 @@ static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), con return 0; } -static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void *cb_arg __attribute__((unused))) { +static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, + void *cb_arg __attribute__((unused))) { debug(1, "pair-list cb"); for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) { @@ -1629,10 +1631,12 @@ static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void *cb_arg __a } } -void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { +void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, + rtsp_message *resp) { uint8_t *body = NULL; size_t body_len = 0; - int ret = pair_add(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_add_cb, NULL, (const uint8_t *)req->content, req->contentlength); + int ret = pair_add(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_add_cb, NULL, + (const uint8_t *)req->content, req->contentlength); if (ret < 0) { debug(1, "pair-add returned an error"); resp->respcode = 451; @@ -1644,10 +1648,12 @@ void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message debug_log_rtsp_message(2, "pair-add response", resp); } -void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { +void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, + rtsp_message *resp) { uint8_t *body = NULL; size_t body_len = 0; - int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL, (const uint8_t *)req->content, req->contentlength); + int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL, + (const uint8_t *)req->content, req->contentlength); if (ret < 0) { debug(1, "pair-list returned an error"); resp->respcode = 451; @@ -1659,10 +1665,12 @@ void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message debug_log_rtsp_message(2, "pair-list response", resp); } -void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { +void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, + rtsp_message *resp) { uint8_t *body = NULL; size_t body_len = 0; - int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL, (const uint8_t *)req->content, req->contentlength); + int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL, + (const uint8_t *)req->content, req->contentlength); if (ret < 0) { debug(1, "pair-remove returned an error"); resp->respcode = 451; @@ -1867,20 +1875,21 @@ void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message } /* - Identifier - 21cc689d-d5de-4814-872c-71d1426b57e0 - Enable_HK_Access_Control - - PublicKey - - qXJDhhL5F3OACL+HO7LVLQVdy0OJtavepjpF720PaOQ= - - Device_Name - MyDevice - Access_Control_Level - 0 + Identifier + 21cc689d-d5de-4814-872c-71d1426b57e0 + Enable_HK_Access_Control + + PublicKey + + qXJDhhL5F3OACL+HO7LVLQVdy0OJtavepjpF720PaOQ= + + Device_Name + MyDevice + Access_Control_Level + 0 */ -void handle_configure(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req __attribute__((unused)), rtsp_message *resp) { +void handle_configure(rtsp_conn_info *conn __attribute__((unused)), + rtsp_message *req __attribute__((unused)), rtsp_message *resp) { uint8_t public_key[32]; pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, config.airplay_device_id); @@ -1889,7 +1898,8 @@ void handle_configure(rtsp_conn_info *conn __attribute__((unused)), rtsp_message plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi)); plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1)); - plist_dict_set_item(response_plist, "PublicKey", plist_new_data((const char *)public_key, sizeof(public_key))); + plist_dict_set_item(response_plist, "PublicKey", + plist_new_data((const char *)public_key, sizeof(public_key))); plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name)); plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0)); @@ -2238,7 +2248,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) // hack. conn->max_frames_per_packet = 352; // number of audio frames per packet. - conn->input_rate = 44100; // we are stuck with this for the moment. + conn->input_rate = 44100; // we are stuck with this for the moment. conn->input_num_channels = 2; conn->input_bit_depth = 16; conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); @@ -2254,7 +2264,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) plist_dict_set_item(stream0dict, "audioBufferSize", plist_new_uint(conn->ap2_audio_buffer_size)); - // this should be cancelled by an activity_monitor_signify_activity(1) // call in the SETRATEANCHORI handler, which should come up right away activity_monitor_signify_activity(0); @@ -4414,7 +4423,7 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) { *p++ = ap1_featuresString; *p++ = firmware_version; *p++ = "md=2"; - *p++ = "am=SPS"; + *p++ = "am=Shairport Sync"; *p++ = "sf=0x4"; *p++ = "tp=UDP"; *p++ = "vn=65537"; @@ -4475,7 +4484,7 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) { *p++ = "acl=0"; *p++ = "rsf=0x0"; *p++ = firmware_version; - *p++ = "model=SPS"; + *p++ = "model=Shairport Sync"; char piString[64]; snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi); *p++ = piString; From e26d3edd5c429da2c011eed6dfbf91bf1a7349a7 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 16 Jul 2021 15:12:33 +0100 Subject: [PATCH 5/5] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 0f0202cb..c10e455d 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,11 +1,17 @@ -Version 4.0-dev-182-gc0ee8d1 +Version 4.0-dev-185-g0c02ee2 ==== + #### New Features - **Home App Integration** An AirPlay 2 build of Shairport Sync now offers limited integration with the Home app. Specifically, a Shairport Sync AirPlay 2 instance can now be added as a speaker to the Home app. Thanks to [ckdo](https://github.com/ckdo) and [ejurgensen](https://github.com/ejurgensen) for figuring this out and for coding it up. It means that some Siri interaction is possible. - **Automated Docker Builds** Thanks to the work of [Charles Omer](https://github.com/charlesomer), Docker builds on the `development` branch should appear from time to time on the [Docker Hub](https://hub.docker.com/r/mikebrady/shairport-sync). #### Bug Fixes - [Crashing bug fix](https://github.com/mikebrady/nqptp/blob/main/RELEASE_NOTES.md) in `nqptp` -- thanks to [ste94pz](https://github.com/ste94pz). +- Honour the `max_volume_db` setting even when `ignore_volume_control` is true. Note that dithering is enabled if software attenuation is needed. + +#### Other Changes +- Spell out `Shairport Sync` instead of `SPS` in a few places. +- Clang format some source files. Version 4.0-dev-153-g9ccde50 ====