Merge branch 'mikebrady:development' into development
This commit is contained in:
@@ -1,3 +1,18 @@
|
||||
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
|
||||
====
|
||||
|
||||
|
||||
+7
-6
@@ -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)
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
|
||||
|
||||
+19
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "activity_monitor.h"
|
||||
#include "config.h"
|
||||
|
||||
#ifdef CONFIG_OPENSSL
|
||||
#include <openssl/md5.h>
|
||||
@@ -1555,6 +1555,133 @@ 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 +1874,58 @@ void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message
|
||||
msg_add_header(resp, "Content-Type", "application/octet-stream");
|
||||
}
|
||||
|
||||
/*
|
||||
<key>Identifier</key>
|
||||
<string>21cc689d-d5de-4814-872c-71d1426b57e0</string>
|
||||
<key>Enable_HK_Access_Control</key>
|
||||
<true/>
|
||||
<key>PublicKey</key>
|
||||
<data>
|
||||
qXJDhhL5F3OACL+HO7LVLQVdy0OJtavepjpF720PaOQ=
|
||||
</data>
|
||||
<key>Device_Name</key>
|
||||
<string>MyDevice</string>
|
||||
<key>Access_Control_Level</key>
|
||||
<integer>0</integer>
|
||||
*/
|
||||
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);
|
||||
@@ -2077,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);
|
||||
@@ -2093,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);
|
||||
@@ -4253,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";
|
||||
@@ -4314,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;
|
||||
|
||||
+5
-8
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user