diff --git a/README.html b/README.html index 09c12e8..cb37b9e 100644 --- a/README.html +++ b/README.html @@ -84,6 +84,7 @@
-avdec forces use of software h264 decoding using Gstreamer element avdec_h264 (libav h264 decoder). This option should prevent autovideosink choosing a hardware-accelerated videosink plugin such as vaapisink.
-as audiosink chooses the GStreamer audiosink, instead of letting autoaudiosink pick it for you. Some audiosink choices are: pulsesink, alsasink, osssink, oss4sink, and osxaudiosink (for macOS). Using quotes “…” might allow some parameters to be included with the audiosink name. (Some choices of audiosink might not work on your system.)
-as 0 (or just -a) suppresses playing of streamed audio, but displays streamed video.
+-reset n sets a limit of n consective timeout failures of the client to respond to ntp requests from the server (these are sent every 3 seconds to check if the client is still present). 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 10; the value n = 0 means “no limit” on timeouts.
-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) if it is still open when the GStreamer pipeline is closed.
-t timeout will cause the server to relaunch (without stopping uxplay) if no connections have been present during the previous timeout seconds. You may wish to use this if the Server is not visible to new Clients that were inactive when the Server was launched, and an idle Bonjour registration eventually becomes unavailable for new connections (this is a workaround for what may be due to a problem with your DNS-SD or Avahi setup). This option is currently disabled in macOS, for the same reason that requires the -nc option.
Note that Uxplay declares itself to be an AppleTV3,2 with a sourceVersion 220.68; this can also be changed in global.h. It is crucial for UxPlay to declare this old value of sourceVersion, as this prompts the Apple client to use a less-encrypted “legacy” protocol needed by third-generation Apple TV’s, which are 32-bit devices that cannot run modern tvOS; it is probably not necessary for UxPlay to claim to be such an old AppleTV model.
1.47 2022-02-05 Added -FPSdata option to display (in the terminal) regular reports sent by the client about video streaming performance.
-Internal cleanups of processing of video packets received from the client.
1.46 2022-01-20 Restore pre-1.44 behavior (1.44 may have broken hardware acceleration): once again use decodebin in the video pipeline; introduce new option “-avdec” to force software h264 decoding by libav h264, if needed (to prevent selection of vaapisink by autovideosink). Update llhttp to v6.0.6. UxPlay now reports itself as AppleTV3,2. Restrict connections to one client at a time (second client must now wait for first client to disconnect).
1.45 2022-01-10 New behavior: close video window when client requests “stop mirroring”. (A new “no close” option “-nc” is added for users who wish to retain previous behavior that does not close the video window).
1.44 2021-12-13 Omit hash of aeskey with ecdh_secret for an AirMyPC client; make an internal rearrangement of where this hash is done. Fully report all initial communications between client and server in -d debug mode. Replace decodebin in GStreamer video pipeline by h264-specific elements.
diff --git a/README.md b/README.md index bdae79d..daafe27 100644 --- a/README.md +++ b/README.md @@ -334,6 +334,11 @@ Also: image transforms that had been added to RPiPlay have been ported to UxPlay **-as 0** (or just **-a**) suppresses playing of streamed audio, but displays streamed video. +**-reset n** sets a limit of n consective timeout failures of the client to respond to ntp requests + from the server (these are sent every 3 seconds to check if the client is still present). 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 10; the value n = 0 means "no limit" on timeouts. + **-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) @@ -463,7 +468,8 @@ not necessary for UxPlay to claim to be such an old AppleTV model. # ChangeLog 1.47 2022-02-05 Added -FPSdata option to display (in the terminal) regular reports sent by the client about video streaming performance. - Internal cleanups of processing of video packets received from the client. + Internal cleanups of processing of video packets received from the client. Added -reset n option to reset the connection + after n ntp timeouts (also reset after ECONNRESET error in video stream). 1.46 2022-01-20 Restore pre-1.44 behavior (1.44 may have broken hardware acceleration): once again use decodebin in the video pipeline; introduce new option "-avdec" to force software h264 decoding by libav h264, if needed (to prevent selection of diff --git a/README.txt b/README.txt index 2b97485..75ed6ac 100644 --- a/README.txt +++ b/README.txt @@ -423,6 +423,13 @@ name. (Some choices of audiosink might not work on your system.) **-as 0** (or just **-a**) suppresses playing of streamed audio, but displays streamed video. +**-reset n** sets a limit of n consective timeout failures of the client +to respond to ntp requests from the server (these are sent every 3 +seconds to check if the client is still present). 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 10; the value n = 0 +means "no limit" on timeouts. + **-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 @@ -594,7 +601,8 @@ ChangeLog 1.47 2022-02-05 Added -FPSdata option to display (in the terminal) regular reports sent by the client about video streaming performance.\ Internal cleanups of processing of video packets received from the -client. +client. Added -reset n option to reset the connection after n ntp +timeouts (also reset after ECONNRESET error in video stream). 1.46 2022-01-20 Restore pre-1.44 behavior (1.44 may have broken hardware acceleration): once again use decodebin in the video pipeline; introduce diff --git a/lib/raop.c b/lib/raop.c index 0b3fc30..7928e11 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -59,6 +59,8 @@ struct raop_s { uint8_t maxFPS; uint8_t overscanned; uint8_t clientFPSdata; + + int max_ntp_timeouts; }; struct raop_conn_s { @@ -443,6 +445,8 @@ raop_init(int max_clients, raop_callbacks_t *callbacks) { /* initialize switch for display of client's streaming data records */ raop->clientFPSdata = 0; + raop->max_ntp_timeouts = 0; + return raop; } @@ -479,24 +483,27 @@ int raop_set_plist(raop_t *raop, const char *plist_item, const int value) { assert(raop); assert(plist_item); - if (strcmp(plist_item,"width") == 0) { + if (strcmp(plist_item, "width") == 0) { raop->width = (uint16_t) value; if ((int) raop->width != value) retval = 1; - } else if (strcmp(plist_item,"height") == 0) { + } else if (strcmp(plist_item, "height") == 0) { raop->height = (uint16_t) value; if ((int) raop->height != value) retval = 1; - } else if (strcmp(plist_item,"refreshRate") == 0) { + } else if (strcmp(plist_item, "refreshRate") == 0) { raop->refreshRate = (uint8_t) value; if ((int) raop->refreshRate != value) retval = 1; - } else if (strcmp(plist_item,"maxFPS") == 0) { + } else if (strcmp(plist_item, "maxFPS") == 0) { raop->maxFPS = (uint8_t) value; if ((int) raop->maxFPS != value) retval = 1; - } else if (strcmp(plist_item,"overscanned") == 0) { + } else if (strcmp(plist_item, "overscanned") == 0) { raop->overscanned = (uint8_t) (value ? 1 : 0); if ((int) raop->overscanned != value) retval = 1; - } else if (strcmp(plist_item,"clientFPSdata") == 0) { + } else if (strcmp(plist_item, "clientFPSdata") == 0) { raop->clientFPSdata = (value ? 1 : 0); if ((int) raop->clientFPSdata != value) retval = 1; + } else if (strcmp(plist_item, "max_ntp_timeouts") == 0) { + raop->max_ntp_timeouts = (value > 0 ? value : 0); + if (raop->max_ntp_timeouts != value) retval = 1; } else { retval = -1; } diff --git a/lib/raop.h b/lib/raop.h index 0e442e6..77772c8 100644 --- a/lib/raop.h +++ b/lib/raop.h @@ -40,10 +40,10 @@ struct raop_callbacks_s { /* Optional but recommended callback functions */ void (*conn_init)(void *cls); void (*conn_destroy)(void *cls); + void (*conn_reset) (void *cls); void (*conn_teardown)(void *cls, bool *teardown_96, bool *teardown_110 ); void (*audio_flush)(void *cls); void (*video_flush)(void *cls); - void (*video_conn_reset) (void *cls); void (*audio_set_volume)(void *cls, float volume); void (*audio_set_metadata)(void *cls, const void *buffer, int buflen); void (*audio_set_coverart)(void *cls, const void *buffer, int buflen); @@ -53,11 +53,9 @@ struct raop_callbacks_s { void (*video_report_size)(void *cls, float *width_source, float *height_source, float *width, float *height); }; typedef struct raop_callbacks_s raop_callbacks_t; - raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const unsigned char *remote_addr, int remote_addr_len, unsigned short timing_rport); RAOP_API raop_t *raop_init(int max_clients, raop_callbacks_t *callbacks); - RAOP_API void raop_set_log_level(raop_t *raop, int level); RAOP_API void raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls); RAOP_API int raop_set_plist(raop_t *raop, const char *plist_item, const int value); diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index 79b2fa8..cb98164 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -416,7 +416,7 @@ raop_handler_setup(raop_conn_t *conn, unsigned short timing_lport = conn->raop->timing_lport; conn->raop_ntp = raop_ntp_init(conn->raop->logger, &conn->raop->callbacks, conn->remote, conn->remotelen, timing_rport); - raop_ntp_start(conn->raop_ntp, &timing_lport); + raop_ntp_start(conn->raop_ntp, &timing_lport, conn->raop->max_ntp_timeouts); conn->raop_rtp = raop_rtp_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey, aesiv); conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey); diff --git a/lib/raop_ntp.c b/lib/raop_ntp.c index 92ea078..181455f 100644 --- a/lib/raop_ntp.c +++ b/lib/raop_ntp.c @@ -47,6 +47,8 @@ struct raop_ntp_s { logger_t *logger; raop_callbacks_t callbacks; + int max_ntp_timeouts; + thread_handle_t thread; mutex_handle_t run_mutex; @@ -250,7 +252,8 @@ raop_ntp_thread(void *arg) raop_ntp_data_t data_sorted[RAOP_NTP_DATA_COUNT]; const unsigned two_pow_n[RAOP_NTP_DATA_COUNT] = {2, 4, 8, 16, 32, 64, 128, 256}; int timeout_counter = 0; - + bool conn_reset = false; + while (1) { MUTEX_LOCK(raop_ntp->run_mutex); if (!raop_ntp->running) { @@ -276,8 +279,13 @@ raop_ntp_thread(void *arg) (struct sockaddr *) &raop_ntp->remote_saddr, &raop_ntp->remote_saddr_len); if (response_len < 0) { timeout_counter++; - logger_log(raop_ntp->logger, LOGGER_ERR, "raop_ntp receive timeout %5d (request sent %llu)", timeout_counter, send_time); - } else { + logger_log(raop_ntp->logger, LOGGER_ERR, "raop_ntp receive timeout %d (limit %d) (request sent %llu)", + timeout_counter, raop_ntp->max_ntp_timeouts, send_time); + if (timeout_counter == raop_ntp->max_ntp_timeouts) { + conn_reset = true; /* client is no longer responding */ + break; + } + } else { timeout_counter = 0; logger_log(raop_ntp->logger, LOGGER_DEBUG, "raop_ntp receive time type_t packetlen = %d", response_len); @@ -342,11 +350,14 @@ raop_ntp_thread(void *arg) MUTEX_UNLOCK(raop_ntp->run_mutex); logger_log(raop_ntp->logger, LOGGER_DEBUG, "raop_ntp exiting thread"); + if (conn_reset && raop_ntp->callbacks.conn_reset) { + raop_ntp->callbacks.conn_reset(raop_ntp->callbacks.cls); + } return 0; } void -raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport) +raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport, int max_ntp_timeouts) { logger_log(raop_ntp->logger, LOGGER_DEBUG, "raop_ntp starting time"); int use_ipv6 = 0; @@ -354,6 +365,7 @@ raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport) assert(raop_ntp); assert(timing_lport); + raop_ntp->max_ntp_timeouts = max_ntp_timeouts; raop_ntp->timing_lport = *timing_lport; MUTEX_LOCK(raop_ntp->run_mutex); @@ -377,7 +389,7 @@ raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport) /* Create the thread and initialize running values */ raop_ntp->running = 1; raop_ntp->joined = 0; - + THREAD_CREATE(raop_ntp->thread, raop_ntp_thread, raop_ntp); MUTEX_UNLOCK(raop_ntp->run_mutex); } diff --git a/lib/raop_ntp.h b/lib/raop_ntp.h index 6455ffe..9983ee8 100644 --- a/lib/raop_ntp.h +++ b/lib/raop_ntp.h @@ -23,7 +23,7 @@ typedef struct raop_ntp_s raop_ntp_t; -void raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport); +void raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport, int max_ntp_timeouts); void raop_ntp_stop(raop_ntp_t *raop_ntp); diff --git a/lib/raop_rtp_mirror.c b/lib/raop_rtp_mirror.c index d5de812..6ddd7b8 100644 --- a/lib/raop_rtp_mirror.c +++ b/lib/raop_rtp_mirror.c @@ -456,7 +456,7 @@ raop_rtp_mirror_thread(void *arg) * Sometimes (e.g, when the client has a locked screen), there is a 25kB trailer attached to the packet. * * This 25000 Byte trailer with unidentified content seems to be the same data each time it is sent. */ - if (payload_size) { + if (payload_size && raop_rtp_mirror->show_client_FPS_data) { //char *str = utils_data_to_string(packet, 128, 16); //logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "type 5 video packet header:\n%s", str); //free (str); @@ -474,14 +474,7 @@ raop_rtp_mirror_thread(void *arg) plist_t root_node = NULL; plist_from_bin((char *) payload, plist_size, &root_node); plist_to_xml(root_node, &plist_xml, &plist_len); - switch (raop_rtp_mirror->show_client_FPS_data) { - case 1: - logger_log(raop_rtp_mirror->logger, LOGGER_INFO, "%s", plist_xml); - break; - default: - logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "%s", plist_xml); - break; - } + logger_log(raop_rtp_mirror->logger, LOGGER_INFO, "%s", plist_xml); free(plist_xml); } } @@ -513,9 +506,8 @@ raop_rtp_mirror_thread(void *arg) MUTEX_UNLOCK(raop_rtp_mirror->run_mutex); logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror exiting TCP thread"); - if (conn_reset && raop_rtp_mirror->callbacks.video_conn_reset) { - logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror: received ECONNRESET from socket"); - raop_rtp_mirror->callbacks.video_conn_reset(raop_rtp_mirror->callbacks.cls); + if (conn_reset && raop_rtp_mirror->callbacks.conn_reset) { + raop_rtp_mirror->callbacks.conn_reset(raop_rtp_mirror->callbacks.cls); } return 0; } diff --git a/uxplay.1 b/uxplay.1 index 2477134..c4d4373 100644 --- a/uxplay.1 +++ b/uxplay.1 @@ -55,6 +55,8 @@ UxPlay 1.47: An open\-source AirPlay mirroring server based on RPiPlay .TP \fB\-as\fR 0 (or \fB\-a\fR) Turn audio off, streamed video only. .TP +\fB\-reset\fR n Reset after 3n seconds client silence (default 10, 0 = never). +.TP \fB\-nc\fR Do not close video window when client stops mirroring .TP \fB\-FPSdata\fR Show video-streaming performance reports sent by client. diff --git a/uxplay.cpp b/uxplay.cpp index b3fe81e..3eab045 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -50,6 +50,7 @@ #define DEFAULT_DEBUG_LOG false #define LOWEST_ALLOWED_PORT 1024 #define HIGHEST_PORT 65535 +#define NTP_TIMEOUT_LIMIT 10 static std::string server_name = DEFAULT_NAME; static int start_raop_server (std::vector