From 2d29b6b6d1912eb3496160c76943a1a39927c0c5 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Sat, 9 Dec 2023 18:34:05 -0500
Subject: [PATCH 01/11] use base 64 to represent public key as string
---
lib/crypto.c | 24 ++++++++++++++++++++++++
lib/crypto.h | 1 +
lib/pairing.c | 12 ++++++++++--
lib/pairing.h | 3 ++-
lib/raop_handlers.h | 18 ++++++++++--------
5 files changed, 47 insertions(+), 11 deletions(-)
diff --git a/lib/crypto.c b/lib/crypto.c
index c1167d5..a3da644 100644
--- a/lib/crypto.c
+++ b/lib/crypto.c
@@ -546,3 +546,27 @@ void sha_destroy(sha_ctx_t *ctx) {
int get_random_bytes(unsigned char *buf, int num) {
return RAND_bytes(buf, num);
}
+#include
+void pk_to_base64(const unsigned char *pk, int pk_len, char *pk_base64, int len) {
+ memset(pk_base64, 0, len);
+ int len64 = (4 * (pk_len /3)) + (pk_len % 3 ? 4 : 0);
+
+ assert (len > len64);
+
+ BIO *b64 = BIO_new(BIO_f_base64());
+ BIO *bio = BIO_new(BIO_s_mem());
+ BUF_MEM *bufferPtr;
+
+
+ bio = BIO_push(b64, bio);
+
+ BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
+ BIO_write(bio, pk, pk_len);
+ BIO_flush(bio);
+
+ BIO_get_mem_ptr(bio, &bufferPtr);
+ BIO_set_close(bio, BIO_NOCLOSE);
+ BIO_free_all(bio);
+ memcpy(pk_base64,(*bufferPtr).data, len64);
+}
+
diff --git a/lib/crypto.h b/lib/crypto.h
index 47ac30a..7f3f758 100644
--- a/lib/crypto.h
+++ b/lib/crypto.h
@@ -67,6 +67,7 @@ x25519_key_t *x25519_key_from_raw(const unsigned char data[X25519_KEY_SIZE]);
void x25519_key_get_raw(unsigned char data[X25519_KEY_SIZE], const x25519_key_t *key);
void x25519_key_destroy(x25519_key_t *key);
int get_random_bytes(unsigned char *buf, int num);
+void pk_to_base64(const unsigned char *pk, int pk_len, char *pk_base64, int len);
void x25519_derive_secret(unsigned char secret[X25519_KEY_SIZE], const x25519_key_t *ours, const x25519_key_t *theirs);
diff --git a/lib/pairing.c b/lib/pairing.c
index 3df0b58..590d0dc 100644
--- a/lib/pairing.c
+++ b/lib/pairing.c
@@ -447,8 +447,16 @@ srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing,
return epk_len;
}
-void access_client_session_data(pairing_session_t *session, char **username, unsigned char **client_pk, bool *setup) {
+void access_client_session_data(pairing_session_t *session, char **username, char **client_pk64, bool *setup) {
+ int len64 = 4 * (1 + (ED25519_KEY_SIZE / 3)) + 1;
+ *client_pk64 = (char *) malloc(len64);
*username = session->username;
- *client_pk = session->client_pk;
+ pk_to_base64(session->client_pk, ED25519_KEY_SIZE, *client_pk64, len64);
setup = &(session->pair_setup);
}
+
+void ed25519_pk_to_base64(const unsigned char *pk, char **pk64) {
+ int len64 = 4 * (1 + (ED25519_KEY_SIZE / 3)) + 1;
+ *pk64 = (char *) malloc(len64);
+ pk_to_base64(pk, ED25519_KEY_SIZE, *pk64, len64);
+}
diff --git a/lib/pairing.h b/lib/pairing.h
index eec1231..84f2077 100644
--- a/lib/pairing.h
+++ b/lib/pairing.h
@@ -60,5 +60,6 @@ int srp_validate_proof(pairing_session_t *session, pairing_t *pairing, const uns
int len_A, unsigned char *proof, int client_proof_len, int proof_len);
int srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing, unsigned char *epk,
unsigned char *auth_tag);
-void access_client_session_data(pairing_session_t *session, char **username, unsigned char **client_pk, bool *setup);
+void access_client_session_data(pairing_session_t *session, char **username, char **client_pk, bool *setup);
+void ed25519_pk_to_base64(const unsigned char *pk, char **pk64);
#endif
diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h
index 679cb72..4bb21d5 100644
--- a/lib/raop_handlers.h
+++ b/lib/raop_handlers.h
@@ -343,13 +343,12 @@ raop_handler_pairsetup_pin(raop_conn_t *conn,
} else {
bool client_pair_setup;
char *client_device_id;
- unsigned char *client_pk;
+ char *client_pk; /* encoded as null-terminated base64 string*/
access_client_session_data(conn->session, &client_device_id, &client_pk, &client_pair_setup);
- char * client_pk_str = utils_pk_to_string(client_pk, ED25519_KEY_SIZE);
if (conn->raop->callbacks.register_client) {
- conn->raop->callbacks.register_client(conn->raop->callbacks.cls, client_device_id, client_pk_str);
+ conn->raop->callbacks.register_client(conn->raop->callbacks.cls, client_device_id, client_pk);
}
- free (client_pk_str);
+ free (client_pk);
logger_log(conn->raop->logger, LOGGER_DEBUG, "pair-pin-setup success\n");
}
pairing_session_set_setup_status(conn->session);
@@ -442,12 +441,15 @@ raop_handler_pairverify(raop_conn_t *conn,
logger_log(conn->raop->logger, LOGGER_ERR, "Error getting ED25519 signature");
}
if (register_check) {
- char *pk_str = utils_pk_to_string((const unsigned char *)(data + 4 + X25519_KEY_SIZE), ED25519_KEY_SIZE);
bool registered_client = true;
- if (conn->raop->callbacks.check_register) {
- registered_client = conn->raop->callbacks.check_register(conn->raop->callbacks.cls, pk_str);
+ if (conn->raop->callbacks.check_register) {
+ const unsigned char *pk = data + 4 + X25519_KEY_SIZE;
+ char *pk64;
+ ed25519_pk_to_base64(pk, &pk64);
+ registered_client = conn->raop->callbacks.check_register(conn->raop->callbacks.cls, pk64);
+ free (pk64);
}
- free (pk_str);
+
if (!registered_client) {
return;
}
From 7406b000b171a41ee0b5b699b0486993ff97a900 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Tue, 12 Dec 2023 00:14:10 -0500
Subject: [PATCH 02/11] client pin-paring register is working now
---
uxplay.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 62 insertions(+), 7 deletions(-)
diff --git a/uxplay.cpp b/uxplay.cpp
index aafaa78..1af4284 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -26,6 +26,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -132,6 +133,10 @@ static unsigned short pin = 0;
static std::string keyfile = "";
static std::string mac_address = "";
static std::string dacpfile = "";
+static bool registration_list = true;
+static std::string pairing_register = "";
+static std::vector registered_keys;
+
/* logging */
static void log(int level, const char* format, ...) {
@@ -1541,15 +1546,38 @@ extern "C" void audio_set_metadata(void *cls, const void *buffer, int buflen) {
}
}
-extern "C" void register_client(void *cls, const char *device_id, const char *client_pk_str) {
- /* pair-setup-pin client registration by the server is not implemented here, do nothing*/
- LOGD("registered new client: DeviceID = %s\nPK = \"%s\"", device_id, client_pk_str);
+extern "C" void register_client(void *cls, const char *device_id, const char *client_pk) {
+ if (!registration_list) {
+ /* we are not maintaining a list of registered clients */
+ return;
+ }
+ LOGI("registered new client: DeviceID = %s PK = \n%s", device_id, client_pk);
+ registered_keys.push_back(client_pk);
+ if (strlen(pairing_register.c_str())) {
+ FILE *fp = fopen(pairing_register.c_str(), "a");
+ if (fp) {
+ fprintf(fp, "%s,%s\n", client_pk, device_id);
+ fclose(fp);
+ }
+ }
}
-extern "C" bool check_register(void *cls, const char *client_pk_str) {
- /* pair-setup-pin client registration by the server is not implemented here, return "true"*/
- LOGD("register check returning client:\nPK = \"%s\"", client_pk_str);
- return true;
+extern "C" bool check_register(void *cls, const char *client_pk) {
+ if (!registration_list) {
+ /* we are not maintaining a list of registered clients */
+ return true;
+ }
+ LOGD("check returning client registration:\n PK:%s", client_pk);
+ if (std::find(registered_keys.rbegin(), registered_keys.rend(), client_pk) != registered_keys.rend()) {
+ LOGD("client registration found");
+ return true;
+ } else {
+ LOGE("returning client's pairing registration not found,\n PK: %s", client_pk);
+ for (int i = 0; i < registered_keys.size(); i++) {
+ printf("%s\n", (registered_keys[i]).c_str());
+ }
+ return false;
+ }
}
extern "C" void log_callback (void *cls, int level, const char *msg) {
@@ -1825,6 +1853,33 @@ int main (int argc, char *argv[]) {
video_parser.append(BT709_FIX);
}
+ if (require_password && registration_list) {
+ if (pairing_register == "") {
+ const char * homedir = get_homedir();
+ if (homedir) {
+ pairing_register = homedir;
+ pairing_register.append("/.uxplay.pair_register");
+ }
+ }
+ }
+
+ /* read in public keys that were previously registered with pair-setup-pin */
+ if (require_password && registration_list && strlen(pairing_register.c_str())) {
+ char * line = NULL;
+ size_t len = 0;
+ std::string key;
+ FILE *fp = fopen(pairing_register.c_str(), "r");
+ if (fp) {
+ while ((getline(&line, &len, fp)) != -1) {
+ /*32 bytes pk -> base64 -> strlen(pk64) = 44 chars = line[0:43]; remove \n at line[44] */
+ line[44] = '\0';
+ registered_keys.push_back(key.assign(line));
+ }
+ fclose(fp);
+ free (line);
+ }
+ }
+
if (require_password && keyfile == "") {
const char * homedir = get_homedir();
if (homedir) {
From 471af2133204e6035ed396d7f0b4138da72c774b Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Tue, 26 Dec 2023 04:57:37 -0500
Subject: [PATCH 03/11] v1.68: improvements to pin-pairing
---
README.html | 72 +++++++++++++++++++++++++++++++++------------
README.md | 41 +++++++++++++++++++++-----
README.txt | 70 +++++++++++++++++++++++++++++++++----------
lib/crypto.c | 14 +++++++--
lib/crypto.h | 2 +-
lib/pairing.c | 16 ++++++----
lib/pairing.h | 2 +-
lib/raop.c | 4 +--
lib/raop.h | 4 +--
lib/raop_handlers.h | 40 ++++++++++++++++---------
uxplay.1 | 16 +++++++---
uxplay.cpp | 51 ++++++++++++++++++++++----------
12 files changed, 244 insertions(+), 88 deletions(-)
diff --git a/README.html b/README.html
index bfe24c3..216b869 100644
--- a/README.html
+++ b/README.html
@@ -1,6 +1,6 @@
UxPlay
-1.67: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix
+id="uxplay-1.68-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay
+1.68: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix
(now also runs on Windows).
Now
@@ -9,9 +9,13 @@ href="https://github.com/FDH2/UxPlay">https://github.com/FDH2/UxPlay
(where ALL user issues should be posted, and latest versions can be
found).
-- NEW in v1.67: support for one-time Apple-style
-“pin” code client authentication (“client-server pairing”) when the
-option “-pin” is used.
+- NEW in v1.68: improved support for one-time
+Apple-style “pin” codes introduced in 1.67: a register of pin-registered
+clients is now optionally maintained to check returning clients; a
+simpler method for generating a persistent public key (based on the MAC
+address, which now can be set in the UxPlay startup file) is now the
+default. (The pem-file method introduced in 1.67 is still available with
+the ’-key” option.)
Highlights:
@@ -461,12 +465,17 @@ clients to “pair” with the UxPlay server the first time they connect to
it, by entering a 4-digit pin code that is displayed on the UxPlay
terminal. (This is optional, but sometimes required if the client is a
corporately-owned and -managed device with MDM Mobile Device
-Management.) Pairing occurs just once, is curently only recorded in the
-client, and persists unless the UxPlay public key (stored in
-$HOME/.uxplay.pem, or elsewhere if option
--key <filename> is used) is moved or deleted, after
-which a new key is generated. (Non-Apple clients might not implement the
-persistence feature.)
+Management.) Pairing occurs just once, is currently only recorded in the
+client, and persists unless the UxPlay public key is changed. By default
+(since v1.68) the public key is now generated using the “Device ID”,
+which is either the server’s hardware MAC address, or can be set with
+the -m option (most conveniently using the startup option file).
+(Storage of a more securely-generated persistent key as an OpenSSL “pem”
+file is still available with the -key option). For use of uxplay in a
+more public environment, a list of previously-registered clients can
+(since v1.68) be optionally-maintained using the -reg option: without
+this option, returning clients claiming to be registered are just
+trusted and not checked.
By default, UxPlay is locked to its current client until that
client drops the connection; since UxPlay-1.58, the option
-nohold modifies this behavior so that when a new client
@@ -895,6 +904,14 @@ UxPlay startups. As long as this file is not deleted or moved, a client
will not have to re-authenticate after an initial authentication.
(Add a “pin” entry in the UxPlay startup file if you wish the UxPlay
server to use this protocol).
+-reg [filename]: (since v1.68). This option
+maintains a list of previously-pin-registered clients in
+$HOME/.uxplay.register (or optionally, in filename). Without
+this option, returning clients claiming to be already pin-registered are
+trusted and not checked. (This option may be useful if UxPlay is used in
+a more public environment, to record client details; the register is
+text, one line per client, with client’s public key (base-64 format),
+Device ID, and Device name.)
-vsync [x] (In Mirror mode:) this option
(now the default) uses timestamps to synchronize audio
with video on the server, with an optional audio delay in (decimal)
@@ -1107,13 +1124,27 @@ card, (more specifically, the MAC address used by the first active
network interface detected) a random MAC address will be used even if
option -m was not specified. (Note that a random MAC
address will be different each time UxPlay is started).
--key [filename]: By default, the storage of
-the Server private key is in the file $HOME/.uxplay.pem. Use the “-key
-filename” option to change this location. This option should be
-set in the UxPlay startup file as a line “key filename” (no
-initial “-”), where filename is a full path. The filename
-may be enclosed in quotes ("...."), (and must be, if the
-filename has any blank spaces).
+-key [filename]: This (more secure) option
+for generating and storing a persistant public key (needed for the -pin
+option) has been replaced by default with a (less secure) method which
+generates a key from the server’s “device ID” (MAC address, which can be
+changed with the -m option, conveniently as a startup file option). When
+the -key option is used, a securely generated keypair is generated and
+stored in $HOME/.uxplay.pem, if that file does not exist,
+or read from it, if it exists. (Optionally, the key can be stored in
+filename.) This method is more secure than the new default
+method, (because the Device ID is broadcast in the DNS_SD announcement)
+but still leaves the private key exposed to anyone who can access the
+pem file. Because the default (but “less-secure”) “Device ID” method is
+simpler, and security of client access to uxplay is unlikely to be an
+important issue, the -key option is no longer recommended.
+By default, the storage of the Server private key is in the file
+$HOME/.uxplay.pem. Use the “-key filename” option to change
+this location. This option should be set in the UxPlay startup file as a
+line “key filename” (no initial “-”), where
+filename is a full path. The filename may be enclosed in
+quotes ("...."), (and must be, if the filename has any
+blank spaces).
-dacp [filename]: Export current client
DACP-ID and Active-Remote key to file: default is $HOME/.uxplay.dacp.
(optionally can be changed to filename). Can be used by remote
@@ -1463,6 +1494,11 @@ an AppleTV6,2 with sourceVersion 380.20.1 (an AppleTV 4K 1st gen,
introduced 2017, running tvOS 12.2.1), so it does not seem to matter
what version UxPlay claims to be.
Changelog
+1.68 2023-12-25 Introduced a simpler (default) method for generating
+a persistent public key from the server MAC address (which can now be
+set with the -m option). (The previous pem-file method is still
+available with -key option). New option -reg to maintain a register of
+pin-authenticated clients.
1.67 2023-11-30 Add support for Apple-style one-time pin
authentication of clients with option “-pin”: (uses SRP6a authentication
protocol and public key persistence). Detection with error message of
diff --git a/README.md b/README.md
index 7827ae7..04db3aa 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,12 @@
-# UxPlay 1.67: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
+# UxPlay 1.68: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
### Now developed at the GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where ALL user issues should be posted, and latest versions can be found).
- * _**NEW in v1.67**: support for one-time Apple-style "pin" code client authentication ("client-server
- pairing") when the option "-pin" is used._
+ * _**NEW in v1.68**: improved support for one-time Apple-style "pin" codes introduced in 1.67: a register of pin-registered clients is
+ now optionally maintained to check returning clients; a simpler method for generating a persistent public key (based on
+ the MAC address, which now can be set in the UxPlay startup file) is now the default. (The pem-file method introduced in 1.67 is
+ still available with the '-key" option.)_
+
## Highlights:
@@ -375,10 +378,14 @@ help with this or other problems.
* Since v 1.67, the UxPlay option "`-pin`" allows clients to "pair" with the UxPlay server
the first time they connect to it, by entering
a 4-digit pin code that is displayed on the UxPlay terminal. (This is optional, but sometimes required if the client is a
-corporately-owned and -managed device with MDM Mobile Device Management.) Pairing occurs just once, is curently only
+corporately-owned and -managed device with MDM Mobile Device Management.) Pairing occurs just once, is currently only
recorded in the client, and persists unless the
-UxPlay public key (stored in $HOME/.uxplay.pem, or elsewhere if option `-key ` is used) is moved or deleted, after
-which a new key is generated. (Non-Apple clients might not implement the persistence feature.)
+UxPlay public key is changed. By default (since v1.68) the public key is now generated using the "Device ID", which is either the server's
+hardware MAC address, or
+can be set with the -m option (most conveniently using the startup option file). (Storage of a more securely-generated
+persistent key as an OpenSSL "pem" file is still available with the -key option). For use of uxplay in a more public environment, a
+list of previously-registered clients can (since v1.68) be optionally-maintained using the -reg option: without this
+option, returning clients claiming to be registered are just trusted and not checked.
* By default, UxPlay is locked to
its current client until that client drops the connection; since UxPlay-1.58, the option `-nohold` modifies this
@@ -708,6 +715,12 @@ with "`#`" are treated as comments, and ignored. Command line options supersede
client will not have to re-authenticate after an initial authentication. _(Add a "pin" entry in the UxPlay startup file if you wish the
UxPlay server to use this protocol)._
+**-reg [_filename_]**: (since v1.68). This option maintains a list of previously-pin-registered clients in $HOME/.uxplay.register (or optionally, in _filename_).
+ Without this option, returning clients claiming to be already pin-registered are trusted and not checked. (This option may be useful if UxPlay is used
+ in a more public environment, to record client details; the register is text, one line per client, with client's public
+ key (base-64 format), Device ID, and Device name.)
+
+
**-vsync [x]** (In Mirror mode:) this option (**now the default**) uses timestamps to synchronize audio with video on the server,
with an optional audio delay in (decimal) milliseconds (_x_ = "20.5" means 0.0205 seconds delay: positive or
negative delays less than a second are allowed.) It is needed on low-power systems such as Raspberry Pi without hardware
@@ -891,7 +904,16 @@ which will not work if a firewall is running.
a random MAC address will be used even if option **-m** was not specified.
(Note that a random MAC address will be different each time UxPlay is started).
-**-key [_filename_]**: By default, the storage of the Server private key is in the file $HOME/.uxplay.pem. Use
+**-key [_filename_]**: This (more secure) option for generating and storing a persistant public key (needed for
+ the -pin option) has been replaced by default with a (less secure) method which generates a key from the server's "device ID"
+ (MAC address, which can be changed with the -m option, conveniently as a startup file option).
+ When the -key option is used, a securely generated keypair is generated and stored in `$HOME/.uxplay.pem`, if that file does not exist,
+ or read from it, if it exists. (Optionally, the key can be stored in _filename_.) This method is more secure than the new default method,
+ (because the Device ID is broadcast in the DNS_SD announcement) but still leaves the private key exposed to anyone who can access the pem file.
+ Because the default (but "less-secure") "Device ID" method is simpler, and security of client access to uxplay is unlikely to be an important issue,
+ the -key option is no longer recommended.
+
+By default, the storage of the Server private key is in the file $HOME/.uxplay.pem. Use
the "-key _filename_" option to change this location. This option should be set in the UxPlay startup file
as a line "`key filename`" (no initial "-"), where ``filename`` is a full path. The filename may be enclosed
in quotes (`"...."`), (and must be, if the filename has any blank spaces).
@@ -1156,6 +1178,11 @@ tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be.
# Changelog
+1.68 2023-12-25 Introduced a simpler (default) method for generating a persistent public key from the server MAC
+ address (which can now be set with the -m option).
+ (The previous pem-file method is still available with -key option).
+ New option -reg to maintain a register of pin-authenticated clients.
+
1.67 2023-11-30 Add support for Apple-style one-time pin authentication of clients with option "-pin":
(uses SRP6a authentication protocol and public key persistence). Detection with error message
of (currently) unsupported H265 video when requesting high resolution over wired ethernet.
diff --git a/README.txt b/README.txt
index 1035f1e..6453826 100644
--- a/README.txt
+++ b/README.txt
@@ -1,10 +1,14 @@
-# UxPlay 1.67: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
+# UxPlay 1.68: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
### Now developed at the GitHub site (where ALL user issues should be posted, and latest versions can be found).
-- ***NEW in v1.67**: support for one-time Apple-style "pin" code
- client authentication ("client-server pairing") when the option
- "-pin" is used.*
+- ***NEW in v1.68**: improved support for one-time Apple-style "pin"
+ codes introduced in 1.67: a register of pin-registered clients is
+ now optionally maintained to check returning clients; a simpler
+ method for generating a persistent public key (based on the MAC
+ address, which now can be set in the UxPlay startup file) is now the
+ default. (The pem-file method introduced in 1.67 is still available
+ with the '-key" option.)*
## Highlights:
@@ -451,11 +455,18 @@ below for help with this or other problems.
entering a 4-digit pin code that is displayed on the UxPlay
terminal. (This is optional, but sometimes required if the client is
a corporately-owned and -managed device with MDM Mobile Device
- Management.) Pairing occurs just once, is curently only recorded in
- the client, and persists unless the UxPlay public key (stored in
- \$HOME/.uxplay.pem, or elsewhere if option `-key ` is
- used) is moved or deleted, after which a new key is generated.
- (Non-Apple clients might not implement the persistence feature.)
+ Management.) Pairing occurs just once, is currently only recorded in
+ the client, and persists unless the UxPlay public key is changed. By
+ default (since v1.68) the public key is now generated using the
+ "Device ID", which is either the server's hardware MAC address, or
+ can be set with the -m option (most conveniently using the startup
+ option file). (Storage of a more securely-generated persistent key
+ as an OpenSSL "pem" file is still available with the -key option).
+ For use of uxplay in a more public environment, a list of
+ previously-registered clients can (since v1.68) be
+ optionally-maintained using the -reg option: without this option,
+ returning clients claiming to be registered are just trusted and not
+ checked.
- By default, UxPlay is locked to its current client until that client
drops the connection; since UxPlay-1.58, the option `-nohold`
@@ -895,6 +906,14 @@ re-authenticate after an initial authentication. *(Add a "pin" entry in
the UxPlay startup file if you wish the UxPlay server to use this
protocol).*
+**-reg \[*filename*\]**: (since v1.68). This option maintains a list of
+previously-pin-registered clients in \$HOME/.uxplay.register (or
+optionally, in *filename*). Without this option, returning clients
+claiming to be already pin-registered are trusted and not checked. (This
+option may be useful if UxPlay is used in a more public environment, to
+record client details; the register is text, one line per client, with
+client's public key (base-64 format), Device ID, and Device name.)
+
**-vsync \[x\]** (In Mirror mode:) this option (**now the default**)
uses timestamps to synchronize audio with video on the server, with an
optional audio delay in (decimal) milliseconds (*x* = "20.5" means
@@ -1125,12 +1144,27 @@ network interface detected) a random MAC address will be used even if
option **-m** was not specified. (Note that a random MAC address will be
different each time UxPlay is started).
-**-key \[*filename*\]**: By default, the storage of the Server private
-key is in the file \$HOME/.uxplay.pem. Use the "-key *filename*" option
-to change this location. This option should be set in the UxPlay startup
-file as a line "`key filename`" (no initial "-"), where `filename` is a
-full path. The filename may be enclosed in quotes (`"...."`), (and must
-be, if the filename has any blank spaces).
+**-key \[*filename*\]**: This (more secure) option for generating and
+storing a persistant public key (needed for the -pin option) has been
+replaced by default with a (less secure) method which generates a key
+from the server's "device ID" (MAC address, which can be changed with
+the -m option, conveniently as a startup file option). When the -key
+option is used, a securely generated keypair is generated and stored in
+`$HOME/.uxplay.pem`, if that file does not exist, or read from it, if it
+exists. (Optionally, the key can be stored in *filename*.) This method
+is more secure than the new default method, (because the Device ID is
+broadcast in the DNS_SD announcement) but still leaves the private key
+exposed to anyone who can access the pem file. Because the default (but
+"less-secure") "Device ID" method is simpler, and security of client
+access to uxplay is unlikely to be an important issue, the -key option
+is no longer recommended.
+
+By default, the storage of the Server private key is in the file
+\$HOME/.uxplay.pem. Use the "-key *filename*" option to change this
+location. This option should be set in the UxPlay startup file as a line
+"`key filename`" (no initial "-"), where `filename` is a full path. The
+filename may be enclosed in quotes (`"...."`), (and must be, if the
+filename has any blank spaces).
**-dacp \[*filename*\]**: Export current client DACP-ID and
Active-Remote key to file: default is \$HOME/.uxplay.dacp. (optionally
@@ -1497,6 +1531,12 @@ what version UxPlay claims to be.
# Changelog
+1.68 2023-12-25 Introduced a simpler (default) method for generating a
+persistent public key from the server MAC address (which can now be set
+with the -m option). (The previous pem-file method is still available
+with -key option). New option -reg to maintain a register of
+pin-authenticated clients.
+
1.67 2023-11-30 Add support for Apple-style one-time pin authentication
of clients with option "-pin": (uses SRP6a authentication protocol and
public key persistence). Detection with error message of (currently)
diff --git a/lib/crypto.c b/lib/crypto.c
index a3da644..906a2fe 100644
--- a/lib/crypto.c
+++ b/lib/crypto.c
@@ -35,6 +35,8 @@
#include "utils.h"
+#define SALT_PK "UxPlay-Persistent-Not-Secure-Public-Key"
+
struct aes_ctx_s {
EVP_CIPHER_CTX *cipher_ctx;
uint8_t key[AES_128_BLOCK_SIZE];
@@ -352,7 +354,7 @@ struct ed25519_key_s {
EVP_PKEY *pkey;
};
-ed25519_key_t *ed25519_key_generate(const char *keyfile, int *result) {
+ed25519_key_t *ed25519_key_generate(const char *device_id, const char *keyfile, int *result) {
ed25519_key_t *key;
EVP_PKEY_CTX *pctx;
BIO *bp;
@@ -379,7 +381,15 @@ ed25519_key_t *ed25519_key_generate(const char *keyfile, int *result) {
new_pk = true;
}
} else {
- new_pk = true;
+ /* generate (insecure) persistent keypair using device_id */
+ unsigned char hash[SHA512_DIGEST_LENGTH];
+ char salt[] = SALT_PK;
+ sha_ctx_t *ctx = sha_init();
+ sha_update(ctx, (const unsigned char *) salt, (unsigned int) strlen(salt));
+ sha_update(ctx, (const unsigned char *) device_id, (unsigned int) strlen(device_id));
+ sha_final(ctx, hash, NULL);
+ sha_destroy(ctx);
+ key->pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, (const unsigned char *) hash, ED25519_KEY_SIZE);
}
if (new_pk) {
diff --git a/lib/crypto.h b/lib/crypto.h
index 7f3f758..7bd2d5e 100644
--- a/lib/crypto.h
+++ b/lib/crypto.h
@@ -83,7 +83,7 @@ int gcm_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *pl
typedef struct ed25519_key_s ed25519_key_t;
-ed25519_key_t *ed25519_key_generate(const char * keyfile, int * result);
+ed25519_key_t *ed25519_key_generate(const char *device_id, const char * keyfile, int * result);
ed25519_key_t *ed25519_key_from_raw(const unsigned char data[ED25519_KEY_SIZE]);
void ed25519_key_get_raw(unsigned char data[ED25519_KEY_SIZE], const ed25519_key_t *key);
/*
diff --git a/lib/pairing.c b/lib/pairing.c
index 590d0dc..e60273c 100644
--- a/lib/pairing.c
+++ b/lib/pairing.c
@@ -84,7 +84,7 @@ derive_key_internal(pairing_session_t *session, const unsigned char *salt, unsig
}
pairing_t *
-pairing_init_generate(const char * keyfile, int *result)
+pairing_init_generate(const char *device_id, const char *keyfile, int *result)
{
pairing_t *pairing;
*result = 0;
@@ -93,7 +93,7 @@ pairing_init_generate(const char * keyfile, int *result)
return NULL;
}
- pairing->ed = ed25519_key_generate(keyfile, result);
+ pairing->ed = ed25519_key_generate(device_id, keyfile, result);
return pairing;
}
@@ -136,7 +136,7 @@ pairing_session_init(pairing_t *pairing)
session->status = STATUS_INITIAL;
session->srp = NULL;
-
+ session->pair_setup = false;
return session;
}
@@ -449,10 +449,14 @@ srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing,
void access_client_session_data(pairing_session_t *session, char **username, char **client_pk64, bool *setup) {
int len64 = 4 * (1 + (ED25519_KEY_SIZE / 3)) + 1;
- *client_pk64 = (char *) malloc(len64);
- *username = session->username;
- pk_to_base64(session->client_pk, ED25519_KEY_SIZE, *client_pk64, len64);
setup = &(session->pair_setup);
+ *username = session->username;
+ if (setup) {
+ *client_pk64 = (char *) malloc(len64);
+ pk_to_base64(session->client_pk, ED25519_KEY_SIZE, *client_pk64, len64);
+ } else {
+ *client_pk64 = NULL;
+ }
}
void ed25519_pk_to_base64(const unsigned char *pk, char **pk64) {
diff --git a/lib/pairing.h b/lib/pairing.h
index 84f2077..54db62b 100644
--- a/lib/pairing.h
+++ b/lib/pairing.h
@@ -36,7 +36,7 @@
typedef struct pairing_s pairing_t;
typedef struct pairing_session_s pairing_session_t;
-pairing_t *pairing_init_generate(const char * keyfile, int *result);
+pairing_t *pairing_init_generate(const char *device_id, const char *keyfile, int *result);
void pairing_get_public_key(pairing_t *pairing, unsigned char public_key[ED25519_KEY_SIZE]);
pairing_session_t *pairing_session_init(pairing_t *pairing);
diff --git a/lib/raop.c b/lib/raop.c
index c42f177..ef9b009 100644
--- a/lib/raop.c
+++ b/lib/raop.c
@@ -411,7 +411,7 @@ conn_destroy(void *ptr) {
}
raop_t *
-raop_init(int max_clients, raop_callbacks_t *callbacks, const char* keyfile) {
+raop_init(int max_clients, raop_callbacks_t *callbacks, const char *device_id, const char *keyfile) {
raop_t *raop;
pairing_t *pairing;
httpd_t *httpd;
@@ -443,7 +443,7 @@ raop_init(int max_clients, raop_callbacks_t *callbacks, const char* keyfile) {
/* create a new public key for pairing */
int new_key;
- pairing = pairing_init_generate(keyfile, &new_key);
+ pairing = pairing_init_generate(device_id, keyfile, &new_key);
if (!pairing) {
free(raop);
return NULL;
diff --git a/lib/raop.h b/lib/raop.h
index ffbeab3..2888d3d 100644
--- a/lib/raop.h
+++ b/lib/raop.h
@@ -60,7 +60,7 @@ struct raop_callbacks_s {
void (*video_report_size)(void *cls, float *width_source, float *height_source, float *width, float *height);
void (*report_client_request) (void *cls, char *deviceid, char *model, char *name, bool *admit);
void (*display_pin) (void *cls, char * pin);
- void (*register_client) (void *cls, const char *device_id, const char *pk_str);
+ void (*register_client) (void *cls, const char *device_id, const char *pk_str, const char *name);
bool (*check_register) (void *cls, const char *pk_str);
void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id);
};
@@ -68,7 +68,7 @@ typedef struct raop_callbacks_s raop_callbacks_t;
raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *remote, int remote_addr_len,
unsigned short timing_rport, timing_protocol_t *time_protocol);
- RAOP_API raop_t *raop_init(int max_clients, raop_callbacks_t *callbacks, const char* keyfile);
+RAOP_API raop_t *raop_init(int max_clients, raop_callbacks_t *callbacks, const char *device_id, const char *keyfile);
RAOP_API void raop_set_log_level(raop_t *raop, int level);
RAOP_API void raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls);
RAOP_API int raop_set_plist(raop_t *raop, const char *plist_item, const int value);
diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h
index 4bb21d5..fff801b 100644
--- a/lib/raop_handlers.h
+++ b/lib/raop_handlers.h
@@ -341,14 +341,6 @@ raop_handler_pairsetup_pin(raop_conn_t *conn,
logger_log(conn->raop->logger, LOGGER_ERR, "pair-pin-setup (step 3): client authentication failed\n");
goto authentication_failed;
} else {
- bool client_pair_setup;
- char *client_device_id;
- char *client_pk; /* encoded as null-terminated base64 string*/
- access_client_session_data(conn->session, &client_device_id, &client_pk, &client_pair_setup);
- if (conn->raop->callbacks.register_client) {
- conn->raop->callbacks.register_client(conn->raop->callbacks.cls, client_device_id, client_pk);
- }
- free (client_pk);
logger_log(conn->raop->logger, LOGGER_DEBUG, "pair-pin-setup success\n");
}
pairing_session_set_setup_status(conn->session);
@@ -586,12 +578,32 @@ raop_handler_setup(raop_conn_t *conn,
if (conn->raop->callbacks.report_client_request) {
conn->raop->callbacks.report_client_request(conn->raop->callbacks.cls, deviceID, model, name, &admit_client);
}
- free (deviceID);
- deviceID = NULL;
- free (model);
- model = NULL;
- free (name);
- name = NULL;
+ if (admit_client && deviceID && name && conn->raop->callbacks.register_client) {
+ bool pending_registration;
+ char *client_device_id;
+ char *client_pk; /* encoded as null-terminated base64 string*/
+ access_client_session_data(conn->session, &client_device_id, &client_pk, &pending_registration);
+ if (pending_registration) {
+ if (client_pk && !strcmp(deviceID, client_device_id)) {
+ conn->raop->callbacks.register_client(conn->raop->callbacks.cls, client_device_id, client_pk, name);
+ }
+ }
+ if (client_pk) {
+ free (client_pk);
+ }
+ }
+ if (deviceID) {
+ free (deviceID);
+ deviceID = NULL;
+ }
+ if (model) {
+ free (model);
+ model = NULL;
+ }
+ if (name) {
+ free (name);
+ name = NULL;
+ }
if (admit_client == false) {
/* client is not authorized to connect */
plist_free(res_root_node);
diff --git a/uxplay.1 b/uxplay.1
index 200e052..99f3a79 100644
--- a/uxplay.1
+++ b/uxplay.1
@@ -1,11 +1,11 @@
-.TH UXPLAY "1" "November 2023" "1.67" "User Commands"
+.TH UXPLAY "1" "December 2023" "1.68" "User Commands"
.SH NAME
uxplay \- start AirPlay server
.SH SYNOPSIS
.B uxplay
[\fI\,-n name\/\fR] [\fI\,-s wxh\/\fR] [\fI\,-p \/\fR[\fI\,n\/\fR]] [more \fI OPTIONS \/\fR ...]
.SH DESCRIPTION
-UxPlay 1.67: An open\-source AirPlay mirroring (+ audio streaming) server:
+UxPlay 1.68: An open\-source AirPlay mirroring (+ audio streaming) server:
.SH OPTIONS
.TP
.B
@@ -17,6 +17,10 @@ UxPlay 1.67: An open\-source AirPlay mirroring (+ audio streaming) server:
.IP
without option, pin is random: optionally use fixed pin xxxx.
.TP
+\fB\-reg\fI [fn]\fR Keep a register in $HOME/.uxplay.register to verify returning
+.IP
+ client pin-registration; (option: use file "fn" for this)
+.TP
\fB\-vsync\fI[x]\fR Mirror mode: sync audio to video using timestamps (default)
.IP
\fIx\fR is optional audio delay: millisecs, decimal, can be neg.
@@ -106,9 +110,13 @@ UxPlay 1.67: An open\-source AirPlay mirroring (+ audio streaming) server:
.TP
\fB\-r\fR {R|L} Rotate 90 degrees Right (cw) or Left (ccw)
.TP
-\fB\-m\fR Use random MAC address (use for concurrent UxPlay's)
+\fB\-m\fI [mac]\fR Set MAC address (also Device ID); use for concurrent UxPlays
+.IP
+ if mac xx:xx:xx:xx:xx:xx is not given, a random MAC is used.
+.PP
.TP
-\fB\-key\fI fn \fR Store private key in file fn (Default: $HOME/.uxplay.pem)
+\fB\-key\fI [fn]\fR Store private key in $HOME/.uxplay.pem (or in file "fn")
+.PP
.TP
\fB\-dacp\fI [fn]\fRExport client DACP information to file $HOME/.uxplay.dacp
.IP
diff --git a/uxplay.cpp b/uxplay.cpp
index 1af4284..623d220 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -61,7 +61,7 @@
#include "renderers/video_renderer.h"
#include "renderers/audio_renderer.h"
-#define VERSION "1.67"
+#define VERSION "1.68"
#define SECOND_IN_USECS 1000000
#define SECOND_IN_NSECS 1000000000UL
@@ -133,7 +133,7 @@ static unsigned short pin = 0;
static std::string keyfile = "";
static std::string mac_address = "";
static std::string dacpfile = "";
-static bool registration_list = true;
+static bool registration_list = false;
static std::string pairing_register = "";
static std::vector registered_keys;
@@ -566,7 +566,9 @@ static void print_info (char *name) {
printf("-n name Specify the network name of the AirPlay server\n");
printf("-nh Do not add \"@hostname\" at the end of AirPlay server name\n");
printf("-pin[xxxx]Use a 4-digit pin code to control client access (default: no)\n");
- printf(" without option, pin is random: optionally use fixed pin xxxx\n");
+ printf(" default pin is random: optionally use fixed pin xxxx\n");
+ printf("-reg [fn] Keep a register in $HOME/.uxplay.register to verify returning\n");
+ printf(" client pin-registration; (option: use file \"fn\" for this)\n");
printf("-vsync [x]Mirror mode: sync audio to video using timestamps (default)\n");
printf(" x is optional audio delay: millisecs, decimal, can be neg.\n");
printf("-vsync no Switch off audio/(server)video timestamp synchronization \n");
@@ -612,8 +614,8 @@ static void print_info (char *name) {
printf("-f {H|V|I}Horizontal|Vertical flip, or both=Inversion=rotate 180 deg\n");
printf("-r {R|L} Rotate 90 degrees Right (cw) or Left (ccw)\n");
printf("-m [mac] Set MAC address (also Device ID);use for concurrent UxPlays\n");
- printf(" if mac xx:xx:xx:xx:xx:xx is not given, a random mac is used\n");
- printf("-key Store private key in file (default:$HOME/.uxplay.pem)\n");
+ printf(" if mac xx:xx:xx:xx:xx:xx is not given, a random MAC is used\n");
+ printf("-key [fn] Store private key in $HOME/.uxplay.pem (or in file \"fn\")\n");
printf("-dacp [fn]Export client DACP information to file $HOME/.uxplay.dacp\n");
printf(" (option to use file \"fn\" instead); used for client remote\n");
printf("-vdmp [n] Dump h264 video output to \"fn.h264\"; fn=\"videodump\",change\n");
@@ -1049,6 +1051,17 @@ static void parse_arguments (int argc, char *argv[]) {
}
pin = n + 10000;
}
+ } else if (arg == "-reg") {
+ registration_list = true;
+ pairing_register.erase();
+ if (i < argc - 1 && *argv[i+1] != '-') {
+ pairing_register.append(argv[++i]);
+ const char * fn = pairing_register.c_str();
+ if (!file_has_write_access(fn)) {
+ fprintf(stderr, "%s cannot be written to:\noption \"-key \" must be to a file with write access\n", fn);
+ exit(1);
+ }
+ }
} else if (arg == "-key") {
keyfile.erase();
if (i < argc - 1 && *argv[i+1] != '-') {
@@ -1059,8 +1072,10 @@ static void parse_arguments (int argc, char *argv[]) {
exit(1);
}
} else {
- fprintf(stderr, "option \"-key \" requires a path to a file for persistent key storage\n");
- exit(1);
+ // fprintf(stderr, "option \"-key \" requires a path to a file for persistent key storage\n");
+ // exit(1);
+ keyfile.erase();
+ keyfile.append("0");
}
} else if (arg == "-dacp") {
dacpfile.erase();
@@ -1546,17 +1561,17 @@ extern "C" void audio_set_metadata(void *cls, const void *buffer, int buflen) {
}
}
-extern "C" void register_client(void *cls, const char *device_id, const char *client_pk) {
+extern "C" void register_client(void *cls, const char *device_id, const char *client_pk, const char *client_name) {
if (!registration_list) {
/* we are not maintaining a list of registered clients */
return;
}
- LOGI("registered new client: DeviceID = %s PK = \n%s", device_id, client_pk);
+ LOGI("registered new client: %s DeviceID = %s PK = \n%s", client_name, device_id, client_pk);
registered_keys.push_back(client_pk);
if (strlen(pairing_register.c_str())) {
FILE *fp = fopen(pairing_register.c_str(), "a");
if (fp) {
- fprintf(fp, "%s,%s\n", client_pk, device_id);
+ fprintf(fp, "%s,%s,%s\n", client_pk, device_id, client_name);
fclose(fp);
}
}
@@ -1628,7 +1643,7 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3],
raop_cbs.export_dacp = export_dacp;
/* set max number of connections = 2 to protect against capture by new client */
- raop = raop_init(max_connections, &raop_cbs, keyfile.c_str());
+ raop = raop_init(max_connections, &raop_cbs, mac_address.c_str(), keyfile.c_str());
if (raop == NULL) {
LOGE("Error initializing raop!");
return -1;
@@ -1858,7 +1873,7 @@ int main (int argc, char *argv[]) {
const char * homedir = get_homedir();
if (homedir) {
pairing_register = homedir;
- pairing_register.append("/.uxplay.pair_register");
+ pairing_register.append("/.uxplay.register");
}
}
}
@@ -1869,25 +1884,30 @@ int main (int argc, char *argv[]) {
size_t len = 0;
std::string key;
FILE *fp = fopen(pairing_register.c_str(), "r");
+ int clients = 0;
if (fp) {
- while ((getline(&line, &len, fp)) != -1) {
+ while ((getline(&line, &len, fp)) != -1) {
/*32 bytes pk -> base64 -> strlen(pk64) = 44 chars = line[0:43]; remove \n at line[44] */
line[44] = '\0';
registered_keys.push_back(key.assign(line));
+ clients ++;
+ }
+ if (clients) {
+ LOGI("Register %s lists %d pin-registered clients", pairing_register.c_str(), clients);
}
fclose(fp);
free (line);
}
}
- if (require_password && keyfile == "") {
+ if (require_password && keyfile == "0") {
const char * homedir = get_homedir();
if (homedir) {
keyfile.erase();
keyfile = homedir;
keyfile.append("/.uxplay.pem");
} else {
- LOGE("could not determine $HOME: public key wiil no be saved, and so will not be persistent");
+ LOGE("could not determine $HOME: public key wiil not be saved, and so will not be persistent");
}
}
@@ -1938,7 +1958,6 @@ int main (int argc, char *argv[]) {
LOGI("using randomly-generated MAC address %s",mac_address.c_str());
}
parse_hw_addr(mac_address, server_hw_addr);
- mac_address.clear();
if (coverart_filename.length()) {
LOGI("any AirPlay audio cover-art will be written to file %s",coverart_filename.c_str());
From 48f810c3447571c82959bd1e6110a2a94b3e4db5 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Tue, 26 Dec 2023 15:03:15 -0500
Subject: [PATCH 04/11] README updates
---
README.html | 22 +++++++++++-----------
README.md | 2 +-
README.txt | 24 ++++++++++++------------
3 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/README.html b/README.html
index 216b869..515febb 100644
--- a/README.html
+++ b/README.html
@@ -465,17 +465,17 @@ clients to “pair” with the UxPlay server the first time they connect to
it, by entering a 4-digit pin code that is displayed on the UxPlay
terminal. (This is optional, but sometimes required if the client is a
corporately-owned and -managed device with MDM Mobile Device
-Management.) Pairing occurs just once, is currently only recorded in the
-client, and persists unless the UxPlay public key is changed. By default
-(since v1.68) the public key is now generated using the “Device ID”,
-which is either the server’s hardware MAC address, or can be set with
-the -m option (most conveniently using the startup option file).
-(Storage of a more securely-generated persistent key as an OpenSSL “pem”
-file is still available with the -key option). For use of uxplay in a
-more public environment, a list of previously-registered clients can
-(since v1.68) be optionally-maintained using the -reg option: without
-this option, returning clients claiming to be registered are just
-trusted and not checked.
+Management.) Pairing occurs just once, is currently only recorded by the
+client unless the -reg option is used, and persists until the UxPlay
+public key is changed. By default (since v1.68) the public key is now
+generated using the “Device ID”, which is either the server’s hardware
+MAC address, or can be set with the -m option (most conveniently using
+the startup option file). (Storage of a more securely-generated
+persistent key as an OpenSSL “pem” file is still available with the -key
+option). For use of uxplay in a more public environment, a list of
+previously-registered clients can (since v1.68) be optionally-maintained
+using the -reg option: without this option, returning clients claiming
+to be registered are just trusted and not checked.
By default, UxPlay is locked to its current client until that
client drops the connection; since UxPlay-1.58, the option
-nohold modifies this behavior so that when a new client
diff --git a/README.md b/README.md
index 04db3aa..eb97463 100644
--- a/README.md
+++ b/README.md
@@ -379,7 +379,7 @@ help with this or other problems.
the first time they connect to it, by entering
a 4-digit pin code that is displayed on the UxPlay terminal. (This is optional, but sometimes required if the client is a
corporately-owned and -managed device with MDM Mobile Device Management.) Pairing occurs just once, is currently only
-recorded in the client, and persists unless the
+recorded by the client unless the -reg option is used, and persists until the
UxPlay public key is changed. By default (since v1.68) the public key is now generated using the "Device ID", which is either the server's
hardware MAC address, or
can be set with the -m option (most conveniently using the startup option file). (Storage of a more securely-generated
diff --git a/README.txt b/README.txt
index 6453826..664bd5c 100644
--- a/README.txt
+++ b/README.txt
@@ -455,18 +455,18 @@ below for help with this or other problems.
entering a 4-digit pin code that is displayed on the UxPlay
terminal. (This is optional, but sometimes required if the client is
a corporately-owned and -managed device with MDM Mobile Device
- Management.) Pairing occurs just once, is currently only recorded in
- the client, and persists unless the UxPlay public key is changed. By
- default (since v1.68) the public key is now generated using the
- "Device ID", which is either the server's hardware MAC address, or
- can be set with the -m option (most conveniently using the startup
- option file). (Storage of a more securely-generated persistent key
- as an OpenSSL "pem" file is still available with the -key option).
- For use of uxplay in a more public environment, a list of
- previously-registered clients can (since v1.68) be
- optionally-maintained using the -reg option: without this option,
- returning clients claiming to be registered are just trusted and not
- checked.
+ Management.) Pairing occurs just once, is currently only recorded by
+ the client unless the -reg option is used, and persists until the
+ UxPlay public key is changed. By default (since v1.68) the public
+ key is now generated using the "Device ID", which is either the
+ server's hardware MAC address, or can be set with the -m option
+ (most conveniently using the startup option file). (Storage of a
+ more securely-generated persistent key as an OpenSSL "pem" file is
+ still available with the -key option). For use of uxplay in a more
+ public environment, a list of previously-registered clients can
+ (since v1.68) be optionally-maintained using the -reg option:
+ without this option, returning clients claiming to be registered are
+ just trusted and not checked.
- By default, UxPlay is locked to its current client until that client
drops the connection; since UxPlay-1.58, the option `-nohold`
From 624fd4138636cff74b3f52fa6e93be0fa2fdd702 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Tue, 26 Dec 2023 19:09:06 -0500
Subject: [PATCH 05/11] correct volume control to match airplay spec, add a -db
l:h option
---
README.html | 36 +++++++++++++++++++++-------
README.md | 27 ++++++++++++++-------
README.txt | 36 +++++++++++++++++++++-------
renderers/audio_renderer.h | 3 ++-
renderers/audio_renderer_gstreamer.c | 22 +++++++++++------
uxplay.1 | 5 ++++
uxplay.cpp | 32 ++++++++++++++++++++++++-
7 files changed, 126 insertions(+), 35 deletions(-)
diff --git a/README.html b/README.html
index 515febb..fb7ddaa 100644
--- a/README.html
+++ b/README.html
@@ -9,13 +9,13 @@ href="https://github.com/FDH2/UxPlay">https://github.com/FDH2/UxPlay
(where ALL user issues should be posted, and latest versions can be
found).
-- NEW in v1.68: improved support for one-time
-Apple-style “pin” codes introduced in 1.67: a register of pin-registered
-clients is now optionally maintained to check returning clients; a
-simpler method for generating a persistent public key (based on the MAC
-address, which now can be set in the UxPlay startup file) is now the
-default. (The pem-file method introduced in 1.67 is still available with
-the ’-key” option.)
+- NEW in v1.68: Volume-control improvements, plus
+improved support for Apple-style one-time “pin” codes introduced in
+1.67: a register of pin-registered clients can now optionally be
+maintained to check returning clients; a simpler method for generating a
+persistent public key (based on the MAC address, which can be set in the
+UxPlay startup file) is now the default. (The OpenSSL “pem-file” method
+introduced in 1.67 is still available with the ’-key” option.)
Highlights:
@@ -517,6 +517,11 @@ on the client to match audio on the server, so leads to a slight delay
before a pause or track-change initiated on the client takes effect on
the audio played by the server.
+AirPlay volume-control attenuates volume (gain) by up to -30dB: the
+range -30dB:0dB can be rescaled from Low:0 (Low <
+0), or Low:High, using the option
+“-db _Low_” or “-db _Low_:_High_” (Rescaling
+is linear in decibels).
The -vsync and -async options also allow an optional positive (or
negative) audio-delay adjustment in milliseconds for
fine-tuning : -vsync 20.5 delays audio relative to video by
@@ -941,6 +946,17 @@ have any effect.
Audio-only mode, but this option may be useful as a command-line option
to switch off a -async option set in a “uxplayrc”
configuration file.
+-db low[:high] Rescales the
+AirPlay volume-control attenuation (gain) from -30dB:0dB to
+low:0dB or low:high. The lower limit
+low must be negative (attenuation); the upper limit
+high can be either sign. (GStreamer restricts
+volume-augmentation by high so that it cannot exceed +20dB).
+The rescaling is “flat”, so that for -db -50:10, a change in Airplay
+attenuation by -7dB is translated to a -7 x (60/30) = -14dB attenuation,
+and the maximum volume (AirPlay 0dB) is a 10dB augmentation, and Airplay
+-30dB would become -50dB. Note that the minimum AirPlay value (-30dB
+exactly) is translated to “mute”.
-s wxh (e.g. -s 1920x1080 , which is the default )
sets the display resolution (width and height, in pixels). (This may be
a request made to the AirPlay client, and perhaps will not be the final
@@ -1494,11 +1510,13 @@ an AppleTV6,2 with sourceVersion 380.20.1 (an AppleTV 4K 1st gen,
introduced 2017, running tvOS 12.2.1), so it does not seem to matter
what version UxPlay claims to be.
Changelog
-1.68 2023-12-25 Introduced a simpler (default) method for generating
+
1.68 2023-12-26 Introduced a simpler (default) method for generating
a persistent public key from the server MAC address (which can now be
set with the -m option). (The previous pem-file method is still
available with -key option). New option -reg to maintain a register of
-pin-authenticated clients.
+pin-authenticated clients. Corrected volume-control: now inteprets
+AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation,
+with new option -db low[:high] for “flat” rescaling of the dB range.
1.67 2023-11-30 Add support for Apple-style one-time pin
authentication of clients with option “-pin”: (uses SRP6a authentication
protocol and public key persistence). Detection with error message of
diff --git a/README.md b/README.md
index eb97463..8fadec5 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,10 @@
### Now developed at the GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where ALL user issues should be posted, and latest versions can be found).
- * _**NEW in v1.68**: improved support for one-time Apple-style "pin" codes introduced in 1.67: a register of pin-registered clients is
- now optionally maintained to check returning clients; a simpler method for generating a persistent public key (based on
- the MAC address, which now can be set in the UxPlay startup file) is now the default. (The pem-file method introduced in 1.67 is
- still available with the '-key" option.)_
+ * _**NEW in v1.68**: Volume-control improvements, plus improved support for Apple-style one-time "pin" codes introduced in 1.67: a
+ register of pin-registered clients can now optionally be maintained to check returning clients; a simpler method for generating
+ a persistent public key (based on the MAC address, which can be set in the UxPlay startup file) is now the default. (The OpenSSL
+ "pem-file" method introduced in 1.67 is still available with the '-key" option.)_
## Highlights:
@@ -415,6 +415,9 @@ if you want to follow the Apple Music lyrics on the client while listening to su
delays the video on the client to match audio on the server, so leads to
a slight delay before a pause or track-change initiated on the client takes effect on the audio played by the server.
+AirPlay volume-control attenuates volume (gain) by up to -30dB: the range -30dB:0dB can be rescaled from _Low_:0 (_Low_ < 0), or _Low_:_High_, using the
+option "`-db _Low_`" or "``-db _Low_:_High_``" (Rescaling is linear in decibels).
+
The -vsync and -async options
also allow an optional positive (or negative) audio-delay adjustment in _milliseconds_ for fine-tuning : `-vsync 20.5`
delays audio relative to video by 0.0205 secs; a negative value advances it.)
@@ -741,6 +744,13 @@ using UxPlay as a second monitor for a mac computer, or monitoring a webcam; wit
**-async no**. This is the still the default behavior in Audio-only mode, but this option may be useful as a command-line option to switch off a
`-async` option set in a "uxplayrc" configuration file.
+**-db _low_[:_high_]** Rescales the AirPlay volume-control attenuation (gain) from -30dB:0dB to _low_:0dB or _low_:_high_. The lower limit _low_
+ must be negative (attenuation); the upper limit _high_ can be either sign. (GStreamer restricts volume-augmentation by _high_ so that it
+ cannot exceed +20dB).
+ The rescaling is "flat", so that for -db -50:10, a change in Airplay attenuation by -7dB is translated to a -7 x (60/30) = -14dB attenuation,
+ and the maximum volume (AirPlay 0dB) is a 10dB augmentation, and Airplay -30dB would become -50dB. Note that the minimum AirPlay value (-30dB exactly)
+ is translated to "mute".
+
**-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display resolution (width and height,
in pixels). (This may be a
request made to the AirPlay client, and perhaps will not
@@ -1178,10 +1188,11 @@ tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be.
# Changelog
-1.68 2023-12-25 Introduced a simpler (default) method for generating a persistent public key from the server MAC
- address (which can now be set with the -m option).
- (The previous pem-file method is still available with -key option).
- New option -reg to maintain a register of pin-authenticated clients.
+1.68 2023-12-26 Introduced a simpler (default) method for generating a persistent public key from the server MAC
+ address (which can now be set with the -m option). (The previous pem-file method is still available
+ with -key option). New option -reg to maintain a register of pin-authenticated clients. Corrected
+ volume-control: now inteprets AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation,
+ with new option -db low[:high] for "flat" rescaling of the dB range.
1.67 2023-11-30 Add support for Apple-style one-time pin authentication of clients with option "-pin":
(uses SRP6a authentication protocol and public key persistence). Detection with error message
diff --git a/README.txt b/README.txt
index 664bd5c..b2e488a 100644
--- a/README.txt
+++ b/README.txt
@@ -2,13 +2,14 @@
### Now developed at the GitHub site (where ALL user issues should be posted, and latest versions can be found).
-- ***NEW in v1.68**: improved support for one-time Apple-style "pin"
- codes introduced in 1.67: a register of pin-registered clients is
- now optionally maintained to check returning clients; a simpler
- method for generating a persistent public key (based on the MAC
- address, which now can be set in the UxPlay startup file) is now the
- default. (The pem-file method introduced in 1.67 is still available
- with the '-key" option.)*
+- ***NEW in v1.68**: Volume-control improvements, plus improved
+ support for Apple-style one-time "pin" codes introduced in 1.67: a
+ register of pin-registered clients can now optionally be maintained
+ to check returning clients; a simpler method for generating a
+ persistent public key (based on the MAC address, which can be set in
+ the UxPlay startup file) is now the default. (The OpenSSL "pem-file"
+ method introduced in 1.67 is still available with the '-key"
+ option.)*
## Highlights:
@@ -509,6 +510,11 @@ helped to prevent this previously when timestamps were not being used.)
slight delay before a pause or track-change initiated on the client
takes effect on the audio played by the server.
+AirPlay volume-control attenuates volume (gain) by up to -30dB: the
+range -30dB:0dB can be rescaled from *Low*:0 (*Low* \< 0), or
+*Low*:*High*, using the option "`-db _Low_`" or "`-db _Low_:_High_`"
+(Rescaling is linear in decibels).
+
The -vsync and -async options also allow an optional positive (or
negative) audio-delay adjustment in *milliseconds* for fine-tuning :
`-vsync 20.5` delays audio relative to video by 0.0205 secs; a negative
@@ -944,6 +950,16 @@ changing this does not seem to have any effect*.
mode, but this option may be useful as a command-line option to switch
off a `-async` option set in a "uxplayrc" configuration file.
+**-db *low*\[:*high*\]** Rescales the AirPlay volume-control attenuation
+(gain) from -30dB:0dB to *low*:0dB or *low*:*high*. The lower limit
+*low* must be negative (attenuation); the upper limit *high* can be
+either sign. (GStreamer restricts volume-augmentation by *high* so that
+it cannot exceed +20dB). The rescaling is "flat", so that for -db
+-50:10, a change in Airplay attenuation by -7dB is translated to a -7 x
+(60/30) = -14dB attenuation, and the maximum volume (AirPlay 0dB) is a
+10dB augmentation, and Airplay -30dB would become -50dB. Note that the
+minimum AirPlay value (-30dB exactly) is translated to "mute".
+
**-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display
resolution (width and height, in pixels). (This may be a request made to
the AirPlay client, and perhaps will not be the final resolution you
@@ -1531,11 +1547,13 @@ what version UxPlay claims to be.
# Changelog
-1.68 2023-12-25 Introduced a simpler (default) method for generating a
+1.68 2023-12-26 Introduced a simpler (default) method for generating a
persistent public key from the server MAC address (which can now be set
with the -m option). (The previous pem-file method is still available
with -key option). New option -reg to maintain a register of
-pin-authenticated clients.
+pin-authenticated clients. Corrected volume-control: now inteprets
+AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation,
+with new option -db low\[:high\] for "flat" rescaling of the dB range.
1.67 2023-11-30 Add support for Apple-style one-time pin authentication
of clients with option "-pin": (uses SRP6a authentication protocol and
diff --git a/renderers/audio_renderer.h b/renderers/audio_renderer.h
index b405dfe..0786283 100644
--- a/renderers/audio_renderer.h
+++ b/renderers/audio_renderer.h
@@ -33,7 +33,8 @@ extern "C" {
#include "../lib/logger.h"
bool gstreamer_init();
-void audio_renderer_init(logger_t *logger, const char* audiosink, const bool *audio_sync, const bool *video_sync);
+void audio_renderer_init(logger_t *logger, const char* audiosink, const bool *audio_sync, const bool *video_sync,
+ float db_low, float db_high);
void audio_renderer_start(unsigned char* compression_type);
void audio_renderer_stop();
void audio_renderer_render_buffer(unsigned char* data, int *data_len, unsigned short *seqnum, uint64_t *ntp_time);
diff --git a/renderers/audio_renderer_gstreamer.c b/renderers/audio_renderer_gstreamer.c
index 2b131e1..dd8f658 100644
--- a/renderers/audio_renderer_gstreamer.c
+++ b/renderers/audio_renderer_gstreamer.c
@@ -40,6 +40,7 @@ static gboolean render_audio = FALSE;
static gboolean async = FALSE;
static gboolean vsync = FALSE;
static gboolean sync = FALSE;
+static float vol_low, vol_high;
typedef struct audio_renderer_s {
GstElement *appsrc;
@@ -125,14 +126,17 @@ bool gstreamer_init(){
return (bool) check_plugins ();
}
-void audio_renderer_init(logger_t *render_logger, const char* audiosink, const bool* audio_sync, const bool* video_sync) {
+void audio_renderer_init(logger_t *render_logger, const char* audiosink, const bool* audio_sync,
+ const bool* video_sync, const float db_low, const float db_high) {
GError *error = NULL;
GstCaps *caps = NULL;
GstClock *clock = gst_system_clock_obtain();
g_object_set(clock, "clock-type", GST_CLOCK_TYPE_REALTIME, NULL);
logger = render_logger;
-
+ vol_low = db_low;
+ vol_high = db_high;
+
aac = check_plugin_feature (avdec_aac);
alac = check_plugin_feature (avdec_alac);
@@ -356,11 +360,15 @@ void audio_renderer_render_buffer(unsigned char* data, int *data_len, unsigned s
}
void audio_renderer_set_volume(float volume) {
- float avol;
- if (fabs(volume) < 28) {
- avol=floorf(((28-fabs(volume))/28)*10)/10;
- g_object_set(renderer->volume, "volume", avol, NULL);
- }
+ /* scale volume from range -30dB:0dB to vol_low: vol_high */
+ double vol = (double) vol_low;
+ if ((volume <= 0) && (volume > -30)) {
+ vol = (double) (vol_low + ((vol_high - vol_low) * (30.0 + volume) / 30));
+ }
+ gdouble avol = (gdouble) pow(10, vol/20);
+ if (avol > 10) avol = 10;
+ if (volume <= -30) avol = 0;
+ g_object_set(renderer->volume, "volume", avol, NULL);
}
void audio_renderer_flush() {
diff --git a/uxplay.1 b/uxplay.1
index 99f3a79..5c817e7 100644
--- a/uxplay.1
+++ b/uxplay.1
@@ -31,6 +31,11 @@ UxPlay 1.68: An open\-source AirPlay mirroring (+ audio streaming) server:
.TP
\fB\-async\fR no Switch off audio/(client)video timestamp synchronization.
.TP
+\fB\-db\fI l[:h]\fR Set minumum volume attenuation to l dB (decibels, negative);
+.IP
+ optional: set maximum to h dB (+ or -); default -30.0:0.0
+.PP
+.TP
\fB\-s\fR wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60]
.TP
\fB\-o\fR Set display "overscanned" mode on (not usually needed)
diff --git a/uxplay.cpp b/uxplay.cpp
index 623d220..78111b4 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -136,6 +136,8 @@ static std::string dacpfile = "";
static bool registration_list = false;
static std::string pairing_register = "";
static std::vector registered_keys;
+static float db_low = -30;
+static float db_high = 0;
/* logging */
@@ -574,6 +576,8 @@ static void print_info (char *name) {
printf("-vsync no Switch off audio/(server)video timestamp synchronization \n");
printf("-async [x]Audio-Only mode: sync audio to client video (default: no)\n");
printf("-async no Switch off audio/(client)video timestamp synchronization\n");
+ printf("-db l[:h] Set minimum volume attenuation to l dB (decibels, negative);\n");
+ printf(" optional: set maximum to h dB (+ or -); default -30.0:0.0 dB\n");
printf("-s wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60]\n");
printf("-o Set display \"overscanned\" mode on (not usually needed)\n");
printf("-fs Full-screen (only works with X11, Wayland and VAAPI)\n");
@@ -1090,6 +1094,32 @@ static void parse_arguments (int argc, char *argv[]) {
dacpfile.append(get_homedir());
dacpfile.append("/.uxplay.dacp");
}
+ } else if (arg == "-db") {
+ bool db_bad = true;
+ float db1, db2;
+ char *start = NULL;
+ if ( i < argc -1) {
+ char *end1, *end2;
+ start = argv[i+1];
+ db1 = strtof(start, &end1);
+ if (end1 > start && *end1 == ':') {
+ db2 = strtof(++end1, &end2);
+ if ( *end2 == '\0' && end2 > end1 && db1 < 0 && db1 < db2) {
+ db_bad = false;
+ }
+ } else if (*end1 =='\0' && end1 > start && db1 < 0 ) {
+ db_bad = false;
+ db2 = 0;
+ }
+ }
+ if (db_bad) {
+ fprintf(stderr, "%s %s requires argument \"low\" or \"low:high\" where low < 0 and high > low are decibel gain values\n", argv[i], start);
+ exit(1);
+ }
+ i++;
+ db_low = db1;
+ db_high = db2;
+ printf("db range %f:%f\n", db_low, db_high);
} else {
fprintf(stderr, "unknown option %s, stopping (for help use option \"-h\")\n",argv[i]);
exit(1);
@@ -1929,7 +1959,7 @@ int main (int argc, char *argv[]) {
logger_set_level(render_logger, log_level);
if (use_audio) {
- audio_renderer_init(render_logger, audiosink.c_str(), &audio_sync, &video_sync);
+ audio_renderer_init(render_logger, audiosink.c_str(), &audio_sync, &video_sync, db_low, db_high);
} else {
LOGI("audio_disabled");
}
From 52d134d59655b1171c476a65f24084b0977b2c94 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Fri, 29 Dec 2023 04:32:31 -0500
Subject: [PATCH 06/11] rework -db option implementation; add -taper vol
control option
---
README.html | 15 +++++--
README.md | 13 ++++--
README.txt | 14 +++++-
renderers/audio_renderer.h | 5 +--
renderers/audio_renderer_gstreamer.c | 20 +++------
uxplay.1 | 2 +
uxplay.cpp | 64 +++++++++++++++++++++++-----
7 files changed, 97 insertions(+), 36 deletions(-)
diff --git a/README.html b/README.html
index fb7ddaa..0becf32 100644
--- a/README.html
+++ b/README.html
@@ -521,7 +521,8 @@ the audio played by the server.
range -30dB:0dB can be rescaled from Low:0 (Low <
0), or Low:High, using the option
“-db _Low_” or “-db _Low_:_High_” (Rescaling
-is linear in decibels).
+is linear in decibels). The option -taper provides a
+“tapered” AirPlay volume-control profile some users may prefer.
The -vsync and -async options also allow an optional positive (or
negative) audio-delay adjustment in milliseconds for
fine-tuning : -vsync 20.5 delays audio relative to video by
@@ -957,6 +958,13 @@ attenuation by -7dB is translated to a -7 x (60/30) = -14dB attenuation,
and the maximum volume (AirPlay 0dB) is a 10dB augmentation, and Airplay
-30dB would become -50dB. Note that the minimum AirPlay value (-30dB
exactly) is translated to “mute”.
+-taper Provides a “tapered” Airplay volume-control
+profile (matching the one called “dasl-tapering” in shairport-sync):
+each time the length of the volume slider (or the number of steps above
+mute, where 16 steps = full volume) is reduced by 50%, the perceived
+volume is halved (a 10dB attenuation). (This is modified at low volumes,
+to use the “untapered” volume if it is louder.)
-s wxh (e.g. -s 1920x1080 , which is the default )
sets the display resolution (width and height, in pixels). (This may be
a request made to the AirPlay client, and perhaps will not be the final
@@ -1510,13 +1518,14 @@ an AppleTV6,2 with sourceVersion 380.20.1 (an AppleTV 4K 1st gen,
introduced 2017, running tvOS 12.2.1), so it does not seem to matter
what version UxPlay claims to be.
Changelog
-1.68 2023-12-26 Introduced a simpler (default) method for generating
+
1.68 2023-12-29 Introduced a simpler (default) method for generating
a persistent public key from the server MAC address (which can now be
set with the -m option). (The previous pem-file method is still
available with -key option). New option -reg to maintain a register of
pin-authenticated clients. Corrected volume-control: now inteprets
AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation,
-with new option -db low[:high] for “flat” rescaling of the dB range.
+with new option -db low[:high] for “flat” rescaling of the dB range. Add
+-taper option for a “tapered” AirPlay volume-control profile.
1.67 2023-11-30 Add support for Apple-style one-time pin
authentication of clients with option “-pin”: (uses SRP6a authentication
protocol and public key persistence). Detection with error message of
diff --git a/README.md b/README.md
index 8fadec5..b7530ea 100644
--- a/README.md
+++ b/README.md
@@ -416,7 +416,8 @@ delays the video on the client to match audio on the server, so leads to
a slight delay before a pause or track-change initiated on the client takes effect on the audio played by the server.
AirPlay volume-control attenuates volume (gain) by up to -30dB: the range -30dB:0dB can be rescaled from _Low_:0 (_Low_ < 0), or _Low_:_High_, using the
-option "`-db _Low_`" or "``-db _Low_:_High_``" (Rescaling is linear in decibels).
+option "`-db _Low_`" or "``-db _Low_:_High_``" (Rescaling is linear in decibels). The option ```-taper``` provides a "tapered" AirPlay volume-control
+profile some users may prefer.
The -vsync and -async options
also allow an optional positive (or negative) audio-delay adjustment in _milliseconds_ for fine-tuning : `-vsync 20.5`
@@ -751,6 +752,11 @@ using UxPlay as a second monitor for a mac computer, or monitoring a webcam; wit
and the maximum volume (AirPlay 0dB) is a 10dB augmentation, and Airplay -30dB would become -50dB. Note that the minimum AirPlay value (-30dB exactly)
is translated to "mute".
+**-taper** Provides a "tapered" Airplay volume-control profile (matching the one called "dasl-tapering"
+ in [shairport-sync](https://github.com/mikebrady/shairport-sync)): each time the length of the
+ volume slider (or the number of steps above mute, where 16 steps = full volume) is reduced by 50%, the perceived volume is halved (a 10dB attenuation).
+ (This is modified at low volumes, to use the "untapered" volume if it is louder.)
+
**-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display resolution (width and height,
in pixels). (This may be a
request made to the AirPlay client, and perhaps will not
@@ -1188,11 +1194,12 @@ tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be.
# Changelog
-1.68 2023-12-26 Introduced a simpler (default) method for generating a persistent public key from the server MAC
+1.68 2023-12-29 Introduced a simpler (default) method for generating a persistent public key from the server MAC
address (which can now be set with the -m option). (The previous pem-file method is still available
with -key option). New option -reg to maintain a register of pin-authenticated clients. Corrected
volume-control: now inteprets AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation,
- with new option -db low[:high] for "flat" rescaling of the dB range.
+ with new option -db low[:high] for "flat" rescaling of the dB range. Add -taper option for a "tapered"
+ AirPlay volume-control profile.
1.67 2023-11-30 Add support for Apple-style one-time pin authentication of clients with option "-pin":
(uses SRP6a authentication protocol and public key persistence). Detection with error message
diff --git a/README.txt b/README.txt
index b2e488a..d1fe26f 100644
--- a/README.txt
+++ b/README.txt
@@ -513,7 +513,8 @@ helped to prevent this previously when timestamps were not being used.)
AirPlay volume-control attenuates volume (gain) by up to -30dB: the
range -30dB:0dB can be rescaled from *Low*:0 (*Low* \< 0), or
*Low*:*High*, using the option "`-db _Low_`" or "`-db _Low_:_High_`"
-(Rescaling is linear in decibels).
+(Rescaling is linear in decibels). The option `-taper` provides a
+"tapered" AirPlay volume-control profile some users may prefer.
The -vsync and -async options also allow an optional positive (or
negative) audio-delay adjustment in *milliseconds* for fine-tuning :
@@ -960,6 +961,14 @@ it cannot exceed +20dB). The rescaling is "flat", so that for -db
10dB augmentation, and Airplay -30dB would become -50dB. Note that the
minimum AirPlay value (-30dB exactly) is translated to "mute".
+**-taper** Provides a "tapered" Airplay volume-control profile (matching
+the one called "dasl-tapering" in
+[shairport-sync](https://github.com/mikebrady/shairport-sync)): each
+time the length of the volume slider (or the number of steps above mute,
+where 16 steps = full volume) is reduced by 50%, the perceived volume is
+halved (a 10dB attenuation). (This is modified at low volumes, to use
+the "untapered" volume if it is louder.)
+
**-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display
resolution (width and height, in pixels). (This may be a request made to
the AirPlay client, and perhaps will not be the final resolution you
@@ -1547,13 +1556,14 @@ what version UxPlay claims to be.
# Changelog
-1.68 2023-12-26 Introduced a simpler (default) method for generating a
+1.68 2023-12-29 Introduced a simpler (default) method for generating a
persistent public key from the server MAC address (which can now be set
with the -m option). (The previous pem-file method is still available
with -key option). New option -reg to maintain a register of
pin-authenticated clients. Corrected volume-control: now inteprets
AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation,
with new option -db low\[:high\] for "flat" rescaling of the dB range.
+Add -taper option for a "tapered" AirPlay volume-control profile.
1.67 2023-11-30 Add support for Apple-style one-time pin authentication
of clients with option "-pin": (uses SRP6a authentication protocol and
diff --git a/renderers/audio_renderer.h b/renderers/audio_renderer.h
index 0786283..232da28 100644
--- a/renderers/audio_renderer.h
+++ b/renderers/audio_renderer.h
@@ -33,12 +33,11 @@ extern "C" {
#include "../lib/logger.h"
bool gstreamer_init();
-void audio_renderer_init(logger_t *logger, const char* audiosink, const bool *audio_sync, const bool *video_sync,
- float db_low, float db_high);
+void audio_renderer_init(logger_t *logger, const char* audiosink, const bool *audio_sync, const bool *video_sync);
void audio_renderer_start(unsigned char* compression_type);
void audio_renderer_stop();
void audio_renderer_render_buffer(unsigned char* data, int *data_len, unsigned short *seqnum, uint64_t *ntp_time);
-void audio_renderer_set_volume(float volume);
+void audio_renderer_set_volume(double volume);
void audio_renderer_flush();
void audio_renderer_destroy();
diff --git a/renderers/audio_renderer_gstreamer.c b/renderers/audio_renderer_gstreamer.c
index dd8f658..c390e68 100644
--- a/renderers/audio_renderer_gstreamer.c
+++ b/renderers/audio_renderer_gstreamer.c
@@ -40,7 +40,6 @@ static gboolean render_audio = FALSE;
static gboolean async = FALSE;
static gboolean vsync = FALSE;
static gboolean sync = FALSE;
-static float vol_low, vol_high;
typedef struct audio_renderer_s {
GstElement *appsrc;
@@ -126,16 +125,13 @@ bool gstreamer_init(){
return (bool) check_plugins ();
}
-void audio_renderer_init(logger_t *render_logger, const char* audiosink, const bool* audio_sync,
- const bool* video_sync, const float db_low, const float db_high) {
+void audio_renderer_init(logger_t *render_logger, const char* audiosink, const bool* audio_sync, const bool* video_sync) {
GError *error = NULL;
GstCaps *caps = NULL;
GstClock *clock = gst_system_clock_obtain();
g_object_set(clock, "clock-type", GST_CLOCK_TYPE_REALTIME, NULL);
logger = render_logger;
- vol_low = db_low;
- vol_high = db_high;
aac = check_plugin_feature (avdec_aac);
alac = check_plugin_feature (avdec_alac);
@@ -359,16 +355,10 @@ void audio_renderer_render_buffer(unsigned char* data, int *data_len, unsigned s
}
}
-void audio_renderer_set_volume(float volume) {
- /* scale volume from range -30dB:0dB to vol_low: vol_high */
- double vol = (double) vol_low;
- if ((volume <= 0) && (volume > -30)) {
- vol = (double) (vol_low + ((vol_high - vol_low) * (30.0 + volume) / 30));
- }
- gdouble avol = (gdouble) pow(10, vol/20);
- if (avol > 10) avol = 10;
- if (volume <= -30) avol = 0;
- g_object_set(renderer->volume, "volume", avol, NULL);
+void audio_renderer_set_volume(double volume) {
+ volume = (volume > 10.0) ? 10.0 : volume;
+ volume = (volume < 0.0) ? 0.0 : volume;
+ g_object_set(renderer->volume, "volume", volume, NULL);
}
void audio_renderer_flush() {
diff --git a/uxplay.1 b/uxplay.1
index 5c817e7..b7dbebd 100644
--- a/uxplay.1
+++ b/uxplay.1
@@ -36,6 +36,8 @@ UxPlay 1.68: An open\-source AirPlay mirroring (+ audio streaming) server:
optional: set maximum to h dB (+ or -); default -30.0:0.0
.PP
.TP
+\fB\-taper\fR Use a "tapered" AirPlay volume-control profile.
+.TP
\fB\-s\fR wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60]
.TP
\fB\-o\fR Set display "overscanned" mode on (not usually needed)
diff --git a/uxplay.cpp b/uxplay.cpp
index 78111b4..e43cb10 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -34,6 +34,7 @@
#include
#include
#include
+#include
#ifdef _WIN32 /*modifications for Windows compilation */
#include
@@ -136,8 +137,9 @@ static std::string dacpfile = "";
static bool registration_list = false;
static std::string pairing_register = "";
static std::vector registered_keys;
-static float db_low = -30;
-static float db_high = 0;
+static double db_low = -30.0;
+static double db_high = 0.0;
+static bool taper_volume = true;
/* logging */
@@ -577,7 +579,8 @@ static void print_info (char *name) {
printf("-async [x]Audio-Only mode: sync audio to client video (default: no)\n");
printf("-async no Switch off audio/(client)video timestamp synchronization\n");
printf("-db l[:h] Set minimum volume attenuation to l dB (decibels, negative);\n");
- printf(" optional: set maximum to h dB (+ or -); default -30.0:0.0 dB\n");
+ printf(" optional: set maximum to h dB (+ or -) default: -30.0:0.0 dB\n");
+ printf("-taper Use a \"tapered\" AirPlay volume-qcontrol profile\n");
printf("-s wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60]\n");
printf("-o Set display \"overscanned\" mode on (not usually needed)\n");
printf("-fs Full-screen (only works with X11, Wayland and VAAPI)\n");
@@ -1094,22 +1097,24 @@ static void parse_arguments (int argc, char *argv[]) {
dacpfile.append(get_homedir());
dacpfile.append("/.uxplay.dacp");
}
+ } else if (arg == "-taper") {
+ taper_volume = true;
} else if (arg == "-db") {
bool db_bad = true;
- float db1, db2;
+ double db1, db2;
char *start = NULL;
if ( i < argc -1) {
char *end1, *end2;
start = argv[i+1];
- db1 = strtof(start, &end1);
+ db1 = strtod(start, &end1);
if (end1 > start && *end1 == ':') {
- db2 = strtof(++end1, &end2);
+ db2 = strtod(++end1, &end2);
if ( *end2 == '\0' && end2 > end1 && db1 < 0 && db1 < db2) {
db_bad = false;
}
} else if (*end1 =='\0' && end1 > start && db1 < 0 ) {
db_bad = false;
- db2 = 0;
+ db2 = 0.0;
}
}
if (db_bad) {
@@ -1504,9 +1509,48 @@ extern "C" void video_flush (void *cls) {
}
extern "C" void audio_set_volume (void *cls, float volume) {
- if (use_audio) {
- audio_renderer_set_volume(volume);
+ double db, db_flat, frac, gst_volume;
+ if (!use_audio) {
+ return;
}
+ /* convert from AirPlay dB volume in range {-30dB : 0dB}, to GStreamer volume */
+ if (volume == -144.0f) { /* AirPlay "mute" signal */
+ frac = 0.0;
+ } else if (volume < -30.0f) {
+ LOGE(" invalid AirPlay volume %f", volume);
+ frac = 0.0;
+ } else if (volume > 0.0f) {
+ LOGE(" invalid AirPlay volume %f", volume);
+ frac = 1.0;
+ } else if (volume == -30.0f) {
+ frac = 0.0;
+ } else if (volume == 0.0f) {
+ frac = 1.0;
+ } else {
+ frac = (double) ( (30.0f + volume) / 30.0f);
+ frac = (frac > 1.0) ? 1.0 : frac;
+ }
+
+ /* frac is length of volume slider as fraction of max length */
+ /* also (steps/16) where steps is number of discrete steps above mute (16 = full volume) */
+ if (frac == 0.0) {
+ gst_volume = 0.0;
+ } else {
+ /* flat rescaling of decibel range from {-30dB : 0dB} to {db_low : db_high} */
+ db_flat = db_low + (db_high-db_low) * frac;
+ if (taper_volume) {
+ /* taper the volume reduction by the (rescaled) Airplay {-30:0} range so each reduction of
+ * the remaining slider length by 50% reduces the perceived volume by 50% (-10dB gain)
+ * (This is the "dasl-tapering" scheme offered by shairport-sync) */
+ db = db_high + 10.0 * (log10(frac) / log10(2.0));
+ db = (db > db_flat) ? db : db_flat;
+ } else {
+ db = db_flat;
+ }
+ /* conversion from (gain) decibels to GStreamer's linear volume scale */
+ gst_volume = pow(10.0, 0.05*db);
+ }
+ audio_renderer_set_volume(gst_volume);
}
extern "C" void audio_get_format (void *cls, unsigned char *ct, unsigned short *spf, bool *usingScreen, bool *isMedia, uint64_t *audioFormat) {
@@ -1959,7 +2003,7 @@ int main (int argc, char *argv[]) {
logger_set_level(render_logger, log_level);
if (use_audio) {
- audio_renderer_init(render_logger, audiosink.c_str(), &audio_sync, &video_sync, db_low, db_high);
+ audio_renderer_init(render_logger, audiosink.c_str(), &audio_sync, &video_sync);
} else {
LOGI("audio_disabled");
}
From e0567bd9238ea68e7d52281464f3932457c1ceac Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Fri, 29 Dec 2023 05:21:53 -0500
Subject: [PATCH 07/11] update specfile
---
uxplay.spec | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/uxplay.spec b/uxplay.spec
index 3cfda37..3e581a8 100644
--- a/uxplay.spec
+++ b/uxplay.spec
@@ -1,5 +1,5 @@
Name: uxplay
-Version: 1.67
+Version: 1.68
Release: 1%{?dist}
%global gittag v%{version}
@@ -11,7 +11,7 @@ Source0: https://github.com/FDH2/UxPlay/archive/%{gittag}/%{name}-%{versi
Packager: UxPlay maintainer
-BuildRequires: cmake >= 3.4.1
+BuildRequires: cmake >= 3.5
BuildRequires: make
BuildRequires: gcc
BuildRequires: gcc-c++
@@ -135,7 +135,7 @@ cd build
%{_docdir}/%{name}/llhttp/LICENSE-MIT
%changelog
-* Wed Nov 29 2023 UxPlay maintainer
+* Fri Dec 29 2023 UxPlay maintainer
Initial uxplay.spec: tested on Fedora 38, Rocky Linux 9.2, OpenSUSE
Leap 15.5, Mageia 9, OpenMandriva ROME, PCLinuxOS
-
From a25f4c82cfa2d8aab6aae09418588d8321d1f483 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Fri, 29 Dec 2023 18:14:31 -0500
Subject: [PATCH 08/11] use C++ std::getline instead of C getline (for WIN32)
---
uxplay.cpp | 28 +++++++++++++---------------
1 file changed, 13 insertions(+), 15 deletions(-)
diff --git a/uxplay.cpp b/uxplay.cpp
index e43cb10..9853d53 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -1656,15 +1656,13 @@ extern "C" bool check_register(void *cls, const char *client_pk) {
/* we are not maintaining a list of registered clients */
return true;
}
- LOGD("check returning client registration:\n PK:%s", client_pk);
- if (std::find(registered_keys.rbegin(), registered_keys.rend(), client_pk) != registered_keys.rend()) {
- LOGD("client registration found");
+ LOGD("check returning client's pairing registration");
+ std::string pk = client_pk;
+ if (std::find(registered_keys.rbegin(), registered_keys.rend(), pk) != registered_keys.rend()) {
+ LOGD("registration found: PK=%s", client_pk);
return true;
} else {
- LOGE("returning client's pairing registration not found,\n PK: %s", client_pk);
- for (int i = 0; i < registered_keys.size(); i++) {
- printf("%s\n", (registered_keys[i]).c_str());
- }
+ LOGE("returning client's pairing registration not found: PK=%s", client_pk);
return false;
}
}
@@ -1954,23 +1952,23 @@ int main (int argc, char *argv[]) {
/* read in public keys that were previously registered with pair-setup-pin */
if (require_password && registration_list && strlen(pairing_register.c_str())) {
- char * line = NULL;
size_t len = 0;
std::string key;
- FILE *fp = fopen(pairing_register.c_str(), "r");
int clients = 0;
- if (fp) {
- while ((getline(&line, &len, fp)) != -1) {
- /*32 bytes pk -> base64 -> strlen(pk64) = 44 chars = line[0:43]; remove \n at line[44] */
+ std::ifstream file(pairing_register);
+ if (file.is_open()) {
+ std::string line;
+ while (std::getline(file, line)) {
+ /*32 bytes pk -> base64 -> strlen(pk64) = 44 chars = line[0:43]; add '\0' at line[44] */
line[44] = '\0';
- registered_keys.push_back(key.assign(line));
+ std::string pk = line.c_str();
+ registered_keys.push_back(key.assign(pk));
clients ++;
}
if (clients) {
LOGI("Register %s lists %d pin-registered clients", pairing_register.c_str(), clients);
}
- fclose(fp);
- free (line);
+ file.close();
}
}
From 26d35dfd6a5efbf7883bccff34bbfe7ddd77fcbc Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Sat, 30 Dec 2023 23:51:34 -0500
Subject: [PATCH 09/11] edited manpage (remove reference to .pem file in FILES
section)
---
uxplay.1 | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/uxplay.1 b/uxplay.1
index b7dbebd..9a0d526 100644
--- a/uxplay.1
+++ b/uxplay.1
@@ -155,13 +155,9 @@ UxPlay 1.68: An open\-source AirPlay mirroring (+ audio streaming) server:
\fB\-h\fR Displays help information
.SH
FILES
-Private key stored (for persistence) in $HOME/.uxplay.pem (can be changed)
-.TP
-(transient) storage of client DACP info: $HOME/uxplay.dacp (can be changed)
-.TP
Options in one of $UXPLAYRC, or ~/.uxplayrc, or ~/.config/uxplayrc
.TP
-are applied first (command-line options may modify them). uxplayrc format:
+are applied first (command-line options may modify them). Format:
.TP
one option per line,\fI no\fR initial "-"; lines beginning with "#" ignored.
.SH
From 682baa03c60360152782c74b2d937aae04c101ba Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Sun, 31 Dec 2023 07:40:52 -0500
Subject: [PATCH 10/11] small cleanup of -db documentation
---
README.html | 27 ++++++++++++++-------------
README.md | 10 +++++-----
README.txt | 24 ++++++++++++------------
uxplay.cpp | 4 ++--
4 files changed, 33 insertions(+), 32 deletions(-)
diff --git a/README.html b/README.html
index 0becf32..2a5298c 100644
--- a/README.html
+++ b/README.html
@@ -518,11 +518,12 @@ before a pause or track-change initiated on the client takes effect on
the audio played by the server.
AirPlay volume-control attenuates volume (gain) by up to -30dB: the
-range -30dB:0dB can be rescaled from Low:0 (Low <
-0), or Low:High, using the option
-“-db _Low_” or “-db _Low_:_High_” (Rescaling
-is linear in decibels). The option -taper provides a
-“tapered” AirPlay volume-control profile some users may prefer.
+range -30dB:0dB can be rescaled from Low:0, or
+Low:High, using the option -db (“-db
+Low” or “-db Low:High”), Low must be
+negative. Rescaling is linear in decibels. The option
+-taper provides a “tapered” AirPlay volume-control profile
+some users may prefer.
The -vsync and -async options also allow an optional positive (or
negative) audio-delay adjustment in milliseconds for
fine-tuning : -vsync 20.5 delays audio relative to video by
@@ -1518,14 +1519,14 @@ an AppleTV6,2 with sourceVersion 380.20.1 (an AppleTV 4K 1st gen,
introduced 2017, running tvOS 12.2.1), so it does not seem to matter
what version UxPlay claims to be.
Changelog
-1.68 2023-12-29 Introduced a simpler (default) method for generating
-a persistent public key from the server MAC address (which can now be
-set with the -m option). (The previous pem-file method is still
-available with -key option). New option -reg to maintain a register of
-pin-authenticated clients. Corrected volume-control: now inteprets
-AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation,
-with new option -db low[:high] for “flat” rescaling of the dB range. Add
--taper option for a “tapered” AirPlay volume-control profile.
+1.68 2023-12-31 New simpler (default) method for generating a
+persistent public key from the server MAC address (which can now be set
+with the -m option). (The previous method is still available with -key
+option). New option -reg to maintain a register of pin-authenticated
+clients. Corrected volume-control: now interprets AirPlay volume range
+-30dB:0dB as decibel gain attenuation, with new option -db low[:high]
+for “flat” rescaling of the dB range. Add -taper option for a “tapered”
+AirPlay volume-control profile.
1.67 2023-11-30 Add support for Apple-style one-time pin
authentication of clients with option “-pin”: (uses SRP6a authentication
protocol and public key persistence). Detection with error message of
diff --git a/README.md b/README.md
index b7530ea..e674cbc 100644
--- a/README.md
+++ b/README.md
@@ -415,8 +415,8 @@ if you want to follow the Apple Music lyrics on the client while listening to su
delays the video on the client to match audio on the server, so leads to
a slight delay before a pause or track-change initiated on the client takes effect on the audio played by the server.
-AirPlay volume-control attenuates volume (gain) by up to -30dB: the range -30dB:0dB can be rescaled from _Low_:0 (_Low_ < 0), or _Low_:_High_, using the
-option "`-db _Low_`" or "``-db _Low_:_High_``" (Rescaling is linear in decibels). The option ```-taper``` provides a "tapered" AirPlay volume-control
+AirPlay volume-control attenuates volume (gain) by up to -30dB: the range -30dB:0dB can be rescaled from _Low_:0, or _Low_:_High_, using the
+option `-db` ("-db _Low_ " or "-db _Low_:_High_ "), _Low_ must be negative. Rescaling is linear in decibels. The option ```-taper``` provides a "tapered" AirPlay volume-control
profile some users may prefer.
The -vsync and -async options
@@ -1194,10 +1194,10 @@ tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be.
# Changelog
-1.68 2023-12-29 Introduced a simpler (default) method for generating a persistent public key from the server MAC
- address (which can now be set with the -m option). (The previous pem-file method is still available
+1.68 2023-12-31 New simpler (default) method for generating a persistent public key from the server MAC
+ address (which can now be set with the -m option). (The previous method is still available
with -key option). New option -reg to maintain a register of pin-authenticated clients. Corrected
- volume-control: now inteprets AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation,
+ volume-control: now interprets AirPlay volume range -30dB:0dB as decibel gain attenuation,
with new option -db low[:high] for "flat" rescaling of the dB range. Add -taper option for a "tapered"
AirPlay volume-control profile.
diff --git a/README.txt b/README.txt
index d1fe26f..c13bf72 100644
--- a/README.txt
+++ b/README.txt
@@ -511,10 +511,10 @@ helped to prevent this previously when timestamps were not being used.)
takes effect on the audio played by the server.
AirPlay volume-control attenuates volume (gain) by up to -30dB: the
-range -30dB:0dB can be rescaled from *Low*:0 (*Low* \< 0), or
-*Low*:*High*, using the option "`-db _Low_`" or "`-db _Low_:_High_`"
-(Rescaling is linear in decibels). The option `-taper` provides a
-"tapered" AirPlay volume-control profile some users may prefer.
+range -30dB:0dB can be rescaled from *Low*:0, or *Low*:*High*, using the
+option `-db` ("-db *Low*" or "-db *Low*:*High*"), *Low* must be
+negative. Rescaling is linear in decibels. The option `-taper` provides
+a "tapered" AirPlay volume-control profile some users may prefer.
The -vsync and -async options also allow an optional positive (or
negative) audio-delay adjustment in *milliseconds* for fine-tuning :
@@ -1556,14 +1556,14 @@ what version UxPlay claims to be.
# Changelog
-1.68 2023-12-29 Introduced a simpler (default) method for generating a
-persistent public key from the server MAC address (which can now be set
-with the -m option). (The previous pem-file method is still available
-with -key option). New option -reg to maintain a register of
-pin-authenticated clients. Corrected volume-control: now inteprets
-AirPlay volume range -30dB:0dB as (gain/amplitude) decibel attenuation,
-with new option -db low\[:high\] for "flat" rescaling of the dB range.
-Add -taper option for a "tapered" AirPlay volume-control profile.
+1.68 2023-12-31 New simpler (default) method for generating a persistent
+public key from the server MAC address (which can now be set with the -m
+option). (The previous method is still available with -key option). New
+option -reg to maintain a register of pin-authenticated clients.
+Corrected volume-control: now interprets AirPlay volume range -30dB:0dB
+as decibel gain attenuation, with new option -db low\[:high\] for "flat"
+rescaling of the dB range. Add -taper option for a "tapered" AirPlay
+volume-control profile.
1.67 2023-11-30 Add support for Apple-style one-time pin authentication
of clients with option "-pin": (uses SRP6a authentication protocol and
diff --git a/uxplay.cpp b/uxplay.cpp
index 9853d53..e995d1b 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -1044,7 +1044,7 @@ static void parse_arguments (int argc, char *argv[]) {
continue;
}
}
- fprintf(stderr, "invalid argument -al %s: must be a decimal time offset in seconds, range [0,10]\n"
+ fprintf(stderr, "invalid -al %s: value must be a decimal time offset in seconds, range [0,10]\n"
"(like 5 or 4.8, which will be converted to a whole number of microseconds)\n", argv[i]);
exit(1);
} else if (arg == "-pin") {
@@ -1118,7 +1118,7 @@ static void parse_arguments (int argc, char *argv[]) {
}
}
if (db_bad) {
- fprintf(stderr, "%s %s requires argument \"low\" or \"low:high\" where low < 0 and high > low are decibel gain values\n", argv[i], start);
+ fprintf(stderr, "invalid %s %s: db value must be \"low\" or \"low:high\", low < 0 and high > low are decibel gains\n", argv[i], start);
exit(1);
}
i++;
From 1b718405bcc747d2d3ebcab16a61683bda131203 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Sun, 31 Dec 2023 13:26:01 -0500
Subject: [PATCH 11/11] cosmetic: typo in help text (uxplay.cpp)
---
uxplay.cpp | 2 +-
uxplay.spec | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/uxplay.cpp b/uxplay.cpp
index e995d1b..56be690 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -580,7 +580,7 @@ static void print_info (char *name) {
printf("-async no Switch off audio/(client)video timestamp synchronization\n");
printf("-db l[:h] Set minimum volume attenuation to l dB (decibels, negative);\n");
printf(" optional: set maximum to h dB (+ or -) default: -30.0:0.0 dB\n");
- printf("-taper Use a \"tapered\" AirPlay volume-qcontrol profile\n");
+ printf("-taper Use a \"tapered\" AirPlay volume-control profile\n");
printf("-s wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60]\n");
printf("-o Set display \"overscanned\" mode on (not usually needed)\n");
printf("-fs Full-screen (only works with X11, Wayland and VAAPI)\n");
diff --git a/uxplay.spec b/uxplay.spec
index 3e581a8..cb55a28 100644
--- a/uxplay.spec
+++ b/uxplay.spec
@@ -1,5 +1,5 @@
Name: uxplay
-Version: 1.68
+Version: 1.68.1
Release: 1%{?dist}
%global gittag v%{version}