Merge pull request #158 from FDH2/master

ipv6 support + various other fixes.
This commit is contained in:
antimof
2024-08-21 17:08:57 +03:00
committed by GitHub
27 changed files with 809 additions and 444 deletions

View File

@@ -47,6 +47,12 @@ if ( GST_MACOS )
message ( STATUS "define GST_MACOS" )
endif()
if ( GST_124 )
add_definitions( -DGST_124 )
message ( STATUS "define GST_124" )
endif()
add_executable( uxplay uxplay.cpp )
target_link_libraries( uxplay
renderers

View File

@@ -1,29 +1,27 @@
<h1
id="uxplay-1.68-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay
1.68: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix
id="uxplay-1.69-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay
1.69: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix
(now also runs on Windows).</h1>
<h3
id="now-developed-at-the-github-site-httpsgithub.comfdh2uxplay-where-all-user-issues-should-be-posted-and-latest-versions-can-be-found.">Now
id="now-developed-at-the-github-site-httpsgithub.comfdh2uxplay-where-all-user-issues-should-be-posted-and-latest-versions-can-be-found."><strong>Now
developed at the GitHub site <a
href="https://github.com/FDH2/UxPlay">https://github.com/FDH2/UxPlay</a>
(where ALL user issues should be posted, and latest versions can be
found).</h3>
found).</strong></h3>
<ul>
<li><em><strong>NEW in v1.68</strong>: Volume-control improvements, plus
improved support for Apple-style one-time “pin” codes introduced in
1.67: a register of pin-registered clients can now optionally be
maintained to check returning clients; a simpler method for generating a
persistent public key (based on the MAC address, which can be set in the
UxPlay startup file) is now the default. (The OpenSSL “pem-file” method
introduced in 1.67 is still available with the “-key” option.)</em></li>
<li><em><strong>NEW in v1.69</strong>: minor changes for users:
-nofreeze option to NOT leave frozen video in place when a network
failure occurs; internal changes/improvements needed for planned future
HLS video streaming support.</em></li>
</ul>
<h2 id="highlights">Highlights:</h2>
<ul>
<li>GPLv3, open source.</li>
<li>Originally supported only AirPlay Mirror protocol, now has added
support for AirPlay Audio-only (Apple Lossless ALAC) streaming from
current iOS/iPadOS clients. <strong>There is no support for Airplay2
video-streaming protocol, and none is planned.</strong></li>
current iOS/iPadOS clients. <strong>There is no current support for
Airplay HLS video-streaming (e.g., YouTube video) but this is in
development.</strong></li>
<li>macOS computers (2011 or later, both Intel and “Apple Silicon” M1/M2
systems) can act either as AirPlay clients, or as the server running
UxPlay. Using AirPlay, UxPlay can emulate a second display for macOS
@@ -166,12 +164,13 @@ app cannot be watched using UxPlays AirPlay Mirror mode (only the
unprotected audio will be streamed, in AAC format), but both video and
audio content from DRM-free apps like “YouTube app” will be streamed by
UxPlay in Mirror mode.</strong></p></li>
<li><p><strong>As UxPlay does not support non-Mirror AirPlay2 video
streaming (where the client controls a web server on the AirPlay server
that directly receives content to avoid it being decoded and re-encoded
by the client), using the icon for AirPlay video in apps such as the
YouTube app will only send audio (in lossless ALAC format) without the
accompanying video.</strong></p></li>
<li><p><strong>As UxPlay does not currently support non-Mirror AirPlay
video streaming (where the client controls a web server on the AirPlay
server that directly receives HLS content to avoid it being decoded and
re-encoded by the client), using the icon for AirPlay video in apps such
as the YouTube app will only send audio (in lossless ALAC format)
without the accompanying video (there are plans to support HLS video in
future releases of UxPlay)</strong></p></li>
</ul>
<h3
id="possibility-for-using-hardware-accelerated-h264-video-decoding-if-available.">Possibility
@@ -257,10 +256,16 @@ libplist 2.0 or later. (This means Debian 10 “Buster” based systems
(e.g, Ubuntu 18.04) or newer; on Debian 10 systems “libplist” is an
older version, you need “libplist3”.) If it does not, you may need to
build and install these from source (see instructions at the end of this
README). If you have a non-standard OpenSSL installation, you may need
to set the environment variable OPENSSL_ROOT_DIR (<em>e.g.</em> ,
README).</p>
<p>If you have a non-standard OpenSSL installation, you may need to set
the environment variable OPENSSL_ROOT_DIR (<em>e.g.</em> ,
<code>export OPENSSL_ROOT_DIR=/usr/local/lib64</code>” if that is where
it is installed).</p>
it is installed). Similarly, for non-standard (or multiple) GStreamer
installations, set the environment variable GSTREAMER_ROOT_DIR to the
directory that contains the “…/gstreamer-1.0/” directory of the
gstreamer installation that UxPlay should use (if this is <em>e.g.</em>
“~/my_gstreamer/lib/gstreamer-1.0/”, set this location with
<code>export GSTREAMER_ROOT_DIR=$HOME/my_gstreamer/lib</code>”).</p>
<ul>
<li>Most users will use the GStreamer supplied by their distribution,
but a few (in particular users of Raspberry Pi OS Lite Legacy (Buster)
@@ -382,8 +387,10 @@ decoding)</li>
<li><strong>plugins-bad</strong>” (for h264 decoding).</li>
</ol>
<p>Plugins that may also be needed include “<strong>gl</strong>” for
OpenGL support (which may be useful, and should be used with h264
decoding by the NVIDIA GPU), and “<strong>x</strong>” for X11 support,
OpenGL support (this provides the “-vs glimagesink” videosink, which can
be very useful in many systems, and should always be used when using
h264 decoding by a NVIDIA GPU), “<strong>gtk3</strong>” (which provides
the “-vs gtksink” videosink), and “<strong>x</strong>” for X11 support,
although these may already be installed; “<strong>vaapi</strong>” is
needed for hardware-accelerated h264 video decoding by Intel or AMD
graphics (but not for use with NVIDIA using proprietary drivers). If
@@ -443,11 +450,12 @@ omitting the initial <code>"-"</code> of the command-line option. Lines
in the configuration file beginning with <code>"#"</code> are treated as
comments and ignored.</p>
<p><strong>Run uxplay in a terminal window</strong>. On some systems,
you can toggle into and out of fullscreen mode with F11 or (held-down
left Alt)+Enter keys. Use Ctrl-C (or close the window) to terminate it
when done. If the UxPlay server is not seen by the iOS clients
drop-down “Screen Mirroring” panel, check that your DNS-SD server
(usually avahi-daemon) is running: do this in a terminal window with
you can specify fullscreen mode with the <code>-fs</code> option, or
toggle into and out of fullscreen mode with F11 or (held-down left
Alt)+Enter keys. Use Ctrl-C (or close the window) to terminate it when
done. If the UxPlay server is not seen by the iOS clients drop-down
“Screen Mirroring” panel, check that your DNS-SD server (usually
avahi-daemon) is running: do this in a terminal window with
<code>systemctl status avahi-daemon</code>. If this shows the
avahi-daemon is not running, control it with
<code>sudo systemctl [start,stop,enable,disable] avahi-daemon</code> (on
@@ -487,9 +495,10 @@ GStreamer internal clock used to try to keep them synchronized.
<strong>Starting with UxPlay-1.64, the other method (GStreamers
<em>sync=true</em>” mode), which uses timestamps in the audio and video
streams sent by the client, is the new default</strong>. On
low-decoding-power UxPlay hosts (such as Raspberry Pi 3 models) this
will drop video frames that cannot be decoded in time to play with the
audio, making the video jerky, but still synchronized.</p></li>
low-decoding-power UxPlay hosts (such as Raspberry Pi Zero W or 3 B+
models) this will drop video frames that cannot be decoded in time to
play with the audio, making the video jerky, but still
synchronized.</p></li>
</ul>
<p>The older method which does not drop late video frames worked well on
more powerful systems, and is still available with the UxPlay option
@@ -513,12 +522,13 @@ before a pause or track-change initiated on the client takes effect on
the audio played by the server.</li>
</ul>
<p>AirPlay volume-control attenuates volume (gain) by up to -30dB: the
range -30dB:0dB can be rescaled from <em>Low</em>:0, or
decibel range -30:0 can be rescaled from <em>Low</em>:0, or
<em>Low</em>:<em>High</em>, using the option <code>-db</code> (“-db
<em>Low</em>” or “-db <em>Low</em>:<em>High</em>”), <em>Low</em> must be
negative. Rescaling is linear in decibels. The option
<code>-taper</code> provides a “tapered” AirPlay volume-control profile
some users may prefer.</p>
negative. Rescaling is linear in decibels. Note that GStreamers audio
format will “clip” any audio gain above +20db, so keep <em>High</em>
below that level. The option <code>-taper</code> provides a “tapered”
AirPlay volume-control profile some users may prefer.</p>
<p>The -vsync and -async options also allow an optional positive (or
negative) audio-delay adjustment in <em>milliseconds</em> for
fine-tuning : <code>-vsync 20.5</code> delays audio relative to video by
@@ -617,10 +627,10 @@ lower-power models to keep audio and video synchronized using
timestamps. In Legacy Raspberry Pi OS (Bullseye), raspi-config
“Performance Options” allows specifying how much memory to allocate to
the GPU, but this setting appears to be absent in Bookworm (but it can
still be set to e.g. 128GB by adding a line “gpu_mem=128” in
/boot/config.txt). A Pi Zero 2 W (which has 512GB memory) worked well
when tested in 32 bit Bullseye or Bookworm Lite with 128GB allocated to
the GPU (default seems to be 64GB).</p>
still be set to e.g. 128MB by adding a line “gpu_mem=128” in
/boot/config.txt). A Pi Zero 2 W (which has 512MB memory) worked well
when tested in 32 bit Bullseye or Bookworm Lite with 128MB allocated to
the GPU (default seems to be 64MB).</p>
<p>The basic uxplay options for R Pi are
<code>uxplay [-vs &lt;videosink&gt;]</code>. The choice
<code>&lt;videosink&gt;</code> = <code>glimagesink</code> is sometimes
@@ -747,9 +757,10 @@ 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; the option “-vs
osxvideosink force-aspect-ratio=true” can be used to make the window
have the correct aspect ratio when it first opens.</p></li>
when the window width is changed by dragging its side; the option
<code>-vs "osxvideosink force-aspect-ratio=true"</code> can be used to
make the window have the correct aspect ratio when it first
opens.</p></li>
</ul>
<h2
id="building-uxplay-on-microsoft-windows-using-msys2-with-the-mingw-64-compiler.">Building
@@ -790,9 +801,8 @@ environment (this uses “<code>ninja</code>” in place of
install UxPlay dependencies (openssl is already installed with
MSYS2):</p>
<p><code>pacman -S mingw-w64-x86_64-libplist mingw-w64-x86_64-gstreamer mingw-w64-x86_64-gst-plugins-base</code></p>
<p>Note that libplist will be linked statically to the uxplay
executable. If you are trying a different Windows build system, MSVC
versions of GStreamer for Windows are available from the <a
<p>If you are trying a different Windows build system, MSVC versions of
GStreamer for Windows are available from the <a
href="https://gstreamer.freedesktop.org/download/">official GStreamer
site</a>, but only the MinGW 64-bit build on MSYS2 has been
tested.</p></li>
@@ -856,14 +866,20 @@ used.</p>
<code>-vs &lt;videosink&gt;</code> option, some choices for
<code>&lt;videosink&gt;</code> are <code>d3d11videosink</code>,
<code>d3dvideosink</code>, <code>glimagesink</code>,
<code>gtksink</code>. With Direct3D 11.0 or greater, you can get the
ability to toggle into and out of fullscreen mode using the Alt-Enter
key combination with option
<code>gtksink</code>.</p>
<ul>
<li>With Direct3D 11.0 or greater, you can either always be in
fullscreen mode using option
<code>-vs "d3d11videosink fullscreen-toggle-mode=property fullscreen=true"</code>,
or get the ability to toggle into and out of fullscreen mode using the
Alt-Enter key combination with option
<code>-vs "d3d11videosink fullscreen-toggle-mode=alt-enter"</code>. For
convenience, this option will be added if just
<code>-vs d3d11videosink</code> (by itself) is used. (You may wish to
add “<code>vs d3d11videosink</code>” (no initial “<code>-</code>”) to
the UxPlay startup options file; see “man uxplay” or “uxplay -h”.)</p>
convenience, these options will be added if just
<code>-vs d3d11videosink</code> with or without the fullscreen option
“-fs” is used. <em>(Windows users may wish to add
<code>vs d3d11videosink</code>” (no initial “<code>-</code>”) to the
UxPlay startup options file; see “man uxplay” or “uxplay -h”.)</em></li>
</ul>
<p>The executable uxplay.exe can also be run without the MSYS2
environment, in the Windows Terminal, with
<code>C:\msys64\mingw64\bin\uxplay</code>.</p>
@@ -977,7 +993,7 @@ full-screen display that overscans, and is not displayed by gstreamer).
Recommendation: <strong>dont use this option</strong> unless there is
some special reason to use it.</p>
<p><strong>-fs</strong> uses fullscreen mode, but only works with X11,
Wayland or VAAPI.</p>
Wayland, VAAPI, and D3D11 (Windows).</p>
<p><strong>-p</strong> allows you to select the network ports used by
UxPlay (these need to be opened if the server is behind a firewall). By
itself, -p sets “legacy” ports TCP 7100, 7000, 7001, UDP 6000, 6001,
@@ -1082,6 +1098,10 @@ present, and synchronize with it). After <em>n</em> failures, the client
will be presumed to be offline, and the connection will be reset to
allow a new connection. The default value of <em>n</em> is 5; the value
<em>n</em> = 0 means “no limit” on timeouts.</p>
<p><strong>-nofreeze</strong> closes the video window after a reset due
to ntp timeout (default is to leave window open to allow a smoother
reconection to the same client). This option may be useful in fullscreen
mode.</p>
<p><strong>-nc</strong> maintains previous UxPlay &lt; 1.45 behavior
that does <strong>not close</strong> the video window when the the
client sends the “Stop Mirroring” signal. <em>This option is currently
@@ -1512,6 +1532,11 @@ an AppleTV6,2 with sourceVersion 380.20.1 (an AppleTV 4K 1st gen,
introduced 2017, running tvOS 12.2.1), so it does not seem to matter
what version UxPlay claims to be.</p>
<h1 id="changelog">Changelog</h1>
<p>1.69 2024-08-09 Internal improvements (e.g. in -nohold option,
identifying GStreamer videosink selected by autovideosink, finding X11
display) in anticipation of future HLS video support. New -nofreeze
option to not leave frozen video in place when a network connection is
reset. Fixes for GStreamer-1.24.x changes.</p>
<p>1.68 2023-12-31 New simpler (default) method for generating a
persistent public key from the server MAC address (which can now be set
with the -m option). (The previous method is still available with -key

View File

@@ -1,19 +1,21 @@
# UxPlay 1.68: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
# UxPlay 1.69: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
### Now developed at the GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where ALL user issues should be posted, and latest versions can be found).
### **Now developed at the GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where ALL user issues should be posted, and latest versions can be found).**
* _**NEW in v1.69**: minor changes for users: -nofreeze option to NOT leave frozen
video in place when a network failure occurs; internal changes/improvements
needed for planned future HLS video streaming support._
* **An experimental ("beta") version of UxPlay with support for HLS streaming of YouTube Videos from the YouTube app on an iOS client is now available at** https://github.com/FDH2/UxPlay/tree/video .
_See the [Wiki page](https://github.com/FDH2/UxPlay/wiki/experimental-version-of-UxPlay-with-support-for-HLS-video-streaming-(you-tube-movies)) for details._
* _**NEW in v1.68**: Volume-control improvements, plus improved support for Apple-style one-time "pin" codes introduced in 1.67: a
register of pin-registered clients can now optionally be maintained to check returning clients; a simpler method for generating
a persistent public key (based on the MAC address, which can be set in the UxPlay startup file) is now the default. (The OpenSSL
"pem-file" method introduced in 1.67 is still available with the "-key" option.)_
## Highlights:
* GPLv3, open source.
* Originally supported only AirPlay Mirror protocol, now has added support
for AirPlay Audio-only (Apple Lossless ALAC) streaming
from current iOS/iPadOS clients. **There is no support for Airplay2 video-streaming protocol, and none is planned.**
from current iOS/iPadOS clients. **There is no current support for Airplay HLS
video-streaming (e.g., YouTube video) but this is in development.**
* macOS computers (2011 or later, both Intel and "Apple Silicon" M1/M2
systems) can act either as AirPlay clients, or
as the server running UxPlay. Using AirPlay, UxPlay can
@@ -125,11 +127,12 @@ switch back by initiating a_ **Mirror** _mode connection; cover-art display stop
the Apple TV app cannot be watched using UxPlay's AirPlay Mirror mode (only the unprotected audio will be streamed, in AAC format),
but both video and audio content from DRM-free apps like "YouTube app" will be streamed by UxPlay in Mirror mode.**
* **As UxPlay does not support non-Mirror AirPlay2 video streaming (where the
* **As UxPlay does not currently support non-Mirror AirPlay video streaming (where the
client controls a web server on the AirPlay server that directly receives
content to avoid it being decoded and re-encoded by the client),
HLS content to avoid it being decoded and re-encoded by the client),
using the icon for AirPlay video in apps such as the YouTube app
will only send audio (in lossless ALAC format) without the accompanying video.**
will only send audio (in lossless ALAC format) without the accompanying
video (there are plans to support HLS video in future releases of UxPlay)**
### Possibility for using hardware-accelerated h264 video-decoding, if available.
@@ -210,9 +213,16 @@ Make sure that your distribution provides OpenSSL 1.1.1 or later, and
libplist 2.0 or later. (This means Debian 10 "Buster" based systems (e.g, Ubuntu 18.04) or newer;
on Debian 10 systems "libplist" is an older version, you need "libplist3".) If it does
not, you may need to build and install these from
source (see instructions at the end of this README). If you have a non-standard OpenSSL
source (see instructions at the end of this README).
If you have a non-standard OpenSSL
installation, you may need to set the environment variable OPENSSL_ROOT_DIR
(_e.g._ , "`export OPENSSL_ROOT_DIR=/usr/local/lib64`" if that is where it is installed).
Similarly, for non-standard (or multiple) GStreamer installations, set the
environment variable GSTREAMER_ROOT_DIR to the directory that contains the
".../gstreamer-1.0/" directory of the gstreamer installation that UxPlay should use
(if this is _e.g._ "~/my_gstreamer/lib/gstreamer-1.0/", set this location
with "`export GSTREAMER_ROOT_DIR=$HOME/my_gstreamer/lib`").
* Most users will use the GStreamer supplied by their distribution, but a few (in particular users
of Raspberry Pi OS Lite Legacy (Buster) on a Raspberry Pi model 4B who wish to stay on that
@@ -311,8 +321,9 @@ Values of `<plugin>` required are:
3. "**plugins-good**" (for v4l2 hardware h264 decoding)
4. "**plugins-bad**" (for h264 decoding).
Plugins that may also be needed include "**gl**" for OpenGL support (which may be useful, and should
be used with h264 decoding by the NVIDIA GPU), and "**x**" for
Plugins that may also be needed include "**gl**" for OpenGL support (this provides the "-vs glimagesink" videosink, which
can be very useful in many systems, and should always be used when using h264 decoding by a NVIDIA GPU), "**gtk3**" (which
provides the "-vs gtksink" videosink), and "**x**" for
X11 support, although these may already be installed; "**vaapi**"
is needed for hardware-accelerated h264 video decoding by Intel
or AMD graphics (but not for use with NVIDIA using proprietary drivers). If sound is
@@ -361,7 +372,8 @@ Since UxPlay-1.64, UxPlay can be started with options read from a configuration
directory ("~"), (3) ``~/.config/uxplayrc``. The format is one option per line, omitting the initial ``"-"`` of
the command-line option. Lines in the configuration file beginning with `"#"` are treated as comments and ignored.
**Run uxplay in a terminal window**. On some systems, you can toggle into and out of fullscreen mode
**Run uxplay in a terminal window**. On some systems, you can specify fullscreen mode with the `-fs` option, or
toggle into and out of fullscreen mode
with F11 or (held-down left Alt)+Enter keys. Use Ctrl-C (or close the window)
to terminate it when done. If the UxPlay server is not seen by the
iOS client's drop-down "Screen Mirroring" panel, check that your DNS-SD
@@ -415,9 +427,9 @@ delays the video on the client to match audio on the server, so leads to
a slight delay before a pause or track-change initiated on the client takes effect on the audio played by the server.
AirPlay volume-control attenuates volume (gain) by up to -30dB: the decibel range -30:0 can be rescaled from _Low_:0, or _Low_:_High_, using the
option `-db` ("-db _Low_ " or "-db _Low_:_High_ "), _Low_ must be negative. Rescaling is linear in decibels. The
option ```-taper``` provides a "tapered" AirPlay volume-control
profile some users may prefer.
option `-db` ("-db _Low_ " or "-db _Low_:_High_ "), _Low_ must be negative. Rescaling is linear in decibels.
Note that GStreamer's audio format will "clip" any audio gain above +20db, so keep *High* below that level. The
option ```-taper``` provides a "tapered" AirPlay volume-control profile some users may prefer.
The -vsync and -async options
also allow an optional positive (or negative) audio-delay adjustment in _milliseconds_ for fine-tuning : `-vsync 20.5`
@@ -493,9 +505,9 @@ See [Usage](#usage) for more run-time options.
Even with GPU video decoding, some frames may be dropped by the lower-power models to keep audio and video synchronized
using timestamps. In Legacy Raspberry Pi OS (Bullseye), raspi-config "Performance Options" allows specifying how much memory
to allocate to the GPU, but this setting appears to be absent in Bookworm (but it can still be set to e.g. 128GB by adding a line "gpu_mem=128" in /boot/config.txt).
A Pi Zero 2 W (which has 512GB memory) worked well when tested in 32 bit Bullseye or Bookworm Lite
with 128GB allocated to the GPU (default seems to be 64GB).
to allocate to the GPU, but this setting appears to be absent in Bookworm (but it can still be set to e.g. 128MB by adding a line "gpu_mem=128" in /boot/config.txt).
A Pi Zero 2 W (which has 512MB memory) worked well when tested in 32 bit Bullseye or Bookworm Lite
with 128MB allocated to the GPU (default seems to be 64MB).
The basic uxplay options for R Pi are ```uxplay [-vs <videosink>]```. The
choice `<videosink>` = ``glimagesink`` is sometimes useful.
@@ -633,7 +645,6 @@ After installing GStreamer, build and install uxplay: open a terminal and change
`pacman -S mingw-w64-x86_64-libplist mingw-w64-x86_64-gstreamer mingw-w64-x86_64-gst-plugins-base`
Note that libplist will be linked statically to the uxplay executable.
If you are trying a different Windows build system, MSVC versions of GStreamer
for Windows are available from the [official GStreamer site](https://gstreamer.freedesktop.org/download/),
but only the MinGW 64-bit build on MSYS2 has been tested.
@@ -687,9 +698,15 @@ default audio device is used.
If you wish to specify the videosink using the `-vs <videosink>` option, some choices for `<videosink>` are
`d3d11videosink`, ``d3dvideosink``, ```glimagesink```,
`gtksink`. With Direct3D 11.0 or greater, you can get the ability to toggle into and out of fullscreen mode using the Alt-Enter key combination with
option `-vs "d3d11videosink fullscreen-toggle-mode=alt-enter"`. For convenience, this option will be added if just ``-vs d3d11videosink`` (by itself) is used.
(You may wish to add "``vs d3d11videosink``" (no initial "`-`") to the UxPlay startup options file; see "man uxplay" or "uxplay -h".)
`gtksink`.
* With Direct3D 11.0 or greater, you can either always be in fullscreen mode using
option `-vs "d3d11videosink fullscreen-toggle-mode=property fullscreen=true"`, or
get the ability to toggle into and out of fullscreen mode using the Alt-Enter key combination with
option `-vs "d3d11videosink fullscreen-toggle-mode=alt-enter"`.
For convenience, these options will be added if just ``-vs d3d11videosink`` with or without the fullscreen
option "-fs" is used. _(Windows users may wish to add "``vs d3d11videosink``" (no initial "`-`") to the
UxPlay startup options file; see "man uxplay" or "uxplay -h".)_
The executable uxplay.exe can also be run without the MSYS2 environment, in
the Windows Terminal, with `C:\msys64\mingw64\bin\uxplay`.
@@ -775,7 +792,7 @@ using UxPlay as a second monitor for a mac computer, or monitoring a webcam; wit
Recommendation: **don't use this option** unless there is some special
reason to use it.
**-fs** uses fullscreen mode, but only works with X11, Wayland or VAAPI.
**-fs** uses fullscreen mode, but only works with X11, Wayland, VAAPI, and D3D11 (Windows).
**-p** allows you to select the network ports used by UxPlay (these need
to be opened if the server is behind a firewall). By itself, -p sets
@@ -861,6 +878,9 @@ which will not work if a firewall is running.
_n_ failures, the client will be presumed to be offline, and the connection will be reset to allow a new
connection. The default value of _n_ is 5; the value _n_ = 0 means "no limit" on timeouts.
**-nofreeze** closes the video window after a reset due to ntp timeout (default is to leave window
open to allow a smoother reconection to the same client). This option may be useful in fullscreen mode.
**-nc** maintains previous UxPlay < 1.45 behavior that does **not close** the video window when the the client
sends the "Stop Mirroring" signal. _This option is currently used by default in macOS,
as the window created in macOS by GStreamer does not terminate correctly (it causes a segfault)
@@ -1191,6 +1211,11 @@ tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be.
# Changelog
1.69 2024-08-09 Internal improvements (e.g. in -nohold option, identifying GStreamer videosink
selected by autovideosink, finding X11 display) in anticipation of future HLS video support.
New -nofreeze option to not leave frozen video in place when a network connection is reset.
Fixes for GStreamer-1.24.x changes.
1.68 2023-12-31 New simpler (default) method for generating a persistent public key from the server MAC
address (which can now be set with the -m option). (The previous method is still available
with -key option). New option -reg to maintain a register of pin-authenticated clients. Corrected

View File

@@ -1,23 +1,20 @@
# UxPlay 1.68: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
# UxPlay 1.69: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
### Now developed at the GitHub site <https://github.com/FDH2/UxPlay> (where ALL user issues should be posted, and latest versions can be found).
### **Now developed at the GitHub site <https://github.com/FDH2/UxPlay> (where ALL user issues should be posted, and latest versions can be found).**
- ***NEW in v1.68**: Volume-control improvements, plus improved
support for Apple-style one-time "pin" codes introduced in 1.67: a
register of pin-registered clients can now optionally be maintained
to check returning clients; a simpler method for generating a
persistent public key (based on the MAC address, which can be set in
the UxPlay startup file) is now the default. (The OpenSSL "pem-file"
method introduced in 1.67 is still available with the "-key"
option.)*
- ***NEW in v1.69**: minor changes for users: -nofreeze option to NOT
leave frozen video in place when a network failure occurs; internal
changes/improvements needed for planned future HLS video streaming
support.*
## Highlights:
- GPLv3, open source.
- Originally supported only AirPlay Mirror protocol, now has added
support for AirPlay Audio-only (Apple Lossless ALAC) streaming from
current iOS/iPadOS clients. **There is no support for Airplay2
video-streaming protocol, and none is planned.**
current iOS/iPadOS clients. **There is no current support for
Airplay HLS video-streaming (e.g., YouTube video) but this is in
development.**
- macOS computers (2011 or later, both Intel and "Apple Silicon" M1/M2
systems) can act either as AirPlay clients, or as the server running
UxPlay. Using AirPlay, UxPlay can emulate a second display for macOS
@@ -158,12 +155,13 @@ stops/restarts as you leave/re-enter* **Audio** *mode.*
and audio content from DRM-free apps like "YouTube app" will be
streamed by UxPlay in Mirror mode.**
- **As UxPlay does not support non-Mirror AirPlay2 video streaming
(where the client controls a web server on the AirPlay server that
directly receives content to avoid it being decoded and re-encoded
by the client), using the icon for AirPlay video in apps such as the
YouTube app will only send audio (in lossless ALAC format) without
the accompanying video.**
- **As UxPlay does not currently support non-Mirror AirPlay video
streaming (where the client controls a web server on the AirPlay
server that directly receives HLS content to avoid it being decoded
and re-encoded by the client), using the icon for AirPlay video in
apps such as the YouTube app will only send audio (in lossless ALAC
format) without the accompanying video (there are plans to support
HLS video in future releases of UxPlay)**
### Possibility for using hardware-accelerated h264 video-decoding, if available.
@@ -257,10 +255,17 @@ libplist 2.0 or later. (This means Debian 10 "Buster" based systems
(e.g, Ubuntu 18.04) or newer; on Debian 10 systems "libplist" is an
older version, you need "libplist3".) If it does not, you may need to
build and install these from source (see instructions at the end of this
README). If you have a non-standard OpenSSL installation, you may need
to set the environment variable OPENSSL_ROOT_DIR (*e.g.* ,
README).
If you have a non-standard OpenSSL installation, you may need to set the
environment variable OPENSSL_ROOT_DIR (*e.g.* ,
"`export OPENSSL_ROOT_DIR=/usr/local/lib64`" if that is where it is
installed).
installed). Similarly, for non-standard (or multiple) GStreamer
installations, set the environment variable GSTREAMER_ROOT_DIR to the
directory that contains the ".../gstreamer-1.0/" directory of the
gstreamer installation that UxPlay should use (if this is *e.g.*
"\~/my_gstreamer/lib/gstreamer-1.0/", set this location with
"`export GSTREAMER_ROOT_DIR=$HOME/my_gstreamer/lib`").
- Most users will use the GStreamer supplied by their distribution,
but a few (in particular users of Raspberry Pi OS Lite Legacy
@@ -378,13 +383,15 @@ are:
4. "**plugins-bad**" (for h264 decoding).
Plugins that may also be needed include "**gl**" for OpenGL support
(which may be useful, and should be used with h264 decoding by the
NVIDIA GPU), and "**x**" for X11 support, although these may already be
installed; "**vaapi**" is needed for hardware-accelerated h264 video
decoding by Intel or AMD graphics (but not for use with NVIDIA using
proprietary drivers). If sound is not working,
"**alsa**"","**pulseaudio**", or "**pipewire**" plugins may need to be
installed, depending on how your audio is set up.
(this provides the "-vs glimagesink" videosink, which can be very useful
in many systems, and should always be used when using h264 decoding by a
NVIDIA GPU), "**gtk3**" (which provides the "-vs gtksink" videosink),
and "**x**" for X11 support, although these may already be installed;
"**vaapi**" is needed for hardware-accelerated h264 video decoding by
Intel or AMD graphics (but not for use with NVIDIA using proprietary
drivers). If sound is not working, "**alsa**"","**pulseaudio**", or
"**pipewire**" plugins may need to be installed, depending on how your
audio is set up.
- Also install "**gstreamer1.0-tools**" to get the utility
gst-inspect-1.0 for examining the GStreamer installation.
@@ -436,14 +443,14 @@ one option per line, omitting the initial `"-"` of the command-line
option. Lines in the configuration file beginning with `"#"` are treated
as comments and ignored.
**Run uxplay in a terminal window**. On some systems, you can toggle
into and out of fullscreen mode with F11 or (held-down left Alt)+Enter
keys. Use Ctrl-C (or close the window) to terminate it when done. If the
UxPlay server is not seen by the iOS client's drop-down "Screen
Mirroring" panel, check that your DNS-SD server (usually avahi-daemon)
is running: do this in a terminal window with
`systemctl status avahi-daemon`. If this shows the avahi-daemon is not
running, control it with
**Run uxplay in a terminal window**. On some systems, you can specify
fullscreen mode with the `-fs` option, or toggle into and out of
fullscreen mode with F11 or (held-down left Alt)+Enter keys. Use Ctrl-C
(or close the window) to terminate it when done. If the UxPlay server is
not seen by the iOS client's drop-down "Screen Mirroring" panel, check
that your DNS-SD server (usually avahi-daemon) is running: do this in a
terminal window with `systemctl status avahi-daemon`. If this shows the
avahi-daemon is not running, control it with
`sudo systemctl [start,stop,enable,disable] avahi-daemon` (on
non-systemd systems, such as \*BSD, use
`sudo service avahi-daemon [status, start, stop, restart, ...]`). If
@@ -481,9 +488,9 @@ below for help with this or other problems.
with UxPlay-1.64, the other method (GStreamer's "*sync=true*" mode),
which uses timestamps in the audio and video streams sent by the
client, is the new default**. On low-decoding-power UxPlay hosts
(such as Raspberry Pi 3 models) this will drop video frames that
cannot be decoded in time to play with the audio, making the video
jerky, but still synchronized.
(such as Raspberry Pi Zero W or 3 B+ models) this will drop video
frames that cannot be decoded in time to play with the audio, making
the video jerky, but still synchronized.
The older method which does not drop late video frames worked well on
more powerful systems, and is still available with the UxPlay option
@@ -506,10 +513,12 @@ helped to prevent this previously when timestamps were not being used.)
takes effect on the audio played by the server.
AirPlay volume-control attenuates volume (gain) by up to -30dB: the
range -30dB:0dB can be rescaled from *Low*:0, or *Low*:*High*, using the
option `-db` ("-db *Low*" or "-db *Low*:*High*"), *Low* must be
negative. Rescaling is linear in decibels. The option `-taper` provides
a "tapered" AirPlay volume-control profile some users may prefer.
decibel range -30:0 can be rescaled from *Low*:0, or *Low*:*High*, using
the option `-db` ("-db *Low*" or "-db *Low*:*High*"), *Low* must be
negative. Rescaling is linear in decibels. Note that GStreamer's audio
format will "clip" any audio gain above +20db, so keep *High* below that
level. The option `-taper` provides a "tapered" AirPlay volume-control
profile some users may prefer.
The -vsync and -async options also allow an optional positive (or
negative) audio-delay adjustment in *milliseconds* for fine-tuning :
@@ -615,10 +624,10 @@ lower-power models to keep audio and video synchronized using
timestamps. In Legacy Raspberry Pi OS (Bullseye), raspi-config
"Performance Options" allows specifying how much memory to allocate to
the GPU, but this setting appears to be absent in Bookworm (but it can
still be set to e.g. 128GB by adding a line "gpu_mem=128" in
/boot/config.txt). A Pi Zero 2 W (which has 512GB memory) worked well
when tested in 32 bit Bullseye or Bookworm Lite with 128GB allocated to
the GPU (default seems to be 64GB).
still be set to e.g. 128MB by adding a line "gpu_mem=128" in
/boot/config.txt). A Pi Zero 2 W (which has 512MB memory) worked well
when tested in 32 bit Bullseye or Bookworm Lite with 128MB allocated to
the GPU (default seems to be 64MB).
The basic uxplay options for R Pi are `uxplay [-vs <videosink>]`. The
choice `<videosink>` = `glimagesink` is sometimes useful. With the
@@ -751,7 +760,7 @@ downloads, "UxPlay" for "git clone" downloads) and build/install with
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; the
option "-vs osxvideosink force-aspect-ratio=true" can be used to
option `-vs "osxvideosink force-aspect-ratio=true"` can be used to
make the window have the correct aspect ratio when it first opens.
## Building UxPlay on Microsoft Windows, using MSYS2 with the MinGW-64 compiler.
@@ -794,11 +803,10 @@ downloads, "UxPlay" for "git clone" downloads) and build/install with
`pacman -S mingw-w64-x86_64-libplist mingw-w64-x86_64-gstreamer mingw-w64-x86_64-gst-plugins-base`
Note that libplist will be linked statically to the uxplay
executable. If you are trying a different Windows build system, MSVC
versions of GStreamer for Windows are available from the [official
GStreamer site](https://gstreamer.freedesktop.org/download/), but
only the MinGW 64-bit build on MSYS2 has been tested.
If you are trying a different Windows build system, MSVC versions of
GStreamer for Windows are available from the [official GStreamer
site](https://gstreamer.freedesktop.org/download/), but only the
MinGW 64-bit build on MSYS2 has been tested.
5. cd to the UxPlay source directory, then "`mkdir build`" and
"`cd build`". The build process assumes that the Bonjour SDK is
@@ -860,14 +868,19 @@ like `\{0.0.0.00000000\}.\{98e35b2b-8eba-412e-b840-fd2c2492cf44\}`. If
If you wish to specify the videosink using the `-vs <videosink>` option,
some choices for `<videosink>` are `d3d11videosink`, `d3dvideosink`,
`glimagesink`, `gtksink`. With Direct3D 11.0 or greater, you can get the
ability to toggle into and out of fullscreen mode using the Alt-Enter
key combination with option
`-vs "d3d11videosink fullscreen-toggle-mode=alt-enter"`. For
convenience, this option will be added if just `-vs d3d11videosink` (by
itself) is used. (You may wish to add "`vs d3d11videosink`" (no initial
"`-`") to the UxPlay startup options file; see "man uxplay" or "uxplay
-h".)
`glimagesink`, `gtksink`.
- With Direct3D 11.0 or greater, you can either always be in
fullscreen mode using option
`-vs "d3d11videosink fullscreen-toggle-mode=property fullscreen=true"`,
or get the ability to toggle into and out of fullscreen mode using
the Alt-Enter key combination with option
`-vs "d3d11videosink fullscreen-toggle-mode=alt-enter"`. For
convenience, these options will be added if just
`-vs d3d11videosink` with or without the fullscreen option "-fs" is
used. *(Windows users may wish to add "`vs d3d11videosink`" (no
initial "`-`") to the UxPlay startup options file; see "man uxplay"
or "uxplay -h".)*
The executable uxplay.exe can also be run without the MSYS2 environment,
in the Windows Terminal, with `C:\msys64\mingw64\bin\uxplay`.
@@ -987,7 +1000,8 @@ display that overscans, and is not displayed by gstreamer).
Recommendation: **don't use this option** unless there is some special
reason to use it.
**-fs** uses fullscreen mode, but only works with X11, Wayland or VAAPI.
**-fs** uses fullscreen mode, but only works with X11, Wayland, VAAPI,
and D3D11 (Windows).
**-p** allows you to select the network ports used by UxPlay (these need
to be opened if the server is behind a firewall). By itself, -p sets
@@ -1101,6 +1115,10 @@ it). After *n* failures, the client will be presumed to be offline, and
the connection will be reset to allow a new connection. The default
value of *n* is 5; the value *n* = 0 means "no limit" on timeouts.
**-nofreeze** closes the video window after a reset due to ntp timeout
(default is to leave window open to allow a smoother reconection to the
same client). This option may be useful in fullscreen mode.
**-nc** maintains previous UxPlay \< 1.45 behavior that does **not
close** the video window when the the client sends the "Stop Mirroring"
signal. *This option is currently used by default in macOS, as the
@@ -1554,6 +1572,12 @@ what version UxPlay claims to be.
# Changelog
1.69 2024-08-09 Internal improvements (e.g. in -nohold option,
identifying GStreamer videosink selected by autovideosink, finding X11
display) in anticipation of future HLS video support. New -nofreeze
option to not leave frozen video in place when a network connection is
reset. Fixes for GStreamer-1.24.x changes.
1.68 2023-12-31 New simpler (default) method for generating a persistent
public key from the server MAC address (which can now be set with the -m
option). (The previous method is still available with -key option). New

View File

@@ -80,17 +80,19 @@ else()
endif()
# libplist
if( APPLE OR WIN32 )
pkg_search_module(PLIST REQUIRED libplist-2.0)
if ( PLIST_FOUND )
message( STATUS "found libplist-${PLIST_VERSION}" )
endif()
if( APPLE )
# use static linking
pkg_search_module( PLIST REQUIRED libplist-2.0 )
find_library( LIBPLIST libplist-2.0.a REQUIRED )
message( STATUS "(Static linking) LIBPLIST " ${LIBPLIST} )
target_link_libraries ( airplay ${LIBPLIST} )
elseif( WIN32)
find_library( LIBPLIST ${PLIST_LIBRARIES} PATH ${PLIST_LIBDIR} )
target_link_libraries ( airplay ${LIBPLIST} )
else ()
pkg_search_module(PLIST libplist>=2.0)
if(NOT PLIST_FOUND)
pkg_search_module(PLIST REQUIRED libplist-2.0)
endif()
find_library( LIBPLIST ${PLIST_LIBRARIES} PATH ${PLIST_LIBDIR} )
target_link_libraries ( airplay PUBLIC ${LIBPLIST} )
endif()

View File

@@ -29,6 +29,7 @@ struct http_request_s {
const char *method;
char *url;
char protocol[9];
char **headers;
int headers_size;
@@ -51,6 +52,9 @@ on_url(llhttp_t *parser, const char *at, size_t length)
request->url[urllen] = '\0';
strncat(request->url, at, length);
strncpy(request->protocol, at + length + 1, 8);
return 0;
}
@@ -183,8 +187,11 @@ http_request_add_data(http_request_t *request, const char *data, int datalen)
assert(request);
ret = llhttp_execute(&request->parser,
data, datalen);
ret = llhttp_execute(&request->parser, data, datalen);
/* support for "Upgrade" to reverse http ("PTTH/1.0") protocol */
llhttp_resume_after_upgrade(&request->parser);
return ret;
}
@@ -230,6 +237,13 @@ http_request_get_url(http_request_t *request)
return request->url;
}
const char *
http_request_get_protocol(http_request_t *request)
{
assert(request);
return request->protocol;
}
const char *
http_request_get_header(http_request_t *request, const char *name)
{

View File

@@ -28,6 +28,7 @@ const char *http_request_get_error_name(http_request_t *request);
const char *http_request_get_error_description(http_request_t *request);
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_protocol(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);

View File

@@ -51,10 +51,29 @@ http_response_add_data(http_response_t *response, const char *data, int datalen)
response->data_length += datalen;
}
http_response_t *
http_response_init(const char *protocol, int code, const char *message)
http_response_create()
{
http_response_t *response;
http_response_t *response = (http_response_t *) calloc(1, sizeof(http_response_t));
if (!response) {
return NULL;
}
/* Allocate response data */
response->data_size = 1024;
response->data = (char *) malloc(response->data_size);
if (!response->data) {
free(response);
return NULL;
}
return response;
}
void
http_response_init(http_response_t *response, const char *protocol, int code, const char *message)
{
assert(response);
response->data_length = 0; /* can be used to reinitialize a previously-initialized response */
char codestr[4];
assert(code >= 100 && code < 1000);
@@ -63,19 +82,6 @@ http_response_init(const char *protocol, int code, const char *message)
memset(codestr, 0, sizeof(codestr));
snprintf(codestr, sizeof(codestr), "%u", code);
response = calloc(1, sizeof(http_response_t));
if (!response) {
return NULL;
}
/* Allocate response data */
response->data_size = 1024;
response->data = malloc(response->data_size);
if (!response->data) {
free(response);
return NULL;
}
/* Add first line of response to the data array */
http_response_add_data(response, protocol, strlen(protocol));
http_response_add_data(response, " ", 1);
@@ -83,8 +89,6 @@ http_response_init(const char *protocol, int code, const char *message)
http_response_add_data(response, " ", 1);
http_response_add_data(response, message, strlen(message));
http_response_add_data(response, "\r\n", 2);
return response;
}
void

View File

@@ -10,6 +10,9 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*==============================================================
* modified fduncanh 2024
*/
#ifndef HTTP_RESPONSE_H
@@ -17,7 +20,8 @@
typedef struct http_response_s http_response_t;
http_response_t *http_response_init(const char *protocol, int code, const char *message);
http_response_t *http_response_create();
void http_response_init(http_response_t *response, const char *protocol, int code, const char *message);
void http_response_add_header(http_response_t *response, const char *name, const char *value);
void http_response_finish(http_response_t *response, const char *data, int datalen);

View File

@@ -19,6 +19,7 @@
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <stdbool.h>
#include "httpd.h"
#include "netutils.h"
@@ -31,6 +32,7 @@ struct http_connection_s {
int socket_fd;
void *user_data;
connection_type_t type;
http_request_t *request;
};
typedef struct http_connection_s http_connection_t;
@@ -42,6 +44,7 @@ struct httpd_s {
int max_connections;
int open_connections;
http_connection_t *connections;
char nohold;
/* These variables only edited mutex locked */
int running;
@@ -54,14 +57,43 @@ struct httpd_s {
int server_fd6;
};
int
httpd_set_connection_type (httpd_t *httpd, void *user_data, connection_type_t type) {
for (int i = 0; i < httpd->max_connections; i++) {
http_connection_t *connection = &httpd->connections[i];
if (!connection->connected) {
continue;
}
if (connection->user_data == user_data) {
connection->type = type;
return i;
}
}
return -1;
}
int
httpd_count_connection_type (httpd_t *httpd, connection_type_t type) {
int count = 0;
for (int i = 0; i < httpd->max_connections; i++) {
http_connection_t *connection = &httpd->connections[i];
if (!connection->connected) {
continue;
}
if (connection->type == type) {
count++;
}
}
return count;
}
#define MAX_CONNECTIONS 12 /* value used in AppleTV 3*/
httpd_t *
httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int max_connections)
httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int nohold)
{
httpd_t *httpd;
assert(logger);
assert(callbacks);
assert(max_connections > 0);
/* Allocate the httpd_t structure */
httpd = calloc(1, sizeof(httpd_t));
@@ -69,8 +101,10 @@ httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int max_connections)
return NULL;
}
httpd->max_connections = max_connections;
httpd->connections = calloc(max_connections, sizeof(http_connection_t));
httpd->nohold = (nohold ? 1 : 0);
httpd->max_connections = MAX_CONNECTIONS;
httpd->connections = calloc(httpd->max_connections, sizeof(http_connection_t));
if (!httpd->connections) {
free(httpd);
return NULL;
@@ -111,11 +145,14 @@ httpd_remove_connection(httpd_t *httpd, http_connection_t *connection)
shutdown(connection->socket_fd, SHUT_WR);
closesocket(connection->socket_fd);
connection->connected = 0;
connection->user_data = NULL;
connection->type = CONNECTION_TYPE_UNKNOWN;
httpd->open_connections--;
}
static int
httpd_add_connection(httpd_t *httpd, int fd, unsigned char *local, int local_len, unsigned char *remote, int remote_len)
httpd_add_connection(httpd_t *httpd, int fd, unsigned char *local, int local_len, unsigned char *remote,
int remote_len, unsigned int zone_id)
{
void *user_data;
int i;
@@ -130,8 +167,7 @@ httpd_add_connection(httpd_t *httpd, int fd, unsigned char *local, int local_len
logger_log(httpd->logger, LOGGER_INFO, "Max connections reached");
return -1;
}
user_data = httpd->callbacks.conn_init(httpd->callbacks.opaque, local, local_len, remote, remote_len);
user_data = httpd->callbacks.conn_init(httpd->callbacks.opaque, local, local_len, remote, remote_len, zone_id);
if (!user_data) {
logger_log(httpd->logger, LOGGER_ERR, "Error initializing HTTP request handler");
return -1;
@@ -141,6 +177,7 @@ httpd_add_connection(httpd_t *httpd, int fd, unsigned char *local, int local_len
httpd->connections[i].socket_fd = fd;
httpd->connections[i].connected = 1;
httpd->connections[i].user_data = user_data;
httpd->connections[i].type = CONNECTION_TYPE_UNKNOWN; //should not be necessary ...
return 0;
}
@@ -152,6 +189,7 @@ httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6)
struct sockaddr_storage local_saddr;
socklen_t local_saddrlen;
unsigned char *local, *remote;
unsigned int local_zone_id, remote_zone_id;
int local_len, remote_len;
int ret, fd;
@@ -172,25 +210,11 @@ httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6)
logger_log(httpd->logger, LOGGER_INFO, "Accepted %s client on socket %d",
(is_ipv6 ? "IPv6" : "IPv4"), fd);
local = netutils_get_address(&local_saddr, &local_len);
remote = netutils_get_address(&remote_saddr, &remote_len);
#ifdef NOHOLD
/* remove existing connections to make way for new connections:
* this will only occur if max_connections > 2 */
if (httpd->open_connections >= 2) {
logger_log(httpd->logger, LOGGER_INFO, "Destroying current connections to allow connection by new client");
for (int i = 0; i<httpd->max_connections; i++) {
http_connection_t *connection = &httpd->connections[i];
if (!connection->connected) {
continue;
}
httpd_remove_connection(httpd, connection);
}
}
#endif
local = netutils_get_address(&local_saddr, &local_len, &local_zone_id);
remote = netutils_get_address(&remote_saddr, &remote_len, &remote_zone_id);
assert (local_zone_id == remote_zone_id);
ret = httpd_add_connection(httpd, fd, local, local_len, remote, remote_len);
ret = httpd_add_connection(httpd, fd, local, local_len, remote, remote_len, local_zone_id);
if (ret == -1) {
shutdown(fd, SHUT_RDWR);
closesocket(fd);
@@ -199,13 +223,30 @@ httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6)
return 1;
}
bool
httpd_nohold(httpd_t *httpd) {
return (httpd->nohold ? true: false);
}
void
httpd_remove_known_connections(httpd_t *httpd) {
for (int i = 0; i < httpd->max_connections; i++) {
http_connection_t *connection = &httpd->connections[i];
if (!connection->connected || connection->type == CONNECTION_TYPE_UNKNOWN) {
continue;
}
httpd_remove_connection(httpd, connection);
}
}
static THREAD_RETVAL
httpd_thread(void *arg)
{
httpd_t *httpd = arg;
char buffer[1024];
int i;
bool logger_debug = (logger_get_level(httpd->logger) >= LOGGER_DEBUG);
assert(httpd);
while (1) {
@@ -298,7 +339,7 @@ httpd_thread(void *arg)
assert(connection->request);
}
logger_log(httpd->logger, LOGGER_DEBUG, "httpd receiving on socket %d", connection->socket_fd);
logger_log(httpd->logger, LOGGER_DEBUG, "httpd receiving on socket %d, connection %d", connection->socket_fd, i);
ret = recv(connection->socket_fd, buffer, sizeof(buffer), 0);
if (ret == 0) {
logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d", connection->socket_fd);
@@ -318,6 +359,13 @@ httpd_thread(void *arg)
if (http_request_is_complete(connection->request)) {
http_response_t *response = NULL;
// Callback the received data to raop
if (logger_debug) {
const char *method = http_request_get_method(connection->request);
const char *url = http_request_get_url(connection->request);
const char *protocol = http_request_get_protocol(connection->request);
logger_log(httpd->logger, LOGGER_INFO, "httpd request received on socket %d, connection %d, "
"method = %s, url = %s, protocol = %s", connection->socket_fd, i, method, url, protocol);
}
httpd->callbacks.conn_request(connection->user_data, connection->request, &response);
http_request_destroy(connection->request);
connection->request = NULL;
@@ -409,11 +457,11 @@ httpd_start(httpd_t *httpd, unsigned short *port)
MUTEX_UNLOCK(httpd->run_mutex);
return -1;
}
httpd->server_fd6 = -1;/*= netutils_init_socket(port, 1, 0);
if (httpd->server_fd6 == -1) {
logger_log(httpd->logger, LOGGER_WARNING, "Error initialising IPv6 socket %d", SOCKET_GET_ERROR());
logger_log(httpd->logger, LOGGER_WARNING, "Continuing without IPv6 support");
}*/
httpd->server_fd6 = netutils_init_socket(port, 1, 0);
if (httpd->server_fd6 == -1) {
logger_log(httpd->logger, LOGGER_WARNING, "Error initialising IPv6 socket %d", SOCKET_GET_ERROR());
logger_log(httpd->logger, LOGGER_WARNING, "Continuing without IPv6 support");
}
if (httpd->server_fd4 != -1 && listen(httpd->server_fd4, backlog) == -1) {
logger_log(httpd->logger, LOGGER_ERR, "Error listening to IPv4 socket");
@@ -473,4 +521,3 @@ httpd_stop(httpd_t *httpd)
httpd->joined = 1;
MUTEX_UNLOCK(httpd->run_mutex);
}

View File

@@ -21,16 +21,26 @@
typedef struct httpd_s httpd_t;
typedef enum connectype_type_e {
CONNECTION_TYPE_UNKNOWN,
CONNECTION_TYPE_RAOP
} connection_type_t;
struct httpd_callbacks_s {
void* opaque;
void* (*conn_init)(void *opaque, unsigned char *local, int locallen, unsigned char *remote, int remotelen);
void (*conn_request)(void *ptr, http_request_t *request, http_response_t **response);
void (*conn_destroy)(void *ptr);
void* opaque;
void* (*conn_init)(void *opaque, unsigned char *local, int locallen, unsigned char *remote,
int remotelen, unsigned int zone_id);
void (*conn_request)(void *ptr, http_request_t *request, http_response_t **response);
void (*conn_destroy)(void *ptr);
};
typedef struct httpd_callbacks_s httpd_callbacks_t;
bool httpd_nohold(httpd_t *httpd);
void httpd_remove_known_connections(httpd_t *httpd);
int httpd_set_connection_type (httpd_t *http, void *user_data, connection_type_t type);
int httpd_count_connection_type (httpd_t *http, connection_type_t type);
httpd_t *httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int max_connections);
httpd_t *httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int nohold);
int httpd_is_running(httpd_t *httpd);

View File

@@ -53,17 +53,17 @@ netutils_cleanup()
}
unsigned char *
netutils_get_address(void *sockaddr, int *length)
netutils_get_address(void *sockaddr, int *length, unsigned int *zone_id)
{
unsigned char ipv4_prefix[] = { 0,0,0,0,0,0,0,0,0,0,255,255 };
struct sockaddr *address = sockaddr;
assert(address);
assert(length);
assert(zone_id);
if (address->sa_family == AF_INET) {
struct sockaddr_in *sin;
*zone_id = 0;
sin = (struct sockaddr_in *)address;
*length = sizeof(sin->sin_addr.s_addr);
return (unsigned char *)&sin->sin_addr.s_addr;
@@ -73,9 +73,11 @@ netutils_get_address(void *sockaddr, int *length)
sin6 = (struct sockaddr_in6 *)address;
if (!memcmp(sin6->sin6_addr.s6_addr, ipv4_prefix, 12)) {
/* Actually an embedded IPv4 address */
*zone_id = 0;
*length = sizeof(sin6->sin6_addr.s6_addr)-12;
return (sin6->sin6_addr.s6_addr+12);
}
*zone_id = (unsigned int) sin6->sin6_scope_id;
*length = sizeof(sin6->sin6_addr.s6_addr);
return sin6->sin6_addr.s6_addr;
}

View File

@@ -19,7 +19,7 @@ int netutils_init();
void netutils_cleanup();
int netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp);
unsigned char *netutils_get_address(void *sockaddr, int *length);
unsigned char *netutils_get_address(void *sockaddr, int *length, unsigned int *zone_id);
int netutils_parse_address(int family, const char *src, void *dst, int dstlen);
#endif

View File

@@ -88,6 +88,10 @@ struct raop_conn_s {
unsigned char *remote;
int remotelen;
unsigned int zone_id;
connection_type_t connection_type;
bool have_active_remote;
};
typedef struct raop_conn_s raop_conn_t;
@@ -95,10 +99,10 @@ typedef struct raop_conn_s raop_conn_t;
#include "raop_handlers.h"
static void *
conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remote, int remotelen) {
conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remote, int remotelen, unsigned int zone_id) {
raop_t *raop = opaque;
raop_conn_t *conn;
char ip_address[40];
assert(raop);
conn = calloc(1, sizeof(raop_conn_t));
@@ -122,26 +126,12 @@ conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remot
return NULL;
}
if (locallen == 4) {
logger_log(conn->raop->logger, LOGGER_INFO,
"Local: %d.%d.%d.%d",
local[0], local[1], local[2], local[3]);
} else if (locallen == 16) {
logger_log(conn->raop->logger, LOGGER_INFO,
"Local: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
local[0], local[1], local[2], local[3], local[4], local[5], local[6], local[7],
local[8], local[9], local[10], local[11], local[12], local[13], local[14], local[15]);
}
if (remotelen == 4) {
logger_log(conn->raop->logger, LOGGER_INFO,
"Remote: %d.%d.%d.%d",
remote[0], remote[1], remote[2], remote[3]);
} else if (remotelen == 16) {
logger_log(conn->raop->logger, LOGGER_INFO,
"Remote: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
remote[0], remote[1], remote[2], remote[3], remote[4], remote[5], remote[6], remote[7],
remote[8], remote[9], remote[10], remote[11], remote[12], remote[13], remote[14], remote[15]);
}
utils_ipaddress_to_string(locallen, local, zone_id, ip_address, (int) sizeof(ip_address));
logger_log(conn->raop->logger, LOGGER_INFO, "Local : %s", ip_address);
utils_ipaddress_to_string(remotelen, remote, zone_id, ip_address, (int) sizeof(ip_address));
logger_log(conn->raop->logger, LOGGER_INFO, "Remote: %s", ip_address);
conn->local = malloc(locallen);
assert(conn->local);
@@ -151,8 +141,13 @@ conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remot
assert(conn->remote);
memcpy(conn->remote, remote, remotelen);
conn->zone_id = zone_id;
conn->locallen = locallen;
conn->remotelen = remotelen;
conn->connection_type = CONNECTION_TYPE_UNKNOWN;
conn->have_active_remote = false;
if (raop->callbacks.conn_init) {
@@ -164,18 +159,40 @@ conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remot
static void
conn_request(void *ptr, http_request_t *request, http_response_t **response) {
raop_conn_t *conn = ptr;
const char *method;
const char *url;
const char *cseq;
char *response_data = NULL;
int response_datalen = 0;
logger_log(conn->raop->logger, LOGGER_DEBUG, "conn_request");
raop_conn_t *conn = ptr;
bool logger_debug = (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG);
method = http_request_get_method(request);
url = http_request_get_url(request);
cseq = http_request_get_header(request, "CSeq");
const char *method = http_request_get_method(request);
const char *url = http_request_get_url(request);
const char *protocol = http_request_get_protocol(request);
const char *cseq = http_request_get_header(request, "CSeq");
if (conn->connection_type == CONNECTION_TYPE_UNKNOWN) {
if (httpd_count_connection_type(conn->raop->httpd, CONNECTION_TYPE_RAOP)) {
char ipaddr[40];
utils_ipaddress_to_string(conn->remotelen, conn->remote, conn->zone_id, ipaddr, (int) (sizeof(ipaddr)));
if (httpd_nohold(conn->raop->httpd)) {
logger_log(conn->raop->logger, LOGGER_INFO, "\"nohold\" feature: switch to new connection request from %s", ipaddr);
if (conn->raop->callbacks.video_reset) {
printf("**************************video_reset*************************\n");
conn->raop->callbacks.video_reset(conn->raop->callbacks.cls);
}
httpd_remove_known_connections(conn->raop->httpd);
} else {
logger_log(conn->raop->logger, LOGGER_WARNING, "rejecting new connection request from %s", ipaddr);
*response = http_response_create();
http_response_init(*response, protocol, 409, "Conflict: Server is connected to another client");
goto finish;
}
}
httpd_set_connection_type(conn->raop->httpd, ptr, CONNECTION_TYPE_RAOP);
conn->connection_type = CONNECTION_TYPE_RAOP;
}
if (!conn->have_active_remote) {
const char *active_remote = http_request_get_header(request, "Active-Remote");
if (active_remote) {
@@ -187,16 +204,22 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
}
}
if (!method || !cseq) {
if (!method) {
return;
}
logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s %s RTSP/1.0", method, url);
/* this rejects unsupported messages from _airplay._tcp for video streaming protocol*/
if (!cseq) {
return;
}
logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s %s %s", method, url, protocol);
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);
bool data_is_text = (strstr(header_str,"text/") != NULL);
free(header_str);
int request_datalen;
const char *request_data = http_request_get_data(request, &request_datalen);
@@ -224,11 +247,11 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
}
}
*response = http_response_init("RTSP/1.0", 200, "OK");
*response = http_response_create();
http_response_init(*response, protocol, 200, "OK");
http_response_add_header(*response, "CSeq", cseq);
//http_response_add_header(*response, "Apple-Jack-Status", "connected; type=analog");
http_response_add_header(*response, "Server", "AirTunes/"GLOBAL_VERSION);
logger_log(conn->raop->logger, LOGGER_DEBUG, "Handling request %s with URL %s", method, url);
raop_handler_t handler = NULL;
@@ -267,6 +290,9 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
if (handler != NULL) {
handler(conn, request, *response, &response_data, &response_datalen);
}
finish:;
http_response_add_header(*response, "Server", "AirTunes/"GLOBAL_VERSION);
http_response_add_header(*response, "CSeq", cseq);
http_response_finish(*response, response_data, response_datalen);
int len;
@@ -342,15 +368,10 @@ conn_destroy(void *ptr) {
}
raop_t *
raop_init(int max_clients, raop_callbacks_t *callbacks, const char *device_id, const char *keyfile) {
raop_init(raop_callbacks_t *callbacks) {
raop_t *raop;
pairing_t *pairing;
httpd_t *httpd;
httpd_callbacks_t httpd_cbs;
assert(callbacks);
assert(max_clients > 0);
assert(max_clients < 100);
/* Initialize the network */
if (netutils_init() < 0) {
@@ -372,46 +393,8 @@ raop_init(int max_clients, raop_callbacks_t *callbacks, const char *device_id, c
/* Initialize the logger */
raop->logger = logger_init();
/* create a new public key for pairing */
int new_key;
pairing = pairing_init_generate(device_id, keyfile, &new_key);
if (!pairing) {
free(raop);
return NULL;
}
/* store PK as a string in raop->pk_str */
memset(raop->pk_str, 0, sizeof(raop->pk_str));
#ifdef PK
strncpy(raop->pk_str, PK, 2*ED25519_KEY_SIZE);
#else
unsigned char public_key[ED25519_KEY_SIZE];
pairing_get_public_key(pairing, public_key);
char *pk_str = utils_pk_to_string(public_key, ED25519_KEY_SIZE);
strncpy(raop->pk_str, (const char *) pk_str, 2*ED25519_KEY_SIZE);
free(pk_str);
#endif
if (new_key) {
printf("*** A new Public Key has been created and stored in %s\n", keyfile);
}
/* Set HTTP callbacks to our handlers */
memset(&httpd_cbs, 0, sizeof(httpd_cbs));
httpd_cbs.opaque = raop;
httpd_cbs.conn_init = &conn_init;
httpd_cbs.conn_request = &conn_request;
httpd_cbs.conn_destroy = &conn_destroy;
/* Initialize the http daemon */
httpd = httpd_init(raop->logger, &httpd_cbs, max_clients);
if (!httpd) {
pairing_destroy(pairing);
free(raop);
return NULL;
}
/* Copy callbacks structure */
memcpy(&raop->callbacks, callbacks, sizeof(raop_callbacks_t));
raop->pairing = pairing;
raop->httpd = httpd;
/* initialize network port list */
raop->port = 0;
@@ -440,6 +423,54 @@ raop_init(int max_clients, raop_callbacks_t *callbacks, const char *device_id, c
return raop;
}
int
raop_init2(raop_t *raop, int nohold, const char *device_id, const char *keyfile) {
pairing_t *pairing;
httpd_t *httpd;
httpd_callbacks_t httpd_cbs;
/* create a new public key for pairing */
int new_key;
pairing = pairing_init_generate(device_id, keyfile, &new_key);
if (!pairing) {
logger_log(raop->logger, LOGGER_ERR, "failed to create new public key for pairing");
return -1;
}
/* store PK as a string in raop->pk_str */
memset(raop->pk_str, 0, sizeof(raop->pk_str));
#ifdef PK
strncpy(raop->pk_str, PK, 2*ED25519_KEY_SIZE);
#else
unsigned char public_key[ED25519_KEY_SIZE];
pairing_get_public_key(pairing, public_key);
char *pk_str = utils_pk_to_string(public_key, ED25519_KEY_SIZE);
strncpy(raop->pk_str, (const char *) pk_str, 2*ED25519_KEY_SIZE);
free(pk_str);
#endif
if (new_key) {
logger_log(raop->logger, LOGGER_INFO,"*** A new Public Key has been created and stored in %s", keyfile);
}
/* Set HTTP callbacks to our handlers */
memset(&httpd_cbs, 0, sizeof(httpd_cbs));
httpd_cbs.opaque = raop;
httpd_cbs.conn_init = &conn_init;
httpd_cbs.conn_request = &conn_request;
httpd_cbs.conn_destroy = &conn_destroy;
/* Initialize the http daemon */
httpd = httpd_init(raop->logger, &httpd_cbs, nohold);
if (!httpd) {
logger_log(raop->logger, LOGGER_ERR, "failed to initialize http daemon");
pairing_destroy(pairing);
return -1;
}
raop->pairing = pairing;
raop->httpd = httpd;
return 0;
}
void
raop_destroy(raop_t *raop) {
if (raop) {

View File

@@ -63,12 +63,14 @@ struct raop_callbacks_s {
void (*register_client) (void *cls, const char *device_id, const char *pk_str, const char *name);
bool (*check_register) (void *cls, const char *pk_str);
void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id);
void (*video_reset) (void *cls);
};
typedef struct raop_callbacks_s raop_callbacks_t;
raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *remote, int remote_addr_len,
unsigned short timing_rport, timing_protocol_t *time_protocol);
raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *remote,
int remote_addr_len, unsigned short timing_rport, timing_protocol_t *time_protocol);
RAOP_API raop_t *raop_init(int max_clients, raop_callbacks_t *callbacks, const char *device_id, const char *keyfile);
RAOP_API raop_t *raop_init(raop_callbacks_t *callbacks);
RAOP_API int raop_init2(raop_t *raop, int nohold, const char *device_id, const char *keyfile);
RAOP_API void raop_set_log_level(raop_t *raop, int level);
RAOP_API void raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls);
RAOP_API int raop_set_plist(raop_t *raop, const char *plist_item, const int value);

View File

@@ -36,33 +36,37 @@ raop_handler_info(raop_conn_t *conn,
{
assert(conn->raop->dnssd);
int airplay_txt_len = 0;
const char *airplay_txt = dnssd_get_airplay_txt(conn->raop->dnssd, &airplay_txt_len);
int name_len = 0;
const char *name = dnssd_get_name(conn->raop->dnssd, &name_len);
plist_t res_node = plist_new_dict();
/* deviceID is the physical hardware address, and will not change */
int hw_addr_raw_len = 0;
const char *hw_addr_raw = dnssd_get_hw_addr(conn->raop->dnssd, &hw_addr_raw_len);
char *hw_addr = calloc(1, 3 * hw_addr_raw_len);
//int hw_addr_len =
utils_hwaddr_airplay(hw_addr, 3 * hw_addr_raw_len, hw_addr_raw, hw_addr_raw_len);
plist_t device_id_node = plist_new_string(hw_addr);
plist_dict_set_item(res_node, "deviceID", device_id_node);
/* Persistent Public Key */
int pk_len = 0;
char *pk = utils_parse_hex(conn->raop->pk_str, strlen(conn->raop->pk_str), &pk_len);
plist_t pk_node = plist_new_data(pk, pk_len);
plist_dict_set_item(res_node, "pk", pk_node);
plist_t r_node = plist_new_dict();
/* airplay_txt is from the _airplay._tcp dnssd announuncement, may not be necessary */
int airplay_txt_len = 0;
const char *airplay_txt = dnssd_get_airplay_txt(conn->raop->dnssd, &airplay_txt_len);
plist_t txt_airplay_node = plist_new_data(airplay_txt, airplay_txt_len);
plist_dict_set_item(r_node, "txtAirPlay", txt_airplay_node);
plist_dict_set_item(res_node, "txtAirPlay", txt_airplay_node);
uint64_t features = dnssd_get_airplay_features(conn->raop->dnssd);
plist_t features_node = plist_new_uint(features);
plist_dict_set_item(r_node, "features", features_node);
plist_dict_set_item(res_node, "features", features_node);
int name_len = 0;
const char *name = dnssd_get_name(conn->raop->dnssd, &name_len);
plist_t name_node = plist_new_string(name);
plist_dict_set_item(r_node, "name", name_node);
plist_dict_set_item(res_node, "name", name_node);
plist_t audio_formats_node = plist_new_array();
plist_t audio_format_0_node = plist_new_dict();
@@ -81,31 +85,25 @@ raop_handler_info(raop_conn_t *conn,
plist_dict_set_item(audio_format_1_node, "audioInputFormats", audio_format_1_audio_input_formats_node);
plist_dict_set_item(audio_format_1_node, "audioOutputFormats", audio_format_1_audio_output_formats_node);
plist_array_append_item(audio_formats_node, audio_format_1_node);
plist_dict_set_item(r_node, "audioFormats", audio_formats_node);
plist_dict_set_item(res_node, "audioFormats", audio_formats_node);
plist_t pi_node = plist_new_string(AIRPLAY_PI);
plist_dict_set_item(r_node, "pi", pi_node);
plist_dict_set_item(res_node, "pi", pi_node);
plist_t vv_node = plist_new_uint(strtol(AIRPLAY_VV, NULL, 10));
plist_dict_set_item(r_node, "vv", vv_node);
plist_dict_set_item(res_node, "vv", vv_node);
plist_t status_flags_node = plist_new_uint(68);
plist_dict_set_item(r_node, "statusFlags", status_flags_node);
plist_dict_set_item(res_node, "statusFlags", status_flags_node);
plist_t keep_alive_low_power_node = plist_new_uint(1);
plist_dict_set_item(r_node, "keepAliveLowPower", keep_alive_low_power_node);
plist_dict_set_item(res_node, "keepAliveLowPower", keep_alive_low_power_node);
plist_t source_version_node = plist_new_string(GLOBAL_VERSION);
plist_dict_set_item(r_node, "sourceVersion", source_version_node);
plist_t pk_node = plist_new_data(pk, pk_len);
plist_dict_set_item(r_node, "pk", pk_node);
plist_dict_set_item(res_node, "sourceVersion", source_version_node);
plist_t keep_alive_send_stats_as_body_node = plist_new_uint(1);
plist_dict_set_item(r_node, "keepAliveSendStatsAsBody", keep_alive_send_stats_as_body_node);
plist_t device_id_node = plist_new_string(hw_addr);
plist_dict_set_item(r_node, "deviceID", device_id_node);
plist_dict_set_item(res_node, "keepAliveSendStatsAsBody", keep_alive_send_stats_as_body_node);
plist_t audio_latencies_node = plist_new_array();
plist_t audio_latencies_0_node = plist_new_dict();
@@ -128,13 +126,13 @@ raop_handler_info(raop_conn_t *conn,
plist_dict_set_item(audio_latencies_1_node, "audioType", audio_latencies_1_audio_type_node);
plist_dict_set_item(audio_latencies_1_node, "inputLatencyMicros", audio_latencies_1_input_latency_micros_node);
plist_array_append_item(audio_latencies_node, audio_latencies_1_node);
plist_dict_set_item(r_node, "audioLatencies", audio_latencies_node);
plist_dict_set_item(res_node, "audioLatencies", audio_latencies_node);
plist_t model_node = plist_new_string(GLOBAL_MODEL);
plist_dict_set_item(r_node, "model", model_node);
plist_dict_set_item(res_node, "model", model_node);
plist_t mac_address_node = plist_new_string(hw_addr);
plist_dict_set_item(r_node, "macAddress", mac_address_node);
plist_dict_set_item(res_node, "macAddress", mac_address_node);
plist_t displays_node = plist_new_array();
plist_t displays_0_node = plist_new_dict();
@@ -164,10 +162,10 @@ raop_handler_info(raop_conn_t *conn,
plist_dict_set_item(displays_0_node, "overscanned", displays_0_overscanned_node);
plist_dict_set_item(displays_0_node, "features", displays_0_features);
plist_array_append_item(displays_node, displays_0_node);
plist_dict_set_item(r_node, "displays", displays_node);
plist_dict_set_item(res_node, "displays", displays_node);
plist_to_bin(r_node, response_data, (uint32_t *) response_datalen);
plist_free(r_node);
plist_to_bin(res_node, response_data, (uint32_t *) response_datalen);
plist_free(res_node);
http_response_add_header(response, "Content-Type", "application/x-apple-binary-plist");
free(pk);
free(hw_addr);
@@ -205,8 +203,8 @@ raop_handler_pairsetup_pin(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen) {
const char *request_data;
int request_datalen;
const char *request_data = NULL;;
int request_datalen = 0;
bool data_is_plist = false;
bool logger_debug = (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG);
request_data = http_request_get_data(request, &request_datalen);
@@ -219,7 +217,8 @@ raop_handler_pairsetup_pin(raop_conn_t *conn,
free(header_str);
}
if (!data_is_plist) {
logger_log(conn->raop->logger, LOGGER_INFO, "did not receive expected plist from client, request_datalen = %d");
logger_log(conn->raop->logger, LOGGER_INFO, "did not receive expected plist from client, request_datalen = %d",
request_datalen);
goto authentication_failed;
}
@@ -355,14 +354,7 @@ raop_handler_pairsetup_pin(raop_conn_t *conn,
return;
}
authentication_failed:;
http_response_destroy(response);
response = http_response_init("RTSP/1.0", 470, "Client Authentication Failure");
const char *cseq = http_request_get_header(request, "CSeq");
http_response_add_header(response, "CSeq", cseq);
http_response_add_header(response, "Server", "AirTunes/"GLOBAL_VERSION);
*response_data = NULL;
response_datalen = 0;
return;
http_response_init(response, "RTSP/1.0", 470, "Client Authentication Failure");
}
static void
@@ -737,36 +729,31 @@ raop_handler_setup(raop_conn_t *conn,
conn->raop_ntp = NULL;
conn->raop_rtp = NULL;
conn->raop_rtp_mirror = NULL;
if (conn->remotelen == 4 || conn->remotelen == 16) {
char remote[40];
if (conn->remotelen == 4) {
/* IPV4 */
snprintf(remote, sizeof(remote), "%d.%d.%d.%d", conn->remote[0], conn->remote[1],
conn->remote[2], conn->remote[3]);
} else {
/*IPV6*/
logger_log(conn->raop->logger, LOGGER_INFO, "client is using IPV6 (untested!)");
snprintf(remote, sizeof(remote), "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
conn->remote[0], conn->remote[1], conn->remote[2], conn->remote[3],
conn->remote[4], conn->remote[5], conn->remote[6], conn->remote[7],
conn->remote[8], conn->remote[9], conn->remote[10], conn->remote[11],
conn->remote[12], conn->remote[13], conn->remote[14], conn->remote[15]);
}
conn->raop_ntp = raop_ntp_init(conn->raop->logger, &conn->raop->callbacks, 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,
remote, conn->remotelen, aeskey, aesiv);
conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks,
conn->raop_ntp, remote, conn->remotelen, aeskey);
char remote[40];
int len = utils_ipaddress_to_string(conn->remotelen, conn->remote, conn->zone_id, remote, (int) sizeof(remote));
if (!len || len > sizeof(remote)) {
char *str = utils_data_to_string(conn->remote, conn->remotelen, 16);
logger_log(conn->raop->logger, LOGGER_ERR, "failed to extract valid client ip address:\n"
"*** UxPlay will be unable to send communications to client.\n"
"*** address length %d, zone_id %u address data:\n%sparser returned \"%s\"\n",
conn->remotelen, conn->zone_id, str, remote);
free(str);
}
conn->raop_ntp = raop_ntp_init(conn->raop->logger, &conn->raop->callbacks, 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,
remote, conn->remotelen, aeskey, aesiv);
conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks,
conn->raop_ntp, remote, conn->remotelen, aeskey);
plist_t res_event_port_node = plist_new_uint(conn->raop->port);
// plist_t res_event_port_node = plist_new_uint(conn->raop->port);
plist_t res_event_port_node = plist_new_uint(0);
plist_t res_timing_port_node = plist_new_uint(timing_lport);
plist_dict_set_item(res_root_node, "timingPort", res_timing_port_node);
plist_dict_set_item(res_root_node, "eventPort", res_event_port_node);
logger_log(conn->raop->logger, LOGGER_DEBUG, "eport = %d, tport = %d", conn->raop->port, timing_lport);
logger_log(conn->raop->logger, LOGGER_DEBUG, "eport = %d, tport = %d", 0, timing_lport);
}
// Process stream setup requests
@@ -793,8 +780,8 @@ raop_handler_setup(raop_conn_t *conn,
" key and iv): %llu", stream_connection_id);
if (conn->raop_rtp_mirror) {
raop_rtp_init_mirror_aes(conn->raop_rtp_mirror, &stream_connection_id);
raop_rtp_start_mirror(conn->raop_rtp_mirror, &dport, conn->raop->clientFPSdata);
raop_rtp_mirror_init_aes(conn->raop_rtp_mirror, &stream_connection_id);
raop_rtp_mirror_start(conn->raop_rtp_mirror, &dport, conn->raop->clientFPSdata);
logger_log(conn->raop->logger, LOGGER_DEBUG, "Mirroring initialized successfully");
} else {
logger_log(conn->raop->logger, LOGGER_ERR, "Mirroring not initialized at SETUP, playing will fail!");

View File

@@ -23,6 +23,7 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#ifdef _WIN32
#define CAST (char *)
#else
@@ -296,7 +297,9 @@ raop_ntp_thread(void *arg)
free(str);
}
if (send_len < 0) {
logger_log(raop_ntp->logger, LOGGER_ERR, "raop_ntp error sending request");
int sock_err = SOCKET_GET_ERROR();
logger_log(raop_ntp->logger, LOGGER_ERR, "raop_ntp error sending request. Error %d:%s",
sock_err, strerror(sock_err));
} else {
// Read response
response_len = recvfrom(raop_ntp->tsock, (char *)response, sizeof(response), 0, NULL, NULL);

View File

@@ -165,7 +165,7 @@ raop_rtp_mirror_t *raop_rtp_mirror_init(logger_t *logger, raop_callbacks_t *call
}
void
raop_rtp_init_mirror_aes(raop_rtp_mirror_t *raop_rtp_mirror, uint64_t *streamConnectionID)
raop_rtp_mirror_init_aes(raop_rtp_mirror_t *raop_rtp_mirror, uint64_t *streamConnectionID)
{
mirror_buffer_init_aes(raop_rtp_mirror->buffer, streamConnectionID);
}
@@ -291,7 +291,7 @@ raop_rtp_mirror_thread(void *arg)
if (payload == NULL && ret == 0) {
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG,
"raop_rtp_mirror tcp socket is closed, got %d bytes of 128 byte header",readstart);
"raop_rtp_mirror tcp socket was closed by client (recv returned 0); got %d bytes of 128 byte header",readstart);
FD_CLR(stream_fd, &rfds);
stream_fd = -1;
continue;
@@ -354,7 +354,7 @@ raop_rtp_mirror_thread(void *arg)
}
if (ret == 0) {
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror tcp socket is closed");
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror tcp socket was closed by client (recv returned 0)");
break;
} else if (ret == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) continue; // Timeouts can happen even if the connection is fine
@@ -609,6 +609,11 @@ raop_rtp_mirror_thread(void *arg)
memcpy(sps_pps + sps_size + 8, payload + sps_size + 11, pps_size);
prepend_sps_pps = true;
uint64_t ntp_offset = 0;
ntp_offset = raop_ntp_convert_remote_time(raop_rtp_mirror->ntp, ntp_offset);
if (!ntp_offset) {
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "ntp synchronization has not yet started: synchronized video may fail");
}
// h264codec_t h264;
// h264.version = payload[0];
// h264.profile_high = payload[1];
@@ -694,7 +699,7 @@ raop_rtp_mirror_thread(void *arg)
}
static int
raop_rtp_init_mirror_sockets(raop_rtp_mirror_t *raop_rtp_mirror, int use_ipv6)
raop_rtp_mirror_init_socket(raop_rtp_mirror_t *raop_rtp_mirror, int use_ipv6)
{
assert(raop_rtp_mirror);
@@ -724,7 +729,7 @@ raop_rtp_init_mirror_sockets(raop_rtp_mirror_t *raop_rtp_mirror, int use_ipv6)
}
void
raop_rtp_start_mirror(raop_rtp_mirror_t *raop_rtp_mirror, unsigned short *mirror_data_lport,
raop_rtp_mirror_start(raop_rtp_mirror_t *raop_rtp_mirror, unsigned short *mirror_data_lport,
uint8_t show_client_FPS_data)
{
logger_log(raop_rtp_mirror->logger, LOGGER_INFO, "raop_rtp_mirror starting mirroring");
@@ -746,8 +751,8 @@ raop_rtp_start_mirror(raop_rtp_mirror_t *raop_rtp_mirror, unsigned short *mirror
//use_ipv6 = 0;
raop_rtp_mirror->mirror_data_lport = *mirror_data_lport;
if (raop_rtp_init_mirror_sockets(raop_rtp_mirror, use_ipv6) < 0) {
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror initializing sockets failed");
if (raop_rtp_mirror_init_socket(raop_rtp_mirror, use_ipv6) < 0) {
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror initializing socket failed");
MUTEX_UNLOCK(raop_rtp_mirror->run_mutex);
return;
}

View File

@@ -27,8 +27,8 @@ 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 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, unsigned short *mirror_data_lport, uint8_t show_client_FPS_data);
void raop_rtp_mirror_init_aes(raop_rtp_mirror_t *raop_rtp_mirror, uint64_t *streamConnectionID);
void raop_rtp_mirror_start(raop_rtp_mirror_t *raop_rtp_mirror, unsigned short *mirror_data_lport, uint8_t show_client_FPS_data);
void raop_rtp_mirror_stop(raop_rtp_mirror_t *raop_rtp_mirror);
void raop_rtp_mirror_destroy(raop_rtp_mirror_t *raop_rtp_mirror);
#endif //RAOP_RTP_MIRROR_H

View File

@@ -594,9 +594,11 @@ static void init_random()
fp = fopen("/dev/urandom", "r");
if (fp)
{
fread(buff, sizeof(buff), 1, fp);
size_t count = fread(buff, sizeof(buff), 1, fp);
fclose(fp);
g_initialized = 1;
if (count == 1) {
g_initialized = 1;
}
}
#endif
if (g_initialized)

View File

@@ -256,3 +256,29 @@ void ntp_timestamp_to_seconds(uint64_t ntp_timestamp, char *timestamp, size_t ma
strftime(timestamp, 3, "%S", &ts);
snprintf(timestamp + 2, 11,".%9.9lu", (unsigned long) ntp_timestamp % SECOND_IN_NSECS);
}
int utils_ipaddress_to_string(int addresslen, const unsigned char *address, unsigned int zone_id, char *string, int sizeof_string) {
int ret = 0;
unsigned char ipv6_link_local_prefix[] = { 0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
assert(sizeof_string > 0);
assert(string);
if (addresslen != 4 && addresslen != 16) { //invalid address length (only ipv4 and ipv6 allowed)
string[0] = '\0';
}
if (addresslen == 4) { /* IPV4 */
ret = snprintf(string, sizeof_string, "%d.%d.%d.%d", address[0], address[1], address[2], address[3]);
} else if (zone_id) { /* IPV6 link-local */
if (memcmp(address, ipv6_link_local_prefix, 8)) {
string[0] = '\0'; //only link-local ipv6 addresses can have a zone_id
} else {
ret = snprintf(string, sizeof_string, "fe80::%02x%02x:%02x%02x:%02x%02x:%02x%02x%%%u",
address[8], address[9], address[10], address[11],
address[12], address[13], address[14], address[15], zone_id);
}
} else { /* IPV6 standard*/
ret = snprintf(string, sizeof_string, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
address[0], address[1], address[2], address[3], address[4], address[5], address[6], address[7],
address[8], address[9], address[10], address[11], address[12], address[13], address[14], address[15]);
}
return ret;
}

View File

@@ -30,5 +30,6 @@ char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per
char *utils_data_to_text(const char *data, int datalen);
void ntp_timestamp_to_time(uint64_t ntp_timestamp, char *timestamp, size_t maxsize);
void ntp_timestamp_to_seconds(uint64_t ntp_timestamp, char *timestamp, size_t maxsize);
int utils_ipaddress_to_string(int addresslen, const unsigned char *address,
unsigned int zone_id, char *string, int len);
#endif

View File

@@ -9,6 +9,12 @@ if (APPLE )
find_program( PKG_CONFIG_EXECUTABLE pkg-config PATHS /Library/FrameWorks/GStreamer.framework/Commands )
set(PKG_CONFIG_EXECUTABLE ${PKG_CONFIG_EXECUTABLE} --define-prefix )
else()
if ( DEFINED ENV{GSTREAMER_ROOT_DIR} )
if ( EXISTS "$ENV{GSTREAMER_ROOT_DIR}/pkgconfig" )
message ( STATUS "*** Using GSTREAMER_ROOT_DIR = " $ENV{GSTREAMER_ROOT_DIR} )
set( ENV{PKG_CONFIG_PATH} "$ENV{GSTREAMER_ROOT_DIR}/pkgconfig:$ENV{PKG_CONFIG_PATH}" )
endif()
endif()
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig" ) # standard location for self-installed gstreamer
endif()
@@ -32,6 +38,13 @@ pkg_check_modules(GST REQUIRED gstreamer-1.0>=1.4
gstreamer-app-1.0>=1.4
)
# temporary hack to deal with an issue in gstreamer 1.24
pkg_check_modules ( GST124 gstreamer-1.0>=1.24 )
if ( GST124_FOUND )
message( STATUS "*** GStreamer >= 1.24: GST_124 will be defined" )
set( GST_124 "1" CACHE STRING "define GST_124" )
endif()
add_library( renderers
STATIC
audio_renderer_gstreamer.c

View File

@@ -30,7 +30,6 @@
#include "x_display_fix.h"
static bool fullscreen = false;
static bool alt_keypress = false;
#define MAX_X11_SEARCH_ATTEMPTS 5 /*should be less than 256 */
static unsigned char X11_search_attempts;
#endif
@@ -40,12 +39,17 @@ static logger_t *logger = NULL;
static unsigned short width, height, width_source, height_source; /* not currently used */
static bool first_packet = false;
static bool sync = false;
static bool auto_videosink;
#ifdef X_DISPLAY_FIX
static bool use_x11 = false;
#endif
struct video_renderer_s {
GstElement *appsrc, *pipeline, *sink;
GstElement *appsrc, *pipeline;
GstBus *bus;
#ifdef X_DISPLAY_FIX
const char * server_name;
const char * server_name;
X11_Window_t * gst_window;
#endif
};
@@ -56,56 +60,56 @@ static void append_videoflip (GString *launch, const videoflip_t *flip, const vi
case INVERT:
switch (*rot) {
case LEFT:
g_string_append(launch, "videoflip method=clockwise ! ");
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_90R ! ");
break;
case RIGHT:
g_string_append(launch, "videoflip method=counterclockwise ! ");
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_90L ! ");
break;
default:
g_string_append(launch, "videoflip method=rotate-180 ! ");
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_180 ! ");
break;
}
break;
case HFLIP:
switch (*rot) {
case LEFT:
g_string_append(launch, "videoflip method=upper-left-diagonal ! ");
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_UL_LR ! ");
break;
case RIGHT:
g_string_append(launch, "videoflip method=upper-right-diagonal ! ");
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_UR_LL ! ");
break;
default:
g_string_append(launch, "videoflip method=horizontal-flip ! ");
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_HORIZ ! ");
break;
}
break;
case VFLIP:
switch (*rot) {
case LEFT:
g_string_append(launch, "videoflip method=upper-right-diagonal ! ");
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_UR_LL ! ");
break;
case RIGHT:
g_string_append(launch, "videoflip method=upper-left-diagonal ! ");
case RIGHT:
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_UL_LR ! ");
break;
default:
g_string_append(launch, "videoflip method=vertical-flip ! ");
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_VERT ! ");
break;
}
break;
default:
switch (*rot) {
case LEFT:
g_string_append(launch, "videoflip method=counterclockwise ! ");
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_90L ! ");
break;
case RIGHT:
g_string_append(launch, "videoflip method=clockwise ! ");
case RIGHT:
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_90R ! ");
break;
default:
break;
}
break;
}
}
}
/* apple uses colorimetry=1:3:5:1 *
* (not recognized by v4l2 plugin in Gstreamer < 1.20.4) *
@@ -135,6 +139,9 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, vide
GstClock *clock = gst_system_clock_obtain();
g_object_set(clock, "clock-type", GST_CLOCK_TYPE_REALTIME, NULL);
/* videosink choices that are auto */
auto_videosink = (strstr(videosink, "autovideosink") || strstr(videosink, "fpsdisplaysink"));
logger = render_logger;
/* this call to g_set_application_name makes server_name appear in the X11 display window title bar, */
@@ -153,11 +160,11 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, vide
g_string_append(launch, " ! ");
g_string_append(launch, decoder);
g_string_append(launch, " ! ");
g_string_append(launch, converter);
g_string_append(launch, " ! ");
append_videoflip(launch, &videoflip[0], &videoflip[1]);
g_string_append(launch, converter);
g_string_append(launch, " ! ");
g_string_append(launch, "videoscale ! ");
g_string_append(launch, videosink);
g_string_append(launch, " name=video_sink");
if (*video_sync) {
g_string_append(launch, " sync=true");
sync = true;
@@ -182,23 +189,13 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, vide
gst_caps_unref(caps);
gst_object_unref(clock);
renderer->sink = gst_bin_get_by_name (GST_BIN (renderer->pipeline), "video_sink");
g_assert(renderer->sink);
#ifdef X_DISPLAY_FIX
use_x11 = (strstr(videosink, "xvimagesink") || strstr(videosink, "ximagesink") || auto_videosink);
fullscreen = *initial_fullscreen;
renderer->server_name = server_name;
renderer->gst_window = NULL;
bool x_display_fix = false;
/* only include X11 videosinks that provide fullscreen mode, or need ZOOMFIX */
/* limit searching for X11 Windows in case autovideosink selects an incompatible videosink */
if (strncmp(videosink,"autovideosink", strlen("autovideosink")) == 0 ||
strncmp(videosink,"ximagesink", strlen("ximagesink")) == 0 ||
strncmp(videosink,"xvimagesink", strlen("xvimagesink")) == 0 ||
strncmp(videosink,"fpsdisplaysink", strlen("fpsdisplaysink")) == 0 ) {
x_display_fix = true;
}
if (x_display_fix) {
X11_search_attempts = 0;
if (use_x11) {
renderer->gst_window = calloc(1, sizeof(X11_Window_t));
g_assert(renderer->gst_window);
get_X11_Display(renderer->gst_window);
@@ -284,7 +281,7 @@ void video_renderer_render_buffer(unsigned char* data, int *data_len, int *nal_c
gst_buffer_fill(buffer, 0, data, *data_len);
gst_app_src_push_buffer (GST_APP_SRC(renderer->appsrc), buffer);
#ifdef X_DISPLAY_FIX
if (renderer->gst_window && !(renderer->gst_window->window) && X11_search_attempts < MAX_X11_SEARCH_ATTEMPTS) {
if (renderer->gst_window && !(renderer->gst_window->window) && use_x11) {
X11_search_attempts++;
logger_log(logger, LOGGER_DEBUG, "Looking for X11 UxPlay Window, attempt %d", (int) X11_search_attempts);
get_x_window(renderer->gst_window, renderer->server_name);
@@ -293,8 +290,6 @@ void video_renderer_render_buffer(unsigned char* data, int *data_len, int *nal_c
if (fullscreen) {
set_fullscreen(renderer->gst_window, &fullscreen);
}
} else if (X11_search_attempts == MAX_X11_SEARCH_ATTEMPTS) {
logger_log(logger, LOGGER_DEBUG, "X11 UxPlay Window not found in %d search attempts", MAX_X11_SEARCH_ATTEMPTS);
}
}
#endif
@@ -305,10 +300,10 @@ void video_renderer_flush() {
}
void video_renderer_stop() {
if (renderer) {
gst_app_src_end_of_stream (GST_APP_SRC(renderer->appsrc));
gst_element_set_state (renderer->pipeline, GST_STATE_NULL);
}
if (renderer) {
gst_app_src_end_of_stream (GST_APP_SRC(renderer->appsrc));
gst_element_set_state (renderer->pipeline, GST_STATE_NULL);
}
}
void video_renderer_destroy() {
@@ -320,7 +315,6 @@ void video_renderer_destroy() {
gst_element_set_state (renderer->pipeline, GST_STATE_NULL);
}
gst_object_unref(renderer->bus);
gst_object_unref(renderer->sink);
gst_object_unref (renderer->appsrc);
gst_object_unref (renderer->pipeline);
#ifdef X_DISPLAY_FIX
@@ -328,7 +322,7 @@ void video_renderer_destroy() {
free(renderer->gst_window);
renderer->gst_window = NULL;
}
#endif
#endif
free (renderer);
renderer = NULL;
}
@@ -370,6 +364,19 @@ gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, gpoin
logger_log(logger, LOGGER_INFO, "GStreamer: End-Of-Stream");
// g_main_loop_quit( (GMainLoop *) loop);
break;
case GST_MESSAGE_STATE_CHANGED:
if (auto_videosink) {
char *sink = strstr(GST_MESSAGE_SRC_NAME(message), "-actual-sink-");
if (sink) {
sink += strlen("-actual-sink-");
logger_log(logger, LOGGER_DEBUG, "GStreamer: automatically-selected videosink is \"%ssink\"", sink);
auto_videosink = false;
#ifdef X_DISPLAY_FIX
use_x11 = (strstr(sink, "ximage") || strstr(sink, "xvimage"));
#endif
}
}
break;
#ifdef X_DISPLAY_FIX
case GST_MESSAGE_ELEMENT:
if (renderer->gst_window && renderer->gst_window->window) {
@@ -417,4 +424,4 @@ gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, gpoin
unsigned int video_renderer_listen(void *loop) {
return (unsigned int) gst_bus_add_watch(renderer->bus, (GstBusFunc)
gstreamer_pipeline_bus_callback, (gpointer) loop);
}
}

View File

@@ -1,11 +1,11 @@
.TH UXPLAY "1" "December 2023" "1.68" "User Commands"
.TH UXPLAY "1" "August 2024" "1.69" "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.68: An open\-source AirPlay mirroring (+ audio streaming) server:
UxPlay 1.69: An open\-source AirPlay mirroring (+ audio streaming) server:
.SH OPTIONS
.TP
.B
@@ -42,7 +42,7 @@ UxPlay 1.68: An open\-source AirPlay mirroring (+ audio streaming) server:
.TP
\fB\-o\fR Set display "overscanned" mode on (not usually needed)
.TP
\fB-fs\fR Full-screen (only works with X11, Wayland and VAAPI)
\fB-fs\fR Full-screen (only works with X11, Wayland, VAAPI, D3D11)
.TP
\fB\-p\fR Use legacy ports UDP 6000:6001:7011 TCP 7000:7001:7100
.TP
@@ -95,7 +95,9 @@ UxPlay 1.68: An open\-source AirPlay mirroring (+ audio streaming) server:
.TP
\fB\-reset\fR n Reset after 3n seconds client silence (default 5, 0=never).
.TP
\fB\-nc\fR Do not close video window when client stops mirroring
\fB\-nofreeze\fR Do NOT leave frozen screen in place after reset.
.TP
\fB\-nc\fR Do NOT close video window when client stops mirroring
.TP
\fB\-nohold\fR Drop current connection when new client connects.
.TP

View File

@@ -62,7 +62,7 @@
#include "renderers/video_renderer.h"
#include "renderers/audio_renderer.h"
#define VERSION "1.68"
#define VERSION "1.69"
#define SECOND_IN_USECS 1000000
#define SECOND_IN_NSECS 1000000000UL
@@ -121,7 +121,8 @@ static unsigned short display[5] = {0}, tcp[3] = {0}, udp[3] = {0};
static bool debug_log = DEFAULT_DEBUG_LOG;
static int log_level = LOGGER_INFO;
static bool bt709_fix = false;
static int max_connections = 2;
static int nohold = 0;
static bool nofreeze = false;
static unsigned short raop_port;
static unsigned short airplay_port;
static uint64_t remote_clock_offset = 0;
@@ -583,7 +584,7 @@ static void print_info (char *name) {
printf("-taper Use a \"tapered\" AirPlay volume-control profile\n");
printf("-s wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60]\n");
printf("-o Set display \"overscanned\" mode on (not usually needed)\n");
printf("-fs Full-screen (only works with X11, Wayland and VAAPI)\n");
printf("-fs Full-screen (only works with X11, Wayland, VAAPI, D3D11)\n");
printf("-p Use legacy ports UDP 6000:6001:7011 TCP 7000:7001:7100\n");
printf("-p n Use TCP and UDP ports n,n+1,n+2. range %d-%d\n", LOWEST_ALLOWED_PORT, HIGHEST_PORT);
printf(" use \"-p n1,n2,n3\" to set each port, \"n1,n2\" for n3 = n2+1\n");
@@ -609,7 +610,8 @@ static void print_info (char *name) {
printf("-al x Audio latency in seconds (default 0.25) reported to client.\n");
printf("-ca <fn> In Airplay Audio (ALAC) mode, write cover-art to file <fn>\n");
printf("-reset n Reset after 3n seconds client silence (default %d, 0=never)\n", NTP_TIMEOUT_LIMIT);
printf("-nc do Not Close video window when client stops mirroring\n");
printf("-nofreeze Do NOT leave frozen screen in place after reset\n");
printf("-nc Do NOT Close video window when client stops mirroring\n");
printf("-nohold Drop current connection when new client connects.\n");
printf("-restrict Restrict clients to those specified by \"-allow <deviceID>\"\n");
printf(" UxPlay displays deviceID when a client attempts to connect\n");
@@ -1033,7 +1035,7 @@ static void parse_arguments (int argc, char *argv[]) {
} else if (arg == "-bt709") {
bt709_fix = true;
} else if (arg == "-nohold") {
max_connections = 3;
nohold = 1;
} else if (arg == "-al") {
int n;
char *end;
@@ -1125,6 +1127,8 @@ static void parse_arguments (int argc, char *argv[]) {
db_low = db1;
db_high = db2;
printf("db range %f:%f\n", db_low, db_high);
} else if (arg == "-nofreeze") {
nofreeze = true;
} else {
fprintf(stderr, "unknown option %s, stopping (for help use option \"-h\")\n",argv[i]);
exit(1);
@@ -1330,6 +1334,95 @@ static int start_dnssd(std::vector<char> hw_addr, std::string name) {
LOGE("Could not initialize dnssd library!: error %d", dnssd_error);
return 1;
}
/* after dnssd starts, reset the default feature set here
* (overwrites features set in dnssdint.h).
* default: FEATURES_1 = 0x5A7FFEE6, FEATURES_2 = 0 */
dnssd_set_airplay_features(dnssd, 0, 0); // AirPlay video supported
dnssd_set_airplay_features(dnssd, 1, 1); // photo supported
dnssd_set_airplay_features(dnssd, 2, 1); // video protected with FairPlay DRM
dnssd_set_airplay_features(dnssd, 3, 0); // volume control supported for videos
dnssd_set_airplay_features(dnssd, 4, 0); // http live streaming (HLS) supported
dnssd_set_airplay_features(dnssd, 5, 1); // slideshow supported
dnssd_set_airplay_features(dnssd, 6, 1); //
dnssd_set_airplay_features(dnssd, 7, 1); // mirroring supported
dnssd_set_airplay_features(dnssd, 8, 0); // screen rotation supported
dnssd_set_airplay_features(dnssd, 9, 1); // audio supported
dnssd_set_airplay_features(dnssd, 10, 1); //
dnssd_set_airplay_features(dnssd, 11, 1); // audio packet redundancy supported
dnssd_set_airplay_features(dnssd, 12, 1); // FaiPlay secure auth supported
dnssd_set_airplay_features(dnssd, 13, 1); // photo preloading supported
dnssd_set_airplay_features(dnssd, 14, 1); // Authentication bit 4: FairPlay authentication
dnssd_set_airplay_features(dnssd, 15, 1); // Metadata bit 1 support: Artwork
dnssd_set_airplay_features(dnssd, 16, 1); // Metadata bit 2 support: Soundtrack Progress
dnssd_set_airplay_features(dnssd, 17, 1); // Metadata bit 0 support: Text (DAACP) "Now Playing" info.
dnssd_set_airplay_features(dnssd, 18, 1); // Audio format 1 support:
dnssd_set_airplay_features(dnssd, 19, 1); // Audio format 2 support: must be set for AirPlay 2 multiroom audio
dnssd_set_airplay_features(dnssd, 20, 1); // Audio format 3 support: must be set for AirPlay 2 multiroom audio
dnssd_set_airplay_features(dnssd, 21, 1); // Audio format 4 support:
dnssd_set_airplay_features(dnssd, 22, 1); // Authentication type 4: FairPlay authentication
dnssd_set_airplay_features(dnssd, 23, 0); // Authentication type 1: RSA Authentication
dnssd_set_airplay_features(dnssd, 24, 0); //
dnssd_set_airplay_features(dnssd, 25, 1); //
dnssd_set_airplay_features(dnssd, 26, 0); // Has Unified Advertiser info
dnssd_set_airplay_features(dnssd, 27, 1); // Supports Legacy Pairing
dnssd_set_airplay_features(dnssd, 28, 1); //
dnssd_set_airplay_features(dnssd, 29, 0); //
dnssd_set_airplay_features(dnssd, 30, 1); // RAOP support: with this bit set, the AirTunes service is not required.
dnssd_set_airplay_features(dnssd, 31, 0); //
for (int i = 32; i < 64; i++) {
dnssd_set_airplay_features(dnssd, i, 0);
}
/* bits 32-63 are not used here: see https://emanualcozzi.net/docs/airplay2/features
dnssd_set_airplay_features(dnssd, 32, 0); // isCarPlay when ON,; Supports InitialVolume when OFF
dnssd_set_airplay_features(dnssd, 33, 0); // Supports Air Play Video Play Queue
dnssd_set_airplay_features(dnssd, 34, 0); // Supports Air Play from cloud (requires that bit 6 is ON)
dnssd_set_airplay_features(dnssd, 35, 0); // Supports TLS_PSK
dnssd_set_airplay_features(dnssd, 36, 0); //
dnssd_set_airplay_features(dnssd, 37, 0); //
dnssd_set_airplay_features(dnssd, 38, 0); // Supports Unified Media Control (CoreUtils Pairing and Encryption)
dnssd_set_airplay_features(dnssd, 39, 0); //
dnssd_set_airplay_features(dnssd, 40, 0); // Supports Buffered Audio
dnssd_set_airplay_features(dnssd, 41, 0); // Supports PTP
dnssd_set_airplay_features(dnssd, 42, 0); // Supports Screen Multi Codec
dnssd_set_airplay_features(dnssd, 43, 0); // Supports System Pairing
dnssd_set_airplay_features(dnssd, 44, 0); // is AP Valeria Screen Sender
dnssd_set_airplay_features(dnssd, 45, 0); //
dnssd_set_airplay_features(dnssd, 46, 0); // Supports HomeKit Pairing and Access Control
dnssd_set_airplay_features(dnssd, 47, 0); //
dnssd_set_airplay_features(dnssd, 48, 0); // Supports CoreUtils Pairing and Encryption
dnssd_set_airplay_features(dnssd, 49, 0); //
dnssd_set_airplay_features(dnssd, 50, 0); // Metadata bit 3: "Now Playing" info sent by bplist not DAACP test
dnssd_set_airplay_features(dnssd, 51, 0); // Supports Unified Pair Setup and MFi Authentication
dnssd_set_airplay_features(dnssd, 52, 0); // Supports Set Peers Extended Message
dnssd_set_airplay_features(dnssd, 53, 0); //
dnssd_set_airplay_features(dnssd, 54, 0); // Supports AP Sync
dnssd_set_airplay_features(dnssd, 55, 0); // Supports WoL
dnssd_set_airplay_features(dnssd, 56, 0); // Supports Wol
dnssd_set_airplay_features(dnssd, 57, 0); //
dnssd_set_airplay_features(dnssd, 58, 0); // Supports Hangdog Remote Control
dnssd_set_airplay_features(dnssd, 59, 0); // Supports AudioStreamConnection setup
dnssd_set_airplay_features(dnssd, 60, 0); // Supports Audo Media Data Control
dnssd_set_airplay_features(dnssd, 61, 0); // Supports RFC2198 redundancy
*/
/* bit 27 of Features determines whether the AirPlay2 client-pairing protocol will be used (1) or not (0) */
dnssd_set_airplay_features(dnssd, 27, (int) setup_legacy_pairing);
return 0;
@@ -1361,6 +1454,14 @@ static bool check_blocked_client(char *deviceid) {
// Server callbacks
extern "C" void video_reset(void *cls) {
reset_loop = true;
remote_clock_offset = 0;
relaunch_video = true;
}
extern "C" void display_pin(void *cls, char *pin) {
int margin = 10;
int spacing = 3;
@@ -1413,8 +1514,9 @@ extern "C" void conn_reset (void *cls, int timeouts, bool reset_video) {
LOGI(" Sometimes the network connection may recover after a longer delay:\n"
" the default timeout limit n = %d can be changed with the \"-reset n\" option", NTP_TIMEOUT_LIMIT);
}
printf("reset_video %d\n",(int) reset_video);
close_window = reset_video; /* leave "frozen" window open if reset_video is false */
if (!nofreeze) {
close_window = reset_video; /* leave "frozen" window open if reset_video is false */
}
raop_stop(raop);
reset_loop = true;
}
@@ -1484,12 +1586,18 @@ extern "C" void video_process (void *cls, raop_ntp_t *ntp, h264_decode_struct *d
}
extern "C" void video_pause (void *cls) {
#ifdef GST_124
return; //pause/resume changes in GStreamer-1.24 break this code
#endif
if (use_video) {
video_renderer_pause();
}
}
extern "C" void video_resume (void *cls) {
#ifdef GST_124
return; //pause/resume changes in GStreamer-1.24 break this code
#endif
if (use_video) {
video_renderer_resume();
}
@@ -1595,6 +1703,14 @@ extern "C" void audio_set_coverart(void *cls, const void *buffer, int buflen) {
}
}
extern "C" void audio_set_progress(void *cls, unsigned int start, unsigned int curr, unsigned int end) {
int duration = (int) (end - start)/44100;
int position = (int) (curr - start)/44100;
int remain = duration - position;
printf("audio progress (min:sec): %d:%2.2d; remaining: %d:%2.2d; track length %d:%2.2d\n",
position/60, position%60, remain/60, remain%60, duration/60, duration%60);
}
extern "C" void audio_set_metadata(void *cls, const void *buffer, int buflen) {
char dmap_tag[5] = {0x0};
const unsigned char *metadata = (const unsigned char *) buffer;
@@ -1708,18 +1824,27 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3],
raop_cbs.video_report_size = video_report_size;
raop_cbs.audio_set_metadata = audio_set_metadata;
raop_cbs.audio_set_coverart = audio_set_coverart;
raop_cbs.audio_set_progress = audio_set_progress;
raop_cbs.report_client_request = report_client_request;
raop_cbs.display_pin = display_pin;
raop_cbs.register_client = register_client;
raop_cbs.check_register = check_register;
raop_cbs.export_dacp = export_dacp;
raop_cbs.video_reset = video_reset;
/* set max number of connections = 2 to protect against capture by new client */
raop = raop_init(max_connections, &raop_cbs, mac_address.c_str(), keyfile.c_str());
raop = raop_init(&raop_cbs);
if (raop == NULL) {
LOGE("Error initializing raop!");
return -1;
}
raop_set_log_callback(raop, log_callback, NULL);
raop_set_log_level(raop, log_level);
/* set nohold = 1 to allow capture by new client */
if (raop_init2(raop, nohold, mac_address.c_str(), keyfile.c_str())){
LOGE("Error initializing raop (2)!");
free (raop);
return -1;
}
/* write desired display pixel width, pixel height, refresh_rate, max_fps, overscanned. */
/* use 0 for default values 1920,1080,60,30,0; these are sent to the Airplay client */
@@ -1738,21 +1863,14 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3],
/* network port selection (ports listed as "0" will be dynamically assigned) */
raop_set_tcp_ports(raop, tcp);
raop_set_udp_ports(raop, udp);
raop_set_log_callback(raop, log_callback, NULL);
raop_set_log_level(raop, log_level);
raop_port = raop_get_port(raop);
raop_start(raop, &raop_port);
raop_set_port(raop, raop_port);
if (tcp[2]) {
airplay_port = tcp[2];
} else {
/* is there a problem if this coincides with a randomly-selected tcp raop_mirror_data port?
* probably not, as the airplay port is only used for initial client contact */
airplay_port = (raop_port != HIGHEST_PORT ? raop_port + 1 : raop_port - 1);
}
/* use raop_port for airplay_port (instead of tcp[2]) */
airplay_port = raop_port;
if (dnssd) {
raop_set_dnssd(raop, dnssd);
} else {
@@ -1930,7 +2048,11 @@ int main (int argc, char *argv[]) {
}
if (videosink == "d3d11videosink" && use_video) {
videosink.append(" fullscreen-toggle-mode=alt-enter");
if (fullscreen) {
videosink.append(" fullscreen-toggle-mode=GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY fullscreen=true ");
} else {
videosink.append(" fullscreen-toggle-mode=GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER ");
}
LOGI("d3d11videosink is being used with option fullscreen-toggle-mode=alt-enter\n"
"Use Alt-Enter key combination to toggle into/out of full-screen mode");
}

View File

@@ -1,5 +1,5 @@
Name: uxplay
Version: 1.68.2
Version: 1.69
Release: 1%{?dist}
%global gittag v%{version}
@@ -135,7 +135,7 @@ cd build
%{_docdir}/%{name}/llhttp/LICENSE-MIT
%changelog
* Fri Dec 29 2023 UxPlay maintainer <https://github.com/FDH2/UxPlay>
* Fri Aug 09 2024 UxPlay maintainer <https://github.com/FDH2/UxPlay>
Initial uxplay.spec: tested on Fedora 38, Rocky Linux 9.2, OpenSUSE
Leap 15.5, Mageia 9, OpenMandriva ROME, PCLinuxOS
-