From b8a705312f5b5f12c94e716dab5028c42d4be4e3 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Sat, 9 Mar 2024 11:18:26 -0500 Subject: [PATCH 01/39] Minor README clarification (max audio gain) --- README.html | 23 +++++++++++++---------- README.md | 6 +++--- README.txt | 18 ++++++++++-------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/README.html b/README.html index f0df080..5734fce 100644 --- a/README.html +++ b/README.html @@ -487,9 +487,10 @@ GStreamer internal clock used to try to keep them synchronized. Starting 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.

+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.

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 +514,13 @@ 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 -range -30dB:0dB can be rescaled from Low:0, or +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.

+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 delays audio relative to video by @@ -747,9 +749,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.

+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.

Building diff --git a/README.md b/README.md index 8bc4aa4..0b08ea0 100644 --- a/README.md +++ b/README.md @@ -415,9 +415,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` diff --git a/README.txt b/README.txt index 9de8972..3d4e7df 100644 --- a/README.txt +++ b/README.txt @@ -481,9 +481,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 +506,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 : @@ -751,7 +753,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. From 32a3bfd1108fb44e3280a06f9e95cc3112e7ecc9 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Fri, 15 Mar 2024 18:18:03 -0400 Subject: [PATCH 02/39] show audio track progress with audio-mode metadata --- uxplay.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/uxplay.cpp b/uxplay.cpp index 0983467..6ba5030 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -1595,6 +1595,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,6 +1716,7 @@ 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; From 9a34792c527dcfdc8391c61fc5e34cb7e1def5e9 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Mon, 25 Mar 2024 01:23:21 -0400 Subject: [PATCH 03/39] minor README edit --- README.html | 6 ++++-- README.md | 5 +++-- README.txt | 16 +++++++++------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/README.html b/README.html index 5734fce..50f2f5c 100644 --- a/README.html +++ b/README.html @@ -382,8 +382,10 @@ decoding)
  • 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, +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 diff --git a/README.md b/README.md index 0b08ea0..a12b6cf 100644 --- a/README.md +++ b/README.md @@ -311,8 +311,9 @@ Values of `` 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 diff --git a/README.txt b/README.txt index 3d4e7df..b619edb 100644 --- a/README.txt +++ b/README.txt @@ -378,13 +378,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. From 088f6be0fb05be513089cef6edf9963702a3fa9e Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Wed, 27 Mar 2024 19:14:14 -0400 Subject: [PATCH 04/39] enable ipv6 support --- lib/httpd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/httpd.c b/lib/httpd.c index 70e607d..3b64569 100644 --- a/lib/httpd.c +++ b/lib/httpd.c @@ -409,11 +409,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); + 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"); From 196507e23e3a556fe343e4fabeafeb1583fa2860 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Thu, 28 Mar 2024 00:47:02 -0400 Subject: [PATCH 05/39] fix broken logger during early initialization stage --- lib/httpd.c | 8 ++--- lib/raop.c | 96 +++++++++++++++++++++++++++++------------------------ lib/raop.h | 3 +- uxplay.cpp | 14 +++++--- 4 files changed, 67 insertions(+), 54 deletions(-) diff --git a/lib/httpd.c b/lib/httpd.c index 3b64569..e1e3704 100644 --- a/lib/httpd.c +++ b/lib/httpd.c @@ -410,10 +410,10 @@ httpd_start(httpd_t *httpd, unsigned short *port) return -1; } 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_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"); diff --git a/lib/raop.c b/lib/raop.c index 56a48fc..bc9554e 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -342,15 +342,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 +367,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 +397,57 @@ raop_init(int max_clients, raop_callbacks_t *callbacks, const char *device_id, c return raop; } +int +raop_init2(raop_t *raop, int max_clients, const char *device_id, const char *keyfile) { + pairing_t *pairing; + httpd_t *httpd; + httpd_callbacks_t httpd_cbs; + + assert(max_clients > 0); + assert(max_clients < 100); + + /* 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, max_clients); + 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) { diff --git a/lib/raop.h b/lib/raop.h index 2888d3d..1982773 100644 --- a/lib/raop.h +++ b/lib/raop.h @@ -68,7 +68,8 @@ 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_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 max_clients, 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); diff --git a/uxplay.cpp b/uxplay.cpp index 6ba5030..22b324d 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -1723,12 +1723,19 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3], raop_cbs.check_register = check_register; raop_cbs.export_dacp = export_dacp; - /* 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 max number of connections = 2 to protect against capture by new client */ + if (raop_init2(raop, max_connections, 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 */ @@ -1747,9 +1754,6 @@ 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); From 2c850d0585f056f10aa3be59b6d64ca481853767 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Thu, 28 Mar 2024 02:10:26 -0400 Subject: [PATCH 06/39] modernize videoflip to use "video-direction" instead of "method" --- renderers/video_renderer_gstreamer.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/renderers/video_renderer_gstreamer.c b/renderers/video_renderer_gstreamer.c index 9c27c91..d346d82 100644 --- a/renderers/video_renderer_gstreamer.c +++ b/renderers/video_renderer_gstreamer.c @@ -56,49 +56,49 @@ 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 ! "); + 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 ! "); + g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_90R ! "); break; default: break; From 005703348527a931b4e7bda6fa8f12a5dec1ca23 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Sun, 21 Apr 2024 17:19:47 -0400 Subject: [PATCH 07/39] Can now specify $GSTREAMER_ROOT_DIR when building UxPlay --- README.html | 12 +++++++++--- README.md | 9 ++++++++- README.txt | 13 ++++++++++--- renderers/CMakeLists.txt | 6 ++++++ 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/README.html b/README.html index 50f2f5c..74a78a2 100644 --- a/README.html +++ b/README.html @@ -257,10 +257,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 (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).

    +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) diff --git a/README.md b/README.md index a12b6cf..f4f1576 100644 --- a/README.md +++ b/README.md @@ -210,9 +210,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 diff --git a/README.txt b/README.txt index b619edb..8320e8e 100644 --- a/README.txt +++ b/README.txt @@ -257,10 +257,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 diff --git a/renderers/CMakeLists.txt b/renderers/CMakeLists.txt index 72c73d3..97cf992 100644 --- a/renderers/CMakeLists.txt +++ b/renderers/CMakeLists.txt @@ -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() From 50799326cab678941840c0453023b79879607b66 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Mon, 22 Apr 2024 01:36:10 -0400 Subject: [PATCH 08/39] test for GStreamer>= 1.24, define GST_124 if found --- CMakeLists.txt | 6 ++++++ renderers/CMakeLists.txt | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a2ab75..225583d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/renderers/CMakeLists.txt b/renderers/CMakeLists.txt index 97cf992..1fb025d 100644 --- a/renderers/CMakeLists.txt +++ b/renderers/CMakeLists.txt @@ -38,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 From 534b1811f7692a73db7913fd9500f751dae62ca6 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Mon, 22 Apr 2024 01:50:14 -0400 Subject: [PATCH 09/39] cosmetic: remove IPV6 detection message (IPV6 is now tested) --- lib/raop_handlers.h | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index 50470a2..c418f70 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -745,7 +745,6 @@ raop_handler_setup(raop_conn_t *conn, 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], From c9cd2ed5981f4b667d6a9ca8ed9e813c953f6294 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Mon, 22 Apr 2024 17:37:03 -0400 Subject: [PATCH 10/39] disable broken pause/resume code in GStreamer>=1.24 --- uxplay.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/uxplay.cpp b/uxplay.cpp index 22b324d..63e8329 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -1484,12 +1484,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(); } From 1c0bd358e31b02dd1fb702247e22c2884cb6a999 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Mon, 22 Apr 2024 17:55:09 -0400 Subject: [PATCH 11/39] update spec file for UxPlay-1.68.3 --- uxplay.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uxplay.spec b/uxplay.spec index 225b1e5..b92fcb1 100644 --- a/uxplay.spec +++ b/uxplay.spec @@ -1,5 +1,5 @@ Name: uxplay -Version: 1.68.2 +Version: 1.68.3 Release: 1%{?dist} %global gittag v%{version} From 78658bc9a9681a33b91ab29c8ff0c38550c926fa Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Tue, 23 Apr 2024 14:50:08 -0400 Subject: [PATCH 12/39] check that ntp server has already initialized when video starts --- lib/raop_rtp_mirror.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/raop_rtp_mirror.c b/lib/raop_rtp_mirror.c index 4b0603e..4e0bc48 100644 --- a/lib/raop_rtp_mirror.c +++ b/lib/raop_rtp_mirror.c @@ -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]; From 098ca2a935716885d555e5638be2345d60f9e52c Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Thu, 9 May 2024 00:06:04 -0400 Subject: [PATCH 13/39] video pipeline: put videoflip before videoconvert, add videoscale --- renderers/video_renderer_gstreamer.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/renderers/video_renderer_gstreamer.c b/renderers/video_renderer_gstreamer.c index d346d82..9dd2a69 100644 --- a/renderers/video_renderer_gstreamer.c +++ b/renderers/video_renderer_gstreamer.c @@ -153,9 +153,10 @@ 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) { From 403f72f2c2909a54641914705f1ac8251029d559 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Fri, 10 May 2024 04:48:18 -0400 Subject: [PATCH 14/39] add fullscreen option for D3D11videosink --- uxplay.1 | 2 +- uxplay.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/uxplay.1 b/uxplay.1 index 9a0d526..568ff65 100644 --- a/uxplay.1 +++ b/uxplay.1 @@ -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 diff --git a/uxplay.cpp b/uxplay.cpp index 63e8329..b68765b 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -583,7 +583,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"); @@ -1949,7 +1949,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=property fullscreen=true"); + } else { + videosink.append(" 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"); } From f3724b4c8c17b4e2d75a23760fada8ad83968a5f Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Fri, 10 May 2024 12:32:11 -0400 Subject: [PATCH 15/39] raop_ntp.c: show errno when sendto fails --- lib/raop_ntp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/raop_ntp.c b/lib/raop_ntp.c index 57cec8d..3f18027 100644 --- a/lib/raop_ntp.c +++ b/lib/raop_ntp.c @@ -23,6 +23,7 @@ #include #include #include +#include #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); From cafc03e0dfcf0bec08b71f5b4472838445c3a3d4 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Fri, 10 May 2024 15:11:53 -0400 Subject: [PATCH 16/39] Update README with new fullscreen option for d3d11videosink (Windows) --- README.html | 33 ++++++++++++++++++++------------- README.md | 17 ++++++++++++----- README.txt | 40 +++++++++++++++++++++++----------------- 3 files changed, 55 insertions(+), 35 deletions(-) diff --git a/README.html b/README.html index 74a78a2..aa61121 100644 --- a/README.html +++ b/README.html @@ -451,11 +451,12 @@ 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 +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 @@ -867,14 +868,20 @@ used.

      -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 +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, 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”.)

        +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.

      @@ -988,7 +995,7 @@ full-screen 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.

      +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 “legacy” ports TCP 7100, 7000, 7001, UDP 6000, 6001, diff --git a/README.md b/README.md index f4f1576..3377cbc 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,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 @@ -695,9 +696,15 @@ default audio device is used. If you wish to specify the videosink using the `-vs ` option, some choices for `` 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`. @@ -783,7 +790,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 diff --git a/README.txt b/README.txt index 8320e8e..6532296 100644 --- a/README.txt +++ b/README.txt @@ -445,14 +445,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 @@ -871,14 +871,19 @@ like `\{0.0.0.00000000\}.\{98e35b2b-8eba-412e-b840-fd2c2492cf44\}`. If If you wish to specify the videosink using the `-vs ` option, some choices for `` 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`. @@ -998,7 +1003,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 From f0407d780e9c88367c56ff394a3a4cb5fbb8a783 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Sun, 12 May 2024 01:24:38 -0400 Subject: [PATCH 17/39] add zone_id to link-local ipv6 addresses --- lib/httpd.c | 15 +++++++++------ lib/httpd.h | 3 ++- lib/netutils.c | 8 +++++--- lib/netutils.h | 2 +- lib/raop.c | 35 +++++++++++++---------------------- lib/raop.h | 4 ++-- lib/raop_handlers.h | 37 ++++++++++++++++--------------------- lib/utils.c | 26 ++++++++++++++++++++++++++ lib/utils.h | 3 ++- 9 files changed, 76 insertions(+), 57 deletions(-) diff --git a/lib/httpd.c b/lib/httpd.c index e1e3704..a4adf21 100644 --- a/lib/httpd.c +++ b/lib/httpd.c @@ -115,7 +115,8 @@ httpd_remove_connection(httpd_t *httpd, http_connection_t *connection) } 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; @@ -131,7 +132,7 @@ httpd_add_connection(httpd_t *httpd, int fd, unsigned char *local, int local_len 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; @@ -152,6 +153,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,9 +174,10 @@ 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); - + 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); + #ifdef NOHOLD /* remove existing connections to make way for new connections: * this will only occur if max_connections > 2 */ @@ -190,7 +193,7 @@ httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6) } #endif - 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); diff --git a/lib/httpd.h b/lib/httpd.h index fc184e1..a606a6a 100644 --- a/lib/httpd.h +++ b/lib/httpd.h @@ -23,7 +23,8 @@ typedef struct httpd_s httpd_t; struct httpd_callbacks_s { void* opaque; - void* (*conn_init)(void *opaque, unsigned char *local, int locallen, unsigned char *remote, int remotelen); + 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); }; diff --git a/lib/netutils.c b/lib/netutils.c index 5832c43..642efba 100644 --- a/lib/netutils.c +++ b/lib/netutils.c @@ -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; } diff --git a/lib/netutils.h b/lib/netutils.h index 63854ad..e64c638 100644 --- a/lib/netutils.h +++ b/lib/netutils.h @@ -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 diff --git a/lib/raop.c b/lib/raop.c index bc9554e..9a249bc 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -88,6 +88,8 @@ struct raop_conn_s { unsigned char *remote; int remotelen; + unsigned int zone_id; + bool have_active_remote; }; typedef struct raop_conn_s raop_conn_t; @@ -95,10 +97,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 +124,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 +139,11 @@ 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->have_active_remote = false; if (raop->callbacks.conn_init) { diff --git a/lib/raop.h b/lib/raop.h index 1982773..43a7a4e 100644 --- a/lib/raop.h +++ b/lib/raop.h @@ -65,8 +65,8 @@ struct raop_callbacks_s { void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id); }; 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(raop_callbacks_t *callbacks); RAOP_API int raop_init2(raop_t *raop, int max_clients, const char *device_id, const char *keyfile); diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index c418f70..b2ba5c9 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -737,28 +737,23 @@ 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*/ - 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_timing_port_node = plist_new_uint(timing_lport); diff --git a/lib/utils.c b/lib/utils.c index 561e79b..f6c89f0 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -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; +} diff --git a/lib/utils.h b/lib/utils.h index 671115a..be5db30 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -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 From bdd61ad0cacae04624cbcfccfb3fc8c6429f069e Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Tue, 14 May 2024 21:29:33 -0400 Subject: [PATCH 18/39] minor cleanup in lib/CMakeLists.txt (libplist) --- lib/CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 4dbdd37..ed9fb67 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -80,17 +80,16 @@ else() endif() # libplist +pkg_search_module(PLIST REQUIRED libplist-2.0) +if ( PLIST_FOUND ) + message( STATUS "found libplist-${PLIST_VERSION}" ) +endif() if( APPLE OR WIN32 ) # 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} ) 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() From c4a17b83cd07f18cb72e22150468efe27cb9e859 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Tue, 14 May 2024 21:33:14 -0400 Subject: [PATCH 19/39] README fix video memory units (GB->MB) in RPi discussion --- README.html | 8 ++++---- README.md | 6 +++--- README.txt | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.html b/README.html index aa61121..cd745df 100644 --- a/README.html +++ b/README.html @@ -628,10 +628,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 diff --git a/README.md b/README.md index 3377cbc..f913862 100644 --- a/README.md +++ b/README.md @@ -502,9 +502,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 ]```. The choice `` = ``glimagesink`` is sometimes useful. diff --git a/README.txt b/README.txt index 6532296..d464317 100644 --- a/README.txt +++ b/README.txt @@ -626,10 +626,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 ]`. The choice `` = `glimagesink` is sometimes useful. With the From 0f638503808915f4c68b03b7e863bbd5c750d571 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Thu, 16 May 2024 00:35:19 -0400 Subject: [PATCH 20/39] set all AirPlay feature bits when dnssd starts --- uxplay.cpp | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 92 insertions(+), 7 deletions(-) diff --git a/uxplay.cpp b/uxplay.cpp index b68765b..43e8a5d 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -1330,6 +1330,95 @@ static int start_dnssd(std::vector 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; @@ -1765,13 +1854,9 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3], 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 { From 23030f1b43a12adb9b4919a5e0b86eb7751cfd5c Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Tue, 21 May 2024 17:49:19 -0400 Subject: [PATCH 21/39] use improved http_request with http_request_get_protocol Thanks to @shuax for finding this method --- lib/http_request.c | 11 +++++++++++ lib/http_request.h | 1 + lib/raop.c | 30 ++++++++++++++++++------------ 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/lib/http_request.c b/lib/http_request.c index e259a60..15bf49d 100644 --- a/lib/http_request.c +++ b/lib/http_request.c @@ -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; } @@ -230,6 +234,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) { diff --git a/lib/http_request.h b/lib/http_request.h index e8ebfd7..d72a6f1 100644 --- a/lib/http_request.h +++ b/lib/http_request.h @@ -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); diff --git a/lib/raop.c b/lib/raop.c index 9a249bc..fa16cba 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -155,18 +155,18 @@ 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"); - bool logger_debug = (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG); + raop_conn_t *conn = ptr; - method = http_request_get_method(request); - url = http_request_get_url(request); - cseq = http_request_get_header(request, "CSeq"); + logger_log(conn->raop->logger, LOGGER_DEBUG, "conn_request"); + + 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"); + + bool logger_debug = (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG); if (!conn->have_active_remote) { const char *active_remote = http_request_get_header(request, "Active-Remote"); if (active_remote) { @@ -178,10 +178,16 @@ 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) { @@ -215,7 +221,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { } } - *response = http_response_init("RTSP/1.0", 200, "OK"); + *response = http_response_init(protocol, 200, "OK"); http_response_add_header(*response, "CSeq", cseq); //http_response_add_header(*response, "Apple-Jack-Status", "connected; type=analog"); From 3579e8402307ff947088837917945a961fa05285 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Thu, 30 May 2024 13:08:42 -0400 Subject: [PATCH 22/39] cleaned up -nohold feature + rejection of new connections when in use (for compatibility with possible future video streaming additions) --- lib/httpd.c | 70 +++++++++++++++++++++++++++++++++++++++-------------- lib/httpd.h | 19 ++++++++++----- lib/raop.c | 30 +++++++++++++++++------ lib/raop.h | 2 +- uxplay.cpp | 8 +++--- 5 files changed, 93 insertions(+), 36 deletions(-) diff --git a/lib/httpd.c b/lib/httpd.c index a4adf21..854fed9 100644 --- a/lib/httpd.c +++ b/lib/httpd.c @@ -31,6 +31,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 +43,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 +56,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 +100,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,6 +144,8 @@ 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--; } @@ -131,7 +166,6 @@ 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, zone_id); if (!user_data) { logger_log(httpd->logger, LOGGER_ERR, "Error initializing HTTP request handler"); @@ -142,6 +176,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; } @@ -178,20 +213,20 @@ httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6) remote = netutils_get_address(&remote_saddr, &remote_len, &remote_zone_id); assert (local_zone_id == remote_zone_id); -#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; imax_connections; i++) { - http_connection_t *connection = &httpd->connections[i]; - if (!connection->connected) { - continue; + /* remove existing connections to make way for new connections, if http->nohold is set: + * this will only occur if open_connections >= 2 and a connection with CONNECTION_TYPE_RAOP already exists */ + if (httpd->nohold && httpd->open_connections >= 2) { + if (httpd_count_connection_type(httpd, CONNECTION_TYPE_RAOP)) { + logger_log(httpd->logger, LOGGER_INFO, "Destroying current connections to allow connection by new client"); + for (int i = 0; imax_connections; i++) { + http_connection_t *connection = &httpd->connections[i]; + if (!connection->connected) { + continue; + } + httpd_remove_connection(httpd, connection); } - httpd_remove_connection(httpd, connection); } } -#endif ret = httpd_add_connection(httpd, fd, local, local_len, remote, remote_len, local_zone_id); if (ret == -1) { @@ -476,4 +511,3 @@ httpd_stop(httpd_t *httpd) httpd->joined = 1; MUTEX_UNLOCK(httpd->run_mutex); } - diff --git a/lib/httpd.h b/lib/httpd.h index a606a6a..3ebde2f 100644 --- a/lib/httpd.h +++ b/lib/httpd.h @@ -21,17 +21,24 @@ 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, unsigned int zone_id); - 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; +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); diff --git a/lib/raop.c b/lib/raop.c index fa16cba..cc3d3f9 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -90,6 +90,8 @@ struct raop_conn_s { unsigned int zone_id; + connection_type_t connection_type; + bool have_active_remote; }; typedef struct raop_conn_s raop_conn_t; @@ -140,10 +142,12 @@ conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remot 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) { @@ -160,13 +164,27 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { raop_conn_t *conn = ptr; logger_log(conn->raop->logger, LOGGER_DEBUG, "conn_request"); + bool logger_debug = (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG); 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"); - bool logger_debug = (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG); + 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))); + logger_log(conn->raop->logger, LOGGER_WARNING, "rejecting new connection request from %s", ipaddr); + *response = http_response_init("RTSP/1.0", 409, "Conflict: Server is connected to another client"); + http_response_add_header(*response, "CSeq", cseq); + http_response_add_header(*response, "Server", "AirTunes/"GLOBAL_VERSION); + 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) { @@ -264,6 +282,7 @@ 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_finish(*response, response_data, response_datalen); int len; @@ -395,14 +414,11 @@ raop_init(raop_callbacks_t *callbacks) { } int -raop_init2(raop_t *raop, int max_clients, const char *device_id, const char *keyfile) { +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; - assert(max_clients > 0); - assert(max_clients < 100); - /* create a new public key for pairing */ int new_key; pairing = pairing_init_generate(device_id, keyfile, &new_key); @@ -433,7 +449,7 @@ raop_init2(raop_t *raop, int max_clients, const char *device_id, const char *key httpd_cbs.conn_destroy = &conn_destroy; /* Initialize the http daemon */ - httpd = httpd_init(raop->logger, &httpd_cbs, max_clients); + httpd = httpd_init(raop->logger, &httpd_cbs, nohold); if (!httpd) { logger_log(raop->logger, LOGGER_ERR, "failed to initialize http daemon"); pairing_destroy(pairing); diff --git a/lib/raop.h b/lib/raop.h index 43a7a4e..63a8ef0 100644 --- a/lib/raop.h +++ b/lib/raop.h @@ -69,7 +69,7 @@ raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const c int remote_addr_len, unsigned short timing_rport, timing_protocol_t *time_protocol); RAOP_API raop_t *raop_init(raop_callbacks_t *callbacks); -RAOP_API int raop_init2(raop_t *raop, int max_clients, const char *device_id, const char *keyfile); +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); diff --git a/uxplay.cpp b/uxplay.cpp index 43e8a5d..fde2bd2 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -121,7 +121,7 @@ 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 unsigned short raop_port; static unsigned short airplay_port; static uint64_t remote_clock_offset = 0; @@ -1033,7 +1033,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; @@ -1825,8 +1825,8 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3], } raop_set_log_callback(raop, log_callback, NULL); raop_set_log_level(raop, log_level); - /* set max number of connections = 2 to protect against capture by new client */ - if (raop_init2(raop, max_connections, mac_address.c_str(), keyfile.c_str())){ + /* 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; From dbcd157de40062cb27a8ab3cf9a568cafdea8a4a Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Fri, 31 May 2024 10:19:40 -0400 Subject: [PATCH 23/39] (cosmetic) rename some rtp_mirror functions for consistency --- lib/raop_handlers.h | 4 ++-- lib/raop_rtp_mirror.c | 4 ++-- lib/raop_rtp_mirror.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index b2ba5c9..c0d31dc 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -787,8 +787,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!"); diff --git a/lib/raop_rtp_mirror.c b/lib/raop_rtp_mirror.c index 4e0bc48..832fb04 100644 --- a/lib/raop_rtp_mirror.c +++ b/lib/raop_rtp_mirror.c @@ -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); } @@ -729,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"); diff --git a/lib/raop_rtp_mirror.h b/lib/raop_rtp_mirror.h index f0e9e56..2e7914d 100644 --- a/lib/raop_rtp_mirror.h +++ b/lib/raop_rtp_mirror.h @@ -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 From 37807914a163fb7d23080c13d131a5ba0ce678b4 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Sun, 2 Jun 2024 18:48:05 -0400 Subject: [PATCH 24/39] (cosmetic) rearrangements in GET /info plist creation --- lib/raop_handlers.h | 58 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index c0d31dc..a6a86bb 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -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); From af195e3f2cd7c096420e0856b53b813bf5259103 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Mon, 3 Jun 2024 05:14:53 -0400 Subject: [PATCH 25/39] improved debug log on httpd: see ALL requests received --- lib/httpd.c | 13 +++++++++++-- lib/raop.c | 1 - 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/httpd.c b/lib/httpd.c index 854fed9..aae0f10 100644 --- a/lib/httpd.c +++ b/lib/httpd.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "httpd.h" #include "netutils.h" @@ -243,7 +244,8 @@ 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) { @@ -336,7 +338,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); @@ -356,6 +358,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; diff --git a/lib/raop.c b/lib/raop.c index cc3d3f9..e94f116 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -163,7 +163,6 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { int response_datalen = 0; raop_conn_t *conn = ptr; - logger_log(conn->raop->logger, LOGGER_DEBUG, "conn_request"); bool logger_debug = (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG); const char *method = http_request_get_method(request); From c361cfb6375ad273ab9da0674afb2efdaace45e6 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Mon, 3 Jun 2024 05:54:04 -0400 Subject: [PATCH 26/39] clearer debug message when remote host closes tcp socket for video --- lib/raop_rtp_mirror.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/raop_rtp_mirror.c b/lib/raop_rtp_mirror.c index 832fb04..141c65e 100644 --- a/lib/raop_rtp_mirror.c +++ b/lib/raop_rtp_mirror.c @@ -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 @@ -699,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); @@ -751,8 +751,8 @@ raop_rtp_mirror_start(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; } From c9806d552758b440bce1c68719c355006a30a502 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Mon, 3 Jun 2024 08:19:02 -0400 Subject: [PATCH 27/39] httpd.c: support for Upgrade to reverse http prototcol (PTTH) --- lib/http_request.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/http_request.c b/lib/http_request.c index 15bf49d..5a6343a 100644 --- a/lib/http_request.c +++ b/lib/http_request.c @@ -187,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; } From e5f2f66f2a006b4bc65a9735ef9c6623c609e014 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Mon, 3 Jun 2024 19:25:57 -0400 Subject: [PATCH 28/39] raop_handlers.h: minor fix in debug output --- lib/raop_handlers.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index a6a86bb..535d103 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -203,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); @@ -217,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; } From c4fb40ee31b61b1b3b5ae406900af72edac2ca47 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Tue, 4 Jun 2024 13:06:56 -0400 Subject: [PATCH 29/39] reorder position of CSeq and Server entries in Response header --- lib/raop.c | 12 ++++++------ lib/raop_handlers.h | 6 ------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/raop.c b/lib/raop.c index e94f116..b388be8 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -175,9 +175,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { char ipaddr[40]; utils_ipaddress_to_string(conn->remotelen, conn->remote, conn->zone_id, ipaddr, (int) (sizeof(ipaddr))); logger_log(conn->raop->logger, LOGGER_WARNING, "rejecting new connection request from %s", ipaddr); - *response = http_response_init("RTSP/1.0", 409, "Conflict: Server is connected to another client"); - http_response_add_header(*response, "CSeq", cseq); - http_response_add_header(*response, "Server", "AirTunes/"GLOBAL_VERSION); + *response = http_response_init(protocol, 409, "Conflict: Server is connected to another client"); goto finish; } httpd_set_connection_type(conn->raop->httpd, ptr, CONNECTION_TYPE_RAOP); @@ -210,7 +208,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { 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); @@ -240,9 +238,9 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { *response = http_response_init(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; @@ -282,6 +280,8 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { 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; diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index 535d103..9942970 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -356,12 +356,6 @@ raop_handler_pairsetup_pin(raop_conn_t *conn, 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; } static void From 53ac57dc428ba79d007e786dd4004f8b22a08ded Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Wed, 10 Jul 2024 10:54:27 -0400 Subject: [PATCH 30/39] switch from static to dynamic linking of libplist in Windows build --- README.html | 5 ++--- README.md | 1 - README.txt | 9 ++++----- lib/CMakeLists.txt | 5 ++++- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.html b/README.html index cd745df..44d79a4 100644 --- a/README.html +++ b/README.html @@ -802,9 +802,8 @@ environment (this uses “ninja” in place of install UxPlay dependencies (openssl is already installed with MSYS2):

      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 If you are trying a different Windows build system, MSVC versions of +GStreamer for Windows are available from the official GStreamer site, but only the MinGW 64-bit build on MSYS2 has been tested.

    • diff --git a/README.md b/README.md index f913862..081aa49 100644 --- a/README.md +++ b/README.md @@ -642,7 +642,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. diff --git a/README.txt b/README.txt index d464317..7d432e6 100644 --- a/README.txt +++ b/README.txt @@ -805,11 +805,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 diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index ed9fb67..7167c94 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -84,11 +84,14 @@ pkg_search_module(PLIST REQUIRED libplist-2.0) if ( PLIST_FOUND ) message( STATUS "found libplist-${PLIST_VERSION}" ) endif() -if( APPLE OR WIN32 ) +if( APPLE ) # use static linking 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 () find_library( LIBPLIST ${PLIST_LIBRARIES} PATH ${PLIST_LIBDIR} ) target_link_libraries ( airplay PUBLIC ${LIBPLIST} ) From 75d64e6b1e36fd1043af0ede6074ac5178cbf26c Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Wed, 10 Jul 2024 16:55:09 -0400 Subject: [PATCH 31/39] rework reinitialization of http_response --- lib/http_response.c | 38 +++++++++++++++++++++----------------- lib/http_response.h | 6 +++++- lib/raop.c | 9 +++++---- lib/raop_handlers.h | 8 ++++---- 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/lib/http_response.c b/lib/http_response.c index 2a18a77..50f1d89 100644 --- a/lib/http_response.c +++ b/lib/http_response.c @@ -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 diff --git a/lib/http_response.h b/lib/http_response.h index 43e1128..fcc7899 100644 --- a/lib/http_response.h +++ b/lib/http_response.h @@ -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); diff --git a/lib/raop.c b/lib/raop.c index b388be8..b3c7921 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -174,8 +174,9 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { 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))); - logger_log(conn->raop->logger, LOGGER_WARNING, "rejecting new connection request from %s", ipaddr); - *response = http_response_init(protocol, 409, "Conflict: Server is connected to another client"); + 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); @@ -236,8 +237,8 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { } } - *response = http_response_init(protocol, 200, "OK"); - + *response = http_response_create(); + http_response_init(*response, protocol, 200, "OK"); //http_response_add_header(*response, "Apple-Jack-Status", "connected; type=analog"); diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index 9942970..6493c33 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -354,8 +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"); + http_response_init(response, "RTSP/1.0", 470, "Client Authentication Failure"); } static void @@ -748,12 +747,13 @@ raop_handler_setup(raop_conn_t *conn, 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 From c628dd16a6c079624ec6417b0624549a43753273 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Wed, 17 Jul 2024 13:47:15 -0400 Subject: [PATCH 32/39] rework nohold option for compatibilty with future video streaming --- lib/httpd.c | 31 ++++++++++++++++--------------- lib/httpd.h | 2 ++ lib/raop.c | 18 ++++++++++++++---- lib/raop.h | 1 + uxplay.cpp | 9 +++++++++ 5 files changed, 42 insertions(+), 19 deletions(-) diff --git a/lib/httpd.c b/lib/httpd.c index aae0f10..7140e2e 100644 --- a/lib/httpd.c +++ b/lib/httpd.c @@ -214,21 +214,6 @@ httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6) remote = netutils_get_address(&remote_saddr, &remote_len, &remote_zone_id); assert (local_zone_id == remote_zone_id); - /* remove existing connections to make way for new connections, if http->nohold is set: - * this will only occur if open_connections >= 2 and a connection with CONNECTION_TYPE_RAOP already exists */ - if (httpd->nohold && httpd->open_connections >= 2) { - if (httpd_count_connection_type(httpd, CONNECTION_TYPE_RAOP)) { - logger_log(httpd->logger, LOGGER_INFO, "Destroying current connections to allow connection by new client"); - for (int i = 0; imax_connections; i++) { - http_connection_t *connection = &httpd->connections[i]; - if (!connection->connected) { - continue; - } - httpd_remove_connection(httpd, connection); - } - } - } - ret = httpd_add_connection(httpd, fd, local, local_len, remote, remote_len, local_zone_id); if (ret == -1) { shutdown(fd, SHUT_RDWR); @@ -238,6 +223,22 @@ 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) { diff --git a/lib/httpd.h b/lib/httpd.h index 3ebde2f..f64d166 100644 --- a/lib/httpd.h +++ b/lib/httpd.h @@ -34,6 +34,8 @@ struct httpd_callbacks_s { 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); diff --git a/lib/raop.c b/lib/raop.c index b3c7921..af14baf 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -174,10 +174,20 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { 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))); - 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; + 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; diff --git a/lib/raop.h b/lib/raop.h index 63a8ef0..dc1f767 100644 --- a/lib/raop.h +++ b/lib/raop.h @@ -63,6 +63,7 @@ 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, diff --git a/uxplay.cpp b/uxplay.cpp index fde2bd2..ddf7af9 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -1450,6 +1450,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; @@ -1817,6 +1825,7 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3], raop_cbs.register_client = register_client; raop_cbs.check_register = check_register; raop_cbs.export_dacp = export_dacp; + raop_cbs.video_reset = video_reset; raop = raop_init(&raop_cbs); if (raop == NULL) { From 4d88240cf3976daade3e4a437da6fe14be262d02 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Sat, 20 Jul 2024 18:19:46 -0400 Subject: [PATCH 33/39] detect auto-selected videosinks; simplify X11 window search --- renderers/video_renderer_gstreamer.c | 46 ++++++++++++++++------------ 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/renderers/video_renderer_gstreamer.c b/renderers/video_renderer_gstreamer.c index 9dd2a69..b8968c3 100644 --- a/renderers/video_renderer_gstreamer.c +++ b/renderers/video_renderer_gstreamer.c @@ -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,9 +39,14 @@ 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; @@ -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, */ @@ -158,7 +165,6 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, vide 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; @@ -183,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); @@ -285,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); @@ -294,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 @@ -321,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 @@ -371,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) { From d47bb01cbaac427741c585a74c97d1080f9de184 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Fri, 9 Aug 2024 18:14:53 -0400 Subject: [PATCH 34/39] add -nofreeze option; prepare for UxPlay-1.69 release --- README.html | 46 +++++++++++++++++++++++++++------------------- README.md | 29 +++++++++++++++++++---------- README.txt | 44 ++++++++++++++++++++++++++------------------ uxplay.1 | 8 +++++--- uxplay.cpp | 13 +++++++++---- uxplay.spec | 4 ++-- 6 files changed, 88 insertions(+), 56 deletions(-) diff --git a/README.html b/README.html index 44d79a4..7a0da6c 100644 --- a/README.html +++ b/README.html @@ -1,29 +1,27 @@

      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).

      Now +id="now-developed-at-the-github-site-httpsgithub.comfdh2uxplay-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).

      +found).

    Highlights:

    Possibility @@ -1099,6 +1098,10 @@ present, and synchronize with 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 @@ -1529,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.

    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 diff --git a/README.md b/README.md index 081aa49..61957f8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -# 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.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: @@ -13,7 +12,8 @@ * 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 +125,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. @@ -875,6 +876,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) @@ -1205,6 +1209,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 diff --git a/README.txt b/README.txt index 7d432e6..e630f5a 100644 --- a/README.txt +++ b/README.txt @@ -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 (where ALL user issues should be posted, and latest versions can be found). +### **Now developed at the GitHub site (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. @@ -1117,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 @@ -1570,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 diff --git a/uxplay.1 b/uxplay.1 index 568ff65..d68fcdf 100644 --- a/uxplay.1 +++ b/uxplay.1 @@ -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 @@ -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 diff --git a/uxplay.cpp b/uxplay.cpp index ddf7af9..c4676e3 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -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 @@ -122,6 +122,7 @@ static bool debug_log = DEFAULT_DEBUG_LOG; static int log_level = LOGGER_INFO; static bool bt709_fix = false; 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; @@ -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 In Airplay Audio (ALAC) mode, write cover-art to file \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 \"\n"); printf(" UxPlay displays deviceID when a client attempts to connect\n"); @@ -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); @@ -1510,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; } diff --git a/uxplay.spec b/uxplay.spec index b92fcb1..07157a7 100644 --- a/uxplay.spec +++ b/uxplay.spec @@ -1,5 +1,5 @@ Name: uxplay -Version: 1.68.3 +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 +* Fri Aug 09 2024 UxPlay maintainer Initial uxplay.spec: tested on Fedora 38, Rocky Linux 9.2, OpenSUSE Leap 15.5, Mageia 9, OpenMandriva ROME, PCLinuxOS - From 9f51a42eb0fc3c57560af8028e67fb5da6a5d88b Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Sat, 10 Aug 2024 13:16:56 -0400 Subject: [PATCH 35/39] silence unused-result warning in srp.c --- lib/srp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/srp.c b/lib/srp.c index 6190de7..d909cbf 100644 --- a/lib/srp.c +++ b/lib/srp.c @@ -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) From 0ce0b7c6dbde5b7945d53d9173fd61cfc7dd88df Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Sat, 10 Aug 2024 13:59:06 -0400 Subject: [PATCH 36/39] cosmetic: silence warning on macOS --- uxplay.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uxplay.cpp b/uxplay.cpp index c4676e3..4dd14c9 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -1336,8 +1336,8 @@ static int start_dnssd(std::vector hw_addr, std::string name) { } /* after dnssd starts, reset the default feature set here - /* (overwrites features set in dnssdint.h */ - /* default: FEATURES_1 = 0x5A7FFEE6, FEATURES_2 = 0 */ + * (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 From db38ac3ed1bec8f2d7d5043ecea4b83c004bc27d Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Sun, 11 Aug 2024 19:50:21 -0400 Subject: [PATCH 37/39] COSMETIC (whitespace cleanup) video_renderer_gstreamer.c --- renderers/video_renderer_gstreamer.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/renderers/video_renderer_gstreamer.c b/renderers/video_renderer_gstreamer.c index b8968c3..1bd133b 100644 --- a/renderers/video_renderer_gstreamer.c +++ b/renderers/video_renderer_gstreamer.c @@ -49,7 +49,7 @@ struct video_renderer_s { GstElement *appsrc, *pipeline; GstBus *bus; #ifdef X_DISPLAY_FIX - const char * server_name; + const char * server_name; X11_Window_t * gst_window; #endif }; @@ -88,7 +88,7 @@ static void append_videoflip (GString *launch, const videoflip_t *flip, const vi case LEFT: g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_UR_LL ! "); break; - case RIGHT: + case RIGHT: g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_UL_LR ! "); break; default: @@ -101,7 +101,7 @@ static void append_videoflip (GString *launch, const videoflip_t *flip, const vi case LEFT: g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_90L ! "); break; - case RIGHT: + case RIGHT: g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_90R ! "); break; default: @@ -109,7 +109,7 @@ static void append_videoflip (GString *launch, const videoflip_t *flip, const vi } break; } -} +} /* apple uses colorimetry=1:3:5:1 * * (not recognized by v4l2 plugin in Gstreamer < 1.20.4) * @@ -300,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() { @@ -322,7 +322,7 @@ void video_renderer_destroy() { free(renderer->gst_window); renderer->gst_window = NULL; } -#endif +#endif free (renderer); renderer = NULL; } @@ -424,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); -} +} From b08143bedeea96f1f98991ed5734a9c3bdfefdd6 Mon Sep 17 00:00:00 2001 From: fduncanh <72711181+fduncanh@users.noreply.github.com> Date: Mon, 19 Aug 2024 04:19:58 -0400 Subject: [PATCH 38/39] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 61957f8..1a5d562 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,10 @@ * _**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._ + ## Highlights: * GPLv3, open source. From be324e96a1d3baebb4f3229b7fb48cb57688b324 Mon Sep 17 00:00:00 2001 From: "F. Duncanh" Date: Mon, 19 Aug 2024 12:19:14 -0400 Subject: [PATCH 39/39] update format of d3d11videosink toggle-mode parameters --- uxplay.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uxplay.cpp b/uxplay.cpp index 4dd14c9..1a7ce2f 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -2049,9 +2049,9 @@ int main (int argc, char *argv[]) { if (videosink == "d3d11videosink" && use_video) { if (fullscreen) { - videosink.append(" fullscreen-toggle-mode=property fullscreen=true"); + videosink.append(" fullscreen-toggle-mode=GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY fullscreen=true "); } else { - videosink.append(" fullscreen-toggle-mode=alt-enter"); + 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");