diff --git a/README.html b/README.html
index 90b9541..ea89611 100644
--- a/README.html
+++ b/README.html
@@ -1,4 +1,4 @@
-
UxPlay 1.50: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
+UxPlay 1.51: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
Now developed at GitHub site https://github.com/FDH2/UxPlay (where user issues should be posted).
Highlights:
@@ -8,7 +8,7 @@
- Support for older 32-bit iOS clients (such as iPad 2nd gen, iPhone 4S, when upgraded to iOS 9.3.5 or later), and a Windows AirPlay-client emulator, AirMyPC.
- Uses GStreamer, with options to select different output “videosinks” and “audiosinks”.
- Support for server behind a firewall.
-- New: Support for Raspberry Pi, with hardware video acceleration by Video4Linux2 (replacement for OpenMAX, which is no longer supplied in Raspberry Pi OS) (may require a patch to the GStreamer Video4Linux2 plugin.)
+- New: Support for Raspberry Pi, with hardware video acceleration by Video4Linux2 (replacement for OpenMAX, which is no longer supplied in Raspberry Pi OS) (may require a patch to the GStreamer Video4Linux2 plugin.) See success reports.
This project is a GPLv3 open source unix AirPlay2 Mirror server for Linux, macOS, and *BSD. It was initially developed by antimof using code from RPiPlay, which in turn derives from AirplayServer, shairplay, and playfair. (The antimof site is mainly inactive, but periodically posts updates pulled from the main UxPlay site).
UxPlay is tested on a number of systems, including (among others) Debian 10.11 “Buster” and 11.2 “Bullseye”, Ubuntu 20.04 and 21.10, Linux Mint 20.2, Pop!_OS 21.10 (NVIDIA edition), Rocky Linux 8.5 (a CentOS successor), OpenSUSE 15.3, Arch Linux 5.16.8, macOS 12.3 (Intel and M1), FreeBSD 13.0.
@@ -21,7 +21,7 @@
For NVIDIA graphics with the proprietary drivers, the nvh264dec plugin (included in gstreamer1.0-plugins-bad since GStreamer-1.18.0) can be used for accelerated video decoding on the NVIDIA GPU after NVIDIA’s CUDA driver libcuda.so is installed. This plugin should be used with options uxplay -vd nvh264dec -vs glimagesink. For GStreamer-1.16.3 or earlier, the plugin is called nvdec, and must be built by the user: see these instructions. This older form of the plugin should be used with the -vd nvdec -vs glimagesink uxplay options.
GPU Support for Raspberry Pi
-Raspberry Pi (RPi) computers can run UxPlay with software decoding of h264 video (by adding -avdec to the uxplay options) but this usually has unacceptable latency, and hardware-accelerated decoding by the Pi’s built-in Broadcom GPU should be used. RPi OS (Bullseye) has abandoned the omx (OpenMAX) driver used till now for this by RPiPlay, in favor of v4l2 (Video4Linux2). The GStreamer Video4Linux2 plugin only works with UxPlay since GStreamer-1.21.0.0 on the development branch, but a (partial) backport to 1.18.4 for RPi OS (Bullseye) has already appeared in current updates. In case the full update has not yet appeared, or you are using a different distribution, you can find patching instructions in the UxPlay Wiki. Patches for GStreamer-1.18.5 (used in Ubuntu 21.10 for RPi) and GStreamer-1.20.1 (used in Manjaro for RPi) are also available. On a non-“Desktop” system without X11 that uses framebuffer video (such as RPi OS Bullseye “Lite”) use option uxplay -rpi with the patched GStreamer. On “Desktop” operating systems, use the options uxplay -rpigl (for openGL video), or uxplay -rpiwl (for Wayland video); note that option -rpigl was working very well on systems with the 5.10.x Linux kernel, but the recent upgrade of RPi OS to 5.15.x kernels seems to have a regression that causes unacceptable latency.
+Raspberry Pi (RPi) computers can run UxPlay with software decoding of h264 video (by adding -avdec to the uxplay options) but this usually has unacceptable latency, and hardware-accelerated decoding by the Pi’s built-in Broadcom GPU should be used. RPi OS (Bullseye) has abandoned the omx (OpenMAX) driver used till now for this by RPiPlay, in favor of v4l2 (Video4Linux2). The GStreamer Video4Linux2 plugin only works with UxPlay since GStreamer-1.21.0.0 on the development branch, but a (partial) backport (as gstreamer1.0-plugins-good-1.18.4-2+~rpt1) for RPi OS (Bullseye) has already appeared in current updates. Until the promised full update appears, or if you are using a different distribution, you can find patching instructions in the UxPlay Wiki. Patches for GStreamer-1.18.5 (used in Ubuntu 21.10 for RPi) and GStreamer-1.20.1 (used in Manjaro for RPi) are also available. On “Desktop” operating systems with X11, use uxplay -v4l2 (or use -rpi as a synonym), and optionally specify a videosink with “-vs ..”); use uxplay -rpiwl as a synonym for “-v4l2 -vs waylandsink” on a Desktop system with Wayland. On a system without X11 with framebuffer video (such as RPi OS Bullseye “Lite”) use uxplay -rpifb as a synonym for “uxplay -v4l2 -vs kmssink”.
Note to packagers: OpenSSL-3.0.0 solves GPL v3 license issues.
Some Linux distributions such as Debian do not allow distribution of compiled GPL code linked to OpenSSL-1.1.1 because its “dual OpenSSL/SSLeay” license has some incompatibilities with GPL, unless all code authors have explicitly given an “exception” to allow such linking (the historical origins of UxPlay make this impossible to obtain). Other distributions treat OpenSSL as a “System Library” which the GPL allows linking to.
@@ -138,6 +138,7 @@
This triggers an unending stream of error messages, and means that the audio decryption key (also used in video decryption) was not correctly extracted from data sent by the client. This should not happen for iOS 9.3 or later clients. However, if a client uses the same older version of the protocol that is used by the Windows-based AirPlay client emulator AirMyPC, the protocol can be switched to the older version by the setting OLD_PROTOCOL_CLIENT_USER_AGENT_LIST in lib/global.h. UxPlay reports the client’s “User Agent” string when it connects. If some other client also fails to decrypt all audio and video, try adding its “User Agent” string in place of “xxx” in the entry “AirMyPC/2.0;xxx” in global.h and rebuild uxplay.
Note that Uxplay declares itself to be an AppleTV3,2 with a sourceVersion 220.68; this can also be changed in global.h. It had been thought that it was necessary for UxPlay to claim to be an older 32 bit AppleTV model that cannot run modern 64bit tvOS, in order for the client to use a “legacy” protocol for pairing with the server (see the “Notes on AirPlay protocol versions” at the end of this README). However, UxPlay still works if it declares itself as an AppleTV6,2 with sourceVersion 380.20.1 (an AppleTV 4K 1st gen, introduced 2017, running tvOS 12.2.1), so it is unclear what setting prompts the client to use the “legacy” protocol needed by UxPlay.
ChangeLog
+1.51 2022-04-24 Reworked options forVideo4Linux2 support (new option -v4l2) and short options -rpi, -rpifb, -rpiwl as synonyms for -v4l2, -v4l2 -vs kmssink, and -v4l2 -vs waylandsink. Reverted a change from 1.48 that broke reconnection after “Stop Mirroring” is sent by client.
1.50 2022-04-22 Added -fs fullscreen option (for Wayland or VAAPI plugins only), Changed -rpi to be for framebuffer (“lite”) RPi systems and added -rpigl (OpenGL) and -rpiwl (Wayland) options for RPi Desktop systems. Also modified timestamps from “DTS” to “PTS” for latency improvement, plus internal cleanups.
1.49 2022-03-28 Addded options for dumping video and/or audio to file, for debugging, etc. h264 PPS/SPS NALU’s are shown with -d. Fixed video-not-working for M1 Mac clients.
1.48 2022-03-11 Made the GStreamer video pipeline fully configurable, for use with hardware h264 decoding. Support for Raspberry Pi.
diff --git a/README.md b/README.md
index 2e24e7d..fcf8f86 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# UxPlay 1.50: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
+# UxPlay 1.51: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
### Now developed at GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where user issues should be posted).
@@ -18,7 +18,8 @@ Highlights:
* **New**: Support for Raspberry Pi, with hardware video acceleration by Video4Linux2 (replacement for OpenMAX, which
is no longer supplied in Raspberry Pi OS)
(may require a [patch](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches) to the GStreamer Video4Linux2 plugin.)
-
+ See [success reports](https://github.com/FDH2/UxPlay/wiki/UxPlay-on-Raspberry-Pi:-success-reports:).
+
This project is a GPLv3 open source unix AirPlay2 Mirror server for Linux, macOS, and \*BSD.
It was initially developed by
[antimof](http://github.com/antimof/Uxplay) using code
@@ -89,18 +90,18 @@ This older form of the plugin should be used with the `-vd nvdec -vs glimagesi
usually has unacceptable latency, and hardware-accelerated decoding by the Pi's built-in Broadcom GPU should be used.
RPi OS (Bullseye) has abandoned the omx (OpenMAX) driver used till now for this by [RPiPlay](http://github.com/FD-/RPiPlay), in
favor of v4l2 (Video4Linux2). The GStreamer Video4Linux2 plugin only works with UxPlay since GStreamer-1.21.0.0 on the development branch,
- but a (partial) backport to 1.18.4 for RPi OS (Bullseye) has already appeared in current updates. In case the full update has not
- yet appeared, or you are using a different distribution,
+ but a (partial) backport (as `gstreamer1.0-plugins-good-1.18.4-2+~rpt1`) for RPi OS (Bullseye) has already appeared in current updates.
+ Until the promised full update appears, or if
+ you are using a different distribution,
you can find [patching instructions](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches) in
the [UxPlay Wiki](https://github.com/FDH2/UxPlay/wiki). Patches for
GStreamer-1.18.5 (used in Ubuntu 21.10 for RPi) and GStreamer-1.20.1 (used in Manjaro for RPi) are also available.
- On a non-"Desktop" system without X11 that uses
- framebuffer video (such as RPi OS Bullseye "Lite") use option `uxplay -rpi` with the patched GStreamer.
- On "Desktop" operating systems, use the options `uxplay -rpigl`
- (for openGL video), or ``uxplay -rpiwl`` (for Wayland video); note that option `-rpigl` was working very well on
- systems with the 5.10.x Linux kernel,
- but the recent upgrade of RPi OS to 5.15.x kernels seems to have a regression that causes unacceptable latency.
-
+ On "Desktop" operating systems with X11, use `uxplay -v4l2` (or use ``-rpi `` as a synonym),
+ and optionally specify a videosink with "`-vs ..`"); use ``uxplay -rpiwl`` as a synonym
+ for "`-v4l2 -vs waylandsink`" on a Desktop system with Wayland.
+ On a system without X11 with
+ framebuffer video (such as RPi OS Bullseye "Lite") use `uxplay -rpifb` as a synonym
+ for "`uxplay -v4l2 -vs kmssink`".
### Note to packagers: OpenSSL-3.0.0 solves GPL v3 license issues.
Some Linux distributions such as Debian do not allow distribution of compiled
@@ -595,6 +596,10 @@ tvOS 12.2.1), so it is unclear what setting prompts the client
to use the "legacy" protocol needed by UxPlay.
# ChangeLog
+1.51 2022-04-24 Reworked options forVideo4Linux2 support (new option -v4l2) and short options -rpi, -rpifb, -rpiwl as
+ synonyms for -v4l2, -v4l2 -vs kmssink, and -v4l2 -vs waylandsink. Reverted a change from 1.48 that broke
+ reconnection after "Stop Mirroring" is sent by client.
+
1.50 2022-04-22 Added -fs fullscreen option (for Wayland or VAAPI plugins only), Changed -rpi to be for framebuffer ("lite") RPi
systems and added -rpigl (OpenGL) and -rpiwl (Wayland) options for RPi Desktop systems.
Also modified timestamps from "DTS" to "PTS" for latency improvement, plus internal cleanups.
diff --git a/README.txt b/README.txt
index b44fdad..d9e5ae5 100644
--- a/README.txt
+++ b/README.txt
@@ -1,4 +1,4 @@
-UxPlay 1.50: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
+UxPlay 1.51: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
======================================================================
### Now developed at GitHub site (where user issues should be posted).
@@ -24,7 +24,8 @@ Highlights:
by Video4Linux2 (replacement for OpenMAX, which is no longer
supplied in Raspberry Pi OS) (may require a
[patch](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches)
- to the GStreamer Video4Linux2 plugin.)
+ to the GStreamer Video4Linux2 plugin.) See [success
+ reports](https://github.com/FDH2/UxPlay/wiki/UxPlay-on-Raspberry-Pi:-success-reports:).
This project is a GPLv3 open source unix AirPlay2 Mirror server for
Linux, macOS, and \*BSD. It was initially developed by
@@ -114,22 +115,21 @@ This older form of the plugin should be used with the
[RPiPlay](http://github.com/FD-/RPiPlay), in favor of v4l2
(Video4Linux2). The GStreamer Video4Linux2 plugin only works with
UxPlay since GStreamer-1.21.0.0 on the development branch, but a
- (partial) backport to 1.18.4 for RPi OS (Bullseye) has already
- appeared in current updates. In case the full update has not yet
- appeared, or you are using a different distribution, you can find
- [patching
+ (partial) backport (as `gstreamer1.0-plugins-good-1.18.4-2+~rpt1`)
+ for RPi OS (Bullseye) has already appeared in current updates. Until
+ the promised full update appears, or if you are using a different
+ distribution, you can find [patching
instructions](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches)
in the [UxPlay Wiki](https://github.com/FDH2/UxPlay/wiki). Patches
for GStreamer-1.18.5 (used in Ubuntu 21.10 for RPi) and
- GStreamer-1.20.1 (used in Manjaro for RPi) are also available. On a
- non-"Desktop" system without X11 that uses framebuffer video (such
- as RPi OS Bullseye "Lite") use option `uxplay -rpi` with the
- patched GStreamer. On "Desktop" operating systems, use the options
- `uxplay -rpigl` (for openGL video), or `uxplay -rpiwl` (for Wayland
- video); note that option `-rpigl` was working very well on systems
- with the 5.10.x Linux kernel, but the recent upgrade of RPi OS to
- 5.15.x kernels seems to have a regression that causes unacceptable
- latency.
+ GStreamer-1.20.1 (used in Manjaro for RPi) are also available. On
+ "Desktop" operating systems with X11, use `uxplay -v4l2` (or use
+ `-rpi` as a synonym), and optionally specify a videosink with
+ "`-vs ..`"); use `uxplay -rpiwl` as a synonym for
+ "`-v4l2 -vs waylandsink`" on a Desktop system with Wayland. On a
+ system without X11 with framebuffer video (such as RPi OS Bullseye
+ "Lite") use `uxplay -rpifb` as a synonym for
+ "`uxplay -v4l2 -vs kmssink`".
### Note to packagers: OpenSSL-3.0.0 solves GPL v3 license issues.
@@ -777,6 +777,11 @@ the "legacy" protocol needed by UxPlay.
ChangeLog
=========
+1.51 2022-04-24 Reworked options forVideo4Linux2 support (new option
+-v4l2) and short options -rpi, -rpifb, -rpiwl as synonyms for -v4l2,
+-v4l2 -vs kmssink, and -v4l2 -vs waylandsink. Reverted a change from
+1.48 that broke reconnection after "Stop Mirroring" is sent by client.
+
1.50 2022-04-22 Added -fs fullscreen option (for Wayland or VAAPI
plugins only), Changed -rpi to be for framebuffer ("lite") RPi systems
and added -rpigl (OpenGL) and -rpiwl (Wayland) options for RPi Desktop
diff --git a/renderers/video_renderer_gstreamer.c b/renderers/video_renderer_gstreamer.c
index 839b99f..462b876 100644
--- a/renderers/video_renderer_gstreamer.c
+++ b/renderers/video_renderer_gstreamer.c
@@ -133,7 +133,7 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, vide
assert(renderer);
gst_init(NULL,NULL);
- GString *launch = g_string_new("appsrc name=video_source stream-type=0 format=GST_FORMAT_TIME is-live=true ! ");
+ GString *launch = g_string_new("appsrc name=video_source ! ");
g_string_append(launch, "queue ! ");
g_string_append(launch, parser);
g_string_append(launch, " ! ");
@@ -152,7 +152,7 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, vide
renderer->appsrc = gst_bin_get_by_name (GST_BIN (renderer->pipeline), "video_source");
assert(renderer->appsrc);
caps = gst_caps_from_string(h264_caps);
- g_object_set(renderer->appsrc, "caps", caps, NULL);
+ g_object_set(renderer->appsrc, "caps", caps, "stream-type", 0, "is-live", TRUE, "format", GST_FORMAT_TIME, NULL);
gst_caps_unref(caps);
renderer->sink = gst_bin_get_by_name (GST_BIN (renderer->pipeline), "video_sink");
diff --git a/uxplay b/uxplay
new file mode 100644
index 0000000..99ebc3c
--- /dev/null
+++ b/uxplay
@@ -0,0 +1,1010 @@
+/**
+ * RPiPlay - An open-source AirPlay mirroring server for Raspberry Pi
+ * Copyright (C) 2019 Florian Draschbacher
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#ifdef __linux__
+#include
+#else
+#include
+#endif
+
+#include "log.h"
+#include "lib/raop.h"
+#include "lib/stream.h"
+#include "lib/logger.h"
+#include "lib/dnssd.h"
+#include "renderers/video_renderer.h"
+#include "renderers/audio_renderer.h"
+
+#define VERSION "1.51"
+
+#define DEFAULT_NAME "UxPlay"
+#define DEFAULT_DEBUG_LOG false
+#define LOWEST_ALLOWED_PORT 1024
+#define HIGHEST_PORT 65535
+#define NTP_TIMEOUT_LIMIT 5
+
+static std::string server_name = DEFAULT_NAME;
+static int start_raop_server (std::vector hw_addr, std::string name, unsigned short display[5],
+ unsigned short tcp[3], unsigned short udp[3], bool debug_log);
+static int stop_raop_server ();
+extern "C" void log_callback (void *cls, int level, const char *msg) ;
+
+static dnssd_t *dnssd = NULL;
+static raop_t *raop = NULL;
+static logger_t *render_logger = NULL;
+
+static bool relaunch_video = false;
+static bool relaunch_server = false;
+static bool reset_loop = false;
+static uint open_connections = 0;
+static bool connections_stopped = false;
+static unsigned int server_timeout = 0;
+static unsigned int counter;
+static std::string videosink = "autovideosink";
+static videoflip_t videoflip[2] = { NONE , NONE };
+static bool use_video = true;
+static unsigned char compression_type = 0;
+static std::string audiosink = "autoaudiosink";
+static bool use_audio = true;
+static bool new_window_closing_behavior = true;
+static bool close_window;
+static std::string video_parser = "h264parse";
+static std::string video_decoder = "decodebin";
+static std::string video_converter = "videoconvert";
+static bool show_client_FPS_data = false;
+static unsigned int max_ntp_timeouts = NTP_TIMEOUT_LIMIT;
+static FILE *video_dumpfile = NULL;
+static std::string video_dumpfile_name = "videodump";
+static int video_dump_limit = 0;
+static int video_dumpfile_count = 0;
+static int video_dump_count = 0;
+static bool dump_video = false;
+static unsigned char mark[] = { 0x00, 0x00, 0x00, 0x01 };
+static FILE *audio_dumpfile = NULL;
+static std::string audio_dumpfile_name = "audiodump";
+static int audio_dump_limit = 0;
+static int audio_dumpfile_count = 0;
+static int audio_dump_count = 0;
+static bool dump_audio = false;
+static unsigned char audio_type = 0x00;
+static unsigned char previous_audio_type = 0x00;
+static bool fullscreen = false;
+static unsigned int audio_delay, video_delay;
+
+void dump_audio_to_file(unsigned char *data, int datalen, unsigned char type) {
+ if (!audio_dumpfile && audio_type != previous_audio_type) {
+ char suffix[20];
+ std::string fn = audio_dumpfile_name;
+ previous_audio_type = audio_type;
+ audio_dumpfile_count++;
+ audio_dump_count = 0;
+ /* type 0x20 is lossless ALAC, type 0x80 is compressed AAC-ELD, type 0x10 is "other" */
+ if (audio_type == 0x20) {
+ snprintf(suffix, sizeof(suffix), ".%d.alac", audio_dumpfile_count);
+ } else if (audio_type == 0x80) {
+ snprintf(suffix, sizeof(suffix), ".%d.aac", audio_dumpfile_count);
+ } else {
+ snprintf(suffix, sizeof(suffix), ".%d.aud", audio_dumpfile_count);
+ }
+ fn.append(suffix);
+ audio_dumpfile = fopen(fn.c_str(),"w");
+ if (audio_dumpfile == NULL) {
+ LOGE("could not open file %s for dumping audio frames",fn.c_str());
+ }
+ }
+
+ if (audio_dumpfile) {
+ fwrite(data, 1, datalen, audio_dumpfile);
+ if (audio_dump_limit) {
+ audio_dump_count++;
+ if (audio_dump_count == audio_dump_limit) {
+ fclose(audio_dumpfile);
+ audio_dumpfile = NULL;
+ }
+ }
+ }
+}
+
+void dump_video_to_file(unsigned char *data, int datalen) {
+ /* SPS NAL has (data[4] & 0x1f) = 0x07 */
+ if ((data[4] & 0x1f) == 0x07 && video_dumpfile && video_dump_limit) {
+ fwrite(mark, 1, sizeof(mark), video_dumpfile);
+ fclose(video_dumpfile);
+ video_dumpfile = NULL;
+ video_dump_count = 0;
+ }
+
+ if (!video_dumpfile) {
+ std::string fn = video_dumpfile_name;
+ if (video_dump_limit) {
+ char suffix[20];
+ video_dumpfile_count++;
+ snprintf(suffix, sizeof(suffix), ".%d", video_dumpfile_count);
+ fn.append(suffix);
+ }
+ fn.append(".h264");
+ video_dumpfile = fopen (fn.c_str(),"w");
+ if (video_dumpfile == NULL) {
+ LOGE("could not open file %s for dumping h264 frames",fn.c_str());
+ }
+ }
+
+ if (video_dumpfile) {
+ if (video_dump_limit == 0) {
+ fwrite(data, 1, datalen, video_dumpfile);
+ } else if (video_dump_count < video_dump_limit) {
+ video_dump_count++;
+ fwrite(data, 1, datalen, video_dumpfile);
+ }
+ }
+}
+
+static gboolean connection_callback (gpointer loop){
+ if (!connections_stopped) {
+ counter = 0;
+ } else {
+ if (++counter == server_timeout) {
+ LOGD("no connections for %d seconds: relaunch server",server_timeout);
+ relaunch_server = true;
+ relaunch_video = false;
+ g_main_loop_quit((GMainLoop *) loop);
+ }
+ }
+ return TRUE;
+}
+
+static gboolean reset_callback(gpointer loop) {
+ if (reset_loop) {
+ g_main_loop_quit((GMainLoop *) loop);
+ }
+ return TRUE;
+}
+
+static gboolean sigint_callback(gpointer loop) {
+ relaunch_video = false;
+ relaunch_server = false;
+ g_main_loop_quit((GMainLoop *) loop);
+ return TRUE;
+}
+
+static gboolean sigterm_callback(gpointer loop) {
+ relaunch_video = false;
+ relaunch_server = false;
+ g_main_loop_quit((GMainLoop *) loop);
+ return TRUE;
+}
+
+static void main_loop() {
+ guint connection_watch_id = 0;
+ guint gst_bus_watch_id = 0;
+ GMainLoop *loop = g_main_loop_new(NULL,FALSE);
+ if (server_timeout) {
+ connection_watch_id = g_timeout_add_seconds(1, (GSourceFunc) connection_callback, (gpointer) loop);
+ }
+ relaunch_video = false;
+ relaunch_server = false;
+ if (use_video) {
+ relaunch_video = true;
+ gst_bus_watch_id = (guint) video_renderer_listen((void *)loop);
+ }
+ guint reset_watch_id = g_timeout_add(100, (GSourceFunc) reset_callback, (gpointer) loop);
+ guint sigterm_watch_id = g_unix_signal_add(SIGTERM, (GSourceFunc) sigterm_callback, (gpointer) loop);
+ guint sigint_watch_id = g_unix_signal_add(SIGINT, (GSourceFunc) sigint_callback, (gpointer) loop);
+ g_main_loop_run(loop);
+
+ if (gst_bus_watch_id > 0) g_source_remove(gst_bus_watch_id);
+ if (sigint_watch_id > 0) g_source_remove(sigint_watch_id);
+ if (sigterm_watch_id > 0) g_source_remove(sigterm_watch_id);
+ if (reset_watch_id > 0) g_source_remove(reset_watch_id);
+ if (connection_watch_id > 0) g_source_remove(connection_watch_id);
+ g_main_loop_unref(loop);
+}
+
+static int parse_hw_addr (std::string str, std::vector &hw_addr) {
+ for (int i = 0; i < str.length(); i += 3) {
+ hw_addr.push_back((char) stol(str.substr(i), NULL, 16));
+ }
+ return 0;
+}
+
+static std::string find_mac () {
+/* finds the MAC address of a network interface *
+ * in a Linux, *BSD or macOS system. */
+ std::string mac = "";
+ struct ifaddrs *ifap, *ifaptr;
+ int non_null_octets = 0;
+ unsigned char octet[6], *ptr;
+ if (getifaddrs(&ifap) == 0) {
+ for(ifaptr = ifap; ifaptr != NULL; ifaptr = ifaptr->ifa_next) {
+ if(ifaptr->ifa_addr == NULL) continue;
+#ifdef __linux__
+ if (ifaptr->ifa_addr->sa_family != AF_PACKET) continue;
+ struct sockaddr_ll *s = (struct sockaddr_ll*) ifaptr->ifa_addr;
+ for (int i = 0; i < 6; i++) {
+ if ((octet[i] = s->sll_addr[i]) != 0) non_null_octets++;
+ }
+#else /* macOS and *BSD */
+ if (ifaptr->ifa_addr->sa_family != AF_LINK) continue;
+ ptr = (unsigned char *) LLADDR((struct sockaddr_dl *) ifaptr->ifa_addr);
+ for (int i= 0; i < 6 ; i++) {
+ if ((octet[i] = *ptr) != 0) non_null_octets++;
+ ptr++;
+ }
+#endif
+ if (non_null_octets) {
+ mac.erase();
+ char str[3];
+ for (int i = 0; i < 6 ; i++) {
+ sprintf(str,"%02x", octet[i]);
+ mac = mac + str;
+ if (i < 5) mac = mac + ":";
+ }
+ break;
+ }
+ }
+ }
+ freeifaddrs(ifap);
+ return mac;
+}
+
+#define MULTICAST 0
+#define LOCAL 1
+#define OCTETS 6
+static std::string random_mac () {
+ char str[3];
+ int octet = rand() % 64;
+ octet = (octet << 1) + LOCAL;
+ octet = (octet << 1) + MULTICAST;
+ snprintf(str,3,"%02x",octet);
+ std::string mac_address(str);
+ for (int i = 1; i < OCTETS; i++) {
+ mac_address = mac_address + ":";
+ octet = rand() % 256;
+ snprintf(str,3,"%02x",octet);
+ mac_address = mac_address + str;
+ }
+ return mac_address;
+}
+
+static void print_info (char *name) {
+ printf("UxPlay %s: An open-source AirPlay mirroring server based on RPiPlay\n", VERSION);
+ printf("Usage: %s [-n name] [-s wxh] [-p [n]]\n", name);
+ printf("Options:\n");
+ printf("-n name Specify the network name of the AirPlay server\n");
+ printf("-nh Do not add \"@hostname\" at the end of the AirPlay server name\n");
+ printf("-s wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60]\n");
+ printf("-delay n delay audio n microsecs (+ or -) relative to video\n");
+ printf("-o Set mirror \"overscanned\" mode on (not usually needed)\n");
+ printf("-fs Full-screen (only with Wayland and VAAPI plugins)\n");
+ printf("-fps n Set maximum allowed streaming framerate, default 30\n");
+ printf("-f {H|V|I}Horizontal|Vertical flip, or both=Inversion=rotate 180 deg\n");
+ printf("-r {R|L} Rotate 90 degrees Right (cw) or Left (ccw)\n");
+ printf("-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");
+ printf(" \"-p tcp n\" or \"-p udp n\" sets TCP or UDP ports separately\n");
+ printf("-m Use random MAC address (use for concurrent UxPlay's)\n");
+ printf("-t n Relaunch server if no connection existed in last n seconds\n");
+ printf("-vp ... Choose the GSteamer h264 parser: default \"h264parse\"\n");
+ printf("-vd ... Choose the GStreamer h264 decoder; default \"decodebin\"\n");
+ printf(" choices: (software) avdec_h264; (hardware) v4l2h264dec,\n");
+ printf(" nvdec, nvh264dec, vaapih64dec, vtdec,etc.\n");
+ printf(" choices: avdec_h264,vaapih264dec,nvdec,nvh264dec,v4l2h264dec\n");
+ printf("-vc ... Choose the GStreamer videoconverter; default \"videoconvert\"\n");
+ printf(" another choice when using v4l2h264decode: v4l2convert\n");
+ printf("-vs ... Choose the GStreamer videosink; default \"autovideosink\"\n");
+ printf(" some choices: ximagesink,xvimagesink,vaapisink,glimagesink,\n");
+ printf(" gtksink,waylandsink,osximagesink,kmssink,fpsdisplaysink etc.\n");
+ printf("-vs 0 Streamed audio only, with no video display window\n");
+ printf("-v4l2 Use Video4Linux2 for GPU hardware h264 decoding\n");
+ printf("-rpi Same as \"-v4l2\" (for RPi, RaspBerry Pi).\n");
+ printf("-rpifb Same as \"-v4l2 -vs kmssink\" for RPi using framebuffer.\n");
+ printf("-rpiwl Same as \"-v4l2 -vs waylandsink\" for RPi using Wayland.\n");
+ printf("-avdec Force software h264 video decoding with libav decoder\n");
+ printf("-as ... Choose the GStreamer audiosink; default \"autoaudiosink\"\n");
+ printf(" choices: pulsesink,alsasink,osssink,oss4sink,osxaudiosink\n");
+ printf("-as 0 (or -a) Turn audio off, streamed video only\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("-FPSdata Show video-streaming performance reports sent by client.\n");
+ printf("-vdmp [n] Dump h264 video output to \"fn.h264\"; fn=\"videodump\",change\n");
+ printf(" with \"-vdmp [n] filename\". If [n] is given, file fn.x.h264\n");
+ printf(" x=1,2,.. opens whenever a new SPS/PPS NAL arrives, and <=n\n");
+ printf(" NAL units are dumped.\n");
+ printf("-admp [n] Dump audio output to \"fn.x.fmt\", fmt ={aac, alac, aud}, x\n");
+ printf(" =1,2,..; fn=\"audiodump\"; change with \"-admp [n] filename\".\n");
+ printf(" x increases when audio format changes. If n is given, <= n\n");
+ printf(" audio packets are dumped. \"aud\"= unknown format.\n");
+ printf("-d Enable debug logging\n");
+ printf("-v or -h Displays this help and version information\n");
+}
+
+bool option_has_value(const int i, const int argc, std::string option, const char *next_arg) {
+ if (i >= argc - 1 || next_arg[0] == '-') {
+ LOGE("invalid: \"%s\" had no argument", option.c_str());
+ return false;
+ }
+ return true;
+}
+
+static bool get_display_settings (std::string value, unsigned short *w, unsigned short *h, unsigned short *r) {
+ // assume str = wxh@r is valid if w and h are positive decimal integers
+ // with no more than 4 digits, r < 256 (stored in one byte).
+ char *end;
+ std::size_t pos = value.find_first_of("x");
+ if (pos == std::string::npos) return false;
+ std::string str1 = value.substr(pos+1);
+ value.erase(pos);
+ if (value.length() == 0 || value.length() > 4 || value[0] == '-') return false;
+ *w = (unsigned short) strtoul(value.c_str(), &end, 10);
+ if (*end || *w == 0) return false;
+ pos = str1.find_first_of("@");
+ if(pos != std::string::npos) {
+ std::string str2 = str1.substr(pos+1);
+ if (str2.length() == 0 || str2.length() > 3 || str2[0] == '-') return false;
+ *r = (unsigned short) strtoul(str2.c_str(), &end, 10);
+ if (*end || *r == 0 || *r > 255) return false;
+ str1.erase(pos);
+ }
+ if (str1.length() == 0 || str1.length() > 4 || str1[0] == '-') return false;
+ *h = (unsigned short) strtoul(str1.c_str(), &end, 10);
+ if (*end || *h == 0) return false;
+ return true;
+}
+
+static bool get_value (const char *str, unsigned int *n) {
+ // if n > 0 str must be a positive decimal <= input value *n
+ // if n = 0, str must be a non-negative decimal
+ if (strlen(str) == 0 || strlen(str) > 10 || str[0] == '-') return false;
+ char *end;
+ unsigned long l = strtoul(str, &end, 10);
+ if (*end) return false;
+ if (*n && (l == 0 || l > *n)) return false;
+ *n = (unsigned int) l;
+ return true;
+}
+
+static bool get_ports (int nports, std::string option, const char * value, unsigned short * const port) {
+ /*valid entries are comma-separated values port_1,port_2,...,port_r, 0 < r <= nports */
+ /*where ports are distinct, and are in the allowed range. */
+ /*missing values are consecutive to last given value (at least one value needed). */
+ char *end;
+ unsigned long l;
+ std::size_t pos;
+ std::string val(value), str;
+ for (int i = 0; i <= nports ; i++) {
+ if(i == nports) break;
+ pos = val.find_first_of(',');
+ str = val.substr(0,pos);
+ if(str.length() == 0 || str.length() > 5 || str[0] == '-') break;
+ l = strtoul(str.c_str(), &end, 10);
+ if (*end || l < LOWEST_ALLOWED_PORT || l > HIGHEST_PORT) break;
+ *(port + i) = (unsigned short) l;
+ for (int j = 0; j < i ; j++) {
+ if( *(port + j) == *(port + i)) break;
+ }
+ if(pos == std::string::npos) {
+ if (nports + *(port + i) > i + 1 + HIGHEST_PORT) break;
+ for (int j = i + 1; j < nports; j++) {
+ *(port + j) = *(port + j - 1) + 1;
+ }
+ return true;
+ }
+ val.erase(0, pos+1);
+ }
+ LOGE("invalid \"%s %s\", all %d ports must be in range [%d,%d]",
+ option.c_str(), value, nports, LOWEST_ALLOWED_PORT, HIGHEST_PORT);
+ return false;
+}
+
+static bool get_videoflip (const char *str, videoflip_t *videoflip) {
+ if (strlen(str) > 1) return false;
+ switch (str[0]) {
+ case 'I':
+ *videoflip = INVERT;
+ break;
+ case 'H':
+ *videoflip = HFLIP;
+ break;
+ case 'V':
+ *videoflip = VFLIP;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+static bool get_videorotate (const char *str, videoflip_t *videoflip) {
+ if (strlen(str) > 1) return false;
+ switch (str[0]) {
+ case 'L':
+ *videoflip = LEFT;
+ break;
+ case 'R':
+ *videoflip = RIGHT;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+static void append_hostname(std::string &server_name) {
+ struct utsname buf;
+ if (!uname(&buf)) {
+ std::string name = server_name;
+ name.append("@");
+ name.append(buf.nodename);
+ server_name = name;
+ }
+}
+
+int main (int argc, char *argv[]) {
+ std::vector server_hw_addr;
+ bool do_append_hostname = true;
+ bool use_random_hw_addr = false;
+ bool debug_log = DEFAULT_DEBUG_LOG;
+ unsigned short display[5] = {0}, tcp[3] = {0}, udp[3] = {0};
+
+#ifdef SUPPRESS_AVAHI_COMPAT_WARNING
+ // suppress avahi_compat nag message. avahi emits a "nag" warning (once)
+ // if getenv("AVAHI_COMPAT_NOWARN") returns null.
+ static char avahi_compat_nowarn[] = "AVAHI_COMPAT_NOWARN=1";
+ if (!getenv("AVAHI_COMPAT_NOWARN")) putenv(avahi_compat_nowarn);
+#endif
+
+
+ // Parse arguments
+ for (int i = 1; i < argc; i++) {
+ std::string arg(argv[i]);
+ if (arg == "-n") {
+ if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
+ server_name = std::string(argv[++i]);
+ } else if (arg == "-nh") {
+ do_append_hostname = false;
+ } else if (arg == "-s") {
+ if (!option_has_value(i, argc, argv[i], argv[i+1])) exit(1);
+ std::string value(argv[++i]);
+ if (!get_display_settings(value, &display[0], &display[1], &display[2])) {
+ fprintf(stderr, "invalid \"-s %s\"; -s wxh : max w,h=9999; -s wxh@r : max r=255\n",
+ argv[i]);
+ exit(1);
+ }
+ } else if (arg == "-fps") {
+ if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
+ unsigned int n = 255;
+ if (!get_value(argv[++i], &n)) {
+ fprintf(stderr, "invalid \"-fps %s\"; -fps n : max n=255, default n=30\n", argv[i]);
+ exit(1);
+ }
+ display[3] = (unsigned short) n;
+ } else if (arg == "-o") {
+ display[4] = 1;
+ } else if (arg == "-f") {
+ if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
+ if (!get_videoflip(argv[++i], &videoflip[0])) {
+ fprintf(stderr,"invalid \"-f %s\" , unknown flip type, choices are H, V, I\n",argv[i]);
+ exit(1);
+ }
+ } else if (arg == "-r") {
+ if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
+ if (!get_videorotate(argv[++i], &videoflip[1])) {
+ fprintf(stderr,"invalid \"-r %s\" , unknown rotation type, choices are R, L\n",argv[i]);
+ exit(1);
+ }
+ } else if (arg == "-p") {
+ if (i == argc - 1 || argv[i + 1][0] == '-') {
+ tcp[0] = 7100; tcp[1] = 7000; tcp[2] = 7001;
+ udp[0] = 7011; udp[1] = 6001; udp[2] = 6000;
+ continue;
+ }
+ std::string value(argv[++i]);
+ if (value == "tcp") {
+ arg.append(" tcp");
+ if(!get_ports(3, arg, argv[++i], tcp)) exit(1);
+ } else if (value == "udp") {
+ arg.append( " udp");
+ if(!get_ports(3, arg, argv[++i], udp)) exit(1);
+ } else {
+ if(!get_ports(3, arg, argv[i], tcp)) exit(1);
+ for (int j = 1; j < 3; j++) {
+ udp[j] = tcp[j];
+ }
+ }
+ } else if (arg == "-m") {
+ use_random_hw_addr = true;
+ } else if (arg == "-a") {
+ use_audio = false;
+ } else if (arg == "-d") {
+ debug_log = !debug_log;
+ } else if (arg == "-h" || arg == "-v") {
+ print_info(argv[0]);
+ exit(0);
+ } else if (arg == "-vp") {
+ if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
+ video_parser.erase();
+ video_parser.append(argv[++i]);
+ } else if (arg == "-vd") {
+ if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
+ video_decoder.erase();
+ video_decoder.append(argv[++i]);
+ } else if (arg == "-vc") {
+ if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
+ video_converter.erase();
+ video_converter.append(argv[++i]);
+ } else if (arg == "-vs") {
+ if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
+ videosink.erase();
+ videosink.append(argv[++i]);
+ } else if (arg == "-as") {
+ if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
+ audiosink.erase();
+ audiosink.append(argv[++i]);
+ } else if (arg == "-t") {
+ if (!option_has_value(i, argc, argv[i], argv[i+1])) exit(1);
+ server_timeout = 0;
+ bool valid = get_value(argv[++i], &server_timeout);
+ if (!valid || server_timeout == 0) {
+ fprintf(stderr,"invalid \"-t %s\", must have -t n with n > 0\n",argv[i]);
+ exit(1);
+ }
+ } else if (arg == "-nc") {
+ new_window_closing_behavior = false;
+ } else if (arg == "-avdec") {
+ video_parser.erase();
+ video_parser = "h264parse";
+ video_decoder.erase();
+ video_decoder = "avdec_h264";
+ video_converter.erase();
+ video_converter = "videoconvert";
+ } else if (arg == "-v4l2" || arg == "rpi") {
+ video_decoder.erase();
+ video_decoder = "v4l2h264dec";
+ video_converter.erase();
+ video_converter = "v4l2convert";
+ } else if (arg == "-rpifb") {
+ video_decoder.erase();
+ video_decoder = "v4l2h264dec";
+ video_converter.erase();
+ video_converter = "v4l2convert";
+ videosink.erase();
+ videosink = "kmssink";
+ } else if (arg == "-rpiwl" ) {
+ video_decoder.erase();
+ video_decoder = "v4l2h264dec";
+ video_converter.erase();
+ video_converter = "v4l2convert";
+ videosink.erase();
+ videosink = "waylandsink";
+ } else if (arg == "-rpi" ) {
+ video_decoder.erase();
+ video_decoder = "v4l2h264dec";
+ video_converter.erase();
+ video_converter = "v4l2convert";
+ videosink.erase();
+ videosink = "kmssink";
+ } else if (arg == "-fs" ) {
+ fullscreen = true;
+ } else if (arg == "-FPSdata") {
+ show_client_FPS_data = true;
+ } else if (arg =="-delay") {
+ if (argv[i+1]) {
+ bool delay_audio = true;
+ unsigned int delay = 0;
+ char *val = argv[++i];
+ if (val[0] == '-') {
+ delay_audio = false;
+ val++;
+ }
+ if (!get_value(val, &delay)) {
+ fprintf(stderr, "invalid \"-delay %s\": -delay n must have integer n\n", argv[i]);
+ exit(1);
+ }
+ if (delay_audio) {
+ audio_delay = delay;
+ video_delay = 0;
+ } else {
+ audio_delay = 0;
+ video_delay = delay;
+ }
+ } else {
+ fprintf(stderr, "invalid \"-delay \": -delay n must have an integer argument n\n");
+ exit(1);
+ }
+ } else if (arg == "-reset") {
+ max_ntp_timeouts = 0;
+ if (!get_value(argv[++i], &max_ntp_timeouts)) {
+ fprintf(stderr, "invalid \"-reset %s\"; -reset n must have n >= 0, default n = %d\n", argv[i], NTP_TIMEOUT_LIMIT);
+ exit(1);
+ }
+ } else if (arg == "-vdmp") {
+ dump_video = true;
+ if (option_has_value(i, argc, arg, argv[i+1])) {
+ unsigned int n = 0;
+ if (get_value (argv[++i], &n)) {
+ if (n == 0) {
+ fprintf(stderr, "invalid \"-vdmp 0 %s\"; -vdmp n needs a non-zero value of n\n", argv[i]);
+ exit(1);
+ }
+ video_dump_limit = n;
+ if (option_has_value(i, argc, arg, argv[i+1])) {
+ video_dumpfile_name.erase();
+ video_dumpfile_name.append(argv[++i]);
+ }
+ } else {
+ video_dumpfile_name.erase();
+ video_dumpfile_name.append(argv[i]);
+ }
+ }
+ printf("dump_video %d %d %s \n",dump_video, video_dump_limit, video_dumpfile_name.c_str());
+ } else if (arg == "-admp") {
+ dump_audio = true;
+ if (option_has_value(i, argc, arg, argv[i+1])) {
+ unsigned int n = 0;
+ if (get_value (argv[++i], &n)) {
+ if (n == 0) {
+ fprintf(stderr, "invalid \"-admp 0 %s\"; -admp n needs a non-zero value of n\n", argv[i]);
+ exit(1);
+ }
+ audio_dump_limit = n;
+ if (option_has_value(i, argc, arg, argv[i+1])) {
+ audio_dumpfile_name.erase();
+ audio_dumpfile_name.append(argv[++i]);
+ }
+ } else {
+ audio_dumpfile_name.erase();
+ audio_dumpfile_name.append(argv[i]);
+ }
+ }
+ } else {
+ LOGE("unknown option %s, stopping\n",argv[i]);
+ exit(1);
+ }
+ }
+
+ if (audiosink == "0") {
+ use_audio = false;
+ }
+
+#if __APPLE__
+ /* force use of -nc option on macOS */
+ LOGI("macOS detected: use -nc option as workaround for GStreamer problem");
+ new_window_closing_behavior = false;
+ server_timeout = 0;
+#endif
+
+ if (videosink == "0") {
+ use_video = false;
+ videosink.erase();
+ videosink.append("fakesink");
+ LOGI("video_disabled");
+ display[3] = 1; /* set fps to 1 frame per sec when no video will be shown */
+ }
+
+ if (fullscreen && use_video) {
+ videosink.append(" fullscreen=true");
+ }
+
+ if (do_append_hostname) append_hostname(server_name);
+
+ render_logger = logger_init();
+ logger_set_callback(render_logger, log_callback, NULL);
+ logger_set_level(render_logger, debug_log ? LOGGER_DEBUG : LOGGER_INFO);
+
+ if (use_audio) {
+ audio_renderer_init(render_logger, audiosink.c_str(), &audio_delay);
+ } else {
+ LOGI("audio_disabled");
+ }
+
+ if (use_video) {
+ video_renderer_init(render_logger, server_name.c_str(), videoflip, video_parser.c_str(),
+ video_decoder.c_str(), video_converter.c_str(), videosink.c_str(), &video_delay);
+ video_renderer_start();
+ }
+
+ if (udp[0]) LOGI("using network ports UDP %d %d %d TCP %d %d %d",
+ udp[0],udp[1], udp[2], tcp[0], tcp[1], tcp[2]);
+
+ std::string mac_address;
+ if (!use_random_hw_addr) mac_address = find_mac();
+ if (mac_address.empty()) {
+ srand(time(NULL) * getpid());
+ mac_address = random_mac();
+ LOGI("using randomly-generated MAC address %s",mac_address.c_str());
+ } else {
+ LOGI("using system MAC address %s",mac_address.c_str());
+ }
+ parse_hw_addr(mac_address, server_hw_addr);
+ mac_address.clear();
+
+ connections_stopped = true;
+ relaunch:
+ if (start_raop_server(server_hw_addr, server_name, display, tcp, udp, debug_log)) {
+ return 1;
+ }
+ reconnect:
+ counter = 0;
+ compression_type = 0;
+ close_window = new_window_closing_behavior;
+ main_loop();
+ if (relaunch_server || relaunch_video || reset_loop) {
+ if(reset_loop) {
+ reset_loop = false;
+ } else {
+ raop_stop(raop);
+ }
+ if (use_audio) audio_renderer_stop();
+ if (use_video && close_window) {
+ video_renderer_destroy();
+ video_renderer_init(render_logger, server_name.c_str(), videoflip, video_parser.c_str(),
+ video_decoder.c_str(), video_converter.c_str(), videosink.c_str(), &video_delay);
+ video_renderer_start();
+ }
+ if (reset_loop) goto reconnect;
+ if (relaunch_video) {
+ unsigned short port = raop_get_port(raop);
+ raop_start(raop, &port);
+ raop_set_port(raop, port);
+ goto reconnect;
+ } else {
+ LOGI("Re-launching RAOP server...");
+ stop_raop_server();
+ goto relaunch;
+ }
+ } else {
+ LOGI("Stopping...");
+ stop_raop_server();
+ }
+ if (use_audio) {
+ audio_renderer_destroy();
+ }
+ if (use_video) {
+ video_renderer_destroy();
+ }
+ logger_destroy(render_logger);
+ render_logger = NULL;
+ if(audio_dumpfile) {
+ fclose(audio_dumpfile);
+ }
+ if (video_dumpfile) {
+ fwrite(mark, 1, sizeof(mark), video_dumpfile);
+ fclose(video_dumpfile);
+ }
+}
+
+// Server callbacks
+extern "C" void conn_init (void *cls) {
+ open_connections++;
+ connections_stopped = false;
+ LOGI("Open connections: %i", open_connections);
+ //video_renderer_update_background(1);
+}
+
+extern "C" void conn_destroy (void *cls) {
+ //video_renderer_update_background(-1);
+ open_connections--;
+ LOGI("Open connections: %i", open_connections);
+ if(!open_connections) {
+ connections_stopped = true;
+ }
+}
+
+extern "C" void conn_reset (void *cls, int timeouts, bool reset_video) {
+ LOGI("***ERROR lost connection with client (network problem?)");
+ if (timeouts) {
+ LOGI(" Client no-response limit of %d timeouts (%d seconds) reached:", timeouts, 3*timeouts);
+ 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 */
+ raop_stop(raop);
+ reset_loop = true;
+}
+
+extern "C" void conn_teardown(void *cls, bool *teardown_96, bool *teardown_110) {
+ if (*teardown_110 && close_window) {
+ reset_loop = true;
+ }
+}
+
+extern "C" void audio_process (void *cls, raop_ntp_t *ntp, aac_decode_struct *data) {
+ if (dump_audio) {
+ dump_audio_to_file(data->data, data->data_len, (data->data)[0] & 0xf0);
+ }
+ if (use_audio) {
+ audio_renderer_render_buffer(ntp, data->data, data->data_len, data->pts);
+ }
+}
+
+extern "C" void video_process (void *cls, raop_ntp_t *ntp, h264_decode_struct *data) {
+ if (dump_video) {
+ dump_video_to_file(data->data, data->data_len);
+ }
+ if (use_video) {
+ video_renderer_render_buffer(ntp, data->data, data->data_len, data->pts, data->nal_count);
+ }
+}
+
+extern "C" void audio_flush (void *cls) {
+ if (use_audio) {
+ audio_renderer_flush();
+ }
+}
+
+extern "C" void video_flush (void *cls) {
+ if (use_video) {
+ video_renderer_flush();
+ }
+}
+
+extern "C" void audio_set_volume (void *cls, float volume) {
+ if (use_audio) {
+ audio_renderer_set_volume(volume);
+ }
+}
+
+extern "C" void audio_get_format (void *cls, unsigned char *ct, unsigned short *spf, bool *usingScreen, bool *isMedia, uint64_t *audioFormat) {
+ unsigned char type;
+ LOGI("ct=%d spf=%d usingScreen=%d isMedia=%d audioFormat=0x%lx",*ct, *spf, *usingScreen, *isMedia, (unsigned long) *audioFormat);
+ switch (*ct) {
+ case 2:
+ type = 0x20;
+ break;
+ case 8:
+ type = 0x80;
+ break;
+ default:
+ type = 0x10;
+ break;
+ }
+ if (audio_dumpfile && type != audio_type) {
+ fclose(audio_dumpfile);
+ audio_dumpfile = NULL;
+ }
+ audio_type = type;
+
+ if (use_audio) {
+ audio_renderer_start(ct);
+ }
+}
+
+extern "C" void video_report_size(void *cls, float *width_source, float *height_source, float *width, float *height) {
+ video_renderer_size(width_source, height_source, width, height);
+}
+
+extern "C" void log_callback (void *cls, int level, const char *msg) {
+ switch (level) {
+ case LOGGER_DEBUG: {
+ LOGD("%s", msg);
+ break;
+ }
+ case LOGGER_WARNING: {
+ LOGW("%s", msg);
+ break;
+ }
+ case LOGGER_INFO: {
+ LOGI("%s", msg);
+ break;
+ }
+ case LOGGER_ERR: {
+ LOGE("%s", msg);
+ break;
+ }
+ default:
+ break;
+ }
+
+}
+
+int start_raop_server (std::vector hw_addr, std::string name, unsigned short display[5],
+ unsigned short tcp[3], unsigned short udp[3], bool debug_log) {
+ raop_callbacks_t raop_cbs;
+ memset(&raop_cbs, 0, sizeof(raop_cbs));
+ raop_cbs.conn_init = conn_init;
+ raop_cbs.conn_destroy = conn_destroy;
+ raop_cbs.conn_reset = conn_reset;
+ raop_cbs.conn_teardown = conn_teardown;
+ raop_cbs.audio_process = audio_process;
+ raop_cbs.video_process = video_process;
+ raop_cbs.audio_flush = audio_flush;
+ raop_cbs.video_flush = video_flush;
+ raop_cbs.audio_set_volume = audio_set_volume;
+ raop_cbs.audio_get_format = audio_get_format;
+ raop_cbs.video_report_size = video_report_size;
+
+ /* set max number of connections = 2 */
+ raop = raop_init(2, &raop_cbs);
+ if (raop == NULL) {
+ LOGE("Error initializing 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 */
+
+ if (display[0]) raop_set_plist(raop, "width", (int) display[0]);
+ if (display[1]) raop_set_plist(raop, "height", (int) display[1]);
+ if (display[2]) raop_set_plist(raop, "refreshRate", (int) display[2]);
+ if (display[3]) raop_set_plist(raop, "maxFPS", (int) display[3]);
+ if (display[4]) raop_set_plist(raop, "overscanned", (int) display[4]);
+
+ if (show_client_FPS_data) raop_set_plist(raop, "clientFPSdata", 1);
+ raop_set_plist(raop, "max_ntp_timeouts", max_ntp_timeouts);
+
+ /* 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, debug_log ? RAOP_LOG_DEBUG : LOGGER_INFO);
+
+ unsigned short port = raop_get_port(raop);
+ raop_start(raop, &port);
+ raop_set_port(raop, port);
+
+ int error;
+ dnssd = dnssd_init(name.c_str(), strlen(name.c_str()), hw_addr.data(), hw_addr.size(), &error);
+ if (error) {
+ LOGE("Could not initialize dnssd library!");
+ stop_raop_server();
+ return -2;
+ }
+
+ raop_set_dnssd(raop, dnssd);
+
+ dnssd_register_raop(dnssd, port);
+ if (tcp[2]) {
+ port = tcp[2];
+ } else {
+ port = (port != HIGHEST_PORT ? port + 1 : port - 1);
+ }
+ dnssd_register_airplay(dnssd, port);
+
+ return 0;
+}
+
+int stop_raop_server () {
+ if (raop) {
+ raop_destroy(raop);
+ raop = NULL;
+ }
+ if (dnssd) {
+ dnssd_unregister_raop(dnssd);
+ dnssd_unregister_airplay(dnssd);
+ dnssd_destroy(dnssd);
+ dnssd = NULL;
+ }
+ return 0;
+}
diff --git a/uxplay.1 b/uxplay.1
index 5f17540..2b430c7 100644
--- a/uxplay.1
+++ b/uxplay.1
@@ -60,11 +60,13 @@ UxPlay 1.50: An open\-source AirPlay mirroring server based on RPiPlay
.TP
\fB\-vs\fR 0 Streamed audio only, with no video display window.
.TP
-\fB\-rpi\fR Raspberry Pi "Lite" GPU video with KMS framebuffer.
+\fB\-v4l2\fR Use Video4Linux2 for GPU hardware h264 video decoding.
.TP
-\fB\-rpigl\fR Raspberry Pi GPU video with OpenGL.
+\fB\-rpi\fR Same as "-v4l2" (for RPi=Rasberry Pi).
.TP
-\fB\-rpiwl\fR Raspberry Pi GPU video with Wayland compositor.
+\fB\-rpifb\fR Same as "-v4l2 -vs kmssink" for RPi using framebuffer.
+.TP
+\fB\-rpiwl\fR Same as "-v4l2 -vs waylandsink" for RPi using Wayland.
.TP
\fB\-avdec\fR Force software h264 video decoding with libav decoder.
.TP
diff --git a/uxplay.cpp b/uxplay.cpp
index 39e44c8..aecf68c 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -44,7 +44,7 @@
#include "renderers/video_renderer.h"
#include "renderers/audio_renderer.h"
-#define VERSION "1.50"
+#define VERSION "1.51"
#define DEFAULT_NAME "UxPlay"
#define DEFAULT_DEBUG_LOG false
@@ -323,9 +323,10 @@ static void print_info (char *name) {
printf(" some choices: ximagesink,xvimagesink,vaapisink,glimagesink,\n");
printf(" gtksink,waylandsink,osximagesink,kmssink,fpsdisplaysink etc.\n");
printf("-vs 0 Streamed audio only, with no video display window\n");
- printf("-rpi Raspberry Pi \"Lite\" GPU video with KMS frame buffer.\n");
- printf("-rpigl Raspberry Pi GPU video with OpenGL.\n");
- printf("-rpiwl Raspberry Pi GPU video with Wayland compositor.\n");
+ printf("-v4l2 Use Video4Linux2 for GPU hardware h264 decoding\n");
+ printf("-rpi Same as \"-v4l2\" (for RPi=Raspberry Pi).\n");
+ printf("-rpifb Same as \"-v4l2 -vs kmssink\" for RPi using framebuffer.\n");
+ printf("-rpiwl Same as \"-v4l2 -vs waylandsink\" for RPi using Wayland.\n");
printf("-avdec Force software h264 video decoding with libav decoder\n");
printf("-as ... Choose the GStreamer audiosink; default \"autoaudiosink\"\n");
printf(" choices: pulsesink,alsasink,osssink,oss4sink,osxaudiosink\n");
@@ -584,13 +585,18 @@ int main (int argc, char *argv[]) {
video_decoder = "avdec_h264";
video_converter.erase();
video_converter = "videoconvert";
- } else if (arg == "-rpigl") {
+ } else if (arg == "-v4l2" || arg == "rpi") {
+ video_decoder.erase();
+ video_decoder = "v4l2h264dec";
+ video_converter.erase();
+ video_converter = "v4l2convert";
+ } else if (arg == "-rpifb") {
video_decoder.erase();
video_decoder = "v4l2h264dec";
video_converter.erase();
video_converter = "v4l2convert";
videosink.erase();
- videosink = "glimagesink";
+ videosink = "kmssink";
} else if (arg == "-rpiwl" ) {
video_decoder.erase();
video_decoder = "v4l2h264dec";
@@ -696,7 +702,7 @@ int main (int argc, char *argv[]) {
}
if (use_video) {
- video_renderer_init(render_logger, server_name.c_str(), videoflip, video_parser.c_str(),
+ video_renderer_init(render_logger, server_name.c_str(), videoflip, video_parser.c_str(),
video_decoder.c_str(), video_converter.c_str(), videosink.c_str());
video_renderer_start();
}