mirror of
https://github.com/morgan9e/UxPlay
synced 2026-04-14 00:04:13 +09:00
100
README.html
100
README.html
@@ -1,6 +1,6 @@
|
||||
<h1
|
||||
id="uxplay-1.67-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">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).</h1>
|
||||
<h3
|
||||
id="now-developed-at-the-github-site-httpsgithub.comfdh2uxplay-where-all-user-issues-should-be-posted-and-latest-versions-can-be-found.">Now
|
||||
@@ -9,9 +9,13 @@ href="https://github.com/FDH2/UxPlay">https://github.com/FDH2/UxPlay</a>
|
||||
(where ALL user issues should be posted, and latest versions can be
|
||||
found).</h3>
|
||||
<ul>
|
||||
<li><em><strong>NEW in v1.67</strong>: support for one-time Apple-style
|
||||
“pin” code client authentication (“client-server pairing”) when the
|
||||
option “-pin” is used.</em></li>
|
||||
<li><em><strong>NEW in v1.68</strong>: 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.)</em></li>
|
||||
</ul>
|
||||
<h2 id="highlights">Highlights:</h2>
|
||||
<ul>
|
||||
@@ -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
|
||||
<code>-key <filename></code> is used) is moved or deleted, after
|
||||
which a new key is generated. (Non-Apple clients might not implement the
|
||||
persistence feature.)</p></li>
|
||||
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.</p></li>
|
||||
<li><p>By default, UxPlay is locked to its current client until that
|
||||
client drops the connection; since UxPlay-1.58, the option
|
||||
<code>-nohold</code> modifies this behavior so that when a new client
|
||||
@@ -508,6 +517,13 @@ 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.</li>
|
||||
</ul>
|
||||
<p>AirPlay volume-control attenuates volume (gain) by up to -30dB: the
|
||||
range -30dB:0dB can be rescaled from <em>Low</em>:0, or
|
||||
<em>Low</em>:<em>High</em>, using the option <code>-db</code> (“-db
|
||||
<em>Low</em>” or “-db <em>Low</em>:<em>High</em>”), <em>Low</em> must be
|
||||
negative. Rescaling is linear in decibels. The option
|
||||
<code>-taper</code> provides a “tapered” AirPlay volume-control profile
|
||||
some users may prefer.</p>
|
||||
<p>The -vsync and -async options also allow an optional positive (or
|
||||
negative) audio-delay adjustment in <em>milliseconds</em> for
|
||||
fine-tuning : <code>-vsync 20.5</code> delays audio relative to video by
|
||||
@@ -895,6 +911,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.
|
||||
<em>(Add a “pin” entry in the UxPlay startup file if you wish the UxPlay
|
||||
server to use this protocol).</em></p>
|
||||
<p><strong>-reg [<em>filename</em>]</strong>: (since v1.68). This option
|
||||
maintains a list of previously-pin-registered clients in
|
||||
$HOME/.uxplay.register (or optionally, in <em>filename</em>). 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.)</p>
|
||||
<p><strong>-vsync [x]</strong> (In Mirror mode:) this option
|
||||
(<strong>now the default</strong>) uses timestamps to synchronize audio
|
||||
with video on the server, with an optional audio delay in (decimal)
|
||||
@@ -924,6 +948,24 @@ have any effect</em>.</p>
|
||||
Audio-only mode, but this option may be useful as a command-line option
|
||||
to switch off a <code>-async</code> option set in a “uxplayrc”
|
||||
configuration file.</p>
|
||||
<p><strong>-db <em>low</em>[:<em>high</em>]</strong> Rescales the
|
||||
AirPlay volume-control attenuation (gain) from -30dB:0dB to
|
||||
<em>low</em>:0dB or <em>low</em>:<em>high</em>. The lower limit
|
||||
<em>low</em> must be negative (attenuation); the upper limit
|
||||
<em>high</em> can be either sign. (GStreamer restricts
|
||||
volume-augmentation by <em>high</em> 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”.</p>
|
||||
<p><strong>-taper</strong> Provides a “tapered” Airplay volume-control
|
||||
profile (matching the one called “dasl-tapering” in <a
|
||||
href="https://github.com/mikebrady/shairport-sync">shairport-sync</a>):
|
||||
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.)</p>
|
||||
<p><strong>-s wxh</strong> (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
|
||||
@@ -1107,13 +1149,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 <strong>-m</strong> was not specified. (Note that a random MAC
|
||||
address will be different each time UxPlay is started).</p>
|
||||
<p><strong>-key [<em>filename</em>]</strong>: By default, the storage of
|
||||
the Server private key is in the file $HOME/.uxplay.pem. Use the “-key
|
||||
<em>filename</em>” option to change this location. This option should be
|
||||
set in the UxPlay startup file as a line “<code>key filename</code>” (no
|
||||
initial “-”), where <code>filename</code> is a full path. The filename
|
||||
may be enclosed in quotes (<code>"...."</code>), (and must be, if the
|
||||
filename has any blank spaces).</p>
|
||||
<p><strong>-key [<em>filename</em>]</strong>: 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 <code>$HOME/.uxplay.pem</code>, if that file does not exist,
|
||||
or read from it, if it exists. (Optionally, the key can be stored in
|
||||
<em>filename</em>.) 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.</p>
|
||||
<p>By default, the storage of the Server private key is in the file
|
||||
$HOME/.uxplay.pem. Use the “-key <em>filename</em>” option to change
|
||||
this location. This option should be set in the UxPlay startup file as a
|
||||
line “<code>key filename</code>” (no initial “-”), where
|
||||
<code>filename</code> is a full path. The filename may be enclosed in
|
||||
quotes (<code>"...."</code>), (and must be, if the filename has any
|
||||
blank spaces).</p>
|
||||
<p><strong>-dacp [<em>filename</em>]</strong>: Export current client
|
||||
DACP-ID and Active-Remote key to file: default is $HOME/.uxplay.dacp.
|
||||
(optionally can be changed to <em>filename</em>). Can be used by remote
|
||||
@@ -1463,6 +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.</p>
|
||||
<h1 id="changelog">Changelog</h1>
|
||||
<p>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.</p>
|
||||
<p>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
|
||||
|
||||
61
README.md
61
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**: 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:
|
||||
|
||||
@@ -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
|
||||
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.)
|
||||
corporately-owned and -managed device with MDM Mobile Device 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
|
||||
@@ -408,6 +415,10 @@ 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, 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 0.0205 secs; a negative value advances it.)
|
||||
@@ -708,6 +719,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
|
||||
@@ -728,6 +745,18 @@ 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".
|
||||
|
||||
**-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
|
||||
@@ -891,7 +920,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 +1194,13 @@ tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be.
|
||||
|
||||
|
||||
# Changelog
|
||||
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 (currently) unsupported H265 video when requesting high resolution over wired ethernet.
|
||||
|
||||
98
README.txt
98
README.txt
@@ -1,10 +1,15 @@
|
||||
# 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> (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**: 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:
|
||||
|
||||
@@ -451,11 +456,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 <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 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`
|
||||
@@ -498,6 +510,12 @@ 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, 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 0.0205 secs; a negative
|
||||
@@ -895,6 +913,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
|
||||
@@ -925,6 +951,24 @@ 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".
|
||||
|
||||
**-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
|
||||
@@ -1125,12 +1169,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 +1556,15 @@ what version UxPlay claims to be.
|
||||
|
||||
# Changelog
|
||||
|
||||
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 (currently)
|
||||
|
||||
38
lib/crypto.c
38
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) {
|
||||
@@ -546,3 +556,27 @@ void sha_destroy(sha_ctx_t *ctx) {
|
||||
int get_random_bytes(unsigned char *buf, int num) {
|
||||
return RAND_bytes(buf, num);
|
||||
}
|
||||
#include <stdio.h>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -82,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);
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -447,8 +447,20 @@ 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) {
|
||||
*username = session->username;
|
||||
*client_pk = session->client_pk;
|
||||
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;
|
||||
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) {
|
||||
int len64 = 4 * (1 + (ED25519_KEY_SIZE / 3)) + 1;
|
||||
*pk64 = (char *) malloc(len64);
|
||||
pk_to_base64(pk, ED25519_KEY_SIZE, *pk64, len64);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -341,15 +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;
|
||||
unsigned char *client_pk;
|
||||
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);
|
||||
}
|
||||
free (client_pk_str);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "pair-pin-setup success\n");
|
||||
}
|
||||
pairing_session_set_setup_status(conn->session);
|
||||
@@ -442,12 +433,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;
|
||||
}
|
||||
@@ -584,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);
|
||||
|
||||
@@ -37,7 +37,7 @@ void audio_renderer_init(logger_t *logger, const char* audiosink, const bool *au
|
||||
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();
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ void audio_renderer_init(logger_t *render_logger, const char* audiosink, const b
|
||||
g_object_set(clock, "clock-type", GST_CLOCK_TYPE_REALTIME, NULL);
|
||||
|
||||
logger = render_logger;
|
||||
|
||||
|
||||
aac = check_plugin_feature (avdec_aac);
|
||||
alac = check_plugin_feature (avdec_alac);
|
||||
|
||||
@@ -355,12 +355,10 @@ 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);
|
||||
}
|
||||
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() {
|
||||
|
||||
29
uxplay.1
29
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.
|
||||
@@ -27,6 +31,13 @@ UxPlay 1.67: 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\-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)
|
||||
@@ -106,9 +117,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
|
||||
@@ -140,13 +155,9 @@ UxPlay 1.67: 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
|
||||
|
||||
186
uxplay.cpp
186
uxplay.cpp
@@ -26,6 +26,7 @@
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@@ -33,6 +34,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <cstdio>
|
||||
#include <stdarg.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifdef _WIN32 /*modifications for Windows compilation */
|
||||
#include <glib.h>
|
||||
@@ -60,7 +62,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
|
||||
@@ -132,6 +134,13 @@ static unsigned short pin = 0;
|
||||
static std::string keyfile = "";
|
||||
static std::string mac_address = "";
|
||||
static std::string dacpfile = "";
|
||||
static bool registration_list = false;
|
||||
static std::string pairing_register = "";
|
||||
static std::vector <std::string> registered_keys;
|
||||
static double db_low = -30.0;
|
||||
static double db_high = 0.0;
|
||||
static bool taper_volume = true;
|
||||
|
||||
/* logging */
|
||||
|
||||
static void log(int level, const char* format, ...) {
|
||||
@@ -561,12 +570,17 @@ 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");
|
||||
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("-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");
|
||||
@@ -607,8 +621,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 <fn> Store private key in file <fn> (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");
|
||||
@@ -1030,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") {
|
||||
@@ -1044,6 +1058,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 <fn>\" 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] != '-') {
|
||||
@@ -1054,8 +1079,10 @@ static void parse_arguments (int argc, char *argv[]) {
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "option \"-key <fn>\" requires a path <fn> to a file for persistent key storage\n");
|
||||
exit(1);
|
||||
// fprintf(stderr, "option \"-key <fn>\" requires a path <fn> to a file for persistent key storage\n");
|
||||
// exit(1);
|
||||
keyfile.erase();
|
||||
keyfile.append("0");
|
||||
}
|
||||
} else if (arg == "-dacp") {
|
||||
dacpfile.erase();
|
||||
@@ -1070,6 +1097,34 @@ 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;
|
||||
double db1, db2;
|
||||
char *start = NULL;
|
||||
if ( i < argc -1) {
|
||||
char *end1, *end2;
|
||||
start = argv[i+1];
|
||||
db1 = strtod(start, &end1);
|
||||
if (end1 > start && *end1 == ':') {
|
||||
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.0;
|
||||
}
|
||||
}
|
||||
if (db_bad) {
|
||||
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++;
|
||||
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);
|
||||
@@ -1454,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) {
|
||||
@@ -1541,15 +1635,36 @@ 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, const char *client_name) {
|
||||
if (!registration_list) {
|
||||
/* we are not maintaining a list of registered clients */
|
||||
return;
|
||||
}
|
||||
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,%s\n", client_pk, device_id, client_name);
|
||||
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'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: PK=%s", client_pk);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void log_callback (void *cls, int level, const char *msg) {
|
||||
@@ -1600,7 +1715,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;
|
||||
@@ -1825,14 +1940,46 @@ int main (int argc, char *argv[]) {
|
||||
video_parser.append(BT709_FIX);
|
||||
}
|
||||
|
||||
if (require_password && keyfile == "") {
|
||||
if (require_password && registration_list) {
|
||||
if (pairing_register == "") {
|
||||
const char * homedir = get_homedir();
|
||||
if (homedir) {
|
||||
pairing_register = homedir;
|
||||
pairing_register.append("/.uxplay.register");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* read in public keys that were previously registered with pair-setup-pin */
|
||||
if (require_password && registration_list && strlen(pairing_register.c_str())) {
|
||||
size_t len = 0;
|
||||
std::string key;
|
||||
int clients = 0;
|
||||
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';
|
||||
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);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1883,7 +2030,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());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: uxplay
|
||||
Version: 1.67
|
||||
Version: 1.68.1
|
||||
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 <https://github.com/FDH2/UxPlay>
|
||||
* Fri Dec 29 2023 UxPlay maintainer <https://github.com/FDH2/UxPlay>
|
||||
Initial uxplay.spec: tested on Fedora 38, Rocky Linux 9.2, OpenSUSE
|
||||
Leap 15.5, Mageia 9, OpenMandriva ROME, PCLinuxOS
|
||||
-
|
||||
|
||||
Reference in New Issue
Block a user