mirror of
https://github.com/morgan9e/UxPlay
synced 2026-04-15 00:34:05 +09:00
Merge pull request #135 from FDH2/master
minor README update (building libplist from source)
This commit is contained in:
102
README.html
102
README.html
@@ -1,6 +1,6 @@
|
||||
<h1
|
||||
id="uxplay-1.64-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay
|
||||
1.64: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix
|
||||
id="uxplay-1.65-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay
|
||||
1.65: 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.">Now
|
||||
@@ -82,27 +82,29 @@ antimof site is no longer involved in development, but periodically
|
||||
posts updates pulled from the new main <a
|
||||
href="https://github.com/FDH2/UxPlay">UxPlay site</a>).</p>
|
||||
<p>UxPlay is tested on a number of systems, including (among others)
|
||||
Debian 10.11 “Buster” and 11.2 “Bullseye”, Ubuntu 20.04 LTS and 22.04.1
|
||||
LTS, (also Ubuntu derivatives Linux Mint 20.3, Pop!_OS 22.04 (NVIDIA
|
||||
edition)), Rocky Linux 9.1 (a CentOS successor), Fedora 36, OpenSUSE
|
||||
15.4, Arch Linux 22.10, macOS 13.3 (Intel and M2), FreeBSD 13.2, Windows
|
||||
10 and 11 (64 bit).</p>
|
||||
Debian (10 “Buster”, 11 “Bullseye”, 12 “Bookworm”), Ubuntu (20.04 LTS,
|
||||
22.04 LTS, 23.04; also Ubuntu derivatives Linux Mint 20.3, Pop!_OS 22.04
|
||||
(NVIDIA edition)), Red Hat and clones (Fedora 38, Rocky Linux 9.2),
|
||||
OpenSUSE 15.4, Arch Linux 23.05, macOS 13.3 (Intel and M2), FreeBSD
|
||||
13.2, Windows 10 and 11 (64 bit).</p>
|
||||
<p>On Raspberry Pi 4 model B, it is tested on Raspberry Pi OS (Bullseye)
|
||||
(32- and 64-bit), Ubuntu 22.04 and 22.10, Manjaro RPi4 23.02, and
|
||||
(32- and 64-bit), Ubuntu 22.04 LTS and 23.04, Manjaro RPi4 23.02, and
|
||||
(without hardware video decoding) on OpenSUSE 15.4. Also tested on
|
||||
Raspberry Pi 3 model B+.</p>
|
||||
<p>Its main use is to act like an AppleTV for screen-mirroring (with
|
||||
audio) of iOS/iPadOS/macOS clients (iPhone, iPod Touch, iPad, Mac
|
||||
computers) on the server display of a host running Linux, macOS, or
|
||||
other unix (and now also Microsoft Windows). UxPlay supports Apple’s
|
||||
AirPlay2 protocol using “Legacy Pairing”, but some features are missing.
|
||||
(Details of what is publicly known about Apple’s AirPlay 2 protocol can
|
||||
be found <a href="https://openairplay.github.io/airplay-spec/">here</a>,
|
||||
<a
|
||||
AirPlay2 protocol using “Legacy Protocol”, but some features are
|
||||
missing. (Details of what is publicly known about Apple’s AirPlay 2
|
||||
protocol can be found <a
|
||||
href="https://openairplay.github.io/airplay-spec/">here</a>, <a
|
||||
href="https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol">here</a>
|
||||
and <a href="https://emanuelecozzi.net/docs/airplay2">here</a>). While
|
||||
there is no guarantee that future iOS releases will keep supporting
|
||||
“Legacy Pairing”, the recent iOS 16 release continues support.</p>
|
||||
and <a href="https://emanuelecozzi.net/docs/airplay2">here</a>; see also
|
||||
<a href="https://pyatv.dev/documentation/protocols">pyatv</a> which
|
||||
could be a resource for adding modern protocols.) While there is no
|
||||
guarantee that future iOS releases will keep supporting “Legacy
|
||||
Protocol”, the recent iOS 16 release continues support.</p>
|
||||
<p>The UxPlay server and its client must be on the same local area
|
||||
network, on which a <strong>Bonjour/Zeroconf mDNS/DNS-SD server</strong>
|
||||
is also running (only DNS-SD “Service Discovery” service is strictly
|
||||
@@ -286,7 +288,8 @@ gstreamer1-devel gstreamer1-plugins-base-devel (+libX11-devel for
|
||||
fullscreen X11) <em>(some of these may be in the “CodeReady” add-on
|
||||
repository, called “PowerTools” by clones)</em></p></li>
|
||||
<li><p><strong>OpenSUSE:</strong> (sudo zypper install) libopenssl-devel
|
||||
libplist-devel avahi-compat-mDNSResponder-devel gstreamer-devel
|
||||
libplist-2_0-devel (formerly libplist-devel)
|
||||
avahi-compat-mDNSResponder-devel gstreamer-devel
|
||||
gstreamer-plugins-base-devel (+ libX11-devel for fullscreen
|
||||
X11).</p></li>
|
||||
<li><p><strong>Arch Linux</strong> (<em>Also available as a package in
|
||||
@@ -1127,6 +1130,15 @@ when the client sends the “Stop Mirroring” signal, try the no-close
|
||||
option “-nc” that leaves the video window open.</p>
|
||||
<h3 id="gstreamer-issues-missing-plugins-etc.">4. GStreamer issues
|
||||
(missing plugins, etc.):</h3>
|
||||
<ul>
|
||||
<li>clearing the user’s GStreamer cache with
|
||||
<code>rm -rf ~/.cache/gstreamer-1.0/*</code> may be the solution to
|
||||
problems where gst-inspect-1.0 does not show a plugin that you believe
|
||||
is installed. The cache will be regenerated next time GStreamer is
|
||||
started. <strong>This is the solution to puzzling problems that turn out
|
||||
to come from corruption of the cache, and should be tried
|
||||
first.</strong></li>
|
||||
</ul>
|
||||
<p>If UxPlay fails to start, with a message that a required GStreamer
|
||||
plugin (such as “libav”) was not found, first check with the GStreamer
|
||||
tool gst-inspect-1.0 to see what GStreamer knows is available. (You may
|
||||
@@ -1138,13 +1150,6 @@ installed (as one user found), try entirely removing and reinstalling
|
||||
the package. That user found that a solution to a “<strong>Required
|
||||
gstreamer plugin ‘libav’ not found</strong>” message that kept recurring
|
||||
was to clear the user’s gstreamer cache.</p>
|
||||
<ul>
|
||||
<li>clearing the user’s GStreamer cache with
|
||||
<code>rm -rf ~/.cache/gstreamer-1.0/*</code> may be the solution to
|
||||
problems where gst-inspect-1.0 does not show a plugin that you believe
|
||||
is installed. The cache will be regenerated next time GStreamer is
|
||||
started.</li>
|
||||
</ul>
|
||||
<p>If it fails to start with an error like
|
||||
‘<code>no element "avdec_aac"</code>’ this is because even though
|
||||
gstreamer-libav is installed. it is incomplete because some plugins are
|
||||
@@ -1211,6 +1216,21 @@ when it is made.</li>
|
||||
id="protocol-issues-such-as-failure-to-decrypt-all-video-and-audio-streams-from-old-or-non-apple-clients">6.
|
||||
Protocol issues, such as failure to decrypt ALL video and audio streams
|
||||
from old or non-Apple clients:</h3>
|
||||
<ul>
|
||||
<li><strong>NEW</strong> As UxPlay only connects to one client at any
|
||||
time, it can work without the client pairing setup, allowing faster
|
||||
connections.</li>
|
||||
</ul>
|
||||
<p>This is allowed by disabling “Supports Legacy Pairing” (bit 27) in
|
||||
the “features” code UxPlay advertises on DNS-SD Service Discovery. Most
|
||||
clients will then not attempt to setup the “shared secret key” for
|
||||
pairing.</p>
|
||||
<ul>
|
||||
<li><strong>This new behavior (since UxPlay-1.65) can be reverted to the
|
||||
previous behavior by uncommenting the previous “FEATURES_1” setting (and
|
||||
commenting out the new one) in lib/dnssdint.h, and then rebuilding
|
||||
UxPlay.</strong></li>
|
||||
</ul>
|
||||
<p>A protocol failure may trigger an unending stream of error messages,
|
||||
and means that the audio decryption key (also used in video decryption)
|
||||
was not correctly extracted from data sent by the client. This should
|
||||
@@ -1230,13 +1250,23 @@ thought that it was necessary for UxPlay to claim to be an older 32 bit
|
||||
AppleTV model that cannot run modern 64bit tvOS, in order for the client
|
||||
to use a “legacy” protocol for pairing with the server. However, UxPlay
|
||||
still works if it declares itself as an AppleTV6,2 with sourceVersion
|
||||
380.20.1 (an AppleTV 4K 1st gen, introduced 2017, running tvOS 12.2.1);
|
||||
it seems that the use of “legacy” protocol just requires bit 27 (listed
|
||||
as “SupportsLegacyPairing”) of the “features” plist code (reported to
|
||||
the client by the AirPlay server) to be set.</p>
|
||||
380.20.1 (an AppleTV 4K 1st gen, introduced 2017, running tvOS 12.2.1).
|
||||
It was previously thought that use of “legacy” protocol requires bit 27
|
||||
(“SupportsLegacyPairing”) of the “features” plist code (reported to the
|
||||
client by the AirPlay server) to be set, but it was recently discovered
|
||||
that it was possible to switch that off (after a small protocol
|
||||
modification) to eliminate a 5 second delay by the client in making
|
||||
connections to the server.</p>
|
||||
<p>The “features” code and other settings are set in
|
||||
<code>UxPlay/lib/dnssdint.h</code>.</p>
|
||||
<h1 id="changelog">Changelog</h1>
|
||||
<p>1.65 2023-05-31 Eliminate pair_setup part of connection protocol to
|
||||
allow faster connections with clients (thanks to <span class="citation"
|
||||
data-cites="shuax">@shuax</span> #176 for this discovery); to revert,
|
||||
uncomment a line in lib/dnssdint.h. Disconnect from audio device when
|
||||
connection closes, to not block its use by other apps if uxplay is
|
||||
running but not connected. Fix for AirMyPC client (broken since 1.60),
|
||||
so its older non-NTP timestamp protocol works with -vsync.</p>
|
||||
<p>1.64 2023-04-23 Timestamp-based synchronization of audio and video is
|
||||
now the default in Mirror mode. (Use “-vsync no” to restore previous
|
||||
behavior.) A configuration file can now be used for startup options.
|
||||
@@ -1390,13 +1420,17 @@ then run “sudo ldconfig”.</p>
|
||||
can avoid this step by installing libplist-dev and libplist3 from Debian
|
||||
10 or Ubuntu 18.04.)</em> As well as the usual build tools (autoconf,
|
||||
automake, libtool), you may need to also install some libpython*-dev
|
||||
package. Download the latest source from <a
|
||||
href="https://github.com/libimobiledevice/libplist">https://github.com/libimobiledevice/libplist</a>:
|
||||
get <a
|
||||
href="https://github.com/libimobiledevice/libplist/archive/refs/heads/master.zip">libplist-master.zip</a>,
|
||||
then (“unzip libplist-master.zip ; cd libplist-master”), build/install
|
||||
(“./autogen.sh ; make ; sudo make install”). This will probably install
|
||||
libplist-2.0.* in /usr/local/lib.</p>
|
||||
package. Download the latest source with git from <a
|
||||
href="https://github.com/libimobiledevice/libplist">https://github.com/libimobiledevice/libplist</a>,
|
||||
or get the source from the Releases section (use the *.tar.bz2 release,
|
||||
<strong>not</strong> the *.zip or *.tar.gz versions): download <a
|
||||
href="https://github.com/libimobiledevice/libplist/releases/download/2.3.0/libplist-2.3.0.tar.bz2">libplist-2.3.0</a>,
|
||||
then unpack it (“tar -xvjf libplist-2.3.0.tar.bz2 ; cd libplist-2.3.0”),
|
||||
and build/install it: (“./configure ; make ; sudo make install”). This
|
||||
will probably install libplist-2.0.* in /usr/local/lib. The new
|
||||
libplist-2.3.0 release should be compatible with UxPlay; <a
|
||||
href="https://github.com/libimobiledevice/libplist/releases/download/2.2.0/libplist-2.2.0.tar.bz2">libplist-2.2.0</a>
|
||||
is also available if there are any issues.</p>
|
||||
<p><em>(Ignore the following for builds on MacOS:)</em> On some systems
|
||||
like Debian or Ubuntu, you may also need to add a missing entry
|
||||
<code>/usr/local/lib</code> in /etc/ld.so.conf (or place a file
|
||||
|
||||
61
README.md
61
README.md
@@ -1,4 +1,4 @@
|
||||
# UxPlay 1.64: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
|
||||
# UxPlay 1.65: 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).
|
||||
|
||||
@@ -59,23 +59,24 @@ from OpenMAX-based [RPiPlay](https://github.com/FD-/RPiPlay), which in turn deri
|
||||
development, but periodically posts updates pulled from the new
|
||||
main [UxPlay site](https://github.com/FDH2/UxPlay)).
|
||||
|
||||
UxPlay is tested on a number of systems, including (among others) Debian 10.11 "Buster" and 11.2 "Bullseye",
|
||||
Ubuntu 20.04 LTS and 22.04.1 LTS, (also Ubuntu derivatives Linux Mint 20.3, Pop!\_OS 22.04 (NVIDIA edition)),
|
||||
Rocky Linux 9.1 (a CentOS successor), Fedora 36, OpenSUSE 15.4, Arch Linux 22.10, macOS 13.3 (Intel and M2),
|
||||
UxPlay is tested on a number of systems, including (among others) Debian (10 "Buster", 11 "Bullseye", 12 "Bookworm"),
|
||||
Ubuntu (20.04 LTS, 22.04 LTS, 23.04; also Ubuntu derivatives Linux Mint 20.3, Pop!\_OS 22.04 (NVIDIA edition)), Red Hat and clones (Fedora 38,
|
||||
Rocky Linux 9.2), OpenSUSE 15.4, Arch Linux 23.05, macOS 13.3 (Intel and M2),
|
||||
FreeBSD 13.2, Windows 10 and 11 (64 bit).
|
||||
|
||||
On Raspberry Pi 4 model B, it is tested on Raspberry Pi OS (Bullseye) (32- and 64-bit), Ubuntu 22.04 and 22.10, Manjaro RPi4 23.02,
|
||||
On Raspberry Pi 4 model B, it is tested on Raspberry Pi OS (Bullseye) (32- and 64-bit), Ubuntu 22.04 LTS and 23.04, Manjaro RPi4 23.02,
|
||||
and (without hardware video decoding) on OpenSUSE 15.4. Also tested on Raspberry Pi 3 model B+.
|
||||
|
||||
Its main use is to act like an AppleTV for screen-mirroring (with audio) of iOS/iPadOS/macOS clients
|
||||
(iPhone, iPod Touch, iPad, Mac computers) on the server display
|
||||
of a host running Linux, macOS, or other unix (and now also Microsoft Windows). UxPlay supports
|
||||
Apple's AirPlay2 protocol using "Legacy Pairing", but some features are missing.
|
||||
Apple's AirPlay2 protocol using "Legacy Protocol", but some features are missing.
|
||||
(Details of what is publicly known about Apple's AirPlay 2 protocol can be found
|
||||
[here](https://openairplay.github.io/airplay-spec/),
|
||||
[here](https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol) and
|
||||
[here](https://emanuelecozzi.net/docs/airplay2)). While there is no guarantee that future
|
||||
iOS releases will keep supporting "Legacy Pairing", the recent iOS 16 release continues support.
|
||||
[here](https://emanuelecozzi.net/docs/airplay2); see also [pyatv](https://pyatv.dev/documentation/protocols) which could be
|
||||
a resource for adding modern protocols.) While there is no guarantee that future
|
||||
iOS releases will keep supporting "Legacy Protocol", the recent iOS 16 release continues support.
|
||||
|
||||
The UxPlay server and its client must be on the same local area network,
|
||||
on which a **Bonjour/Zeroconf mDNS/DNS-SD server** is also running
|
||||
@@ -241,7 +242,7 @@ may be in the "CodeReady" add-on repository, called "PowerTools" by clones)_
|
||||
|
||||
|
||||
* **OpenSUSE:**
|
||||
(sudo zypper install) libopenssl-devel libplist-devel
|
||||
(sudo zypper install) libopenssl-devel libplist-2_0-devel (formerly libplist-devel)
|
||||
avahi-compat-mDNSResponder-devel gstreamer-devel
|
||||
gstreamer-plugins-base-devel (+ libX11-devel for fullscreen X11).
|
||||
|
||||
@@ -922,6 +923,9 @@ the client sends the "Stop Mirroring" signal, try the no-close option "-nc" that
|
||||
|
||||
### 4. GStreamer issues (missing plugins, etc.):
|
||||
|
||||
* clearing the user's GStreamer cache with `rm -rf ~/.cache/gstreamer-1.0/*` may be the solution to problems
|
||||
where gst-inspect-1.0 does not show a plugin that you believe is installed. The cache will be regenerated next time
|
||||
GStreamer is started. **This is the solution to puzzling problems that turn out to come from corruption of the cache, and should be tried first.**
|
||||
|
||||
If UxPlay fails to start, with a message that a required GStreamer plugin (such as "libav") was not found, first check with the GStreamer tool
|
||||
gst-inspect-1.0 to see what GStreamer knows is available. (You may need to install some additional GStreamer "tools" package to get gst-inspect-1.0).
|
||||
@@ -929,11 +933,6 @@ For, _e.g._ a libav problem, check with "`gst-inspect-1.0 libav`". If it is no
|
||||
shows the relevant package as installed (as one user found), try entirely removing and reinstalling the package.
|
||||
That user found that a solution to a "**Required gstreamer plugin 'libav' not found**" message that kept recurring was to clear the user's gstreamer
|
||||
cache.
|
||||
|
||||
* clearing the user's GStreamer cache with `rm -rf ~/.cache/gstreamer-1.0/*` may be the solution to problems
|
||||
where gst-inspect-1.0 does not show a plugin that you believe is installed. The cache will be regenerated next time
|
||||
GStreamer is started.
|
||||
|
||||
|
||||
If it fails to start with an error like '`no element "avdec_aac"`' this is
|
||||
because even though gstreamer-libav is installed. it is incomplete because some plugins are missing: "`gst-inspect-1.0 | grep avdec_aac`" will
|
||||
@@ -984,6 +983,14 @@ new connections, and will be taken over by a new client connection when it is ma
|
||||
|
||||
### 6. Protocol issues, such as failure to decrypt ALL video and audio streams from old or non-Apple clients:
|
||||
|
||||
* **NEW** As UxPlay only connects to one client at any time, it can work without the client pairing setup, allowing faster connections.
|
||||
|
||||
This is allowed by disabling "Supports Legacy Pairing" (bit 27) in the "features" code UxPlay advertises
|
||||
on DNS-SD Service Discovery. Most clients will then not attempt to setup the "shared secret key" for pairing.
|
||||
|
||||
* **This new behavior (since UxPlay-1.65) can be reverted to the previous behavior by uncommenting the previous "FEATURES_1" setting
|
||||
(and commenting out the new one) in lib/dnssdint.h, and then rebuilding UxPlay.**
|
||||
|
||||
A protocol failure may trigger an unending stream of error messages, and means that the
|
||||
audio decryption key (also used in video decryption)
|
||||
was not correctly extracted from data sent by the client.
|
||||
@@ -1004,13 +1011,21 @@ AppleTV model that cannot run modern 64bit tvOS, in order for the client
|
||||
to use a "legacy" protocol for pairing with the server.
|
||||
However, UxPlay still works if it declares itself as an AppleTV6,2 with
|
||||
sourceVersion 380.20.1 (an AppleTV 4K 1st gen, introduced 2017, running
|
||||
tvOS 12.2.1); it seems that the use of "legacy" protocol just requires bit 27 (listed as
|
||||
"SupportsLegacyPairing") of the
|
||||
"features" plist code (reported to the client by the AirPlay server) to be set.
|
||||
tvOS 12.2.1). It was previously thought that
|
||||
use of "legacy" protocol requires bit 27 ("SupportsLegacyPairing") of the
|
||||
"features" plist code (reported to the client by the AirPlay server) to be set,
|
||||
but it was recently discovered that it was possible to switch that off (after a small protocol
|
||||
modification) to eliminate a 5 second delay by the client in making connections to the server.
|
||||
|
||||
The "features" code and other settings are set in `UxPlay/lib/dnssdint.h`.
|
||||
|
||||
# Changelog
|
||||
1.65 2023-05-31 Eliminate pair_setup part of connection protocol to allow faster connections with clients
|
||||
(thanks to @shuax #176 for this discovery); to revert, uncomment a line in lib/dnssdint.h.
|
||||
Disconnect from audio device when connection closes, to not block its use by other apps if
|
||||
uxplay is running but not connected. Fix for AirMyPC client (broken since 1.60), so its
|
||||
older non-NTP timestamp protocol works with -vsync.
|
||||
|
||||
1.64 2023-04-23 Timestamp-based synchronization of audio and video is now the default in Mirror mode.
|
||||
(Use "-vsync no" to restore previous behavior.) A configuration file can now be used
|
||||
for startup options. Also some internal cleanups and a minor bugfix that fixes #192.
|
||||
@@ -1159,10 +1174,14 @@ _(Note: on Debian 9 "Stretch" or Ubuntu 16.04 LTS editions, you can avoid this s
|
||||
and libplist3 from Debian 10 or Ubuntu 18.04.)_
|
||||
As well as the usual build tools (autoconf, automake, libtool), you
|
||||
may need to also install some libpython\*-dev package. Download the latest source
|
||||
from [https://github.com/libimobiledevice/libplist](https://github.com/libimobiledevice/libplist): get
|
||||
[libplist-master.zip](https://github.com/libimobiledevice/libplist/archive/refs/heads/master.zip), then
|
||||
("unzip libplist-master.zip ; cd libplist-master"), build/install
|
||||
("./autogen.sh ; make ; sudo make install"). This will probably install libplist-2.0.* in /usr/local/lib.
|
||||
with git from [https://github.com/libimobiledevice/libplist](https://github.com/libimobiledevice/libplist), or
|
||||
get the source from the Releases section (use the \*.tar.bz2 release, **not** the \*.zip or \*.tar.gz versions):
|
||||
download [libplist-2.3.0](https://github.com/libimobiledevice/libplist/releases/download/2.3.0/libplist-2.3.0.tar.bz2),
|
||||
then unpack it ("tar -xvjf libplist-2.3.0.tar.bz2 ; cd libplist-2.3.0"), and build/install it:
|
||||
("./configure ; make ; sudo make install"). This will probably install libplist-2.0.\* in /usr/local/lib.
|
||||
The new libplist-2.3.0 release should be compatible with
|
||||
UxPlay; [libplist-2.2.0](https://github.com/libimobiledevice/libplist/releases/download/2.2.0/libplist-2.2.0.tar.bz2) is
|
||||
also available if there are any issues.
|
||||
|
||||
_(Ignore the following for builds on MacOS:)_ On some systems like
|
||||
Debian or Ubuntu, you may also need to add a missing entry ```/usr/local/lib```
|
||||
|
||||
89
README.txt
89
README.txt
@@ -1,4 +1,4 @@
|
||||
# UxPlay 1.64: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
|
||||
# UxPlay 1.65: 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).
|
||||
|
||||
@@ -77,14 +77,14 @@ is no longer involved in development, but periodically posts updates
|
||||
pulled from the new main [UxPlay site](https://github.com/FDH2/UxPlay)).
|
||||
|
||||
UxPlay is tested on a number of systems, including (among others) Debian
|
||||
10.11 "Buster" and 11.2 "Bullseye", Ubuntu 20.04 LTS and 22.04.1 LTS,
|
||||
(also Ubuntu derivatives Linux Mint 20.3, Pop!\_OS 22.04 (NVIDIA
|
||||
edition)), Rocky Linux 9.1 (a CentOS successor), Fedora 36, OpenSUSE
|
||||
15.4, Arch Linux 22.10, macOS 13.3 (Intel and M2), FreeBSD 13.2, Windows
|
||||
10 and 11 (64 bit).
|
||||
(10 "Buster", 11 "Bullseye", 12 "Bookworm"), Ubuntu (20.04 LTS, 22.04
|
||||
LTS, 23.04; also Ubuntu derivatives Linux Mint 20.3, Pop!\_OS 22.04
|
||||
(NVIDIA edition)), Red Hat and clones (Fedora 38, Rocky Linux 9.2),
|
||||
OpenSUSE 15.4, Arch Linux 23.05, macOS 13.3 (Intel and M2), FreeBSD
|
||||
13.2, Windows 10 and 11 (64 bit).
|
||||
|
||||
On Raspberry Pi 4 model B, it is tested on Raspberry Pi OS (Bullseye)
|
||||
(32- and 64-bit), Ubuntu 22.04 and 22.10, Manjaro RPi4 23.02, and
|
||||
(32- and 64-bit), Ubuntu 22.04 LTS and 23.04, Manjaro RPi4 23.02, and
|
||||
(without hardware video decoding) on OpenSUSE 15.4. Also tested on
|
||||
Raspberry Pi 3 model B+.
|
||||
|
||||
@@ -92,13 +92,15 @@ Its main use is to act like an AppleTV for screen-mirroring (with audio)
|
||||
of iOS/iPadOS/macOS clients (iPhone, iPod Touch, iPad, Mac computers) on
|
||||
the server display of a host running Linux, macOS, or other unix (and
|
||||
now also Microsoft Windows). UxPlay supports Apple's AirPlay2 protocol
|
||||
using "Legacy Pairing", but some features are missing. (Details of what
|
||||
using "Legacy Protocol", but some features are missing. (Details of what
|
||||
is publicly known about Apple's AirPlay 2 protocol can be found
|
||||
[here](https://openairplay.github.io/airplay-spec/),
|
||||
[here](https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol)
|
||||
and [here](https://emanuelecozzi.net/docs/airplay2)). While there is no
|
||||
guarantee that future iOS releases will keep supporting "Legacy
|
||||
Pairing", the recent iOS 16 release continues support.
|
||||
and [here](https://emanuelecozzi.net/docs/airplay2); see also
|
||||
[pyatv](https://pyatv.dev/documentation/protocols) which could be a
|
||||
resource for adding modern protocols.) While there is no guarantee that
|
||||
future iOS releases will keep supporting "Legacy Protocol", the recent
|
||||
iOS 16 release continues support.
|
||||
|
||||
The UxPlay server and its client must be on the same local area network,
|
||||
on which a **Bonjour/Zeroconf mDNS/DNS-SD server** is also running (only
|
||||
@@ -284,7 +286,8 @@ installed)
|
||||
*(some of these may be in the "CodeReady" add-on repository, called
|
||||
"PowerTools" by clones)*
|
||||
|
||||
- **OpenSUSE:** (sudo zypper install) libopenssl-devel libplist-devel
|
||||
- **OpenSUSE:** (sudo zypper install) libopenssl-devel
|
||||
libplist-2_0-devel (formerly libplist-devel)
|
||||
avahi-compat-mDNSResponder-devel gstreamer-devel
|
||||
gstreamer-plugins-base-devel (+ libX11-devel for fullscreen X11).
|
||||
|
||||
@@ -1158,6 +1161,13 @@ option "-nc" that leaves the video window open.
|
||||
|
||||
### 4. GStreamer issues (missing plugins, etc.):
|
||||
|
||||
- clearing the user's GStreamer cache with
|
||||
`rm -rf ~/.cache/gstreamer-1.0/*` may be the solution to problems
|
||||
where gst-inspect-1.0 does not show a plugin that you believe is
|
||||
installed. The cache will be regenerated next time GStreamer is
|
||||
started. **This is the solution to puzzling problems that turn out
|
||||
to come from corruption of the cache, and should be tried first.**
|
||||
|
||||
If UxPlay fails to start, with a message that a required GStreamer
|
||||
plugin (such as "libav") was not found, first check with the GStreamer
|
||||
tool gst-inspect-1.0 to see what GStreamer knows is available. (You may
|
||||
@@ -1170,12 +1180,6 @@ user found that a solution to a "**Required gstreamer plugin 'libav' not
|
||||
found**" message that kept recurring was to clear the user's gstreamer
|
||||
cache.
|
||||
|
||||
- clearing the user's GStreamer cache with
|
||||
`rm -rf ~/.cache/gstreamer-1.0/*` may be the solution to problems
|
||||
where gst-inspect-1.0 does not show a plugin that you believe is
|
||||
installed. The cache will be regenerated next time GStreamer is
|
||||
started.
|
||||
|
||||
If it fails to start with an error like '`no element "avdec_aac"`' this
|
||||
is because even though gstreamer-libav is installed. it is incomplete
|
||||
because some plugins are missing: "`gst-inspect-1.0 | grep avdec_aac`"
|
||||
@@ -1247,6 +1251,19 @@ causes UxPlay to reset the connection.
|
||||
|
||||
### 6. Protocol issues, such as failure to decrypt ALL video and audio streams from old or non-Apple clients:
|
||||
|
||||
- **NEW** As UxPlay only connects to one client at any time, it can
|
||||
work without the client pairing setup, allowing faster connections.
|
||||
|
||||
This is allowed by disabling "Supports Legacy Pairing" (bit 27) in the
|
||||
"features" code UxPlay advertises on DNS-SD Service Discovery. Most
|
||||
clients will then not attempt to setup the "shared secret key" for
|
||||
pairing.
|
||||
|
||||
- **This new behavior (since UxPlay-1.65) can be reverted to the
|
||||
previous behavior by uncommenting the previous "FEATURES_1" setting
|
||||
(and commenting out the new one) in lib/dnssdint.h, and then
|
||||
rebuilding UxPlay.**
|
||||
|
||||
A protocol failure may trigger an unending stream of error messages, and
|
||||
means that the audio decryption key (also used in video decryption) was
|
||||
not correctly extracted from data sent by the client. This should not
|
||||
@@ -1265,16 +1282,27 @@ thought that it was necessary for UxPlay to claim to be an older 32 bit
|
||||
AppleTV model that cannot run modern 64bit tvOS, in order for the client
|
||||
to use a "legacy" protocol for pairing with the server. However, UxPlay
|
||||
still works if it declares itself as an AppleTV6,2 with sourceVersion
|
||||
380.20.1 (an AppleTV 4K 1st gen, introduced 2017, running tvOS 12.2.1);
|
||||
it seems that the use of "legacy" protocol just requires bit 27 (listed
|
||||
as "SupportsLegacyPairing") of the "features" plist code (reported to
|
||||
the client by the AirPlay server) to be set.
|
||||
380.20.1 (an AppleTV 4K 1st gen, introduced 2017, running tvOS 12.2.1).
|
||||
It was previously thought that use of "legacy" protocol requires bit 27
|
||||
("SupportsLegacyPairing") of the "features" plist code (reported to the
|
||||
client by the AirPlay server) to be set, but it was recently discovered
|
||||
that it was possible to switch that off (after a small protocol
|
||||
modification) to eliminate a 5 second delay by the client in making
|
||||
connections to the server.
|
||||
|
||||
The "features" code and other settings are set in
|
||||
`UxPlay/lib/dnssdint.h`.
|
||||
|
||||
# Changelog
|
||||
|
||||
1.65 2023-05-31 Eliminate pair_setup part of connection protocol to
|
||||
allow faster connections with clients (thanks to @shuax #176 for this
|
||||
discovery); to revert, uncomment a line in lib/dnssdint.h. Disconnect
|
||||
from audio device when connection closes, to not block its use by other
|
||||
apps if uxplay is running but not connected. Fix for AirMyPC client
|
||||
(broken since 1.60), so its older non-NTP timestamp protocol works with
|
||||
-vsync.
|
||||
|
||||
1.64 2023-04-23 Timestamp-based synchronization of audio and video is
|
||||
now the default in Mirror mode. (Use "-vsync no" to restore previous
|
||||
behavior.) A configuration file can now be used for startup options.
|
||||
@@ -1454,12 +1482,17 @@ ldconfig".
|
||||
avoid this step by installing libplist-dev and libplist3 from Debian 10
|
||||
or Ubuntu 18.04.)* As well as the usual build tools (autoconf, automake,
|
||||
libtool), you may need to also install some libpython\*-dev package.
|
||||
Download the latest source from
|
||||
<https://github.com/libimobiledevice/libplist>: get
|
||||
[libplist-master.zip](https://github.com/libimobiledevice/libplist/archive/refs/heads/master.zip),
|
||||
then ("unzip libplist-master.zip ; cd libplist-master"), build/install
|
||||
("./autogen.sh ; make ; sudo make install"). This will probably install
|
||||
libplist-2.0.\* in /usr/local/lib.
|
||||
Download the latest source with git from
|
||||
<https://github.com/libimobiledevice/libplist>, or get the source from
|
||||
the Releases section (use the \*.tar.bz2 release, **not** the \*.zip or
|
||||
\*.tar.gz versions): download
|
||||
[libplist-2.3.0](https://github.com/libimobiledevice/libplist/releases/download/2.3.0/libplist-2.3.0.tar.bz2),
|
||||
then unpack it ("tar -xvjf libplist-2.3.0.tar.bz2 ; cd libplist-2.3.0"),
|
||||
and build/install it: ("./configure ; make ; sudo make install"). This
|
||||
will probably install libplist-2.0.\* in /usr/local/lib. The new
|
||||
libplist-2.3.0 release should be compatible with UxPlay;
|
||||
[libplist-2.2.0](https://github.com/libimobiledevice/libplist/releases/download/2.2.0/libplist-2.2.0.tar.bz2)
|
||||
is also available if there are any issues.
|
||||
|
||||
*(Ignore the following for builds on MacOS:)* On some systems like
|
||||
Debian or Ubuntu, you may also need to add a missing entry
|
||||
|
||||
@@ -42,7 +42,7 @@ endif()
|
||||
|
||||
if( APPLE )
|
||||
set( ENV{PKG_CONFIG_PATH} "/usr/local/lib/pkgconfig" ) # standard location, and Brew
|
||||
set( ENV{PKG_CONFIG_PATH} "/opt/homebrew/lib/pkgconfig" ) # Brew for M1 macs
|
||||
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/homebrew/lib/pkgconfig" ) # Brew for M1 macs
|
||||
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/local/lib/pkgconfig/" ) # MacPorts
|
||||
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/opt/openssl@3/lib/pkgconfig" ) # Brew openssl
|
||||
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/homebrew/opt/openssl@3/lib/pkgconfig" ) # Brew M1 openssl
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
#define RAOP_CN "0,1,2,3" /* Audio codec: PCM, ALAC, AAC, AAC ELD */
|
||||
#define RAOP_ET "0,3,5" /* Encryption type: None, FairPlay, FairPlay SAPv2.5 */
|
||||
#define RAOP_VV "2"
|
||||
#define FEATURES_1 "0x5A7FFEE6" /* first 32 bits of features */
|
||||
//#define FEATURES_1 "0x5A7FFEE6" /* first 32 bits of features, with bit 27 ("supports legacy pairing") ON */
|
||||
#define FEATURES_1 "0x527FFEE6" /* first 32 bits of features, with bit 27 ("supports legacy pairing") OFF */
|
||||
#define FEATURES_2 "0x0" /* second 32 bits of features */
|
||||
#define RAOP_FT FEATURES_1 "," FEATURES_2
|
||||
#define RAOP_RHD "5.6.0.0"
|
||||
|
||||
@@ -90,14 +90,19 @@ pairing_get_public_key(pairing_t *pairing, unsigned char public_key[ED25519_KEY_
|
||||
ed25519_key_get_raw(public_key, pairing->ed);
|
||||
}
|
||||
|
||||
void
|
||||
int
|
||||
pairing_get_ecdh_secret_key(pairing_session_t *session, unsigned char ecdh_secret[X25519_KEY_SIZE])
|
||||
{
|
||||
assert(session);
|
||||
memcpy(ecdh_secret, session->ecdh_secret, X25519_KEY_SIZE);
|
||||
switch (session->status) {
|
||||
case STATUS_INITIAL:
|
||||
return 0;
|
||||
default:
|
||||
memcpy(ecdh_secret, session->ecdh_secret, X25519_KEY_SIZE);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pairing_session_t *
|
||||
pairing_session_init(pairing_t *pairing)
|
||||
{
|
||||
|
||||
@@ -38,6 +38,6 @@ void pairing_session_destroy(pairing_session_t *session);
|
||||
|
||||
void pairing_destroy(pairing_t *pairing);
|
||||
|
||||
void pairing_get_ecdh_secret_key(pairing_session_t *session, unsigned char ecdh_secret[X25519_KEY_SIZE]);
|
||||
int pairing_get_ecdh_secret_key(pairing_session_t *session, unsigned char ecdh_secret[X25519_KEY_SIZE]);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -70,8 +70,9 @@ struct raop_callbacks_s {
|
||||
void (*video_report_size)(void *cls, float *width_source, float *height_source, float *width, float *height);
|
||||
};
|
||||
typedef struct raop_callbacks_s raop_callbacks_t;
|
||||
raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const unsigned char *remote_addr, int remote_addr_len, unsigned short timing_rport);
|
||||
|
||||
raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const unsigned char *remote_addr, 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);
|
||||
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);
|
||||
|
||||
@@ -395,14 +395,6 @@ raop_handler_setup(raop_conn_t *conn,
|
||||
free(str);
|
||||
}
|
||||
|
||||
unsigned char ecdh_secret[X25519_KEY_SIZE];
|
||||
pairing_get_ecdh_secret_key(conn->pairing, ecdh_secret);
|
||||
if (logger_debug) {
|
||||
char *str = utils_data_to_string(ecdh_secret, X25519_KEY_SIZE, 16);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "32 byte shared ecdh_secret:\n%s", str);
|
||||
free(str);
|
||||
}
|
||||
|
||||
const char *user_agent = http_request_get_header(request, "User-Agent");
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "Client identified as User-Agent: %s", user_agent);
|
||||
|
||||
@@ -413,17 +405,33 @@ raop_handler_setup(raop_conn_t *conn,
|
||||
if (old_protocol) { /* some windows AirPlay-client emulators use old AirPlay 1 protocol with unhashed AES key */
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "Client identifed as using old protocol (unhashed) AES audio key)");
|
||||
} else {
|
||||
memcpy(eaeskey, aeskey, 16);
|
||||
sha_ctx_t *ctx = sha_init();
|
||||
sha_update(ctx, eaeskey, 16);
|
||||
sha_update(ctx, ecdh_secret, 32);
|
||||
sha_final(ctx, eaeskey, NULL);
|
||||
sha_destroy(ctx);
|
||||
memcpy(aeskey, eaeskey, 16);
|
||||
if (logger_debug) {
|
||||
char *str = utils_data_to_string(aeskey, 16, 16);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aeskey after sha-256 hash with ecdh_secret:\n%s", str);
|
||||
free(str);
|
||||
unsigned char ecdh_secret[X25519_KEY_SIZE];
|
||||
if (pairing_get_ecdh_secret_key(conn->pairing, ecdh_secret)) {
|
||||
/* In this case (legacy) pairing with client was successfully set up and created the shared ecdh_secret:
|
||||
* aeskey must be hashed with it
|
||||
*
|
||||
* If byte 27 of features ("supports legacy pairing") is turned off, the client does not request pairsetup
|
||||
* and does NOT set up pairing (this eliminates a 5 second delay in connecting with no apparent bad effects).
|
||||
* This may be because uxplay currently does not support connections with more than one client at a time
|
||||
* while AppleTV supports up to 12 clients, and uses pairing to give each a distinct SessionID .*/
|
||||
|
||||
if (logger_debug) {
|
||||
char *str = utils_data_to_string(ecdh_secret, X25519_KEY_SIZE, 16);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "32 byte shared ecdh_secret:\n%s", str);
|
||||
free(str);
|
||||
}
|
||||
memcpy(eaeskey, aeskey, 16);
|
||||
sha_ctx_t *ctx = sha_init();
|
||||
sha_update(ctx, eaeskey, 16);
|
||||
sha_update(ctx, ecdh_secret, 32);
|
||||
sha_final(ctx, eaeskey, NULL);
|
||||
sha_destroy(ctx);
|
||||
memcpy(aeskey, eaeskey, 16);
|
||||
if (logger_debug) {
|
||||
char *str = utils_data_to_string(aeskey, 16, 16);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aeskey after sha-256 hash with ecdh_secret:\n%s", str);
|
||||
free(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,13 +445,28 @@ raop_handler_setup(raop_conn_t *conn,
|
||||
" Only AirPlay v1 protocol (using NTP and timing port) is supported");
|
||||
}
|
||||
}
|
||||
uint64_t string_len;
|
||||
uint64_t string_len = 0;
|
||||
const char *timing_protocol;
|
||||
timing_protocol_t time_protocol;
|
||||
plist_t req_timing_protocol_node = plist_dict_get_item(req_root_node, "timingProtocol");
|
||||
timing_protocol = plist_get_string_ptr(req_timing_protocol_node, &string_len);
|
||||
if (strcmp(timing_protocol, "NTP")) {
|
||||
logger_log(conn->raop->logger, LOGGER_ERR, "Client specified timingProtocol=%s, but timingProtocol= NTP is required here", timing_protocol);
|
||||
}
|
||||
if (string_len) {
|
||||
if (strncmp(timing_protocol, "NTP", string_len) == 0) {
|
||||
time_protocol = NTP;
|
||||
} else if (strncmp(timing_protocol, "None", string_len) == 0) {
|
||||
time_protocol = TP_NONE;
|
||||
} else {
|
||||
time_protocol = TP_OTHER;
|
||||
}
|
||||
if (time_protocol != NTP) {
|
||||
logger_log(conn->raop->logger, LOGGER_ERR, "Client specified timingProtocol=%s,"
|
||||
" but timingProtocol= NTP is required here", timing_protocol);
|
||||
}
|
||||
} else {
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "Client did not specify timingProtocol,"
|
||||
" old protocol without offset will be used");
|
||||
time_protocol = TP_UNSPECIFIED;
|
||||
}
|
||||
timing_protocol = NULL;
|
||||
uint64_t timing_rport = 0;
|
||||
plist_t req_timing_port_node = plist_dict_get_item(req_root_node, "timingPort");
|
||||
@@ -453,14 +476,18 @@ raop_handler_setup(raop_conn_t *conn,
|
||||
if (timing_rport) {
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "timing_rport = %llu", timing_rport);
|
||||
} else {
|
||||
logger_log(conn->raop->logger, LOGGER_ERR, "Client did not supply timing_rport, may be using unsupported AirPlay2 \"Remote Control\" protocol");
|
||||
logger_log(conn->raop->logger, LOGGER_ERR, "Client did not supply timing_rport,"
|
||||
" may be using unsupported AirPlay2 \"Remote Control\" protocol");
|
||||
}
|
||||
unsigned short timing_lport = conn->raop->timing_lport;
|
||||
conn->raop_ntp = raop_ntp_init(conn->raop->logger, &conn->raop->callbacks, conn->remote, conn->remotelen, timing_rport);
|
||||
conn->raop_ntp = raop_ntp_init(conn->raop->logger, &conn->raop->callbacks, conn->remote,
|
||||
conn->remotelen, (unsigned short) timing_rport, &time_protocol);
|
||||
raop_ntp_start(conn->raop_ntp, &timing_lport, conn->raop->max_ntp_timeouts);
|
||||
|
||||
conn->raop_rtp = raop_rtp_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey, aesiv);
|
||||
conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey);
|
||||
conn->raop_rtp = raop_rtp_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp,
|
||||
conn->remote, conn->remotelen, aeskey, aesiv);
|
||||
conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks,
|
||||
conn->raop_ntp, conn->remote, conn->remotelen, aeskey);
|
||||
|
||||
plist_t res_event_port_node = plist_new_uint(conn->raop->port);
|
||||
plist_t res_timing_port_node = plist_new_uint(timing_lport);
|
||||
@@ -490,7 +517,8 @@ raop_handler_setup(raop_conn_t *conn,
|
||||
plist_t stream_id_node = plist_dict_get_item(req_stream_node, "streamConnectionID");
|
||||
uint64_t stream_connection_id;
|
||||
plist_get_uint_val(stream_id_node, &stream_connection_id);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "streamConnectionID (needed for AES-CTR video decryption key and iv): %llu", stream_connection_id);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "streamConnectionID (needed for AES-CTR video decryption"
|
||||
" key and iv): %llu", stream_connection_id);
|
||||
|
||||
if (conn->raop_rtp_mirror) {
|
||||
raop_rtp_init_mirror_aes(conn->raop_rtp_mirror, &stream_connection_id);
|
||||
|
||||
@@ -91,6 +91,8 @@ struct raop_ntp_s {
|
||||
|
||||
// UDP socket
|
||||
int tsock;
|
||||
|
||||
timing_protocol_t time_protocol;
|
||||
};
|
||||
|
||||
|
||||
@@ -140,7 +142,7 @@ raop_ntp_parse_remote_address(raop_ntp_t *raop_ntp, const unsigned char *remote_
|
||||
}
|
||||
|
||||
raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const unsigned char *remote_addr,
|
||||
int remote_addr_len, unsigned short timing_rport) {
|
||||
int remote_addr_len, unsigned short timing_rport, timing_protocol_t *time_protocol) {
|
||||
raop_ntp_t *raop_ntp;
|
||||
|
||||
assert(logger);
|
||||
@@ -150,6 +152,7 @@ raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const u
|
||||
if (!raop_ntp) {
|
||||
return NULL;
|
||||
}
|
||||
raop_ntp->time_protocol = *time_protocol;
|
||||
raop_ntp->logger = logger;
|
||||
memcpy(&raop_ntp->callbacks, callbacks, sizeof(raop_callbacks_t));
|
||||
raop_ntp->timing_rport = timing_rport;
|
||||
@@ -322,10 +325,10 @@ raop_ntp_thread(void *arg)
|
||||
int64_t t0 = (int64_t) byteutils_get_ntp_timestamp(response, 8);
|
||||
|
||||
// Local time of the client when the NTP request packet arrives at the client
|
||||
int64_t t1 = (int64_t) byteutils_get_ntp_timestamp(response, 16);
|
||||
int64_t t1 = (int64_t) raop_remote_timestamp_to_nano_seconds(raop_ntp, byteutils_get_long_be(response, 16));
|
||||
|
||||
// Local time of the client when the response message leaves the client
|
||||
int64_t t2 = (int64_t) byteutils_get_ntp_timestamp(response, 24);
|
||||
int64_t t2 = (int64_t) raop_remote_timestamp_to_nano_seconds(raop_ntp, byteutils_get_long_be(response, 24));
|
||||
|
||||
if (logger_debug) {
|
||||
char *str = utils_data_to_string(response, response_len, 16);
|
||||
@@ -480,6 +483,12 @@ uint64_t raop_ntp_timestamp_to_nano_seconds(uint64_t ntp_timestamp, bool account
|
||||
return (seconds * SECOND_IN_NSECS) + ((fraction * SECOND_IN_NSECS) >> 32);
|
||||
}
|
||||
|
||||
uint64_t raop_remote_timestamp_to_nano_seconds(raop_ntp_t *raop_ntp, uint64_t timestamp) {
|
||||
uint64_t seconds = ((timestamp >> 32) & 0xffffffff);
|
||||
if (raop_ntp->time_protocol == NTP) seconds -= SECONDS_FROM_1900_TO_1970;
|
||||
uint64_t fraction = (timestamp & 0xffffffff);
|
||||
return (seconds * SECOND_IN_NSECS) + ((fraction * SECOND_IN_NSECS) >> 32);
|
||||
}
|
||||
/**
|
||||
* Returns the current time in nano seconds according to the local wall clock.
|
||||
* The system Unix time is used as the local wall clock.
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
typedef struct raop_ntp_s raop_ntp_t;
|
||||
|
||||
typedef enum timing_protocol_e { NTP, TP_NONE, TP_OTHER, TP_UNSPECIFIED } timing_protocol_t;
|
||||
|
||||
void raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport, int max_ntp_timeouts);
|
||||
|
||||
@@ -35,6 +36,7 @@ unsigned short raop_ntp_get_port(raop_ntp_t *raop_ntp);
|
||||
void raop_ntp_destroy(raop_ntp_t *raop_rtp);
|
||||
|
||||
uint64_t raop_ntp_timestamp_to_nano_seconds(uint64_t ntp_timestamp, bool account_for_epoch_diff);
|
||||
uint64_t raop_remote_timestamp_to_nano_seconds(raop_ntp_t *raop_ntp, uint64_t timestamp);
|
||||
|
||||
uint64_t raop_ntp_get_local_time(raop_ntp_t *raop_ntp);
|
||||
uint64_t raop_ntp_get_remote_time(raop_ntp_t *raop_ntp);
|
||||
|
||||
@@ -557,7 +557,7 @@ raop_rtp_thread_udp(void *arg)
|
||||
have_synced = true;
|
||||
}
|
||||
uint64_t sync_ntp_raw = byteutils_get_long_be(packet, 8);
|
||||
uint64_t sync_ntp_remote = raop_ntp_timestamp_to_nano_seconds(sync_ntp_raw, true);
|
||||
uint64_t sync_ntp_remote = raop_remote_timestamp_to_nano_seconds(raop_rtp->ntp, sync_ntp_raw);
|
||||
if (logger_debug) {
|
||||
uint64_t sync_ntp_local = raop_ntp_convert_remote_time(raop_rtp->ntp, sync_ntp_remote);
|
||||
char *str = utils_data_to_string(packet, packetlen, 20);
|
||||
|
||||
@@ -321,11 +321,13 @@ raop_rtp_mirror_thread(void *arg)
|
||||
/* packet[4] + packet[5] identify the payload type: values seen are: *
|
||||
* 0x00 0x00: encrypted packet containing a non-IDR type 1 VCL NAL unit *
|
||||
* 0x00 0x10: encrypted packet containing an IDR type 5 VCL NAL unit *
|
||||
* 0x01 0x00 unencrypted packet containing a type 7 SPS NAL + a type 8 PPS NAL unit *
|
||||
* 0x01 0x00: unencrypted packet containing a type 7 SPS NAL + a type 8 PPS NAL unit *
|
||||
* 0x02 0x00: unencryted packet (old protocol) no payload, sent once every second *
|
||||
* 0x05 0x00 unencrypted packet with a "streaming report", sent once per second. */
|
||||
|
||||
/* packet[6] + packet[7] may list a payload "option": values seen are: *
|
||||
* 0x00 0x00 : encrypted and "streaming report" packets *
|
||||
* 0x1e 0x00 : old protocol (seen in AirMyPC) no-payload once-per-second packets *
|
||||
* 0x16 0x01 : seen in most unencrypted SPS+PPS packets *
|
||||
* 0x56 0x01 : occasionally seen in unencrypted SPS+PPS packets (why different?) */
|
||||
|
||||
@@ -400,15 +402,19 @@ raop_rtp_mirror_thread(void *arg)
|
||||
* that has not yet been sent. This will trigger prepending it to the current NAL, and the prepend_sps_pps
|
||||
* flag will be set to false after it has been prepended. */
|
||||
|
||||
if (prepend_sps_pps & (ntp_timestamp_raw != ntp_timestamp_nal)) {
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG,
|
||||
"raop_rtp_mirror: prepended sps_pps timestamp does not match timestamp of "
|
||||
"video payload\n%llu\n%llu , discarding", ntp_timestamp_raw, ntp_timestamp_nal);
|
||||
free (sps_pps);
|
||||
sps_pps = NULL;
|
||||
prepend_sps_pps = false;
|
||||
}
|
||||
|
||||
if (prepend_sps_pps) {
|
||||
assert(sps_pps);
|
||||
payload_out = (unsigned char*) malloc(payload_size + sps_pps_len);
|
||||
payload_decrypted = payload_out + sps_pps_len;
|
||||
if (ntp_timestamp_raw != ntp_timestamp_nal) {
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING,
|
||||
"raop_rtp_mirror: prepended sps_pps timestamp does not match timestamp of "
|
||||
"video payload\n%llu\n%llu", ntp_timestamp_raw, ntp_timestamp_nal);
|
||||
}
|
||||
memcpy(payload_out, sps_pps, sps_pps_len);
|
||||
free (sps_pps);
|
||||
sps_pps = NULL;
|
||||
@@ -441,6 +447,7 @@ raop_rtp_mirror_thread(void *arg)
|
||||
int nalu_type = payload_decrypted[nalu_size] & 0x1f;
|
||||
int ref_idc = (payload_decrypted[nalu_size] >> 5);
|
||||
switch (nalu_type) {
|
||||
case 14: /* Prefix NALu , seen before all VCL Nalu's in AirMyPc */
|
||||
case 5: /*IDR, slice_layer_without_partitioning */
|
||||
case 1: /*non-IDR, slice_layer_without_partitioning */
|
||||
break;
|
||||
@@ -461,6 +468,24 @@ raop_rtp_mirror_thread(void *arg)
|
||||
free(str);
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
if (logger_debug) {
|
||||
char *str = utils_data_to_string(payload_decrypted + nalu_size, nc_len, 16);
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror SPS NAL size = %d", nc_len);
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG,
|
||||
"raop_rtp_mirror h264 Sequence Parameter Set:\n%s", str);
|
||||
free(str);
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
if (logger_debug) {
|
||||
char *str = utils_data_to_string(payload_decrypted + nalu_size, nc_len, 16);
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror PPS NAL size = %d", nc_len);
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG,
|
||||
"raop_rtp_mirror h264 Picture Parameter Set :\n%s", str);
|
||||
free(str);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_INFO,
|
||||
"unexpected non-VCL NAL unit: nalu_type = %d, ref_idc = %d, nalu_size = %d,"
|
||||
@@ -580,6 +605,10 @@ raop_rtp_mirror_thread(void *arg)
|
||||
// memcpy(h264.picture_parameter_set, picture_parameter_set, pps_size);
|
||||
|
||||
break;
|
||||
case 0x02:
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "\nReceived old-protocol once-per-second packet from client:"
|
||||
" payload_size %d header %s ts_raw = %llu", payload_size, packet_description, ntp_timestamp_raw);
|
||||
/* "old protocol" (used by AirMyPC), rest of 128-byte packet is empty */
|
||||
case 0x05:
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "\nReceived video streaming performance info packet from client:"
|
||||
" payload_size %d header %s ts_raw = %llu", payload_size, packet_description, ntp_timestamp_raw);
|
||||
@@ -615,7 +644,7 @@ raop_rtp_mirror_thread(void *arg)
|
||||
break;
|
||||
default:
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "\nReceived unexpected TCP packet from client, "
|
||||
"size %d, %s ts_raw = raw%llu", payload_size, packet_description, ntp_timestamp_raw);
|
||||
"size %d, %s ts_raw = %llu", payload_size, packet_description, ntp_timestamp_raw);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
if (APPLE )
|
||||
set( ENV{PKG_CONFIG_PATH} "/Library/FrameWorks/GStreamer.framework/Libraries/pkgconfig" ) # GStreamer.framework, preferred
|
||||
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig" ) # Brew or self-installed gstreamer
|
||||
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig" ) # Brew or self-installed gstreamer
|
||||
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/homebrew/lib/pkgconfig" ) # Brew, M1/M2 macs
|
||||
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/local/lib/pkgconfig/" ) # MacPorts
|
||||
message( "PKG_CONFIG_PATH (Apple, renderers) = " $ENV{PKG_CONFIG_PATH} )
|
||||
find_program( PKG_CONFIG_EXECUTABLE pkg-config PATHS /Library/FrameWorks/GStreamer.framework/Commands )
|
||||
|
||||
6
uxplay.1
6
uxplay.1
@@ -1,11 +1,11 @@
|
||||
.TH UXPLAY "1" "April 2023" "1.64" "User Commands"
|
||||
.TH UXPLAY "1" "June 2023" "1.65" "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.64: An open\-source AirPlay mirroring (+ audio streaming) server:
|
||||
UxPlay 1.65: An open\-source AirPlay mirroring (+ audio streaming) server:
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B
|
||||
@@ -142,7 +142,7 @@ COPYRIGHT
|
||||
.TP
|
||||
Various, see website or distribution. License: GPL v3+: GNU GPL version 3 or later.
|
||||
.TP
|
||||
(some parts LGPL v.2.1 and MIT).
|
||||
(some parts LGPL v.2.1+ or MIT).
|
||||
.SH
|
||||
SEE ALSO
|
||||
.TP
|
||||
|
||||
70
uxplay.cpp
70
uxplay.cpp
@@ -59,7 +59,7 @@
|
||||
#include "renderers/video_renderer.h"
|
||||
#include "renderers/audio_renderer.h"
|
||||
|
||||
#define VERSION "1.64"
|
||||
#define VERSION "1.65"
|
||||
|
||||
#define SECOND_IN_USECS 1000000
|
||||
#define SECOND_IN_NSECS 1000000000UL
|
||||
@@ -1055,6 +1055,9 @@ extern "C" void conn_destroy (void *cls) {
|
||||
//LOGD("Open connections: %i", open_connections);
|
||||
if (open_connections == 0) {
|
||||
remote_clock_offset = 0;
|
||||
if (use_audio) {
|
||||
audio_renderer_stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1315,17 +1318,62 @@ static void read_config_file(const char * filename, const char * uxplay_name) {
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (line[0] == '#') continue;
|
||||
std::stringstream ss(line);
|
||||
std::istream_iterator<std::string> begin(ss);
|
||||
std::istream_iterator<std::string> end;
|
||||
std::vector<std::string> tokens(begin,end);
|
||||
if (tokens.size() > 0) {
|
||||
options.push_back(option_char + tokens[0]);
|
||||
for (int i = 1; i < tokens.size(); i++) {
|
||||
options.push_back(tokens[i].c_str());
|
||||
}
|
||||
// first process line into separate option items with '\0' as delimiter
|
||||
bool is_part_of_item, in_quotes;
|
||||
char endchar;
|
||||
is_part_of_item = false;
|
||||
for (int i = 0; i < line.size(); i++) {
|
||||
switch (is_part_of_item) {
|
||||
case false:
|
||||
if (line[i] == ' ') {
|
||||
line[i] = '\0';
|
||||
} else {
|
||||
// start of new item
|
||||
is_part_of_item = true;
|
||||
switch (line[i]) {
|
||||
case '\'':
|
||||
case '\"':
|
||||
endchar = line[i];
|
||||
line[i] = '\0';
|
||||
in_quotes = true;
|
||||
break;
|
||||
default:
|
||||
in_quotes = false;
|
||||
endchar = ' ';
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case true:
|
||||
/* previous character was inside this item */
|
||||
if (line[i] == endchar) {
|
||||
if (in_quotes) {
|
||||
/* cases where endchar is inside quoted item */
|
||||
if (i > 0 && line[i - 1] == '\\') continue;
|
||||
if (i + 1 < line.size() && line[i + 1] != ' ') continue;
|
||||
}
|
||||
line[i] = '\0';
|
||||
is_part_of_item = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now tokenize the processed line
|
||||
std::istringstream iss(line);
|
||||
std::string token;
|
||||
bool first = true;
|
||||
while (std::getline(iss, token, '\0')) {
|
||||
if (token.size() > 0) {
|
||||
if (first) {
|
||||
options.push_back(option_char + token.c_str());
|
||||
first = false;
|
||||
} else {
|
||||
options.push_back(token.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
} else {
|
||||
fprintf(stderr,"UxPlay: failed to open configuration file at %s\n", config_file.c_str());
|
||||
|
||||
Reference in New Issue
Block a user