diff --git a/README.html b/README.html index ad3444b..c30e515 100644 --- a/README.html +++ b/README.html @@ -1,6 +1,6 @@

UxPlay -1.64: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix +id="uxplay-1.65-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay +1.65: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).

Now @@ -82,27 +82,29 @@ antimof site is no longer involved in development, but periodically posts updates pulled from the new main UxPlay site).

UxPlay is tested on a number of systems, including (among others) -Debian 10.11 “Buster” and 11.2 “Bullseye”, Ubuntu 20.04 LTS and 22.04.1 -LTS, (also Ubuntu derivatives Linux Mint 20.3, Pop!_OS 22.04 (NVIDIA -edition)), Rocky Linux 9.1 (a CentOS successor), Fedora 36, OpenSUSE -15.4, Arch Linux 22.10, macOS 13.3 (Intel and M2), FreeBSD 13.2, Windows -10 and 11 (64 bit).

+Debian (10 “Buster”, 11 “Bullseye”, 12 “Bookworm”), Ubuntu (20.04 LTS, +22.04 LTS, 23.04; also Ubuntu derivatives Linux Mint 20.3, Pop!_OS 22.04 +(NVIDIA edition)), Red Hat and clones (Fedora 38, Rocky Linux 9.2), +OpenSUSE 15.4, Arch Linux 23.05, macOS 13.3 (Intel and M2), FreeBSD +13.2, Windows 10 and 11 (64 bit).

On Raspberry Pi 4 model B, it is tested on Raspberry Pi OS (Bullseye) -(32- and 64-bit), Ubuntu 22.04 and 22.10, Manjaro RPi4 23.02, and +(32- and 64-bit), Ubuntu 22.04 LTS and 23.04, Manjaro RPi4 23.02, and (without hardware video decoding) on OpenSUSE 15.4. Also tested on Raspberry Pi 3 model B+.

Its main use is to act like an AppleTV for screen-mirroring (with audio) of iOS/iPadOS/macOS clients (iPhone, iPod Touch, iPad, Mac computers) on the server display of a host running Linux, macOS, or other unix (and now also Microsoft Windows). UxPlay supports Apple’s -AirPlay2 protocol using “Legacy Pairing”, but some features are missing. -(Details of what is publicly known about Apple’s AirPlay 2 protocol can -be found here, -here, here -and here). While -there is no guarantee that future iOS releases will keep supporting -“Legacy Pairing”, the recent iOS 16 release continues support.

+and here; see also +pyatv which +could be a resource for adding modern protocols.) While there is no +guarantee that future iOS releases will keep supporting “Legacy +Protocol”, the recent iOS 16 release continues support.

The UxPlay server and its client must be on the same local area network, on which a Bonjour/Zeroconf mDNS/DNS-SD server is also running (only DNS-SD “Service Discovery” service is strictly @@ -286,7 +288,8 @@ gstreamer1-devel gstreamer1-plugins-base-devel (+libX11-devel for fullscreen X11) (some of these may be in the “CodeReady” add-on repository, called “PowerTools” by clones)

  • OpenSUSE: (sudo zypper install) libopenssl-devel -libplist-devel avahi-compat-mDNSResponder-devel gstreamer-devel +libplist-2_0-devel (formerly libplist-devel) +avahi-compat-mDNSResponder-devel gstreamer-devel gstreamer-plugins-base-devel (+ libX11-devel for fullscreen X11).

  • Arch Linux (Also available as a package in @@ -1127,6 +1130,15 @@ when the client sends the “Stop Mirroring” signal, try the no-close option “-nc” that leaves the video window open.

    4. GStreamer issues (missing plugins, etc.):

    +
      +
    • clearing the user’s GStreamer cache with +rm -rf ~/.cache/gstreamer-1.0/* may be the solution to +problems where gst-inspect-1.0 does not show a plugin that you believe +is installed. The cache will be regenerated next time GStreamer is +started. This is the solution to puzzling problems that turn out +to come from corruption of the cache, and should be tried +first.
    • +

    If UxPlay fails to start, with a message that a required GStreamer plugin (such as “libav”) was not found, first check with the GStreamer tool gst-inspect-1.0 to see what GStreamer knows is available. (You may @@ -1138,13 +1150,6 @@ installed (as one user found), try entirely removing and reinstalling the package. That user found that a solution to a “Required gstreamer plugin ‘libav’ not found” message that kept recurring was to clear the user’s gstreamer cache.

    -
      -
    • clearing the user’s GStreamer cache with -rm -rf ~/.cache/gstreamer-1.0/* may be the solution to -problems where gst-inspect-1.0 does not show a plugin that you believe -is installed. The cache will be regenerated next time GStreamer is -started.
    • -

    If it fails to start with an error like ‘no element "avdec_aac"’ this is because even though gstreamer-libav is installed. it is incomplete because some plugins are @@ -1211,6 +1216,21 @@ when it is made.

  • id="protocol-issues-such-as-failure-to-decrypt-all-video-and-audio-streams-from-old-or-non-apple-clients">6. Protocol issues, such as failure to decrypt ALL video and audio streams from old or non-Apple clients:

    + +

    This is allowed by disabling “Supports Legacy Pairing” (bit 27) in +the “features” code UxPlay advertises on DNS-SD Service Discovery. Most +clients will then not attempt to setup the “shared secret key” for +pairing.

    +

    A protocol failure may trigger an unending stream of error messages, and means that the audio decryption key (also used in video decryption) was not correctly extracted from data sent by the client. This should @@ -1230,13 +1250,23 @@ thought that it was necessary for UxPlay to claim to be an older 32 bit AppleTV model that cannot run modern 64bit tvOS, in order for the client to use a “legacy” protocol for pairing with the server. However, UxPlay still works if it declares itself as an AppleTV6,2 with sourceVersion -380.20.1 (an AppleTV 4K 1st gen, introduced 2017, running tvOS 12.2.1); -it seems that the use of “legacy” protocol just requires bit 27 (listed -as “SupportsLegacyPairing”) of the “features” plist code (reported to -the client by the AirPlay server) to be set.

    +380.20.1 (an AppleTV 4K 1st gen, introduced 2017, running tvOS 12.2.1). +It was previously thought that use of “legacy” protocol requires bit 27 +(“SupportsLegacyPairing”) of the “features” plist code (reported to the +client by the AirPlay server) to be set, but it was recently discovered +that it was possible to switch that off (after a small protocol +modification) to eliminate a 5 second delay by the client in making +connections to the server.

    The “features” code and other settings are set in UxPlay/lib/dnssdint.h.

    Changelog

    +

    1.65 2023-05-31 Eliminate pair_setup part of connection protocol to +allow faster connections with clients (thanks to @shuax #176 for this discovery); to revert, +uncomment a line in lib/dnssdint.h. Disconnect from audio device when +connection closes, to not block its use by other apps if uxplay is +running but not connected. Fix for AirMyPC client (broken since 1.60), +so its older non-NTP timestamp protocol works with -vsync.

    1.64 2023-04-23 Timestamp-based synchronization of audio and video is now the default in Mirror mode. (Use “-vsync no” to restore previous behavior.) A configuration file can now be used for startup options. @@ -1390,13 +1420,17 @@ then run “sudo ldconfig”.

    can avoid this step by installing libplist-dev and libplist3 from Debian 10 or Ubuntu 18.04.)
    As well as the usual build tools (autoconf, automake, libtool), you may need to also install some libpython*-dev -package. Download the latest source from https://github.com/libimobiledevice/libplist: -get libplist-master.zip, -then (“unzip libplist-master.zip ; cd libplist-master”), build/install -(“./autogen.sh ; make ; sudo make install”). This will probably install -libplist-2.0.* in /usr/local/lib.

    +package. Download the latest source with git from https://github.com/libimobiledevice/libplist, +or get the source from the Releases section (use the *.tar.bz2 release, +not the *.zip or *.tar.gz versions): download libplist-2.3.0, +then unpack it (“tar -xvjf libplist-2.3.0.tar.bz2 ; cd libplist-2.3.0”), +and build/install it: (“./configure ; make ; sudo make install”). This +will probably install libplist-2.0.* in /usr/local/lib. The new +libplist-2.3.0 release should be compatible with UxPlay; libplist-2.2.0 +is also available if there are any issues.

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