Merge pull request #72 from FDH2/master

ongoing pull requests from development branch UxPlay 1.43
This commit is contained in:
antimof
2021-12-20 17:28:52 +03:00
committed by GitHub
22 changed files with 605 additions and 297 deletions

View File

@@ -1,29 +1,30 @@
<h1 id="uxplay-1.43-airplayairplay-mirror-server-for-linux-macos-and-unix.">UxPlay 1.43: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.</h1>
<p>This project is a GPLv3 unix AirPlay2 server which now also works on macOS. Its main use is to act like an AppleTV for screen-mirroring (with audio) of iOS/macOS clients (iPads, iPhones, MacBooks) in a window on the server display (with the possibility of sharing that window on screen-sharing applications such as Zoom) on a host running Linux, macOS, or other unix, using Apples AirPlay Mirror protocol first available in iOS 5. (Details of what is known about the AirPlay2 protocol can be found <a href="https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol">here</a> and <a href="https://emanuelecozzi.net/docs/airplay2">here</a>). <strong>Note that Apple DRM (as in Apple TV app content) cannot be decrypted by UxPlay.</strong></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 necessary, it is not necessary that the local network also be of the “.local” mDNS-based type). On Linux and BSD Unix servers, this is usually provided by <a href="https://www.avahi.org">Avahi</a>, through the avahi-daemon service, and is included in most Linux distributions (this service can also be provided by macOS, iOS or Windows servers).</p>
<p><em>Since v1.38, UxPlay now also supports the Airplay audio-only protocol as well as AirPlay Mirror protocol, and (when the client screen is not being mirrored) can play Apple Lossless (ALAC) audio streamed from the client without video (the accompanying cover-art and metadata is not displayed). The initial connection to the client can be either AirPlay audio or Airplay Mirror mode. An Airplay Mirror connection (with “Advanced Audio Coding” AAC-ELD lossy-compression audio) switches to ALAC if the mirrow window is closed and an AirPlay audio connection is started, and back again to AAC if a new Airplay Mirror connection is made</em>.</p>
<p>UxPlay is based on https://github.com/FD-/RPiPlay, with GStreamer integration from https://github.com/antimof/UxPlay. (UxPlay only uses GStreamer, and does not contain the alternative Raspberry-Pi-specific audio and video renderers also found in RPiPlay.) Tested on a number of systems, including Ubuntu 20.04, Linux Mint 20.2, OpenSUSE 15.3, macOS 10.15, FreeBSD 13.0.</p>
<h1 id="uxplay-1.44-airplayairplay-mirror-server-for-linux-macos-and-unix.">UxPlay 1.44: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.</h1>
<p>This project is a GPLv3 open source unix AirPlay2 Mirror server for Linux, macOS, and *BSD. It is now hosted at the github site <a href="https://github.com/FDH2/UxPlay">https://github.com/FDH2/UxPlay</a> (where development and user-assistance now takes place), although it initially was developed by <a href="http://github.com/antimof/Uxplay">antimof</a> using code from <a href="https://github.com/FD-/RPiPlay">RPiPlay</a>, which in turn derives from <a href="https://github.com/KqsMea8/AirplayServer">AirplayServer</a>, <a href="https://github.com/juhovh/shairplay">shairplay</a>, and <a href="https://github.com/EstebanKubata/playfair">playfair</a>. (The antimof site is mainly inactive, but periodically posts updates pulled from the <a href="https://github.com/FDH2/UxPlay">main UxPlay site</a>).</p>
<p>Its main use is to act like an AppleTV for screen-mirroring (with audio) of iOS/iPadOS/macOS clients (iPhones, iPads, MacBooks) in a window on the server display (with the possibility of sharing that window on screen-sharing applications such as Zoom) on a host running Linux, macOS, or other unix. UxPlay supports a “legacy” form of Apples AirPlay Mirror protocol introduced in iOS 12; client devices running iOS/iPadOS 12 or later are supported, as is a (nonfree) Windows-based AirPlay-client software emulator, AirMyPC. Older (32-bit) client devices that can only run iOS 9.3 or iOS 10.3 are currently partially supported by UxPlay: reports indicate that screen-mirroring video works, audio is a work in progess. (Details of what is publically known about Apples AirPlay2 protocol can be found <a href="https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol">here</a> and <a href="https://emanuelecozzi.net/docs/airplay2">here</a>).</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 necessary, it is not necessary that the local network also be of the “.local” mDNS-based type). On Linux and BSD Unix servers, this is usually provided by <a href="https://www.avahi.org">Avahi</a>, through the avahi-daemon service, and is included in most Linux distributions (this service can also be provided by macOS, iOS or Windows servers).</p>
<p>Connections to the UxPlay server by iOS/MacOS clients can be initiated both in AirPlay Mirror mode (which streams lossily-compressed AAC audio while mirroring the client screen, or in the alternative AirPlay Audio mode which streams Apple Lossless (ALAC) audio without screen mirroring (the accompanying metadata and cover art in this mode is not displayed). <em>Switching between these two modes during an active connection is possible: in Mirror mode, close the mirror window and start an Audio mode connection, switch back by initiating a Mirror mode connection.</em> <strong>Note that Apple DRM (as in Apple TV app content on the client) cannot be decrypted by UxPlay, and (unlike with a true AppleTV), the client cannot run a http connection on the server instead of streaming content from one on the client.</strong></p>
<p>UxPlay uses GStreamer Plugins for rendering audio and video, and does not offer the alternative Raspberry-Pi-specific audio and video renderers available in <a href="https://github.com/FD-/RPiPlay">RPiPlay</a>. It is tested on a number of systems, including (among others) Debian 11.2, Ubuntu 20.04 and 21.10, Linux Mint 20.2, OpenSUSE 15.3, macOS 10.15.7, FreeBSD 13.0.</p>
<p>Using Gstreamer means that video and audio are supported “out of the box”, using a choice of plugins. Gstreamer decoding is plugin agnostic, and uses accelerated decoders if available. For Intel integrated graphics, the VAAPI plugin is preferable, (but dont use it with nVidia).</p>
<h3 id="note-to-packagers-openssl-3.0.0-solves-gpl-v3-license-issues.">Note to packagers: OpenSSL-3.0.0 solves GPL v3 license issues.</h3>
<p>Some Linux distributions such as Debian do not allow distribution of compiled GPL code linked to OpenSLL-1.1.1 because its “dual OpenSSL/SSLeay” license has some incompatibilites with GPL, unless all code authors have explicitly given an “exception” to allow such linking (the historical origins of UxPlay make this impossible to obtain). Other distributions treat OpenSSL as a “System Library” which the GPL allows linking to.</p>
<p>Some Linux distributions such as Debian do not allow distribution of compiled GPL code linked to OpenSSL-1.1.1 because its “dual OpenSSL/SSLeay” license has some incompatibilites with GPL, unless all code authors have explicitly given an “exception” to allow such linking (the historical origins of UxPlay make this impossible to obtain). Other distributions treat OpenSSL as a “System Library” which the GPL allows linking to.</p>
<p>For “GPL-strict” distributions, UxPlay can be built using OpenSSL- 3.0.0, which has a new <a href="https://www.openssl.org/blog/blog/2021/09/07/OpenSSL3.Final/">GPLv3-compatible license</a>.</p>
<h1 id="getting-uxplay">Getting UxPlay:</h1>
<p>Either download and unzip <a href="https://github.com/FDH2/UxPlay/archive/refs/heads/master.zip">UxPlay-master.zip</a>, or (if git is installed): “git clone https://github.com/FDH2/UxPlay”.</p>
<p>*This is also a pull request on the original site https://github.com/antimof/UxPlay ; but is unlikely to ever get committed into the codebase there, as that project appears to be inactive.</p>
<p>*This is also a pull request on the original site https://github.com/antimof/UxPlay ; that original project is inactive, but the pull request with changes up to 2021-12-10 were recently merged with the antimof tree (thank you antimof!).</p>
<h2 id="building-uxplay-on-linux-or-bsd">Building UxPlay on Linux (or *BSD):</h2>
<p>(Instructions for Debian/Ubuntu; adapt these for other Linuxes; for macOS, see below).</p>
<p>You need a C/C++ compiler (e.g. g++) with the standard development libraries installed.</p>
<p>Make sure that cmake&gt;=3.4.1 and pkg-config are also installed: “sudo apt-get install cmake pkg-config”. In a terminal window, change directories to the source directory of the downloaded source code (“UxPlay-master” for zipfile downloads, “UxPlay” for “git clone” downloads), then do</p>
<ol type="1">
<li><code>sudo apt-get install libssl-dev libplist-dev libavahi-compat-libdnssd-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-libav gstreamer1.0-plugins-bad</code></li>
<li><code>sudo apt-get install gstreamer1.0-vaapi</code> (For Intel graphics, but not nVidia graphics)</li>
<li><code>sudo apt-get install gstreamer1.0-vaapi</code> (For hardware-accelerated Intel graphics, but not nVidia graphics)</li>
<li><code>sudo apt-get install libx11-dev</code> (for the “ZOOMFIX” X11_display name fix for screen-sharing with e.g., ZOOM)</li>
<li><code>cmake .</code> (or “<code>cmake -DZOOMFIX=ON .</code>” to get a screen-sharing fix to make X11 mirror display windows visible to screen-sharing applications such as Zoom, see below).</li>
<li><code>make</code></li>
<li><code>sudo make install</code> (you can afterwards uninstall with <code>sudo make uninstall</code> in the same directory in which this was run)</li>
</ol>
<p><em>If you intend to modify the code, use a separate “build” directory: replace</em><code>cmake [ ] .</code><em>by</em><code>mkdir build ; cd build ; cmake [ ] ..</code>”; <em>you can then clean the build directory with</em><code>rm -rf build/*</code><em>(run from within the UxPlay source directory) without affecting the source directories which contain your modifications</em>.</p>
<p>The above script installs the executable file “<code>uxplay</code>” to <code>/usr/local/bin</code>, (and installs a manpage to <code>/usr/local/man/man1</code> and README files to <code>/usr/local/share/doc/uxplay</code>). It can also be found in the build directory after the build processs. Run uxplay in a terminal window.</p>
<p>The above script installs the executable file “<code>uxplay</code>” to <code>/usr/local/bin</code>, (and installs a manpage to somewhere like <code>/usr/local/share/man/man1</code> and README files to somewhere like <code>/usr/local/share/doc/uxplay</code>). It can also be found in the build directory after the build processs. Run uxplay in a terminal window.</p>
<p><strong>Note libplist-dev (version 2.0 or greater) is a new dependency (the original antimof version UxPlay-1.2 supplied it). Older Linux distributions may only supply libplist 1.x, which is too old. [Installing libplist-dev (with libplist3) from ubuntu 18.04 solves this problem on ubuntu 16.04.]</strong> If you cannot find a libplist-2.x package that installs on your older distribution, you can get it at <a href="https://github.com/libimobiledevice/libplist">https://github.com/libimobiledevice/libplist</a> and build it from source. _(You will need build tools autoconf, automake, libtool, and may need to also install some libpython*-dev package). By default, libplist installs in /usr/local/lib. If this is not in the library path (as in ubuntu), create a file /etc/ld.so.conf.d/libplist.conf containing the text “/usr/local/lib”, and run “sudo ldconfig” to permanently add /usr/local/lib to the library path._</p>
<p><strong>Red Hat, Fedora, CentOS:</strong> (sudo yum install) openssl-devel libplist-devel avahi-compat-libdns_sd-devel (+libX11-devel for ZOOMFIX). The required GStreamer packages are: gstreamer1-devel gstreamer1-plugins-base-devel gstreamer1-libav gstreamer1-plugins-bad-free ( + gstreamer1-vaapi for intel graphics).</p>
<p><strong>OpenSUSE:</strong> (sudo zypper install) libopenssl-devel libplist-devel avahi-compat-mDNSResponder-devel (+ libX11-devel for ZOOMFIX). The required GStreamer packages are: gstreamer-devel gstreamer-plugins-base-devel gstreamer-plugins-libav gstreamer-plugins-bad (+ gstreamer-plugins-vaapi for Intel graphics).</p>
@@ -35,7 +36,7 @@
<p>First get the latest macOS release of GStreamer-1.0 from <a href="https://gstreamer.freedesktop.org/download/">https://gstreamer.freedesktop.org/download/</a>. Install both the macOS runtime and development installer packages. Assuming that the latest release is 1.18.5 they are <code>gstreamer-1.0-1.18.5-x86_64.pkg</code> and <code>gstreamer-1.0-devel-1.18.5-x86_64.pkg</code>. Click on them to install (they install to /Library/FrameWorks/GStreamer.framework). It is recommended you use GStreamer.framework rather than install Gstreamer with Homebrew or MacPorts (see later).</p>
<p>Next install OpenSSL and libplist: these can be built from source (see below) but it is easier to get them using MacPorts “sudo port install openssl libplist-devel” or Homebrew “brew install openssl libplist”. Only the static forms of the two libraries will used for the macOS build, so they do not need to remain installed after you have built uxplay: if you dont have MacPorts or Homebrew installed, you can just install one of these package-managers before building uxplay, and uninstall it afterwards if you do not want to keep it. Unfortunately, Finks openssl11-dev package currently doesnt supply the static (libcrypto.a) form of the needed OpenSLL library libcrypto, and its libplist1 package is too old.</p>
<p>Finally, build and install uxplay (without ZOOMFIX): open a terminal and change into the UxPlay source directory (“UxPlay-master” for zipfile downloads, “UxPlay” for “git clone” downloads) and build/install with “cmake . ; make ; sudo make install” (same as for Linux).</p>
<p>The macOS build uses OpenGL, not X11, to create the mirror display window. This has some “quirks”: the window title is “OpenGL renderer” instead of the Airplay server name, but it is visible to screen-sharing apps (e.g., Zoom). The option -t <em>timeout</em> cannot be used because if the GStreamer pipeline is destroyed while the OpenGL window is still open, and uxplay is left running, a segfault occurs (this is an issue with the glimagesink GStreamer OpenGL plugin, not UxPlay). Also, the resolution settings “-s wxh” do not affect the (small) initial mirror window size, but the window can be expanded using the mouse or trackpad.</p>
<p>On the macOS build, autovideosink uses OpenGL, not X11, to create the mirror display window (equivalent to “-vs glimagesink”; “-vs osxvideosink” can also be used). The window title does not show the Airplay server name, but it is visible to screen-sharing apps (e.g., Zoom). On macOS, The option -t <em>timeout</em> cannot be used because if the GStreamer pipeline is destroyed while the mirror window is still open, a segfault occurs (this is an issue with the GStreamer plugins, not UxPlay). Also, the resolution settings “-s wxh” do not affect the (small) initial OpenGL mirror window size, but the window can be expanded using the mouse or trackpad. In contrast, a window created with “-vs osxvideosink” is initially big, but has the wrong aspect ratio (stretched image); in this case the aspect ratio changes when the window width is changed by dragging its side.</p>
<p><strong><em>Building OpenSSL and libplist from source on macOS</em></strong></p>
<p>If you have have the standard GNU toolset (autoconf, automake, libtool, etc.) installed, you can also download and compile the source code for these libraries from <a href="https://www.openssl.org/source/">https://www.openssl.org/source/</a>, <a href="https://github.com/libimobiledevice/libplist">https://github.com/libimobiledevice/libplist</a>. Install the downloaded openssl by opening a terminal in your Downloads directory, and unpacking the source distribution openssl-3.0.0.tar.gz , (“tar -xvzf openssl-3.0.0.tar.gz ; cd openssl-3.0.0”). Then build/install with “./config ; make ; sudo make install_dev” and clean up after building uxplay with “sudo make uninstall” in the same directory. Similarly, for libplist, download the source as a zipfile from github as <a href="https://github.com/libimobiledevice/libplist/archive/refs/heads/master.zip">libplist-master.zip</a>, then unpack (“unzip libplist-master.zip ; cd libplist-master”), build/install (“./autogen.sh ; make ; sudo make install”) and clean up after uxplay is built with “sudo make uninstall” in the same directory.</p>
<p><strong><em>Other ways (Homebrew, MacPorts) to install GStreamer on macOS (not recommended):</em></strong></p>
@@ -45,18 +46,26 @@
<li><p>with MacPorts: “sudo port install gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good gstreamer1-gst-plugins-bad gstreamer1-gst-libav”. The MacPorts GStreamer is built to use X11, so must be run from an XQuartz terminal, can use ZOOMFIX, and needs option “-vs ximagesink”. On an older unibody MacBook Pro, the default resolution wxh = 1920x1080 was too large for the non-retina display, but using option “-s 800x600” worked; However, the GStreamer pipeline is fragile against attempts to change the X11 window size, or to rotations that switch a connected client between portrait and landscape mode while uxplay is running. Using the MacPorts X11 GStreamer is only viable if the image size is left unchanged from the initial “-s wxh” setting (also use the iPad/iPhone setting that locks the screen orientation against switching between portrait and landscape mode as the device is rotated).</p></li>
</ol>
<h1 id="troubleshooting"><strong>Troubleshooting:</strong></h1>
<p>If uxplay starts, but stalls after “Initialized server socket(s)” appears, it is probably because a firewall is blocking access to the server on which it is running. If possible, either turn off the firewall to see if that is the problem, or get three consecutive network ports, starting at port n, all three in the range 1024-65535, opened for both tcp and udp, and use “uxplay -p n” (or open UDP 6000, 6001, 6011 TCP 7000,7001,7100 and use “uxplay -p”).</p>
<p>Stalling after “Initialize server socket(s)”, with the server showing as available on the client iPad/iPhone, is almost certainly a firewall problem: one user was unaware that <em>two</em> firewalls (ufw and firewalld) were both active on their system.</p>
<p>Stalling this way, but <em>without</em> the server showing as available on the client, probably means that your network <strong>does not have a running Bonjour/zeroconf DNS-SD server.</strong> On Linux, make sure Avahi is installed, and start the avahi-daemon service (your distribution will document how to do this). Some systems may instead use the mdnsd daemon as an alternative to provide DNS-SD service. (FreeBSD offers both alternatives, but only Avahi was tested: one of the steps needed for getting Avahi running on this system is to edit /usr/local/etc/avahi/avahi-daemon.conf to uncomment a line for airplay support.)</p>
<p>For other problems after a connection is made, use “uxplay -d” (debug log option) to see what is happening. <strong>Such problems are usually due to a GStreamer plugin that doesnt work on your system</strong>: (by default, GStreamer uses an algorithm to guess what is the “best” plugin to use on your system). If you use an nVidia graphics card, make sure that the gstreamer1.0-vaapi plugin for Intel graphics is <em>NOT</em> installed (<strong>uninstall it</strong> if it is installed!). (You can test for this by explicitly choosing the GStreamer videosink with option “-vs ximagesink” or “-vs xvimagesink”, to see if this fixes the problem, or “-vs vaapisink” to see if this reproduces the problem.)</p>
<p>There are some reports of GStreamer problems with Intel graphics. One user (on Debian) solved this with “sudo apt install intel-media-va-driver-non-free”. This is a driver for 8th (or later) generation "*-lake" Intel chips, that seems to be related to VAAPI accelerated graphics.</p>
<p>Note: <code>uxplay</code> is run from a terminal command line, and informational messages are written to the terminal.</p>
<h3 id="uxplay-starts-but-stalls-after-initialized-server-sockets-appears-without-any-server-name-showing-on-the-client.">1. uxplay starts, but stalls after “Initialized server socket(s)” appears, <em>without any server name showing on the client</em>.</h3>
<p>Stalling this way, with <em>no</em> server name showing <em>on the client</em> as available, probably means that your network <strong>does not have a running Bonjour/zeroconf DNS-SD server.</strong> On Linux, make sure Avahi is installed, and start the avahi-daemon service on the system running uxplay (your distribution will document how to do this). Some systems may instead use the mdnsd daemon as an alternative to provide DNS-SD service. <em>(FreeBSD offers both alternatives, but only Avahi was tested: one of the steps needed for getting Avahi running on a FreeBSD system is to edit <code>/usr/local/etc/avahi/avahi-daemon.conf</code> to uncomment a line for airplay support.</em>)</p>
<h3 id="uxplay-starts-but-stalls-after-initialized-server-sockets-appears-with-the-server-name-showing-on-the-client-but-the-client-fails-to-connect-when-the-uxplay-server-is-selected.">2. uxplay starts, but stalls after “Initialized server socket(s)” appears, <em>with the server name showing on the client</em> (but the client fails to connect when the UxPlay server is selected).</h3>
<p>This shows that a <em>dns_sd</em> service is working, but a firewall on the server is probably blocking the connection request from the client. (One user who insisted that the firewall had been turned off turned out to have had <em>two</em> active firewalls (<em>firewalld</em> and <em>ufw</em>) <em>both</em> running on the server!) If possible, either turn off the firewall to see if that is the problem, or get three consecutive network ports, starting at port n, all three in the range 1024-65535, opened for both tcp and udp, and use “uxplay -p n” (or open UDP 6000, 6001, 6011 TCP 7000,7001,7100 and use “uxplay -p”).</p>
<h3 id="problems-after-the-client-server-connection-has-been-made">3. Problems <em>after</em> the client-server connection has been made:</h3>
<p>For such problems, use “uxplay -d” (debug log option) to see what is happening: it will show how far the connection process gets before the failure occurs.</p>
<p><strong>Most such problems are due to a GStreamer plugin that doesnt work on your system</strong>: (by default, GStreamer uses an algorithm to guess what is the “best” plugin to use on your system). A common case is that the GStreamer VAAPI plugin (for hardware-accelerated intel graphics) is being used on a system with nVidia graphics, If you use an nVidia graphics card, make sure that the gstreamer1.0-vaapi plugin for Intel graphics is <em>NOT</em> installed (<strong>uninstall it</strong> if it is installed!). (You can test for this by explicitly choosing the GStreamer videosink with option “-vs ximagesink” or “-vs xvimagesink”, to see if this fixes the problem, or “-vs vaapisink” to see if this reproduces the problem.)</p>
<p>There are some reports of other GStreamer problems with hardware-accelerated Intel graphics. One user (on Debian) solved this with “sudo apt install intel-media-va-driver-non-free”. This is a driver for 8th (or later) generation "*-lake" Intel chips, that seems to be related to VAAPI accelerated graphics.</p>
<p>You can try to fix audio problems by using the “-as <em>audiosink</em>” option to choose the GStreamer audiosink , rather than have autoaudiosink pick one for you. The command “gst_inspect-1.0 | grep Sink | grep Audio” " will show you which audiosinks are available on your system. (Replace “Audio” by “Video” to see videosinks). Some possible audiosinks are pulsesink, alsasink, osssink, oss4sink, and osxaudiosink (macOS).</p>
<p>If you ran cmake with “-DZOOMFIX=ON”, check if the problem is still there without ZOOMFIX. ZOOMFIX is only applied to the default videosink choice (“autovideosink”) and the two X11 videosinks “ximagesink” and “xvimagesink”. ZOOMFIX is only designed for these last two; if autovideosink chooses a different videosink, ZOOMFIX is now ignored. If you are using the X11 windowing system (standard on Linux), and have trouble with screen-sharing on Zoom, use ZOOMFIX and “-vs xvimagesink” (or “-vs ximagesink” if the previous choice doesnt work).</p>
<p>As other videosink choices are not affected by ZOOMFIX, they may or may not be visible to screen-sharing apps. Cairo-based windows created on Linux with “-vs gtksink” are visible to screen-sharing aps without ZOOMFIX; windows on macOS created by “-vs glimagesink” (default choice) and “-vs osximagesink” are also visible.</p>
<p>The “OpenGL renderer” window created on Linux by “-vs glimagesink” sometimes does not close properly when its “close” button is clicked. (this is a GStreamer issue). You may need to terminate uxplay with Ctrl-C to close a “zombie” OpenGl window.</p>
<p><strong>GStreamer issues:</strong> To troubleshoot GStreamer execute “export GST_DEBUG=2” to set the GStreamer debug-level environment-variable in the terminal where you will run uxplay, so that you see warning and error messages; (replace “2” by “4” to see much (much) more of what is happening inside GStreamer). Run “gst-inspect-1.0” to see which GStreamer plugins are installed on your system.</p>
<h3 id="gstreamer-issues-missing-plugins-etc.">4. GStreamer issues (missing plugins, etc.):</h3>
<p>To troubleshoot GStreamer execute “export GST_DEBUG=2” to set the GStreamer debug-level environment-variable in the terminal where you will run uxplay, so that you see warning and error messages; (replace “2” by “4” to see much (much) more of what is happening inside GStreamer). Run “gst-inspect-1.0” to see which GStreamer plugins are installed on your system.</p>
<p>Some extra GStreamer packages for special plugins may need to be installed (or reinstalled: a user using a Wayland display system as an alternative to X11 reported that after reinstalling Lubuntu 18.4, UxPlay would not work until gstreamer1.0-x was installed, presumably for Waylands X11-compatibility mode). Different distributions may break up GStreamer 1.x into packages in different ways; the packages listed above in the build instructions should bring in other required GStreamer packages as dependencies, but will not install all possible plugins.</p>
<p><strong>Use with non-Apple clients</strong>: one user tried to use UxPlay with an <em>airmypc</em> client (a non-free commercial Windows application that can mirror a Windows screen on an Apple TV using AirPlay mirror protocol). While <em>airmypc</em> can mirror to a true AppleTV and some other AirPlay receivers, UxPlay appears to correctly pair with this client, but then fails to decrypt both the audio and video streams. Possibly a different variant of the AirPlay encryption/decryption protocol not supported by UxPlay is used by this client. Without further information, there is no obvious fix. UxPlay reports itself to clients as “model appleTV2,1, sourceVersion 220.68”.</p>
<h3 id="failure-to-decrypt-all-video-and-audio-streams-from-a-particular-older-client">5. Failure to decrypt ALL video and audio streams from a particular (older) client:</h3>
<p>This triggers an error message, and will be due to use of an incorrect protocol for getting the AES decryption key from the client.</p>
<p>Modern Apple clients use a more-encrypted protocol than older ones. Which protocol is used by UxPlay depends on the client <em>User-Agent</em> string (reported by the client and now shown in the terminal output). iOS 9 and 10 clients only use iTunes FairPlay encryption on the AES decryption key they send to the server. Somewhere around iOS sourceVersion 330 (part of the User-Agent string) Apple started to further encrypt it by a sha-512 hash with a “shared secret” created during the Server-Client pairing process. The sourceVersion 330 above which the extra decryption step is carried out is set in lib/global.h if you need to change it. (This applies only to audio decryption; the AES key used for video decryption has had this extra encryption since iOS 9).</p>
<p>The third-party non-free Windows software <em>AirMyPC</em> (a commercial AirPlay emulator) uses an unhashed AES key for both audio and video encryption. <em>AirMyPC</em> has a distinctive <em>User-Agent</em> string, which is detected using two other settings in lib/global.h that can be adjusted if necessary. These settings might be useful if other AirPlay-emulators need support. Uxplay declares itself to be an AppleTV2,1 with sourceVersion 220.68; this can also be changed in global.h.</p>
<h1 id="usage"><strong>Usage:</strong></h1>
<p>Options:</p>
<p><strong>-n server_name</strong> (Default: UxPlay); server_name@_hostname_ will be the name that appears offering AirPlay services to your iPad, iPhone etc, where <em>hostname</em> is the name of the server running uxplay. This will also now be the name shown above the mirror display (X11) window.</p>
@@ -75,8 +84,10 @@
<p><strong>-vs 0</strong> suppresses display of streamed video, but plays streamed audio. (The clients screen is still mirrored at a reduced rate of 1 frame per second, but is not rendered or displayed.) This feature (which streams audio in AAC audio format) is now probably unneeded, as UxPlay can now stream superior-quality Apple Lossless audio without video in Airplay non-mirror mode.</p>
<p><strong>-as <em>audiosink</em></strong> chooses the GStreamer audiosink, instead of letting autoaudiosink pick it for you. Some audiosink choices are: pulsesink, alsasink, osssink, oss4sink, and osxaudiosink (for macOS). Using quotes “…” might allow some parameters to be included with the audiosink name. (Some choices of audiosink might not work on your system.)</p>
<p><strong>-as 0</strong> (or just <strong>-a</strong>) suppresses playing of streamed audio, but displays streamed video.</p>
<p><strong>-t <em>timeout</em></strong> will cause the server to relaunch (without stopping uxplay) if no connections have been present during the previous <em>timeout</em> seconds. (You may wish to use this because the Server may not be visible to new Clients that were inactive when the Server was launched, and an idle Bonjour registration also eventually becomes unavailable for new connections.) The timer only starts once a Client has first made a mirror connection and then has disconnected with “Stop Mirrroring”. <em>This option should <strong>not</strong> be used if the display window is an OpenGL window on macOS, as such an OpenGL window created by GStreamer does not terminate correctly (it causes a segfault) if it is still open when the GStreamer pipeline is closed.</em></p>
<p><strong>-t <em>timeout</em></strong> will cause the server to relaunch (without stopping uxplay) if no connections have been present during the previous <em>timeout</em> seconds. You may wish to use this if the Server is not visible to new Clients that were inactive when the Server was launched, and an idle Bonjour registration eventually becomes unavailable for new connections (this is a workaround for what may be due to a problem with your dns-sd or Avahi setup).<br />
<em>This option should <strong>not</strong> be used on macOS, as a window created by GStreamer does not terminate correctly (it causes a segfault) if it is still open when the GStreamer pipeline is closed.</em></p>
<h1 id="changelog">ChangeLog</h1>
<p>1.44 2021-12-13 Omit hash of aeskey with ecdh_secret if sourceVersion &lt;= 280.33 (this supports AirMyPC); internal rearrangement of where this hash is done. Replace decodebin by h264-specific elements in the GStreamer video pipeline. Fully report initial communications between client and server in -d debug mode.</p>
<p>1.43 2021-12-07 Various internal changes, such as tests for successful decryption, uniform treatment of informational/debug messages, etc., updated README.</p>
<p>1.42 2021-11-20 Fix MAC detection to work with modern Linux interface naming practices, MacOS and *BSD.</p>
<p>1.41 2021-11-11 Further cleanups of multiple audio format support (internal changes, separated RAOP and GStreamer audio/video startup)</p>
@@ -103,6 +114,7 @@
<li><p>UxPlay now builds on macOS.</p></li>
<li><p>The hostname of the server running uxplay is now appended to the AirPlay server name, which is now displayed as <em>name</em>@hostname, where <em>name</em> is “UxPlay”, (or whatever is set with the <strong>-n</strong> option).</p></li>
<li><p>Added support for audio-only streaming with original (non-Mirror) AirPlay protocol, with Apple Lossless (ALAC) audio.</p></li>
<li><p>Added suppport for the older AirPlay protocol used by third-party Windows-based AirPlay mirror emulators such as AirMyPC.</p></li>
</ol>
<h1 id="disclaimer">Disclaimer</h1>
<p>All the resources in this repository are written using only freely available information from the internet. The code and related resources are meant for educational purposes only. It is the responsibility of the user to make sure all local laws are adhered to.</p>
@@ -127,7 +139,7 @@
<p>When the Apple TV 2nd generation was introduced in 2010, it received support for the AirTunes protocol. However, because this device allowed playback of visual content, the protocol was extended and renamed AirPlay. It was now possible to stream photo slideshows and videos. Shortly after the release of the Apple TV 2nd generation, AirPlay support for iOS was included in the iOS 4.2 update. It seems like at that point, the audio stream was still actually using the same AirTunes 2 protocol as described above. The video and photo streams were added as a whole new protocol based on HTTP, pretty much independent from the audio stream. Soon, the first curious developers began to <a href="https://web.archive.org/web/20101211213705/http://www.tuaw.com/2010/12/08/dear-aunt-tuaw-can-i-airplay-to-my-mac/">investigate how it worked</a>. Their conclusion was that visual content is streamed unencrypted.</p>
<p>In April 2011, a talented hacker <a href="http://www.macrumors.com/2011/04/11/apple-airplay-private-key-exposed-opening-door-to-airport-express-emulators/">extracted the AirPlay private key</a> from an AirPort Express. This meant that finally, third-party developers were able to also build AirPlay receiver (server) programs.</p>
<p>For iOS 5, released in 2011, Apple added a new protocol to the AirPlay suite: AirPlay mirroring. <a href="https://www.aorensoftware.com/blog/2011/08/20/exploring-airplay-mirroring-internals/">Initial investigators</a> found this new protocol used encryption in order to protect the transferred video data.</p>
<p>By 2012, most of AirPlays protocols had been reverse-engineered and <a href="https://nto.github.io/AirPlay.html">documented</a>. At this point, audio still used the AirTunes 2 protocol from around 2008, video, photos and mirroring still used their respective protocols in an unmodified form, so you could still speak of AirPlay 1 (building upon AirTunes 2). The Airplay server running on the Apple TV reported as version 130. The setup of AirPlay mirroring used the xml format, in particular a stream.xml file. Additionally, it seems like the actual audio data is using the ALAC codec for audio-only (AirTunes 2) streaming and AAC for mirror audio. At least these different formats were used in <a href="https://github.com/espes/Slave-in-the-Magic-Mirror/issues/12#issuecomment-372380451">later iOS versions</a>.</p>
<p>By 2012, most of AirPlays protocols had been reverse-engineered and <a href="https://nto.github.io/AirPlay.html">documented</a> (see also <a href="https://openairplay.github.io/airplay-spec">updated version</a>). At this point, audio still used the AirTunes 2 protocol from around 2008, video, photos and mirroring still used their respective protocols in an unmodified form, so you could still speak of AirPlay 1 (building upon AirTunes 2). The Airplay server running on the Apple TV reported as version 130. The setup of AirPlay mirroring used the xml format, in particular a stream.xml file. Additionally, it seems like the actual audio data is using the ALAC codec for audio-only (AirTunes 2) streaming and AAC for mirror audio. At least these different formats were used in <a href="https://github.com/espes/Slave-in-the-Magic-Mirror/issues/12#issuecomment-372380451">later iOS versions</a>.</p>
<p>Sometime before iOS 9, the protocol for mirroring was slightly modified: Instead of the “stream.xml” API endpoint, the same information could also be querried in binary plist form, just by changing the API endpoint to “stream”, without any extension. I wasnt able to figure out which of these was actually used by what specific client / server versions.</p>
<p>For iOS 9, Apple made <a href="https://9to5mac.com/2015/09/11/apple-ios-9-airplay-improvements-screen-mirroring/">considerable changes</a> to the AirPlay protocol in 2015, including audio and mirroring. Apparently, the audio protocol was only slightly modified, and a <a href="https://github.com/juhovh/shairplay/issues/43">minor change</a> restored compatibility. For mirroring, an <a href="https://github.com/juhovh/shairplay/issues/43#issuecomment-142115959">additional pairing phase</a> was added to the connection establishment procedure, consisting of pair-setup and pair-verify calls. Seemingly, these were added in order to simplify usage with devices that are connected frequently. Pair-setup is used only the first time an iOS device connects to an AirPlay receiver. The generated cryptographic binding can be used for pair-verify in later sessions. Additionally, the stream / stream.xml endpoint was replaced with the info endpoint (only available as binary plist AFAICT). As of iOS 12, the protocol introduced with iOS 9 was still supported with only slight modifications, albeit as a legacy mode. While iOS 9 used two SETUP calls (one for general connection and mirroring video, and one for audio), iOS 12 legacy mode uses 3 SETUP calls (one for general connection (timing and events), one for mirroring video, one for audio).</p>
<p>The release of tvOS 10.2 broke many third-party AirPlay sender (client) programs in 2017. The reason was that it was now mandatory to perform device verification via a pin in order to stream content to an Apple TV. The functionality had been in the protocol before, but was not mandatory. Some discussion about the new scheme can be found <a href="https://github.com/postlund/pyatv/issues/79">here</a>. A full specification of the pairing and authentication protocol was made available on <a href="https://htmlpreview.github.io/?https://github.com/philippe44/RAOP-Player/blob/master/doc/auth_protocol.html">GitHub</a>. At that point, tvOS 10.2 reported as AirTunes/320.20.</p>

176
README.md
View File

@@ -1,33 +1,50 @@
# UxPlay 1.44: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
# UxPlay 1.43: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
This project is a GPLv3 open source unix AirPlay2 Mirror server for Linux, macOS, and \*BSD.
It is now hosted at the
github site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where development and user-assistance now takes place), although it initially was developed by
[antimof](http://github.com/antimof/Uxplay) using code
from [RPiPlay](https://github.com/FD-/RPiPlay), which in turn derives from
[AirplayServer](https://github.com/KqsMea8/AirplayServer),
[shairplay](https://github.com/juhovh/shairplay), and [playfair](https://github.com/EstebanKubata/playfair). (The antimof site is
mainly inactive, but periodically posts updates pulled from the [main UxPlay site](https://github.com/FDH2/UxPlay)).
This project is a GPLv3 unix AirPlay2 server which now also works on macOS.
Its main use is to act like an AppleTV for screen-mirroring (with audio) of iOS/macOS clients
(iPads, iPhones, MacBooks) in a window on the server display (with the possibility of
Its main use is to act like an AppleTV for screen-mirroring (with audio) of iOS/iPadOS/macOS clients
(iPhones, iPads, MacBooks) in a window
on the server display (with the possibility of
sharing that window on screen-sharing applications such as Zoom)
on a host running Linux, macOS, or other unix, using Apple's AirPlay Mirror protocol first available in iOS 5.
(Details of what is known about the AirPlay2 protocol can be found [here](https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol) and
[here](https://emanuelecozzi.net/docs/airplay2)). **Note that Apple DRM (as in Apple TV app content) cannot be decrypted by UxPlay.**
on a host running Linux, macOS, or other unix. UxPlay supports a "legacy" form of Apple's AirPlay Mirror protocol introduced
in iOS 12; client devices running iOS/iPadOS 12 or later are supported, as is a (nonfree) Windows-based
AirPlay-client software emulator, AirMyPC. Older (32-bit) client devices that can only run iOS 9.3 or iOS 10.3 are
currently partially supported by UxPlay: reports indicate that
screen-mirroring video works, audio is a work in progess.
(Details of what is publically known about Apple's AirPlay2 protocol can be found
[here](https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol) and
[here](https://emanuelecozzi.net/docs/airplay2)).
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 DNS-SD "Service Discovery" service is necessary, it is not necessary that the local network also be of the ".local" mDNS-based type).
(only DNS-SD "Service Discovery" service is strictly necessary, it is not necessary that the local network also be of the ".local" mDNS-based type).
On Linux and BSD Unix servers, this is usually provided by [Avahi](https://www.avahi.org),
through the avahi-daemon service, and is included in most Linux distributions (this
service can also be provided by macOS, iOS or Windows servers).
_Since v1.38, UxPlay now also supports the Airplay audio-only protocol as well as AirPlay Mirror protocol, and
(when the client screen is not being mirrored)
can play Apple Lossless (ALAC) audio streamed from the client without video (the accompanying cover-art and metadata is not displayed).
The initial connection to the client can be either AirPlay audio or Airplay Mirror mode. An Airplay Mirror connection (with "Advanced Audio Coding" AAC-ELD lossy-compression audio)
switches to ALAC if the mirrow window is closed and an AirPlay audio connection is started, and back again to AAC if a new
Airplay Mirror connection is made_.
Connections to the UxPlay server by
iOS/MacOS clients can be initiated both in AirPlay Mirror mode (which streams
lossily-compressed AAC audio while mirroring the client screen,
or in the alternative AirPlay Audio mode which streams
Apple Lossless (ALAC) audio without screen mirroring (the accompanying metadata and cover art in
this mode is not displayed). _Switching between these two modes during an active connection is
possible: in Mirror mode, close the mirror window and start an Audio mode connection,
switch back by initiating a Mirror mode connection._ **Note that Apple DRM
(as in Apple TV app content on the client) cannot be decrypted by UxPlay,
and (unlike with a true AppleTV), the client cannot run a http connection on the server
instead of streaming content from one on the client.**
UxPlay is based on https://github.com/FD-/RPiPlay, with GStreamer integration from
https://github.com/antimof/UxPlay.
(UxPlay only uses GStreamer, and does not contain the alternative Raspberry-Pi-specific
audio and video renderers also found in RPiPlay.)
Tested on a number of systems, including Ubuntu 20.04, Linux Mint 20.2, OpenSUSE 15.3, macOS 10.15, FreeBSD 13.0.
UxPlay uses GStreamer Plugins for rendering audio and video,
and does not offer the alternative Raspberry-Pi-specific
audio and video renderers available in [RPiPlay](https://github.com/FD-/RPiPlay).
It is tested on a number of systems, including (among others) Debian 11.2, Ubuntu 20.04 and 21.10, Linux Mint 20.2, OpenSUSE 15.3, macOS 10.15.7, FreeBSD 13.0.
Using Gstreamer means that video and audio are supported "out of the box", using a choice of plugins.
Gstreamer decoding is plugin agnostic, and uses accelerated decoders if
@@ -36,7 +53,7 @@ available. For Intel integrated graphics, the VAAPI plugin is preferable, (but d
### Note to packagers: OpenSSL-3.0.0 solves GPL v3 license issues.
Some Linux distributions such as Debian do not allow distribution of compiled
GPL code linked to OpenSLL-1.1.1 because its "dual OpenSSL/SSLeay" license
GPL code linked to OpenSSL-1.1.1 because its "dual OpenSSL/SSLeay" license
has some incompatibilites with GPL, unless all code authors have explicitly given an "exception" to allow
such linking (the historical origins of UxPlay make this impossible to obtain). Other distributions
treat OpenSSL as a "System Library" which the GPL allows linking to.
@@ -50,9 +67,10 @@ Either download and unzip [UxPlay-master.zip](https://github.com/FDH2/UxPlay/arc
or (if git is installed): "git clone https://github.com/FDH2/UxPlay".
*This is also a pull request on the
original site https://github.com/antimof/UxPlay ; but is unlikely to ever
get committed into the codebase there, as that
project appears to be inactive.
original site https://github.com/antimof/UxPlay ; that original
project is inactive, but the pull request with
changes up to 2021-12-10
were recently merged with the antimof tree (thank you antimof!).
## Building UxPlay on Linux (or \*BSD):
@@ -65,7 +83,7 @@ In a terminal window, change directories to the source directory of the
downloaded source code ("UxPlay-master" for zipfile downloads, "UxPlay" for "git clone" downloads), then do
1. `sudo apt-get install libssl-dev libplist-dev libavahi-compat-libdnssd-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-libav gstreamer1.0-plugins-bad`
2. `sudo apt-get install gstreamer1.0-vaapi` (For Intel graphics, but not nVidia graphics)
2. `sudo apt-get install gstreamer1.0-vaapi` (For hardware-accelerated Intel graphics, but not nVidia graphics)
3. `sudo apt-get install libx11-dev` (for the "ZOOMFIX" X11_display name fix for screen-sharing with e.g., ZOOM)
4. `cmake .` (or "`cmake -DZOOMFIX=ON .`" to get a screen-sharing fix to
make X11 mirror display windows visible to screen-sharing applications such as
@@ -76,8 +94,8 @@ Zoom, see below).
_If you intend to modify the code, use a separate "build" directory: replace_ "```cmake [ ] . ```" _by_ "```mkdir build ; cd build ; cmake [ ] ..```"; _you can then clean
the build directory with_ "```rm -rf build/* ```" _(run from within the UxPlay source directory) without affecting the source directories which contain your modifications_.
The above script installs the executable file "`uxplay`" to `/usr/local/bin`, (and installs a manpage to `/usr/local/man/man1` and README
files to `/usr/local/share/doc/uxplay`).
The above script installs the executable file "`uxplay`" to `/usr/local/bin`, (and installs a manpage to somewhere like `/usr/local/share/man/man1` and README
files to somewhere like `/usr/local/share/doc/uxplay`).
It can also be found in the build directory after the build
processs. Run uxplay in a terminal window.
@@ -139,15 +157,16 @@ Finally, build and install uxplay (without ZOOMFIX): open a terminal and change
("UxPlay-master" for zipfile downloads, "UxPlay" for "git clone" downloads) and build/install with
"cmake . ; make ; sudo make install " (same as for Linux).
The macOS build uses OpenGL, not X11, to create the mirror display window. This has some "quirks":
the window title is "OpenGL renderer" instead of the Airplay server name, but it is visible to
screen-sharing apps (e.g., Zoom). The option -t _timeout_
cannot be used because if the GStreamer pipeline is destroyed while the OpenGL window is still open,
and uxplay is left running, a segfault occurs (this is an issue with the glimagesink GStreamer OpenGL
plugin, not UxPlay).
Also, the resolution settings "-s wxh" do not affect
the (small) initial mirror window size, but the window can be expanded using the mouse or trackpad.
On the macOS build, autovideosink uses OpenGL, not X11, to create the mirror display window (equivalent to
"-vs glimagesink"; "-vs osxvideosink" can also be used).
The window title does not show the Airplay server name, but it is visible to
screen-sharing apps (e.g., Zoom). On macOS, The option -t _timeout_
cannot be used because if the GStreamer pipeline is destroyed while the mirror window is still open,
a segfault occurs (this is an issue with the GStreamer plugins, not UxPlay).
Also, the resolution settings "-s wxh" do not affect
the (small) initial OpenGL mirror window size, but the window can be expanded using the mouse or trackpad.
In contrast, a window created with "-vs osxvideosink" is initially big, but has the wrong aspect ratio (stretched image);
in this case the aspect ratio changes when the window width is changed by dragging its side.
***Building OpenSSL and libplist from source on macOS***
@@ -184,30 +203,37 @@ as the device is rotated).
# **Troubleshooting:**
If uxplay starts, but stalls after "Initialized server socket(s)" appears,
it is probably because a firewall is blocking
access to the server on which it is running. If possible, either turn off the firewall
Note: ```uxplay``` is run from a terminal command line, and informational messages are written to the terminal.
### 1. uxplay starts, but stalls after "Initialized server socket(s)" appears, *without any server name showing on the client*.
Stalling this way, with _no_ server name showing _on the client_ as available,
probably means that your network **does not have a running Bonjour/zeroconf DNS-SD server.**
On Linux, make sure Avahi is installed,
and start the avahi-daemon service on the system running uxplay (your distribution will document how to do this).
Some systems may instead use the mdnsd daemon as an alternative to provide DNS-SD service.
_(FreeBSD offers both alternatives, but only Avahi was tested: one of the steps needed for
getting Avahi running on a FreeBSD system is to edit ```/usr/local/etc/avahi/avahi-daemon.conf``` to
uncomment a line for airplay support._)
### 2. uxplay starts, but stalls after "Initialized server socket(s)" appears, *with the server name showing on the client* (but the client fails to connect when the UxPlay server is selected).
This shows that a *dns_sd* service is working, but a firewall on the server is probably blocking the connection request from the client.
(One user who insisted that the firewall had been turned off turned out to have had _two_ active firewalls (*firewalld* and *ufw*)
_both_ running on the server!) If possible, either turn off the firewall
to see if that is the problem, or get three consecutive network ports,
starting at port n, all three in the range 1024-65535, opened for both tcp and udp, and use "uxplay -p n"
(or open UDP 6000, 6001, 6011 TCP 7000,7001,7100 and use "uxplay -p").
Stalling after "Initialize server socket(s)", with the server showing as available on the client iPad/iPhone,
is almost certainly a firewall problem: one user was unaware that
_two_ firewalls (ufw and firewalld) were both active on their system.
### 3. Problems _after_ the client-server connection has been made:
Stalling this way, but _without_ the server showing as available on the client,
probably means that your network **does not have a running Bonjour/zeroconf DNS-SD server.**
On Linux, make sure Avahi is installed,
and start the avahi-daemon service (your distribution will document how to do this).
Some systems may instead use the mdnsd daemon as an alternative to provide DNS-SD service.
(FreeBSD offers both alternatives, but only Avahi was tested: one of the steps needed for
getting Avahi running on this system is to edit /usr/local/etc/avahi/avahi-daemon.conf to
uncomment a line for airplay support.)
For such problems, use "uxplay -d " (debug log option) to see what is happening: it will show how far the connection process gets before
the failure occurs.
For other problems after a connection is made, use "uxplay -d " (debug log option) to see what is happening.
**Such problems are usually due to a GStreamer plugin that doesn't work on your system**: (by default,
**Most such problems are due to a GStreamer plugin that doesn't work on your system**: (by default,
GStreamer uses an algorithm to guess what is the "best"
plugin to use on your system).
plugin to use on your system). A common case is that the GStreamer VAAPI plugin
(for hardware-accelerated intel graphics) is being used on a system with nVidia graphics,
If you use an
nVidia graphics card, make sure that the gstreamer1.0-vaapi
plugin for Intel graphics is *NOT* installed (**uninstall it** if it is installed!).
@@ -215,7 +241,7 @@ plugin for Intel graphics is *NOT* installed (**uninstall it** if it is installe
"-vs ximagesink" or "-vs xvimagesink", to see if this fixes the problem, or "-vs vaapisink" to see if this
reproduces the problem.)
There are some reports of GStreamer problems with Intel graphics. One user
There are some reports of other GStreamer problems with hardware-accelerated Intel graphics. One user
(on Debian) solved this with "sudo apt install intel-media-va-driver-non-free". This is a driver for 8'th (or later) generation
"*-lake" Intel chips, that seems to be related to VAAPI accelerated graphics.
@@ -238,7 +264,9 @@ Cairo-based windows created on Linux with "-vs gtksink" are visible to screen-sh
The "OpenGL renderer" window created on Linux by "-vs glimagesink" sometimes does not close properly when its "close" button is clicked.
(this is a GStreamer issue). You may need to terminate uxplay with Ctrl-C to close a "zombie" OpenGl window.
__GStreamer issues:__ To troubleshoot GStreamer execute "export GST_DEBUG=2"
### 4. GStreamer issues (missing plugins, etc.):
To troubleshoot GStreamer execute "export GST_DEBUG=2"
to set the GStreamer debug-level environment-variable in the terminal
where you will run uxplay, so that you see warning and error messages;
(replace "2" by "4" to see much (much) more of what is happening inside
@@ -250,12 +278,20 @@ reported that after reinstalling Lubuntu 18.4, UxPlay would not work until gstr
Different distributions may break up GStreamer 1.x into packages in different ways; the packages listed above in the build instructions should bring in
other required GStreamer packages as dependencies, but will not install all possible plugins.
**Use with non-Apple clients**: one user tried to use UxPlay with an *airmypc* client (a non-free commercial
Windows application that can mirror a Windows screen on an Apple TV using AirPlay mirror protocol). While *airmypc*
can mirror to a true AppleTV and some other AirPlay receivers, UxPlay appears to correctly pair with this client, but
then fails to decrypt both the audio and video streams. Possibly a different variant of the AirPlay encryption/decryption
protocol not supported by UxPlay is used by this client. Without further information, there is no obvious fix.
UxPlay reports itself to clients as "model appleTV2,1, sourceVersion 220.68".
### 5. Failure to decrypt ALL video and audio streams from a particular (older) client:
This triggers an error message, and will be due to use of an incorrect protocol for getting the AES decryption key from the client.
Modern Apple clients use a more-encrypted protocol than older ones.
Which protocol is used by UxPlay depends on the client _User-Agent_ string (reported by the client and now shown in the terminal output).
iOS 9 and 10 clients only use iTunes FairPlay encryption on the AES decryption key they send to the server.
Somewhere around iOS sourceVersion 330 (part of the User-Agent string) Apple started to further encrypt it by a sha-512 hash with a "shared secret" created
during the Server-Client pairing process. The sourceVersion 330 above which the extra decryption step is carried out is set in lib/global.h if you need to
change it. (This applies only to audio decryption; the AES key used for video decryption has had this extra encryption since iOS 9).
The third-party non-free Windows software _AirMyPC_ (a commercial AirPlay emulator) uses an unhashed AES key for both audio and video encryption. _AirMyPC_ has a distinctive
_User-Agent_ string, which is detected using two other settings in lib/global.h that can be adjusted if necessary. These settings might be useful if
other AirPlay-emulators need support. Uxplay declares itself to be an AppleTV2,1 with sourceVersion 220.68; this can also be changed in global.h.
# **Usage:**
@@ -350,17 +386,21 @@ Also: image transforms that had been added to RPiPlay have been ported to UxPlay
**-as 0** (or just **-a**) suppresses playing of streamed audio, but displays streamed video.
**-t _timeout_** will cause the server to relaunch (without stopping uxplay) if no connections
have been present during the previous _timeout_ seconds. (You may wish to use this because the Server may not be
visible to new Clients that were inactive when the Server was launched, and an idle Bonjour
registration also eventually becomes unavailable for new connections.) The timer only starts once a
Client has first made a mirror connection and then has disconnected with "Stop Mirrroring".
_This option should **not** be
used if the display window is an OpenGL window on macOS, as such an OpenGL window created
have been present during the previous _timeout_ seconds. You may wish to use this if the Server
is not visible to new Clients that were inactive when the Server was launched, and an idle Bonjour
registration eventually becomes unavailable for new connections (this is a workaround for what
may be due to a problem with your dns-sd or Avahi setup).
_This option should **not** be used on macOS, as a window created
by GStreamer does not terminate correctly (it causes a segfault)
if it is still open when the GStreamer pipeline is closed._
# ChangeLog
1.44 2021-12-13 Omit hash of aeskey with ecdh_secret if sourceVersion <= 280.33 (this supports AirMyPC);
internal rearrangement of where this hash is done. Replace decodebin by h264-specific
elements in the GStreamer video pipeline. Fully report initial communications between
client and server in -d debug mode.
1.43 2021-12-07 Various internal changes, such as tests for successful decryption, uniform treatment
of informational/debug messages, etc., updated README.
@@ -465,6 +505,8 @@ is compiled.) On macOS, Avahi is not used.
12. Added support for audio-only streaming with original (non-Mirror) AirPlay protocol, with Apple Lossless (ALAC) audio.
13. Added suppport for the older AirPlay protocol used by third-party Windows-based AirPlay mirror emulators such as AirMyPC.
# Disclaimer
All the resources in this repository are written using only freely available information from the internet. The code and related resources are meant for educational purposes only. It is the responsibility of the user to make sure all local laws are adhered to.
@@ -511,7 +553,7 @@ In April 2011, a talented hacker [extracted the AirPlay private key](http://www.
For iOS 5, released in 2011, Apple added a new protocol to the AirPlay suite: AirPlay mirroring. [Initial investigators](https://www.aorensoftware.com/blog/2011/08/20/exploring-airplay-mirroring-internals/) found this new protocol used encryption in order to protect the transferred video data.
By 2012, most of AirPlay's protocols had been reverse-engineered and [documented](https://nto.github.io/AirPlay.html). At this point, audio still used the AirTunes 2 protocol from around 2008, video, photos and mirroring still used their respective protocols in an unmodified form, so you could still speak of AirPlay 1 (building upon AirTunes 2). The Airplay server running on the Apple TV reported as version 130. The setup of AirPlay mirroring used the xml format, in particular a stream.xml file.
By 2012, most of AirPlay's protocols had been reverse-engineered and [documented](https://nto.github.io/AirPlay.html) (see also [updated version](https://openairplay.github.io/airplay-spec)). At this point, audio still used the AirTunes 2 protocol from around 2008, video, photos and mirroring still used their respective protocols in an unmodified form, so you could still speak of AirPlay 1 (building upon AirTunes 2). The Airplay server running on the Apple TV reported as version 130. The setup of AirPlay mirroring used the xml format, in particular a stream.xml file.
Additionally, it seems like the actual audio data is using the ALAC codec for audio-only (AirTunes 2) streaming and AAC for mirror audio. At least these different formats were used in [later iOS versions](https://github.com/espes/Slave-in-the-Magic-Mirror/issues/12#issuecomment-372380451).

View File

@@ -1,44 +1,61 @@
UxPlay 1.43: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
UxPlay 1.44: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
======================================================================
This project is a GPLv3 unix AirPlay2 server which now also works on
macOS. Its main use is to act like an AppleTV for screen-mirroring (with
audio) of iOS/macOS clients (iPads, iPhones, MacBooks) in a window on
This project is a GPLv3 open source unix AirPlay2 Mirror server for
Linux, macOS, and \*BSD. It is now hosted at the github site
<https://github.com/FDH2/UxPlay> (where development and user-assistance
now takes place), although it initially was developed by
[antimof](http://github.com/antimof/Uxplay) using code from
[RPiPlay](https://github.com/FD-/RPiPlay), which in turn derives from
[AirplayServer](https://github.com/KqsMea8/AirplayServer),
[shairplay](https://github.com/juhovh/shairplay), and
[playfair](https://github.com/EstebanKubata/playfair). (The antimof site
is mainly inactive, but periodically posts updates pulled from the [main
UxPlay site](https://github.com/FDH2/UxPlay)).
Its main use is to act like an AppleTV for screen-mirroring (with audio)
of iOS/iPadOS/macOS clients (iPhones, iPads, MacBooks) in a window on
the server display (with the possibility of sharing that window on
screen-sharing applications such as Zoom) on a host running Linux,
macOS, or other unix, using Apple's AirPlay Mirror protocol first
available in iOS 5. (Details of what is known about the AirPlay2
protocol can be found
macOS, or other unix. UxPlay supports a "legacy" form of Apple's AirPlay
Mirror protocol introduced in iOS 12; client devices running iOS/iPadOS
12 or later are supported, as is a (nonfree) Windows-based
AirPlay-client software emulator, AirMyPC. Older (32-bit) client devices
that can only run iOS 9.3 or iOS 10.3 are currently partially supported
by UxPlay: reports indicate that screen-mirroring video works, audio is
a work in progess. (Details of what is publically known about Apple's
AirPlay2 protocol can be found
[here](https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol)
and [here](https://emanuelecozzi.net/docs/airplay2)). **Note that Apple
DRM (as in Apple TV app content) cannot be decrypted by UxPlay.**
and [here](https://emanuelecozzi.net/docs/airplay2)).
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
DNS-SD "Service Discovery" service is necessary, it is not necessary
that the local network also be of the ".local" mDNS-based type). On
Linux and BSD Unix servers, this is usually provided by
DNS-SD "Service Discovery" service is strictly necessary, it is not
necessary that the local network also be of the ".local" mDNS-based
type). On Linux and BSD Unix servers, this is usually provided by
[Avahi](https://www.avahi.org), through the avahi-daemon service, and is
included in most Linux distributions (this service can also be provided
by macOS, iOS or Windows servers).
*Since v1.38, UxPlay now also supports the Airplay audio-only protocol
as well as AirPlay Mirror protocol, and (when the client screen is not
being mirrored) can play Apple Lossless (ALAC) audio streamed from the
client without video (the accompanying cover-art and metadata is not
displayed). The initial connection to the client can be either AirPlay
audio or Airplay Mirror mode. An Airplay Mirror connection (with
"Advanced Audio Coding" AAC-ELD lossy-compression audio) switches to
ALAC if the mirrow window is closed and an AirPlay audio connection is
started, and back again to AAC if a new Airplay Mirror connection is
made*.
Connections to the UxPlay server by iOS/MacOS clients can be initiated
both in AirPlay Mirror mode (which streams lossily-compressed AAC audio
while mirroring the client screen, or in the alternative AirPlay Audio
mode which streams Apple Lossless (ALAC) audio without screen mirroring
(the accompanying metadata and cover art in this mode is not displayed).
*Switching between these two modes during an active connection is
possible: in Mirror mode, close the mirror window and start an Audio
mode connection, switch back by initiating a Mirror mode connection.*
**Note that Apple DRM (as in Apple TV app content on the client) cannot
be decrypted by UxPlay, and (unlike with a true AppleTV), the client
cannot run a http connection on the server instead of streaming content
from one on the client.**
UxPlay is based on https://github.com/FD-/RPiPlay, with GStreamer
integration from https://github.com/antimof/UxPlay. (UxPlay only uses
GStreamer, and does not contain the alternative Raspberry-Pi-specific
audio and video renderers also found in RPiPlay.) Tested on a number of
systems, including Ubuntu 20.04, Linux Mint 20.2, OpenSUSE 15.3, macOS
10.15, FreeBSD 13.0.
UxPlay uses GStreamer Plugins for rendering audio and video, and does
not offer the alternative Raspberry-Pi-specific audio and video
renderers available in [RPiPlay](https://github.com/FD-/RPiPlay). It is
tested on a number of systems, including (among others) Debian 11.2,
Ubuntu 20.04 and 21.10, Linux Mint 20.2, OpenSUSE 15.3, macOS 10.15.7,
FreeBSD 13.0.
Using Gstreamer means that video and audio are supported "out of the
box", using a choice of plugins. Gstreamer decoding is plugin agnostic,
@@ -49,7 +66,7 @@ nVidia).
### Note to packagers: OpenSSL-3.0.0 solves GPL v3 license issues.
Some Linux distributions such as Debian do not allow distribution of
compiled GPL code linked to OpenSLL-1.1.1 because its "dual
compiled GPL code linked to OpenSSL-1.1.1 because its "dual
OpenSSL/SSLeay" license has some incompatibilites with GPL, unless all
code authors have explicitly given an "exception" to allow such linking
(the historical origins of UxPlay make this impossible to obtain). Other
@@ -68,9 +85,9 @@ Either download and unzip
or (if git is installed): "git clone https://github.com/FDH2/UxPlay".
\*This is also a pull request on the original site
https://github.com/antimof/UxPlay ; but is unlikely to ever get
committed into the codebase there, as that project appears to be
inactive.
https://github.com/antimof/UxPlay ; that original project is inactive,
but the pull request with changes up to 2021-12-10 were recently merged
with the antimof tree (thank you antimof!).
Building UxPlay on Linux (or \*BSD):
------------------------------------
@@ -88,8 +105,8 @@ directories to the source directory of the downloaded source code
downloads), then do
1. `sudo apt-get install libssl-dev libplist-dev libavahi-compat-libdnssd-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-libav gstreamer1.0-plugins-bad`
2. `sudo apt-get install gstreamer1.0-vaapi` (For Intel graphics, but
not nVidia graphics)
2. `sudo apt-get install gstreamer1.0-vaapi` (For hardware-accelerated
Intel graphics, but not nVidia graphics)
3. `sudo apt-get install libx11-dev` (for the "ZOOMFIX" X11\_display
name fix for screen-sharing with e.g., ZOOM)
4. `cmake .` (or "`cmake -DZOOMFIX=ON .`" to get a screen-sharing fix
@@ -107,10 +124,10 @@ directory) without affecting the source directories which contain your
modifications*.
The above script installs the executable file "`uxplay`" to
`/usr/local/bin`, (and installs a manpage to `/usr/local/man/man1` and
README files to `/usr/local/share/doc/uxplay`). It can also be found in
the build directory after the build processs. Run uxplay in a terminal
window.
`/usr/local/bin`, (and installs a manpage to somewhere like
`/usr/local/share/man/man1` and README files to somewhere like
`/usr/local/share/doc/uxplay`). It can also be found in the build
directory after the build processs. Run uxplay in a terminal window.
**Note libplist-dev (version 2.0 or greater) is a new dependency (the
original antimof version UxPlay-1.2 supplied it). Older Linux
@@ -189,15 +206,19 @@ change into the UxPlay source directory ("UxPlay-master" for zipfile
downloads, "UxPlay" for "git clone" downloads) and build/install with
"cmake . ; make ; sudo make install" (same as for Linux).
The macOS build uses OpenGL, not X11, to create the mirror display
window. This has some "quirks": the window title is "OpenGL renderer"
instead of the Airplay server name, but it is visible to screen-sharing
apps (e.g., Zoom). The option -t *timeout* cannot be used because if the
GStreamer pipeline is destroyed while the OpenGL window is still open,
and uxplay is left running, a segfault occurs (this is an issue with the
glimagesink GStreamer OpenGL plugin, not UxPlay). Also, the resolution
settings "-s wxh" do not affect the (small) initial mirror window size,
but the window can be expanded using the mouse or trackpad.
On the macOS build, autovideosink uses OpenGL, not X11, to create the
mirror display window (equivalent to "-vs glimagesink"; "-vs
osxvideosink" can also be used). The window title does not show the
Airplay server name, but it is visible to screen-sharing apps (e.g.,
Zoom). On macOS, The option -t *timeout* cannot be used because if the
GStreamer pipeline is destroyed while the mirror window is still open, a
segfault occurs (this is an issue with the GStreamer plugins, not
UxPlay). Also, the resolution settings "-s wxh" do not affect the
(small) initial OpenGL mirror window size, but the window can be
expanded using the mouse or trackpad. In contrast, a window created with
"-vs osxvideosink" is initially big, but has the wrong aspect ratio
(stretched image); in this case the aspect ratio changes when the window
width is changed by dragging its side.
***Building OpenSSL and libplist from source on macOS***
@@ -249,46 +270,56 @@ pkgconfig" ; MacPorts: "sudo port install pkgconfig" ).
**Troubleshooting:**
====================
If uxplay starts, but stalls after "Initialized server socket(s)"
appears, it is probably because a firewall is blocking access to the
server on which it is running. If possible, either turn off the firewall
to see if that is the problem, or get three consecutive network ports,
starting at port n, all three in the range 1024-65535, opened for both
tcp and udp, and use "uxplay -p n" (or open UDP 6000, 6001, 6011 TCP
7000,7001,7100 and use "uxplay -p").
Note: `uxplay` is run from a terminal command line, and informational
messages are written to the terminal.
Stalling after "Initialize server socket(s)", with the server showing as
available on the client iPad/iPhone, is almost certainly a firewall
problem: one user was unaware that *two* firewalls (ufw and firewalld)
were both active on their system.
### 1. uxplay starts, but stalls after "Initialized server socket(s)" appears, *without any server name showing on the client*.
Stalling this way, but *without* the server showing as available on the
client, probably means that your network **does not have a running
Stalling this way, with *no* server name showing *on the client* as
available, probably means that your network **does not have a running
Bonjour/zeroconf DNS-SD server.** On Linux, make sure Avahi is
installed, and start the avahi-daemon service (your distribution will
document how to do this). Some systems may instead use the mdnsd daemon
as an alternative to provide DNS-SD service. (FreeBSD offers both
alternatives, but only Avahi was tested: one of the steps needed for
getting Avahi running on this system is to edit
/usr/local/etc/avahi/avahi-daemon.conf to uncomment a line for airplay
support.)
installed, and start the avahi-daemon service on the system running
uxplay (your distribution will document how to do this). Some systems
may instead use the mdnsd daemon as an alternative to provide DNS-SD
service. *(FreeBSD offers both alternatives, but only Avahi was tested:
one of the steps needed for getting Avahi running on a FreeBSD system is
to edit `/usr/local/etc/avahi/avahi-daemon.conf` to uncomment a line for
airplay support.*)
For other problems after a connection is made, use "uxplay -d" (debug
log option) to see what is happening. **Such problems are usually due to
a GStreamer plugin that doesn't work on your system**: (by default,
GStreamer uses an algorithm to guess what is the "best" plugin to use on
your system). If you use an nVidia graphics card, make sure that the
gstreamer1.0-vaapi plugin for Intel graphics is *NOT* installed
(**uninstall it** if it is installed!). (You can test for this by
explicitly choosing the GStreamer videosink with option "-vs ximagesink"
or "-vs xvimagesink", to see if this fixes the problem, or "-vs
vaapisink" to see if this reproduces the problem.)
### 2. uxplay starts, but stalls after "Initialized server socket(s)" appears, *with the server name showing on the client* (but the client fails to connect when the UxPlay server is selected).
There are some reports of GStreamer problems with Intel graphics. One
user (on Debian) solved this with "sudo apt install
intel-media-va-driver-non-free". This is a driver for 8'th (or later)
generation \"\*-lake\" Intel chips, that seems to be related to VAAPI
accelerated graphics.
This shows that a *dns\_sd* service is working, but a firewall on the
server is probably blocking the connection request from the client. (One
user who insisted that the firewall had been turned off turned out to
have had *two* active firewalls (*firewalld* and *ufw*) *both* running
on the server!) If possible, either turn off the firewall to see if that
is the problem, or get three consecutive network ports, starting at port
n, all three in the range 1024-65535, opened for both tcp and udp, and
use "uxplay -p n" (or open UDP 6000, 6001, 6011 TCP 7000,7001,7100 and
use "uxplay -p").
### 3. Problems *after* the client-server connection has been made:
For such problems, use "uxplay -d" (debug log option) to see what is
happening: it will show how far the connection process gets before the
failure occurs.
**Most such problems are due to a GStreamer plugin that doesn't work on
your system**: (by default, GStreamer uses an algorithm to guess what is
the "best" plugin to use on your system). A common case is that the
GStreamer VAAPI plugin (for hardware-accelerated intel graphics) is
being used on a system with nVidia graphics, If you use an nVidia
graphics card, make sure that the gstreamer1.0-vaapi plugin for Intel
graphics is *NOT* installed (**uninstall it** if it is installed!). (You
can test for this by explicitly choosing the GStreamer videosink with
option "-vs ximagesink" or "-vs xvimagesink", to see if this fixes the
problem, or "-vs vaapisink" to see if this reproduces the problem.)
There are some reports of other GStreamer problems with
hardware-accelerated Intel graphics. One user (on Debian) solved this
with "sudo apt install intel-media-va-driver-non-free". This is a driver
for 8'th (or later) generation \"\*-lake\" Intel chips, that seems to be
related to VAAPI accelerated graphics.
You can try to fix audio problems by using the "-as *audiosink*" option
to choose the GStreamer audiosink , rather than have autoaudiosink pick
@@ -317,12 +348,14 @@ sometimes does not close properly when its "close" button is clicked.
(this is a GStreamer issue). You may need to terminate uxplay with
Ctrl-C to close a "zombie" OpenGl window.
**GStreamer issues:** To troubleshoot GStreamer execute "export
GST\_DEBUG=2" to set the GStreamer debug-level environment-variable in
the terminal where you will run uxplay, so that you see warning and
error messages; (replace "2" by "4" to see much (much) more of what is
happening inside GStreamer). Run "gst-inspect-1.0" to see which
GStreamer plugins are installed on your system.
### 4. GStreamer issues (missing plugins, etc.):
To troubleshoot GStreamer execute "export GST\_DEBUG=2" to set the
GStreamer debug-level environment-variable in the terminal where you
will run uxplay, so that you see warning and error messages; (replace
"2" by "4" to see much (much) more of what is happening inside
GStreamer). Run "gst-inspect-1.0" to see which GStreamer plugins are
installed on your system.
Some extra GStreamer packages for special plugins may need to be
installed (or reinstalled: a user using a Wayland display system as an
@@ -333,16 +366,31 @@ GStreamer 1.x into packages in different ways; the packages listed above
in the build instructions should bring in other required GStreamer
packages as dependencies, but will not install all possible plugins.
**Use with non-Apple clients**: one user tried to use UxPlay with an
*airmypc* client (a non-free commercial Windows application that can
mirror a Windows screen on an Apple TV using AirPlay mirror protocol).
While *airmypc* can mirror to a true AppleTV and some other AirPlay
receivers, UxPlay appears to correctly pair with this client, but then
fails to decrypt both the audio and video streams. Possibly a different
variant of the AirPlay encryption/decryption protocol not supported by
UxPlay is used by this client. Without further information, there is no
obvious fix. UxPlay reports itself to clients as "model appleTV2,1,
sourceVersion 220.68".
### 5. Failure to decrypt ALL video and audio streams from a particular (older) client:
This triggers an error message, and will be due to use of an incorrect
protocol for getting the AES decryption key from the client.
Modern Apple clients use a more-encrypted protocol than older ones.
Which protocol is used by UxPlay depends on the client *User-Agent*
string (reported by the client and now shown in the terminal output).
iOS 9 and 10 clients only use iTunes FairPlay encryption on the AES
decryption key they send to the server. Somewhere around iOS
sourceVersion 330 (part of the User-Agent string) Apple started to
further encrypt it by a sha-512 hash with a "shared secret" created
during the Server-Client pairing process. The sourceVersion 330 above
which the extra decryption step is carried out is set in lib/global.h if
you need to change it. (This applies only to audio decryption; the AES
key used for video decryption has had this extra encryption since iOS
9).
The third-party non-free Windows software *AirMyPC* (a commercial
AirPlay emulator) uses an unhashed AES key for both audio and video
encryption. *AirMyPC* has a distinctive *User-Agent* string, which is
detected using two other settings in lib/global.h that can be adjusted
if necessary. These settings might be useful if other AirPlay-emulators
need support. Uxplay declares itself to be an AppleTV2,1 with
sourceVersion 220.68; this can also be changed in global.h.
**Usage:**
==========
@@ -446,19 +494,24 @@ displays streamed video.
**-t *timeout*** will cause the server to relaunch (without stopping
uxplay) if no connections have been present during the previous
*timeout* seconds. (You may wish to use this because the Server may not
be visible to new Clients that were inactive when the Server was
launched, and an idle Bonjour registration also eventually becomes
unavailable for new connections.) The timer only starts once a Client
has first made a mirror connection and then has disconnected with "Stop
Mirrroring". *This option should **not** be used if the display window
is an OpenGL window on macOS, as such an OpenGL window created by
*timeout* seconds. You may wish to use this if the Server is not visible
to new Clients that were inactive when the Server was launched, and an
idle Bonjour registration eventually becomes unavailable for new
connections (this is a workaround for what may be due to a problem with
your dns-sd or Avahi setup).\
*This option should **not** be used on macOS, as a window created by
GStreamer does not terminate correctly (it causes a segfault) if it is
still open when the GStreamer pipeline is closed.*
ChangeLog
=========
1.44 2021-12-13 Omit hash of aeskey with ecdh\_secret if sourceVersion
\<= 280.33 (this supports AirMyPC); internal rearrangement of where this
hash is done. Replace decodebin by h264-specific elements in the
GStreamer video pipeline. Fully report initial communications between
client and server in -d debug mode.
1.43 2021-12-07 Various internal changes, such as tests for successful
decryption, uniform treatment of informational/debug messages, etc.,
updated README.
@@ -582,6 +635,9 @@ Improvements since the original UxPlay by antimof:
12. Added support for audio-only streaming with original (non-Mirror)
AirPlay protocol, with Apple Lossless (ALAC) audio.
13. Added suppport for the older AirPlay protocol used by third-party
Windows-based AirPlay mirror emulators such as AirMyPC.
Disclaimer
==========
@@ -697,11 +753,12 @@ found this new protocol used encryption in order to protect the
transferred video data.
By 2012, most of AirPlay's protocols had been reverse-engineered and
[documented](https://nto.github.io/AirPlay.html). At this point, audio
still used the AirTunes 2 protocol from around 2008, video, photos and
mirroring still used their respective protocols in an unmodified form,
so you could still speak of AirPlay 1 (building upon AirTunes 2). The
Airplay server running on the Apple TV reported as version 130. The
[documented](https://nto.github.io/AirPlay.html) (see also [updated
version](https://openairplay.github.io/airplay-spec)). At this point,
audio still used the AirTunes 2 protocol from around 2008, video, photos
and mirroring still used their respective protocols in an unmodified
form, so you could still speak of AirPlay 1 (building upon AirTunes 2).
The Airplay server running on the Apple TV reported as version 130. The
setup of AirPlay mirroring used the xml format, in particular a
stream.xml file. Additionally, it seems like the actual audio data is
using the ALAC codec for audio-only (AirTunes 2) streaming and AAC for

View File

@@ -5,6 +5,16 @@
#define GLOBAL_MODEL "AppleTV2,1"
#define GLOBAL_VERSION "220.68"
/* use old protocol (unhashed AESkey for audio decryption if clients source version is not greater than 330.x.x */
#define OLD_PROTOCOL_AUDIO_CLIENT_SOURCEVERSION "330.0.0"
/* use old protocol for audio or video AES key if client's User-Agent string is contained in these strings */
/* replace xxx by any new User-Agent string as needed */
#define OLD_PROTOCOL_AUDIO_CLIENT_USER_AGENT_LIST "AirMyPC/2.0;xxx"
#define OLD_PROTOCOL_VIDEO_CLIENT_USER_AGENT_LIST "AirMyPC/2.0;xxx"
#define DECRYPTION_TEST 0 /* set to 1 or 2 to examine audio decryption */
#define MAX_HWADDR_LEN 6
#endif

View File

@@ -15,6 +15,7 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include "http_request.h"
#include "llhttp/llhttp.h"
@@ -251,3 +252,38 @@ http_request_get_data(http_request_t *request, int *datalen)
}
return request->data;
}
int
http_request_get_header_string(http_request_t *request, char **header_str)
{
if(!request || request->headers_size == 0) {
*header_str = NULL;
return 0;
}
int len = 0;
for (int i = 0; i < request->headers_size; i++) {
len += strlen(request->headers[i]);
if (i%2 == 0) {
len += 2;
} else {
len++;
}
}
char *str = calloc(len+1, sizeof(char));
assert(str);
*header_str = str;
char *p = str;
for (int i = 0; i < request->headers_size; i++) {
sprintf(p,"%s", request->headers[i]);
p += strlen(request->headers[i]);
if (i%2 == 0) {
sprintf(p, ": ");
p +=2;
} else {
sprintf(p, "\n");
p++;
}
}
assert(p == &(str[len]));
return len;
}

View File

@@ -30,6 +30,7 @@ const char *http_request_get_method(http_request_t *request);
const char *http_request_get_url(http_request_t *request);
const char *http_request_get_header(http_request_t *request, const char *name);
const char *http_request_get_data(http_request_t *request, int *datalen);
int http_request_get_header_string(http_request_t *request, char **header_str);
void http_request_destroy(http_request_t *request);

View File

@@ -34,19 +34,13 @@ struct mirror_buffer_s {
/* AES key and IV */
// Need secondary processing to use
unsigned char aeskey[RAOP_AESKEY_LEN];
unsigned char ecdh_secret[32];
};
void
mirror_buffer_init_aes(mirror_buffer_t *mirror_buffer, uint64_t streamConnectionID)
{
sha_ctx_t *ctx = sha_init();
unsigned char eaeskey[64] = {};
unsigned char eaeskey[16];
memcpy(eaeskey, mirror_buffer->aeskey, 16);
sha_update(ctx, eaeskey, 16);
sha_update(ctx, mirror_buffer->ecdh_secret, 32);
sha_final(ctx, eaeskey, NULL);
unsigned char hash1[64];
unsigned char hash2[64];
char* skey = "AirPlayStreamKey";
@@ -55,7 +49,8 @@ mirror_buffer_init_aes(mirror_buffer_t *mirror_buffer, uint64_t streamConnection
unsigned char sivall[255];
sprintf((char*) skeyall, "%s%" PRIu64, skey, streamConnectionID);
sprintf((char*) sivall, "%s%" PRIu64, siv, streamConnectionID);
sha_reset(ctx);
sha_ctx_t *ctx = sha_init();
sha_update(ctx, skeyall, strlen((char*) skeyall));
sha_update(ctx, eaeskey, 16);
sha_final(ctx, hash1, NULL);
@@ -82,22 +77,18 @@ mirror_buffer_init_aes(mirror_buffer_t *mirror_buffer, uint64_t streamConnection
}
mirror_buffer_t *
mirror_buffer_init(logger_t *logger,
const unsigned char *aeskey,
const unsigned char *ecdh_secret)
mirror_buffer_init(logger_t *logger, const unsigned char *aeskey)
{
mirror_buffer_t *mirror_buffer;
assert(aeskey);
assert(ecdh_secret);
mirror_buffer = calloc(1, sizeof(mirror_buffer_t));
if (!mirror_buffer) {
return NULL;
}
memcpy(mirror_buffer->aeskey, aeskey, RAOP_AESKEY_LEN);
memcpy(mirror_buffer->ecdh_secret, ecdh_secret, 32);
mirror_buffer->logger = logger;
mirror_buffer->nextDecryptCount = 0;
//mirror_buffer_init_aes(mirror_buffer, aeskey, ecdh_secret, streamConnectionID);
//mirror_buffer_init_aes(mirror_buffer, aeskey, streamConnectionID);
return mirror_buffer;
}

View File

@@ -21,9 +21,7 @@
typedef struct mirror_buffer_s mirror_buffer_t;
mirror_buffer_t *mirror_buffer_init( logger_t *logger,
const unsigned char *aeskey,
const unsigned char *ecdh_secret);
mirror_buffer_t *mirror_buffer_init( logger_t *logger, const unsigned char *aeskey);
void mirror_buffer_init_aes(mirror_buffer_t *mirror_buffer, uint64_t streamConnectionID);
void mirror_buffer_decrypt(mirror_buffer_t *raop_mirror, unsigned char* input, unsigned char* output, int datalen);
void mirror_buffer_destroy(mirror_buffer_t *mirror_buffer);

View File

@@ -161,6 +161,38 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
if (!method || !cseq) {
return;
}
logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s %s RTSP/1.0", method, url);
char *header_str= NULL;
http_request_get_header_string(request, &header_str);
if (header_str) {
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", header_str);
bool data_is_plist = (strstr(header_str,"apple-binary-plist") != NULL);
bool data_is_text = (strstr(header_str,"text/parameters") != NULL);
free(header_str);
int request_datalen;
const char *request_data = http_request_get_data(request, &request_datalen);
if (request_data) {
if (request_datalen > 0) {
if (data_is_plist) {
plist_t req_root_node = NULL;
plist_from_bin(request_data, request_datalen, &req_root_node);
char * plist_xml;
uint32_t plist_len;
plist_to_xml(req_root_node, &plist_xml, &plist_len);
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
free(plist_xml);
} else if (data_is_text) {
char *data_str = utils_data_to_text((char *) request_data, request_datalen);
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
} else {
char *data_str = utils_data_to_string((unsigned char *) request_data, request_datalen, 16);
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
}
}
}
}
*response = http_response_init("RTSP/1.0", 200, "OK");
@@ -262,8 +294,42 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
if (handler != NULL) {
handler(conn, request, *response, &response_data, &response_datalen);
}
http_response_finish(*response, response_data, response_datalen);
int len;
const char *data = http_response_get_data(*response, &len);
if (response_data && response_datalen > 0) {
len -= response_datalen;
} else {
len -= 2;
}
header_str = utils_data_to_text(data, len);
logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s", header_str);
bool data_is_plist = (strstr(header_str,"apple-binary-plist") != NULL);
bool data_is_text = (strstr(header_str,"text/parameters") != NULL);
free(header_str);
if (response_data) {
if (response_datalen > 0) {
if (data_is_plist) {
plist_t res_root_node = NULL;
plist_from_bin(response_data, response_datalen, &res_root_node);
char * plist_xml;
uint32_t plist_len;
plist_to_xml(res_root_node, &plist_xml, &plist_len);
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
free(plist_xml);
} else if (data_is_text) {
char *data_str = utils_data_to_text((char*) response_data, response_datalen);
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
} else {
char *data_str = utils_data_to_string((unsigned char *) response_data, response_datalen, 16);
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
}
}
free(response_data);
response_data = NULL;
response_datalen = 0;

View File

@@ -26,6 +26,8 @@
#include "crypto.h"
#include "compat.h"
#include "stream.h"
#include "global.h"
#include "utils.h"
#define RAOP_BUFFER_LENGTH 32
@@ -57,24 +59,20 @@ struct raop_buffer_s {
raop_buffer_entry_t entries[RAOP_BUFFER_LENGTH];
};
void
raop_buffer_init_key_iv(raop_buffer_t *raop_buffer,
const unsigned char *aeskey,
const unsigned char *aesiv,
const unsigned char *ecdh_secret)
raop_buffer_t *
raop_buffer_init(logger_t *logger,
const unsigned char *aeskey,
const unsigned char *aesiv)
{
// Initialization key
unsigned char eaeskey[64];
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(raop_buffer->aeskey, eaeskey, 16);
raop_buffer_t *raop_buffer;
assert(aeskey);
assert(aesiv);
raop_buffer = calloc(1, sizeof(raop_buffer_t));
if (!raop_buffer) {
return NULL;
}
raop_buffer->logger = logger;
memcpy(raop_buffer->aeskey, aeskey, RAOP_AESKEY_LEN);
memcpy(raop_buffer->aesiv, aesiv, RAOP_AESIV_LEN);
#ifdef DUMP_AUDIO
@@ -84,24 +82,6 @@ raop_buffer_init_key_iv(raop_buffer_t *raop_buffer,
fclose(file_keyiv);
}
#endif
}
raop_buffer_t *
raop_buffer_init(logger_t *logger,
const unsigned char *aeskey,
const unsigned char *aesiv,
const unsigned char *ecdh_secret)
{
raop_buffer_t *raop_buffer;
assert(aeskey);
assert(aesiv);
assert(ecdh_secret);
raop_buffer = calloc(1, sizeof(raop_buffer_t));
if (!raop_buffer) {
return NULL;
}
raop_buffer->logger = logger;
raop_buffer_init_key_iv(raop_buffer, aeskey, aesiv, ecdh_secret);
for (int i = 0; i < RAOP_BUFFER_LENGTH; i++) {
raop_buffer_entry_t *entry = &raop_buffer->entries[i];
@@ -171,6 +151,10 @@ raop_buffer_decrypt(raop_buffer_t *raop_buffer, unsigned char *data, unsigned ch
}
#endif
if (DECRYPTION_TEST) {
printf("encrypted header %s", utils_data_to_string(data,12,12));
if (payload_size) printf("%d before %s", payload_size, utils_data_to_string(&data[12],16,16 ));
}
encryptedlen = payload_size / 16*16;
memset(output, 0, payload_size);
// Need to be initialized internally
@@ -180,7 +164,18 @@ raop_buffer_decrypt(raop_buffer_t *raop_buffer, unsigned char *data, unsigned ch
memcpy(output + encryptedlen, &data[12 + encryptedlen], payload_size - encryptedlen);
*outputlen = payload_size;
if (payload_size && DECRYPTION_TEST){
if ( !(output[0] == 0x8d || output[0] == 0x8e || output[0] == 0x81 || output[0] == 0x82 || output[0] == 0x20)) {
printf("***ERROR AUDIO FRAME IS NOT AAC_ELD OR ALAC\n");
}
if (DECRYPTION_TEST == 2) {
printf("decrypted audio frame, len = %d\n", *outputlen);
printf("%s",utils_data_to_string(output,payload_size,16));
printf("\n");
} else {
printf("%d after %s\n", payload_size, utils_data_to_string(output,16,16 ));
}
}
#ifdef DUMP_AUDIO
// Decrypted file
if (file_aac != NULL) {

View File

@@ -24,8 +24,7 @@ typedef int (*raop_resend_cb_t)(void *opaque, unsigned short seqno, unsigned sho
raop_buffer_t *raop_buffer_init(logger_t *logger,
const unsigned char *aeskey,
const unsigned char *aesiv,
const unsigned char *ecdh_secret);
const unsigned char *aesiv);
int raop_buffer_enqueue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned short datalen, uint64_t timestamp, int use_seqnum);
void *raop_buffer_dequeue(raop_buffer_t *raop_buffer, unsigned int *length, uint64_t *timestamp, int no_resend);
void raop_buffer_handle_resends(raop_buffer_t *raop_buffer, raop_resend_cb_t resend_cb, void *opaque);

View File

@@ -162,12 +162,6 @@ raop_handler_info(raop_conn_t *conn,
plist_to_bin(r_node, response_data, (uint32_t *) response_datalen);
http_response_add_header(response, "Content-Type", "application/x-apple-binary-plist");
logger_log(conn->raop->logger, LOGGER_DEBUG, "UxPlay server info sent in response to client \"GET /info\" request, len = %d:", *response_datalen);
char * plist_xml;
uint32_t plist_len;
plist_to_xml(r_node, &plist_xml, &plist_len);
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
free(plist_xml);
free(pk);
free(hw_addr);
}
@@ -346,15 +340,9 @@ raop_handler_setup(raop_conn_t *conn,
// Parsing bplist
plist_t req_root_node = NULL;
plist_from_bin(data, data_len, &req_root_node);
char * plist_xml;
uint32_t plist_len;
plist_to_xml(req_root_node, &plist_xml, &plist_len);
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
free(plist_xml);
plist_t req_streams_node = plist_dict_get_item(req_root_node, "streams");
plist_t req_eiv_node = plist_dict_get_item(req_root_node, "eiv");
plist_t req_ekey_node = plist_dict_get_item(req_root_node, "ekey");
plist_t req_eiv_node = plist_dict_get_item(req_root_node, "eiv");
// For the response
plist_t res_root_node = plist_new_dict();
@@ -362,7 +350,12 @@ raop_handler_setup(raop_conn_t *conn,
// The first SETUP call that initializes keys and timing
unsigned char aesiv[16];
unsigned char aeskey_old[16];
unsigned char aeskey[16];
unsigned char *aeskey_audio = aeskey;
unsigned char *aeskey_video = aeskey;
logger_log(conn->raop->logger, LOGGER_DEBUG, "SETUP 1");
// First setup
@@ -371,16 +364,75 @@ raop_handler_setup(raop_conn_t *conn,
plist_get_data_val(req_eiv_node, &eiv, &eiv_len);
memcpy(aesiv, eiv, 16);
logger_log(conn->raop->logger, LOGGER_DEBUG, "eiv_len = %llu", eiv_len);
char* str = utils_data_to_string(aesiv, 16, 16);
logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aesiv (needed for AES-CBC audio decryption iv):\n%s", str);
free(str);
char* ekey = NULL;
uint64_t ekey_len = 0;
plist_get_data_val(req_ekey_node, &ekey, &ekey_len);
logger_log(conn->raop->logger, LOGGER_DEBUG, "ekey_len = %llu", ekey_len);
// ekey is 72 bytes, aeskey is 16 bytes
int ret = fairplay_decrypt(conn->fairplay, (unsigned char*) ekey, aeskey);
str = utils_data_to_string((unsigned char *) ekey, ekey_len, 16);
logger_log(conn->raop->logger, LOGGER_DEBUG, "ekey:\n%s", str);
free (str);
int ret = fairplay_decrypt(conn->fairplay, (unsigned char*) ekey, aeskey_old);
logger_log(conn->raop->logger, LOGGER_DEBUG, "fairplay_decrypt ret = %d", ret);
str = utils_data_to_string(aeskey_old, 16, 16);
logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aeskey (fairplay-decrypted from ekey):\n%s", str);
free(str);
unsigned char ecdh_secret[X25519_KEY_SIZE];
pairing_get_ecdh_secret_key(conn->pairing, ecdh_secret);
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);
unsigned char eaeskey[64] = {};
memcpy(eaeskey, aeskey_old, 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);
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);
/* old-protocol clients such as AirMyPC use the unhashed key aeskey_old for both audio and video (ios9, ios10 support)*/
/* clients with sourceVersion <= OLD_PROTOCOL_AUDIO_CLIENT_SOURCEVERSION use unhashed aeskey_old for audio decryption */
/* this is also done for audio or video if clients User-Agent string is included in OLD_PROTOCOL_[AUDIO,VIDEO]_CLIENT_LIST (AirMyPC support)*/
/* OLD_PROTOCOL_AUDIO_CLIENT_LIST, OLD_PROTOCOL_VIDEO_CLIENT_LIST, OLD_PROTOCOL_AUDIO_CLIENT_SOURCEVERSION are defined in global.h */
plist_t req_source_version_node = plist_dict_get_item(req_root_node, "sourceVersion");
char *sourceVersion;
plist_get_string_val(req_source_version_node, &sourceVersion);
const char *user_agent = http_request_get_header(request, "User-Agent");
logger_log(conn->raop->logger, LOGGER_INFO, "Client identified as User-Agent: %s, sourceVersion: %s", user_agent, sourceVersion);
#ifdef OLD_PROTOCOL_AUDIO_CLIENT_SOURCEVERSION
char *end_ptr;
if (strtoul(sourceVersion, &end_ptr, 10) <= strtoul(OLD_PROTOCOL_AUDIO_CLIENT_SOURCEVERSION , &end_ptr, 10)) aeskey_audio = aeskey_old;
#endif
#ifdef OLD_PROTOCOL_AUDIO_CLIENT_USER_AGENT_LIST
if (strstr(OLD_PROTOCOL_AUDIO_CLIENT_USER_AGENT_LIST, user_agent)) aeskey_audio = aeskey_old;
#endif
#ifdef OLD_PROTOCOL_VIDEO_CLIENT_USER_AGENT_LIST
if (strstr(OLD_PROTOCOL_AUDIO_CLIENT_USER_AGENT_LIST, user_agent)) aeskey_video = aeskey_old;
#endif
if (aeskey_audio == aeskey_old) {
logger_log(conn->raop->logger, LOGGER_INFO, "Client identifed as using old protocol (unhashed) AES audio key)");
}
if (aeskey_video == aeskey_old) {
logger_log(conn->raop->logger, LOGGER_INFO, "Client identifed as using old protocol (unhashed) AES video key)");
}
// Time port
uint64_t timing_rport;
@@ -392,8 +444,8 @@ raop_handler_setup(raop_conn_t *conn,
conn->raop_ntp = raop_ntp_init(conn->raop->logger, conn->remote, conn->remotelen, timing_rport);
raop_ntp_start(conn->raop_ntp, &timing_lport);
conn->raop_rtp = raop_rtp_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey, aesiv, ecdh_secret);
conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey, ecdh_secret);
conn->raop_rtp = raop_rtp_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey_audio, aesiv);
conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey_video);
plist_t res_event_port_node = plist_new_uint(conn->raop->port);
plist_t res_timing_port_node = plist_new_uint(timing_lport);
@@ -404,6 +456,7 @@ raop_handler_setup(raop_conn_t *conn,
}
// Process stream setup requests
plist_t req_streams_node = plist_dict_get_item(req_root_node, "streams");
if (PLIST_IS_ARRAY(req_streams_node)) {
plist_t res_streams_node = plist_new_array();
@@ -422,7 +475,7 @@ 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 = %llu", stream_connection_id);
logger_log(conn->raop->logger, LOGGER_DEBUG, "streamConnectionID (needed for AES-CTR video decryption iv): %llu", stream_connection_id);
if (conn->raop_rtp_mirror) {
raop_rtp_init_mirror_aes(conn->raop_rtp_mirror, stream_connection_id);

View File

@@ -132,8 +132,8 @@ raop_rtp_parse_remote(raop_rtp_t *raop_rtp, const unsigned char *remote, int rem
}
raop_rtp_t *
raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, const unsigned char *remote, int remotelen,
const unsigned char *aeskey, const unsigned char *aesiv, const unsigned char *ecdh_secret)
raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, const unsigned char *remote,
int remotelen, const unsigned char *aeskey, const unsigned char *aesiv)
{
raop_rtp_t *raop_rtp;
@@ -156,7 +156,7 @@ raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, co
}
memcpy(&raop_rtp->callbacks, callbacks, sizeof(raop_callbacks_t));
raop_rtp->buffer = raop_buffer_init(logger, aeskey, aesiv, ecdh_secret);
raop_rtp->buffer = raop_buffer_init(logger, aeskey, aesiv);
if (!raop_rtp->buffer) {
free(raop_rtp);
return NULL;

View File

@@ -26,8 +26,8 @@
typedef struct raop_rtp_s raop_rtp_t;
raop_rtp_t *raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, const unsigned char *remote, int remotelen,
const unsigned char *aeskey, const unsigned char *aesiv, const unsigned char *ecdh_secret);
raop_rtp_t *raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, const unsigned char *remote,
int remotelen, const unsigned char *aeskey, const unsigned char *aesiv);
void raop_rtp_start_audio(raop_rtp_t *raop_rtp, int use_udp, unsigned short control_rport,
unsigned short *control_lport, unsigned short *data_lport);

View File

@@ -109,8 +109,7 @@ raop_rtp_parse_remote(raop_rtp_mirror_t *raop_rtp_mirror, const unsigned char *r
#define NO_FLUSH (-42)
raop_rtp_mirror_t *raop_rtp_mirror_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp,
const unsigned char *remote, int remotelen,
const unsigned char *aeskey, const unsigned char *ecdh_secret)
const unsigned char *remote, int remotelen, const unsigned char *aeskey)
{
raop_rtp_mirror_t *raop_rtp_mirror;
@@ -125,7 +124,7 @@ raop_rtp_mirror_t *raop_rtp_mirror_init(logger_t *logger, raop_callbacks_t *call
raop_rtp_mirror->ntp = ntp;
memcpy(&raop_rtp_mirror->callbacks, callbacks, sizeof(raop_callbacks_t));
raop_rtp_mirror->buffer = mirror_buffer_init(logger, aeskey, ecdh_secret);
raop_rtp_mirror->buffer = mirror_buffer_init(logger, aeskey);
if (!raop_rtp_mirror->buffer) {
free(raop_rtp_mirror);
return NULL;

View File

@@ -24,8 +24,7 @@ typedef struct raop_rtp_mirror_s raop_rtp_mirror_t;
typedef struct h264codec_s h264codec_t;
raop_rtp_mirror_t *raop_rtp_mirror_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp,
const unsigned char *remote, int remotelen,
const unsigned char *aeskey, const unsigned char *ecdh_secret);
const unsigned char *remote, int remotelen, const unsigned char *aeskey);
void raop_rtp_init_mirror_aes(raop_rtp_mirror_t *raop_rtp_mirror, uint64_t streamConnectionID);
void raop_rtp_start_mirror(raop_rtp_mirror_t *raop_rtp_mirror, int use_udp, unsigned short *mirror_data_lport);
void raop_rtp_mirror_stop(raop_rtp_mirror_t *raop_rtp_mirror);

View File

@@ -178,4 +178,36 @@ char *utils_parse_hex(const char *str, int str_len, int *data_len) {
*data_len = (str_len / 2);
return data;
}
}
char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per_line) {
int len = 3*datalen + ((datalen-1)/chars_per_line ) + 1;
char *str = (char *) calloc(len + 1, sizeof(char));
assert(str);
char *p = str;
for (int i = 0; i < datalen; i++) {
if (i > 0 && i % chars_per_line == 0) {
sprintf(p,"\n");
p++;
}
sprintf(p,"%2.2x ", (unsigned int) data[i]);
p += 3;
}
sprintf(p,"\n");
p++;
assert(p == &(str[len]));
return str;
}
char *utils_data_to_text(const char *data, int datalen) {
char *ptr = (char *) calloc(datalen + 1, sizeof(char));
assert(ptr);
strncpy(ptr, data, datalen);
char *p = ptr;
while (p) {
p = strchr(p, '\r'); /* replace occurences of '\r' by ' ' */
if (p) *p = ' ';
}
return ptr;
}

View File

@@ -20,4 +20,6 @@ int utils_read_file(char **dst, const char *pemstr);
int utils_hwaddr_raop(char *str, int strlen, const char *hwaddr, int hwaddrlen);
int utils_hwaddr_airplay(char *str, int strlen, const char *hwaddr, int hwaddrlen);
char *utils_parse_hex(const char *str, int str_len, int *data_len);
#endif
char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per_line);
char *utils_data_to_text(const char *data, int datalen);
#endif

View File

@@ -193,6 +193,7 @@ void audio_renderer_render_buffer(raop_ntp_t *ntp, unsigned char* data, int data
switch (renderer->ct) {
case 8: /*AAC-ELD*/
valid = (data[0] == 0x8d || data[0] == 0x8e);
valid = valid || (data[0] == 0x81 || data[0] == 0x82); /* old protocol iOS 8, iOS 9 */
break;
case 2: /*ALAC*/
valid = (data[0] == 0x20);
@@ -209,10 +210,14 @@ void audio_renderer_render_buffer(raop_ntp_t *ntp, unsigned char* data, int data
if (counter == 2) broken_audio = false;
if (!broken_audio) gst_app_src_push_buffer(GST_APP_SRC(renderer->appsrc), buffer);
} else {
if (!broken_audio) logger_log(logger, LOGGER_ERR, "*** ERROR decryption of audio (compression_type %d) failed ", renderer->ct);
if (!broken_audio) {
logger_log(logger, LOGGER_ERR, "*** ERROR decryption of audio frame (compression_type %d) failed ", renderer->ct);
} else {
logger_log(logger, LOGGER_DEBUG, "*** ERROR decryption of audio frame (compression_type %d) failed ", renderer->ct);
}
broken_audio = true;
counter = 0;
}
}
}
void audio_renderer_set_volume(float volume) {

View File

@@ -113,7 +113,7 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, vide
gst_init(NULL,NULL);
GString *launch = g_string_new("appsrc name=video_source stream-type=0 format=GST_FORMAT_TIME is-live=true !"
"queue ! decodebin ! videoconvert ! ");
"queue ! h264parse ! avdec_h264 ! videoconvert ! ");
append_videoflip(launch, &videoflip[0], &videoflip[1]);
g_string_append(launch, videosink);
g_string_append(launch, " name=video_sink sync=false");
@@ -141,6 +141,17 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, vide
get_X11_Display(renderer->gst_window);
}
#endif
gst_element_set_state (renderer->pipeline, GST_STATE_READY);
GstState state;
if (gst_element_get_state (renderer->pipeline, &state, NULL, 0)) {
if (state == GST_STATE_READY) {
logger_log(logger, LOGGER_DEBUG, "Initialized GStreamer video renderer");
} else {
logger_log(logger, LOGGER_ERR, "Failed to initialize GStreamer video renderer");
}
} else {
logger_log(logger, LOGGER_ERR, "Failed to initialize GStreamer video renderer");
}
}
void video_renderer_start() {
@@ -155,7 +166,11 @@ void video_renderer_render_buffer(raop_ntp_t *ntp, unsigned char* data, int data
/* first four bytes of valid video data are 0x0, 0x0, 0x0, 0x1 */
/* first byte of invalid data (decryption failed) is 0x1 */
if (data[0]) {
if (!broken_video) logger_log(logger, LOGGER_ERR, "*** ERROR decryption of video failed ");
if (!broken_video) {
logger_log(logger, LOGGER_ERR, "*** ERROR decryption of video packet failed ");
} else {
logger_log(logger, LOGGER_DEBUG, "*** ERROR decryption of video packet failed ");
}
broken_video = true;
} else {
broken_video = false;

View File

@@ -1,11 +1,11 @@
.TH UXPLAY "1" "December 2021" "1.43" "User Commands"
.TH UXPLAY "1" "December 2021" "1.44" "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.43: An open\-source AirPlay mirroring server based on RPiPlay
UxPlay 1.44: An open\-source AirPlay mirroring server based on RPiPlay
.SH OPTIONS
.TP
.B

View File

@@ -36,10 +36,6 @@
#include <net/if_dl.h>
#endif
#include "log.h"
#include "lib/raop.h"
#include "lib/stream.h"
@@ -48,7 +44,7 @@
#include "renderers/video_renderer.h"
#include "renderers/audio_renderer.h"
#define VERSION "1.43"
#define VERSION "1.44"
#define DEFAULT_NAME "UxPlay"
#define DEFAULT_DEBUG_LOG false
@@ -74,13 +70,12 @@ static unsigned char compression_type = 0;
static std::string audiosink = "autoaudiosink";
static bool use_audio = true;
gboolean connection_callback (gpointer loop){
if (!connections_stopped) {
counter = 0;
} else {
if (++counter == server_timeout) {
LOGI("no connections for %d seconds: relaunch server\n",server_timeout);
LOGD("no connections for %d seconds: relaunch server",server_timeout);
g_main_loop_quit((GMainLoop *) loop);
}
}
@@ -478,10 +473,11 @@ int main (int argc, char *argv[]) {
}
parse_hw_addr(mac_address, server_hw_addr);
mac_address.clear();
connections_stopped = true;
relaunch:
counter = 0;
compression_type = 0;
connections_stopped = false;
if (start_raop_server(server_hw_addr, server_name, display, tcp, udp, debug_log)) {
return 1;
}